Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
c1a2bc4
feat(scouts): scouts control pane in the agents area
andrewm4894 Jun 10, 2026
bf9828e
fix(scouts): stop config selects overflowing fleet row cards
andrewm4894 Jun 10, 2026
ccc1d5e
feat(scouts): inline expandable fleet section + 24h run window
andrewm4894 Jun 10, 2026
91bb50d
feat(scouts): unify emitted-signal copy and link scout skill to PostH…
andrewm4894 Jun 10, 2026
3b0a70c
feat(scouts): per-run outcome boxes on fleet rows
andrewm4894 Jun 10, 2026
c1d5c5e
feat(scouts): per-row settings gear with inline config form
andrewm4894 Jun 10, 2026
9e356b4
feat(scouts): single-line fleet rows
andrewm4894 Jun 10, 2026
13ba3e6
feat(scouts): cloud skill link beside scout name on fleet rows
andrewm4894 Jun 10, 2026
d9b7ebd
feat(scouts): show emitted-signal count on fleet rows when nonzero
andrewm4894 Jun 10, 2026
bb3fde1
feat(scouts): shared ScoutRowCard on fleet list and detail screen
andrewm4894 Jun 10, 2026
b98c448
feat(scouts): run boxes open the task run in PostHog cloud
andrewm4894 Jun 10, 2026
a50ff5f
feat(scouts): fleet summary shows emit rate and tighter window label
andrewm4894 Jun 10, 2026
14d0b67
feat(scouts): signals section on scout detail above the run list
andrewm4894 Jun 10, 2026
5cd6ac8
feat(scouts): emission cards collapse to a two-line preview
andrewm4894 Jun 10, 2026
632b9ab
feat(scouts): run rows collapse-expand like emission cards
andrewm4894 Jun 10, 2026
5162ee8
feat(scouts): remove in-app run detail page in favor of cloud task runs
andrewm4894 Jun 10, 2026
d702466
feat(scouts): cloud skill link always visible on scout rows
andrewm4894 Jun 10, 2026
26c363c
feat(scouts): whole fleet row navigates to scout detail
andrewm4894 Jun 10, 2026
974ea39
feat(scouts): link official authoring/exploring helper skills in flee…
andrewm4894 Jun 10, 2026
67a562e
feat(scouts): drop per-row status dot, run boxes carry the signal
andrewm4894 Jun 10, 2026
b3defb4
feat(scouts): explain canonical/custom and dry-run badges with tooltips
andrewm4894 Jun 10, 2026
e9cfb6d
fix(scouts): lift origin/dry-run badges above the row stretched-link …
andrewm4894 Jun 10, 2026
8b8adb8
feat(scouts): add learn-more link to the scouts subsection description
andrewm4894 Jun 10, 2026
07a4ccb
feat(scouts): run boxes recede quiet runs to gray so emitted and fail…
andrewm4894 Jun 10, 2026
f66a2a5
feat(scouts): fleet-overview chat CTA prefilling a task with the expl…
andrewm4894 Jun 10, 2026
c34ec2c
feat(scouts): move fleet-overview chat CTA above the scout list
andrewm4894 Jun 10, 2026
c2fc6fa
feat(scouts): fleet-overview CTA fires an auto-mode cloud task like d…
andrewm4894 Jun 10, 2026
77c5e95
feat(scouts): second chat CTA for recent emitted signals, generic chi…
andrewm4894 Jun 10, 2026
234f2d7
feat(scouts): per-scout chat CTA sparkle on row cards
andrewm4894 Jun 10, 2026
51cbe5d
feat(scouts): per-scout chat tooltip says chat with PostHog
andrewm4894 Jun 10, 2026
c458e52
feat(scouts): instrument scout UI surfaces with analytics events
andrewm4894 Jun 10, 2026
bde0f5c
Merge remote-tracking branch 'origin/main' into feat/scouts-ui
andrewm4894 Jun 11, 2026
5e27aba
fix(scouts): address review-bot findings on error states, races, and …
andrewm4894 Jun 11, 2026
1c862db
/simplify
Twixes Jun 11, 2026
978e125
Em-dash →  en-dash
Twixes Jun 11, 2026
e426eae
Fix a lowercase
Twixes Jun 11, 2026
6051a85
Show skills in Skills
Twixes Jun 11, 2026
d67eea3
Fix navbar highlight
Twixes Jun 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions packages/api-client/src/posthog-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,67 @@ export interface SignalSourceConfig {
status: "running" | "completed" | "failed" | null;
}

// ── Signals scouts ───────────────────────────────────────────────────────────
// Backend: posthog `products/signals/backend/scout_harness/views.py`.
// Endpoints live under /api/projects/{id}/signals/scout/ and require the
// `signal_scout:read` / `signal_scout:write` scopes.

export interface ScoutConfig {
id: string;
skill_name: string;
enabled: boolean;
/** False means dry-run: the scout runs but findings are not emitted. */
emit: boolean;
run_interval_minutes: number;
last_run_at: string | null;
created_at: string;
}

export interface ScoutRun {
run_id: string;
skill_name: string;
skill_version: number;
/** TaskRun-derived status, e.g. "completed" | "failed" | "in_progress" | "queued". */
status: string;
started_at: string | null;
completed_at: string | null;
task_id: string | null;
task_run_id: string | null;
/** Relative PostHog cloud path to the backing task run. */
task_url: string | null;
summary: string;
emitted_count: number | null;
emitted_finding_ids: string[];
}

export interface ScoutEmission {
id: string;
run_id: string;
finding_id: string;
description: string;
weight: number;
confidence: number;
severity: string | null;
source_id: string;
emitted_at: string;
}

export interface ScoutScratchpadEntry {
key: string;
content: string;
created_at: string;
updated_at: string;
created_by_run_id: string | null;
}

export interface ScoutRunsQueryParams {
date_from?: string;
date_to?: string;
text?: string;
emitted?: boolean;
limit?: number;
}

export interface ExternalDataSourceSchema {
id: string;
name: string;
Expand Down Expand Up @@ -1047,6 +1108,112 @@ export class PostHogAPIClient {
return (await response.json()) as SignalSourceConfig;
}

private async scoutGet<T>(
projectId: number,
subPath: string,
query?: Record<string, string | number | boolean | undefined>,
): Promise<T> {
const urlPath = `/api/projects/${projectId}/signals/scout/${subPath}`;
const url = new URL(`${this.api.baseUrl}${urlPath}`);
for (const [key, value] of Object.entries(query ?? {})) {
if (value !== undefined) url.searchParams.set(key, String(value));
}
const response = await this.api.fetcher.fetch({
method: "get",
url,
path: urlPath,
});
if (!response.ok) {
throw new Error(
`Scout request failed (${subPath}): ${response.statusText}`,
);
}
return (await response.json()) as T;
}

async listScoutConfigs(projectId: number): Promise<ScoutConfig[]> {
const data = await this.scoutGet<
{ results: ScoutConfig[] } | ScoutConfig[]
>(projectId, "configs/");
return Array.isArray(data) ? data : (data.results ?? []);
}

async updateScoutConfig(
projectId: number,
configId: string,
updates: {
enabled?: boolean;
emit?: boolean;
run_interval_minutes?: number;
},
): Promise<ScoutConfig> {
const urlPath = `/api/projects/${projectId}/signals/scout/configs/${configId}/`;
const url = new URL(`${this.api.baseUrl}${urlPath}`);
const response = await this.api.fetcher.fetch({
method: "patch",
url,
path: urlPath,
overrides: {
body: JSON.stringify(updates),
},
});
if (!response.ok) {
const errorData = (await response.json().catch(() => ({}))) as {
detail?: string;
};
throw new Error(
errorData.detail ??
`Failed to update scout config: ${response.statusText}`,
);
}
return (await response.json()) as ScoutConfig;
}

async listScoutRuns(
projectId: number,
params?: ScoutRunsQueryParams,
): Promise<ScoutRun[]> {
const data = await this.scoutGet<{ results: ScoutRun[] } | ScoutRun[]>(
projectId,
"runs/",
{
date_from: params?.date_from,
date_to: params?.date_to,
text: params?.text,
emitted: params?.emitted,
limit: params?.limit,
},
);
return Array.isArray(data) ? data : (data.results ?? []);
}

async getScoutRun(projectId: number, runId: string): Promise<ScoutRun> {
return await this.scoutGet<ScoutRun>(projectId, `runs/${runId}/`);
}

async listScoutRunEmissions(
projectId: number,
runId: string,
): Promise<ScoutEmission[]> {
const data = await this.scoutGet<
{ results: ScoutEmission[] } | ScoutEmission[]
>(projectId, `runs/${runId}/emissions/`);
return Array.isArray(data) ? data : (data.results ?? []);
}

async searchScoutScratchpad(
projectId: number,
params?: { text?: string; limit?: number },
): Promise<ScoutScratchpadEntry[]> {
const data = await this.scoutGet<
{ results: ScoutScratchpadEntry[] } | ScoutScratchpadEntry[]
>(projectId, "scratchpad/", {
text: params?.text,
limit: params?.limit,
});
return Array.isArray(data) ? data : (data.results ?? []);
}

async listEvaluations(projectId: number): Promise<Evaluation[]> {
const data = await this.api.get(
"/api/environments/{project_id}/evaluations/",
Expand Down
Loading
Loading