@sapiom/harness 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +151 -0
- package/dist/cli/args.d.ts +16 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +80 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/bin.js +30 -66
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/consent.js +1 -1
- package/dist/cli/settings.d.ts.map +1 -1
- package/dist/cli/settings.js +33 -1
- package/dist/cli/settings.js.map +1 -1
- package/dist/core/adapters/adapter.d.ts +2 -2
- package/dist/core/adapters/claude-code.d.ts +60 -2
- package/dist/core/adapters/claude-code.d.ts.map +1 -1
- package/dist/core/adapters/claude-code.js +119 -32
- package/dist/core/adapters/claude-code.js.map +1 -1
- package/dist/core/adapters/codex.d.ts +21 -2
- package/dist/core/adapters/codex.d.ts.map +1 -1
- package/dist/core/adapters/codex.js +53 -2
- package/dist/core/adapters/codex.js.map +1 -1
- package/dist/core/adapters/detect.d.ts +9 -0
- package/dist/core/adapters/detect.d.ts.map +1 -1
- package/dist/core/adapters/detect.js +9 -0
- package/dist/core/adapters/detect.js.map +1 -1
- package/dist/core/api-key-provider.d.ts +79 -0
- package/dist/core/api-key-provider.d.ts.map +1 -0
- package/dist/core/api-key-provider.js +91 -0
- package/dist/core/api-key-provider.js.map +1 -0
- package/dist/core/canvas-body.d.ts +28 -28
- package/dist/core/canvas-body.d.ts.map +1 -1
- package/dist/core/canvas-body.js +117 -55
- package/dist/core/canvas-body.js.map +1 -1
- package/dist/core/canvas-derive.d.ts +43 -0
- package/dist/core/canvas-derive.d.ts.map +1 -0
- package/dist/core/canvas-derive.js +95 -0
- package/dist/core/canvas-derive.js.map +1 -0
- package/dist/core/canvas-enrichment.d.ts +52 -127
- package/dist/core/canvas-enrichment.d.ts.map +1 -1
- package/dist/core/canvas-enrichment.js +17 -178
- package/dist/core/canvas-enrichment.js.map +1 -1
- package/dist/core/canvas-graph.d.ts +40 -1
- package/dist/core/canvas-graph.d.ts.map +1 -1
- package/dist/core/canvas-graph.js +71 -9
- package/dist/core/canvas-graph.js.map +1 -1
- package/dist/core/canvas-interconnections.d.ts +26 -8
- package/dist/core/canvas-interconnections.d.ts.map +1 -1
- package/dist/core/canvas-interconnections.js +132 -24
- package/dist/core/canvas-interconnections.js.map +1 -1
- package/dist/core/canvas-manifest-check.d.ts.map +1 -1
- package/dist/core/canvas-manifest-check.js +15 -2
- package/dist/core/canvas-manifest-check.js.map +1 -1
- package/dist/core/canvas-render.d.ts +8 -15
- package/dist/core/canvas-render.d.ts.map +1 -1
- package/dist/core/canvas-render.js +29 -41
- package/dist/core/canvas-render.js.map +1 -1
- package/dist/core/canvas-run-state.d.ts +50 -5
- package/dist/core/canvas-run-state.d.ts.map +1 -1
- package/dist/core/canvas-run-state.js +176 -11
- package/dist/core/canvas-run-state.js.map +1 -1
- package/dist/core/canvas-svg.d.ts +9 -4
- package/dist/core/canvas-svg.d.ts.map +1 -1
- package/dist/core/canvas-svg.js +84 -13
- package/dist/core/canvas-svg.js.map +1 -1
- package/dist/core/canvas-template.d.ts +9 -10
- package/dist/core/canvas-template.d.ts.map +1 -1
- package/dist/core/canvas-template.js +57 -20
- package/dist/core/canvas-template.js.map +1 -1
- package/dist/core/canvas-watcher.d.ts +22 -1
- package/dist/core/canvas-watcher.d.ts.map +1 -1
- package/dist/core/canvas-watcher.js +111 -30
- package/dist/core/canvas-watcher.js.map +1 -1
- package/dist/core/collector/normalizer.d.ts +8 -0
- package/dist/core/collector/normalizer.d.ts.map +1 -1
- package/dist/core/collector/normalizer.js +8 -0
- package/dist/core/collector/normalizer.js.map +1 -1
- package/dist/core/collector/store.d.ts +75 -1
- package/dist/core/collector/store.d.ts.map +1 -1
- package/dist/core/collector/store.js +334 -2
- package/dist/core/collector/store.js.map +1 -1
- package/dist/core/definition-slug-resolver.d.ts +13 -0
- package/dist/core/definition-slug-resolver.d.ts.map +1 -1
- package/dist/core/definition-slug-resolver.js +29 -0
- package/dist/core/definition-slug-resolver.js.map +1 -1
- package/dist/core/errors.d.ts +10 -4
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +11 -5
- package/dist/core/errors.js.map +1 -1
- package/dist/core/example-seed.d.ts +8 -0
- package/dist/core/example-seed.d.ts.map +1 -1
- package/dist/core/example-seed.js +47 -10
- package/dist/core/example-seed.js.map +1 -1
- package/dist/core/inject/system-prompt.d.ts +8 -0
- package/dist/core/inject/system-prompt.d.ts.map +1 -1
- package/dist/core/inject/system-prompt.js +3 -1
- package/dist/core/inject/system-prompt.js.map +1 -1
- package/dist/core/macros.d.ts.map +1 -1
- package/dist/core/macros.js +5 -6
- package/dist/core/macros.js.map +1 -1
- package/dist/core/paths.d.ts +1 -0
- package/dist/core/paths.d.ts.map +1 -1
- package/dist/core/paths.js +1 -0
- package/dist/core/paths.js.map +1 -1
- package/dist/core/record-archive.d.ts +187 -0
- package/dist/core/record-archive.d.ts.map +1 -0
- package/dist/core/record-archive.js +465 -0
- package/dist/core/record-archive.js.map +1 -0
- package/dist/core/rehydration.d.ts +72 -0
- package/dist/core/rehydration.d.ts.map +1 -0
- package/dist/core/rehydration.js +72 -0
- package/dist/core/rehydration.js.map +1 -0
- package/dist/core/render-local-run.d.ts +82 -0
- package/dist/core/render-local-run.d.ts.map +1 -0
- package/dist/core/render-local-run.js +105 -0
- package/dist/core/render-local-run.js.map +1 -0
- package/dist/core/render-log-slice.d.ts +30 -0
- package/dist/core/render-log-slice.d.ts.map +1 -0
- package/dist/core/render-log-slice.js +49 -0
- package/dist/core/render-log-slice.js.map +1 -0
- package/dist/core/render-run-state.d.ts.map +1 -1
- package/dist/core/render-run-state.js +30 -49
- package/dist/core/render-run-state.js.map +1 -1
- package/dist/core/resume-brief.d.ts +130 -0
- package/dist/core/resume-brief.d.ts.map +1 -0
- package/dist/core/resume-brief.js +404 -0
- package/dist/core/resume-brief.js.map +1 -0
- package/dist/core/rolling-summary.d.ts +130 -0
- package/dist/core/rolling-summary.d.ts.map +1 -0
- package/dist/core/rolling-summary.js +227 -0
- package/dist/core/rolling-summary.js.map +1 -0
- package/dist/core/run-local-bootstrap.d.ts +93 -0
- package/dist/core/run-local-bootstrap.d.ts.map +1 -0
- package/dist/core/run-local-bootstrap.js +155 -0
- package/dist/core/run-local-bootstrap.js.map +1 -0
- package/dist/core/run-state.d.ts +16 -1
- package/dist/core/run-state.d.ts.map +1 -1
- package/dist/core/run-state.js +75 -25
- package/dist/core/run-state.js.map +1 -1
- package/dist/core/session-manager.d.ts +11 -3
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +56 -7
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-record.d.ts +165 -0
- package/dist/core/session-record.d.ts.map +1 -0
- package/dist/core/session-record.js +470 -0
- package/dist/core/session-record.js.map +1 -0
- package/dist/core/spawn-target.d.ts +12 -0
- package/dist/core/spawn-target.d.ts.map +1 -0
- package/dist/core/spawn-target.js +187 -0
- package/dist/core/spawn-target.js.map +1 -0
- package/dist/core/stub-feedback.d.ts +52 -0
- package/dist/core/stub-feedback.d.ts.map +1 -0
- package/dist/core/stub-feedback.js +33 -0
- package/dist/core/stub-feedback.js.map +1 -0
- package/dist/core/task-manager.d.ts.map +1 -1
- package/dist/core/task-manager.js +9 -1
- package/dist/core/task-manager.js.map +1 -1
- package/dist/core/template-catalog.d.ts +48 -0
- package/dist/core/template-catalog.d.ts.map +1 -0
- package/dist/core/template-catalog.js +338 -0
- package/dist/core/template-catalog.js.map +1 -0
- package/dist/core/workflow-registry.d.ts.map +1 -1
- package/dist/core/workflow-registry.js +2 -2
- package/dist/core/workflow-registry.js.map +1 -1
- package/dist/core/workspace-watcher.d.ts.map +1 -1
- package/dist/core/workspace-watcher.js +4 -3
- package/dist/core/workspace-watcher.js.map +1 -1
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -1
- package/dist/profiles/default.d.ts.map +1 -1
- package/dist/profiles/default.js +9 -14
- package/dist/profiles/default.js.map +1 -1
- package/dist/server/actions.d.ts +139 -0
- package/dist/server/actions.d.ts.map +1 -0
- package/dist/server/actions.js +430 -0
- package/dist/server/actions.js.map +1 -0
- package/dist/server/auth-routes.d.ts +92 -0
- package/dist/server/auth-routes.d.ts.map +1 -0
- package/dist/server/auth-routes.js +151 -0
- package/dist/server/auth-routes.js.map +1 -0
- package/dist/server/canvas.d.ts.map +1 -1
- package/dist/server/canvas.js +7 -7
- package/dist/server/canvas.js.map +1 -1
- package/dist/server/fs.d.ts.map +1 -1
- package/dist/server/fs.js +17 -1
- package/dist/server/fs.js.map +1 -1
- package/dist/server/index.d.ts +12 -3
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +0 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/ingest.d.ts +11 -0
- package/dist/server/ingest.d.ts.map +1 -1
- package/dist/server/ingest.js +1 -0
- package/dist/server/ingest.js.map +1 -1
- package/dist/server/macros.d.ts +4 -6
- package/dist/server/macros.d.ts.map +1 -1
- package/dist/server/macros.js.map +1 -1
- package/dist/server/rest.d.ts +18 -6
- package/dist/server/rest.d.ts.map +1 -1
- package/dist/server/rest.js +185 -50
- package/dist/server/rest.js.map +1 -1
- package/dist/server/runs.d.ts +16 -15
- package/dist/server/runs.d.ts.map +1 -1
- package/dist/server/runs.js +15 -80
- package/dist/server/runs.js.map +1 -1
- package/dist/server/static.d.ts +15 -1
- package/dist/server/static.d.ts.map +1 -1
- package/dist/server/static.js +54 -4
- package/dist/server/static.js.map +1 -1
- package/dist/server/templates.d.ts +24 -0
- package/dist/server/templates.d.ts.map +1 -0
- package/dist/server/templates.js +54 -0
- package/dist/server/templates.js.map +1 -0
- package/dist/shared/types.d.ts +558 -66
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js +19 -11
- package/dist/shared/types.js.map +1 -1
- package/dist/web/assets/index-DC65cskq.js +446 -0
- package/dist/web/assets/index-xtD-3FFa.css +32 -0
- package/dist/web/canvas/sess-boot/index.html +305 -0
- package/dist/web/index.html +4 -9
- package/package.json +8 -4
- package/dist/core/canvas-enrich.d.ts +0 -115
- package/dist/core/canvas-enrich.d.ts.map +0 -1
- package/dist/core/canvas-enrich.js +0 -186
- package/dist/core/canvas-enrich.js.map +0 -1
- package/dist/core/run-spend.d.ts +0 -55
- package/dist/core/run-spend.d.ts.map +0 -1
- package/dist/core/run-spend.js +0 -109
- package/dist/core/run-spend.js.map +0 -1
- package/dist/core/run-transactions.d.ts +0 -60
- package/dist/core/run-transactions.d.ts.map +0 -1
- package/dist/core/run-transactions.js +0 -129
- package/dist/core/run-transactions.js.map +0 -1
- package/dist/profiles/canvas-guidelines.d.ts +0 -20
- package/dist/profiles/canvas-guidelines.d.ts.map +0 -1
- package/dist/profiles/canvas-guidelines.js +0 -48
- package/dist/profiles/canvas-guidelines.js.map +0 -1
- package/dist/server/skills.d.ts +0 -51
- package/dist/server/skills.d.ts.map +0 -1
- package/dist/server/skills.js +0 -312
- package/dist/server/skills.js.map +0 -1
- package/dist/web/assets/index-DM84J83Y.js +0 -234
- package/dist/web/assets/index-DemtWW4L.css +0 -32
package/dist/shared/types.d.ts
CHANGED
|
@@ -21,11 +21,27 @@ export declare const HARNESS_PATHS: {
|
|
|
21
21
|
readonly settings: "~/.sapiom/harness/settings.json";
|
|
22
22
|
/** Generated per-session agent config (claude settings/mcp-config files). */
|
|
23
23
|
readonly generated: "~/.sapiom/harness/generated";
|
|
24
|
-
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
/**
|
|
25
|
+
* Archived session records — one compacted `<harnessSessionId>.json` per
|
|
26
|
+
* conversation (core/record-archive.ts). Deliberately NOT under `generated`:
|
|
27
|
+
* that directory is deleted the moment a session's pty exits, and these have
|
|
28
|
+
* to outlive `events.ndjson`'s 30-day retention, not undercut it.
|
|
29
|
+
*/
|
|
30
|
+
readonly records: "~/.sapiom/harness/records";
|
|
31
|
+
/** Where the bundled example project is seeded. Written ONLY by
|
|
32
|
+
* `scripts/seed-example.mjs` (demo prep) since the in-app sample action and
|
|
33
|
+
* its `POST /api/sample-project` route were removed — the running Studio
|
|
34
|
+
* neither seeds nor reads this path, so a directory here is leftover output.
|
|
35
|
+
* Kept stable so re-seeding reuses one copy instead of scattering fresh ones. */
|
|
27
36
|
readonly sampleProject: "~/.sapiom/harness/sample-project";
|
|
28
37
|
};
|
|
38
|
+
/**
|
|
39
|
+
* The file that makes a directory an agent project. Canonical home: three
|
|
40
|
+
* copies of this literal used to live in core/workspace-watcher.ts,
|
|
41
|
+
* core/workflow-registry.ts and (now) server/fs.ts, the first of which carried
|
|
42
|
+
* a "kept in sync with" comment admitting the duplication. Import it.
|
|
43
|
+
*/
|
|
44
|
+
export declare const AGENT_PROJECT_MARKER = "sapiom.json";
|
|
29
45
|
/**
|
|
30
46
|
* Canvas convention: agents write static HTML here, relative to the session
|
|
31
47
|
* cwd. The server watches this directory and serves it at
|
|
@@ -42,14 +58,6 @@ export declare const CANVAS_INDEX = ".sapiom/canvas/index.html";
|
|
|
42
58
|
* pipeline.
|
|
43
59
|
*/
|
|
44
60
|
export declare const CANVAS_RENDERS_DIR = ".sapiom/canvas/renders";
|
|
45
|
-
/**
|
|
46
|
-
* Per-workflow enrichment cache lives here (one `<slug>.json` per workflow,
|
|
47
|
-
* same slug scheme as CANVAS_RENDERS_DIR), relative to the session cwd:
|
|
48
|
-
* `{ graph, enrichment, sourceFingerprint, enrichedAt }`. A stale
|
|
49
|
-
* sourceFingerprint keeps the enrichment displayed (with a "stale" chip)
|
|
50
|
-
* until a re-run replaces it — see core/canvas-enrich.ts.
|
|
51
|
-
*/
|
|
52
|
-
export declare const CANVAS_CACHE_DIR = ".sapiom/canvas/cache";
|
|
53
61
|
/**
|
|
54
62
|
* Composer image attachments (upload / paste / drag-drop) land here, relative
|
|
55
63
|
* to the session cwd, before their path is relayed into the agent's prompt.
|
|
@@ -129,6 +137,16 @@ export interface HarnessSession {
|
|
|
129
137
|
* via `PATCH /api/sessions/:id/workflow`; mirrored into
|
|
130
138
|
* HARNESS_CONTEXT_FILE in the session's cwd so the agent can read it. */
|
|
131
139
|
boundWorkflowPath: string | null;
|
|
140
|
+
/**
|
|
141
|
+
* The prior session this one was seeded from (portable continue — see
|
|
142
|
+
* core/rehydration.ts), when a brief was ACTUALLY produced and delivered.
|
|
143
|
+
* Null/absent otherwise, including when the client asked to rehydrate from
|
|
144
|
+
* an id our event log holds nothing for: this field is the record of what
|
|
145
|
+
* happened, not of what was requested, so the UI can never present an
|
|
146
|
+
* empty-handed fresh session as a continuation. Absent on sessions
|
|
147
|
+
* persisted by builds from before this existed.
|
|
148
|
+
*/
|
|
149
|
+
rehydratedFrom?: string | null;
|
|
132
150
|
/**
|
|
133
151
|
* `status === "running"` only means the pty is alive — the agent's TUI
|
|
134
152
|
* can still be sitting on a blocking prompt (most commonly: "trust this
|
|
@@ -144,7 +162,23 @@ export interface HarnessSession {
|
|
|
144
162
|
*/
|
|
145
163
|
ready: boolean;
|
|
146
164
|
}
|
|
147
|
-
/**
|
|
165
|
+
/**
|
|
166
|
+
* What resuming a past session would ACTUALLY do — resolved server-side
|
|
167
|
+
* against the agent's own conversation store, never guessed from whether we
|
|
168
|
+
* happen to hold an `agentSessionId`.
|
|
169
|
+
*
|
|
170
|
+
* - `agent-resume`: the agent still holds this conversation, so
|
|
171
|
+
* `HarnessAdapter.resume()` reattaches to it for real.
|
|
172
|
+
* - `rehydrate`: the agent does NOT hold it. Most commonly a session that
|
|
173
|
+
* ended before its first prompt — Claude Code writes no transcript at all
|
|
174
|
+
* for those, and Codex writes no rollout file (see CodexAdapter's
|
|
175
|
+
* `detectBlockingPrompt` for the same rule from the other direction). The
|
|
176
|
+
* conversation is unrecoverable from the agent; the only honest options are
|
|
177
|
+
* a fresh session in the same directory, or (once H3 lands) replaying our
|
|
178
|
+
* own recorded context into one.
|
|
179
|
+
*/
|
|
180
|
+
export type SessionResumeMode = "agent-resume" | "rehydrate";
|
|
181
|
+
/** A past session discovered from agent transcripts or our registry. */
|
|
148
182
|
export interface SessionSummary {
|
|
149
183
|
/** Back-reference to our session when the registry tracked it (source "registry"). */
|
|
150
184
|
harnessSessionId?: string;
|
|
@@ -154,6 +188,14 @@ export interface SessionSummary {
|
|
|
154
188
|
title: string;
|
|
155
189
|
lastActiveAt: string;
|
|
156
190
|
source: "registry" | "transcript";
|
|
191
|
+
/**
|
|
192
|
+
* Whether this row can genuinely be handed back to the agent — verified
|
|
193
|
+
* against the agent's own store by `GET /api/sessions/history`, for BOTH
|
|
194
|
+
* row sources. Holding an `agentSessionId` is not evidence of anything:
|
|
195
|
+
* we capture it from the SessionStart hook, which fires long before the
|
|
196
|
+
* agent has a conversation worth resuming.
|
|
197
|
+
*/
|
|
198
|
+
resumeMode: SessionResumeMode;
|
|
157
199
|
/**
|
|
158
200
|
* Git branch the session was last active on, when the transcript records it.
|
|
159
201
|
* The strongest within-directory differentiator between otherwise-similar
|
|
@@ -167,7 +209,26 @@ export interface SessionSummary {
|
|
|
167
209
|
* full read (see the claude-code adapter's full-scan size cap).
|
|
168
210
|
*/
|
|
169
211
|
messageCount?: number;
|
|
212
|
+
/**
|
|
213
|
+
* Number of human prompts (turns) the harness itself recorded for this
|
|
214
|
+
* session — from the event store's byte-offset index, or from the archived
|
|
215
|
+
* record once the events behind it have been swept. Exact and cheap at any
|
|
216
|
+
* file size — unlike {@link messageCount}, which the claude-code adapter
|
|
217
|
+
* leaves undefined above its full-scan cap. Undefined when the harness has
|
|
218
|
+
* neither events nor an archived record for the session (a transcript the
|
|
219
|
+
* Studio never ran). Prefer this over messageCount when both are present.
|
|
220
|
+
*/
|
|
221
|
+
turnCount?: number;
|
|
170
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* What an adapter reports having FOUND — everything in a `SessionSummary`
|
|
225
|
+
* except `resumeMode`. Deciding what resuming a row would do is the server's
|
|
226
|
+
* job (`GET /api/sessions/history`), so the adapter contract deliberately
|
|
227
|
+
* can't express it: that's what kept transcript rows hardcoded to
|
|
228
|
+
* un-resumable while registry rows were hardcoded to resumable, each wrong in
|
|
229
|
+
* the opposite direction.
|
|
230
|
+
*/
|
|
231
|
+
export type PastSessionRecord = Omit<SessionSummary, "resumeMode">;
|
|
171
232
|
export interface DoctorCheck {
|
|
172
233
|
name: string;
|
|
173
234
|
ok: boolean;
|
|
@@ -205,7 +266,31 @@ export interface LaunchOpts {
|
|
|
205
266
|
/** Only consulted by `launchTask` — hard cap on agent turns
|
|
206
267
|
* (`--max-turns`), so a bounded task can't run away. */
|
|
207
268
|
maxTurns?: number;
|
|
269
|
+
/**
|
|
270
|
+
* Set by the launch-opts builder when this launch's context was seeded from
|
|
271
|
+
* a prior session's recorded events (portable continue — see
|
|
272
|
+
* core/rehydration.ts). Carries the id the brief was built from. Adapters
|
|
273
|
+
* ignore it entirely; `SessionManager.create()` copies it onto
|
|
274
|
+
* {@link HarnessSession.rehydratedFrom} so the UI can say whether the
|
|
275
|
+
* continue really carried context instead of implying it did.
|
|
276
|
+
*/
|
|
277
|
+
rehydratedFrom?: string;
|
|
208
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* How a harness receives the rehydration brief for a continued session.
|
|
281
|
+
*
|
|
282
|
+
* - `launch-flag`: the adapter puts `LaunchOpts.systemPromptFile`'s contents in
|
|
283
|
+
* front of the agent at spawn time (claude-code's `--append-system-prompt`,
|
|
284
|
+
* codex's `developer_instructions`), so composing the brief into that file is
|
|
285
|
+
* the whole delivery.
|
|
286
|
+
* - `post-ready-injection`: the harness has no such flag, so the brief is sent
|
|
287
|
+
* through the ordinary input path once the session reports `ready` — gated on
|
|
288
|
+
* readiness so it is never written into a TUI sitting on a trust prompt.
|
|
289
|
+
*
|
|
290
|
+
* See `systemPromptDeliveryFor` (core/rehydration.ts) for why the absent case
|
|
291
|
+
* resolves to the fallback rather than the flag.
|
|
292
|
+
*/
|
|
293
|
+
export type SystemPromptDelivery = "launch-flag" | "post-ready-injection";
|
|
209
294
|
/**
|
|
210
295
|
* One implementation per supported coding agent. Implementations must be
|
|
211
296
|
* side-effect free until `launch`/`resume` specs are actually spawned.
|
|
@@ -218,8 +303,34 @@ export interface HarnessAdapter {
|
|
|
218
303
|
resume(agentSessionId: string, opts: LaunchOpts): SpawnSpec;
|
|
219
304
|
/** How analytics events are sourced for this harness. */
|
|
220
305
|
eventSource: "hooks" | "transcript-tail";
|
|
221
|
-
/**
|
|
222
|
-
|
|
306
|
+
/**
|
|
307
|
+
* Whether `launch`/`resume` actually put `LaunchOpts.systemPromptFile` in
|
|
308
|
+
* front of the agent. Declared rather than inferred, because the alternative
|
|
309
|
+
* — assuming every adapter honours the field — silently drops a rehydration
|
|
310
|
+
* brief for one that doesn't. Omitted resolves to `post-ready-injection`
|
|
311
|
+
* (see `systemPromptDeliveryFor` in core/rehydration.ts).
|
|
312
|
+
*/
|
|
313
|
+
systemPromptDelivery?: SystemPromptDelivery;
|
|
314
|
+
/** Past sessions this agent recorded for a directory (agent-side history).
|
|
315
|
+
* Reports what it found; `resumeMode` is the server's call — see
|
|
316
|
+
* {@link PastSessionRecord}. */
|
|
317
|
+
listPastSessions(cwd: string): Promise<PastSessionRecord[]>;
|
|
318
|
+
/**
|
|
319
|
+
* Does this agent's OWN conversation store still hold `agentSessionId` for
|
|
320
|
+
* `cwd` — i.e. would `resume()` reattach to a real conversation rather than
|
|
321
|
+
* dying on startup?
|
|
322
|
+
*
|
|
323
|
+
* This exists because holding an `agentSessionId` proves nothing: we capture
|
|
324
|
+
* it from the SessionStart hook, which fires before the user has submitted
|
|
325
|
+
* anything, and an agent that never received a prompt writes no transcript
|
|
326
|
+
* at all. Without this check, one in three history rows was a Resume button
|
|
327
|
+
* guaranteed to fail with `No conversation found with session ID: …`.
|
|
328
|
+
*
|
|
329
|
+
* Contract: **never throws**. A missing, unreadable, or empty store is
|
|
330
|
+
* `false` — a resumability probe must not be able to break the history
|
|
331
|
+
* endpoint or `resume()`'s pre-flight.
|
|
332
|
+
*/
|
|
333
|
+
canResume(agentSessionId: string, cwd: string): Promise<boolean>;
|
|
223
334
|
/**
|
|
224
335
|
* Best-effort scrollback check for this harness's own known blocking
|
|
225
336
|
* prompts (e.g. "trust this directory?"), for harnesses whose real
|
|
@@ -337,38 +448,38 @@ export type BusMessage = {
|
|
|
337
448
|
type: "session.activity";
|
|
338
449
|
harnessSessionId: string;
|
|
339
450
|
at: string;
|
|
340
|
-
};
|
|
341
|
-
/** Per-step cost summary from the spend endpoint. */
|
|
342
|
-
export interface RunStepSpend {
|
|
343
|
-
name: string;
|
|
344
|
-
totalUsd: string;
|
|
345
|
-
entryCount: number;
|
|
346
451
|
}
|
|
347
|
-
/**
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
452
|
+
/**
|
|
453
|
+
* Auth state changed — fired after a successful sign-in (`POST
|
|
454
|
+
* /api/auth/start`) or sign-out (`POST /api/auth/disconnect`). The SPA
|
|
455
|
+
* should refetch `/api/auth/status` (or `/api/state`) on receipt to update
|
|
456
|
+
* its auth UI without a full page reload.
|
|
457
|
+
*/
|
|
458
|
+
| {
|
|
459
|
+
type: "auth.changed";
|
|
460
|
+
authenticated: boolean;
|
|
461
|
+
organizationName: string | null;
|
|
462
|
+
};
|
|
463
|
+
/**
|
|
464
|
+
* One capability call a step made during the run. Capability-scoped and
|
|
465
|
+
* provider-agnostic: `capability` is a dotted capability id (e.g.
|
|
466
|
+
* `web.search`, `models.coding.run`) — never a provider or model name.
|
|
467
|
+
* `stubUsed` records whether this call was served by a supplied stub instead
|
|
468
|
+
* of a real capability call, which is the single most load-bearing fact when
|
|
469
|
+
* explaining a local (offline) run. Optional fields are ABSENT (not null)
|
|
470
|
+
* when the source does not carry the value — honest absence.
|
|
471
|
+
*/
|
|
472
|
+
export interface StepCall {
|
|
473
|
+
/** Dotted capability id (provider-agnostic — never a provider/model name). */
|
|
367
474
|
capability: string;
|
|
368
|
-
/**
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
|
|
475
|
+
/** True when a supplied stub served this call rather than the real capability. */
|
|
476
|
+
stubUsed?: boolean;
|
|
477
|
+
/** The arguments the call was made with, when the source carries them. Any
|
|
478
|
+
* JSON shape; ABSENT (not null) otherwise. */
|
|
479
|
+
args?: unknown;
|
|
480
|
+
/** The value the call returned — the served stub value for a local run, or
|
|
481
|
+
* the capability result for a prod run. ABSENT when the source has no result. */
|
|
482
|
+
result?: unknown;
|
|
372
483
|
}
|
|
373
484
|
/**
|
|
374
485
|
* A step's render status, folded from the raw projection step status into the
|
|
@@ -378,17 +489,15 @@ export interface RunCall {
|
|
|
378
489
|
*/
|
|
379
490
|
export type StepStatus = "pending" | "running" | "passed" | "failed";
|
|
380
491
|
/** One step as the canvas renders it — status plus the deterministically
|
|
381
|
-
* derived
|
|
492
|
+
* derived latency/error/log slice. Optional fields are ABSENT (not
|
|
382
493
|
* `undefined`/`0`) when the decoded projection carries no value — honest
|
|
383
|
-
* absence,
|
|
494
|
+
* absence. The inspector surfaces logs, latency, and pass/fail only. */
|
|
384
495
|
export interface StepView {
|
|
385
496
|
/** Stable id for keyed rendering — the OTel span id, else a step-order key. */
|
|
386
497
|
id: string;
|
|
387
498
|
/** Human step label (the projection's stepName). */
|
|
388
499
|
name: string;
|
|
389
500
|
status: StepStatus;
|
|
390
|
-
/** Captured USD for this step; absent when the read carries no cost. */
|
|
391
|
-
costUsd?: number;
|
|
392
501
|
/** finishedAt − startedAt in ms; absent while running or on bad timestamps. */
|
|
393
502
|
latencyMs?: number;
|
|
394
503
|
/** Terminal error message; present only for a failed step that recorded one. */
|
|
@@ -396,13 +505,59 @@ export interface StepView {
|
|
|
396
505
|
/** Tail-preserving, character-capped executor log text — the debug-macro
|
|
397
506
|
* context source (trimmed further before injection). Absent when no logs. */
|
|
398
507
|
logSlice?: string;
|
|
508
|
+
/** The resolved input the step actually ran on, when the source carries it
|
|
509
|
+
* (populated by local stub runs today; a production run projection may
|
|
510
|
+
* expose it in future). Any JSON shape. ABSENT — never `null`/`{}` — when
|
|
511
|
+
* the source has no per-step input, so the inspector shows nothing rather
|
|
512
|
+
* than fabricating a payload. */
|
|
513
|
+
input?: unknown;
|
|
514
|
+
/** The value the step produced, on the same honest-absence terms as
|
|
515
|
+
* {@link StepView.input}: present only when the source captured a real
|
|
516
|
+
* output, absent otherwise (a still-running or output-less step shows no
|
|
517
|
+
* Output block). Any JSON shape. */
|
|
518
|
+
output?: unknown;
|
|
519
|
+
/** The capability calls this step made during the run, in call order. Each
|
|
520
|
+
* entry is capability-scoped and provider-agnostic. Absent (never `[]`)
|
|
521
|
+
* when the source records no call information for this step — honest
|
|
522
|
+
* absence, mirroring `input`/`output`. A local run populates this from
|
|
523
|
+
* the stub client's per-call sink; a prod run leaves it absent when the
|
|
524
|
+
* step projection does not carry dotted-capability call records. */
|
|
525
|
+
calls?: StepCall[];
|
|
526
|
+
}
|
|
527
|
+
/** A supplied stub key that no capability call ever matched in its step — almost
|
|
528
|
+
* always a typo or the wrong path form. Surfaced read-only in the inspector so a
|
|
529
|
+
* no-op mock (a stub that silently served nothing) is visible instead of a
|
|
530
|
+
* mystery. Mirrors agent-core's `UnusedStub` (consumed as-is). */
|
|
531
|
+
export interface UnusedStubView {
|
|
532
|
+
step: string;
|
|
533
|
+
key: string;
|
|
399
534
|
}
|
|
400
535
|
/** A whole run as the canvas renders it. `status` is the run lifecycle folded
|
|
401
|
-
* to the four states the UI distinguishes; `steps` is order-preserving.
|
|
536
|
+
* to the four states the UI distinguishes; `steps` is order-preserving.
|
|
537
|
+
*
|
|
538
|
+
* Stub fields are RUN-LEVEL and honest-absence: they are set only by
|
|
539
|
+
* {@link renderLocalRun} for an offline stub run (prod runs from renderRunState
|
|
540
|
+
* never carry them), and only when they carry real signal. A local run is
|
|
541
|
+
* stub-served by construction — every `ctx.sapiom.*` call resolves from a stub —
|
|
542
|
+
* so `stubbed` is the honest per-run truth the inspector marks each executed
|
|
543
|
+
* step with (agent-core records no per-CALL stub attribution, so the chip lives
|
|
544
|
+
* at the granularity the trace actually supports). `unusedStubs`/`stubWarnings`
|
|
545
|
+
* come from the run's terminal NDJSON summary and are ABSENT (not `[]`) when
|
|
546
|
+
* empty, so the read-only notice renders nothing when there is nothing wrong. */
|
|
402
547
|
export interface RunView {
|
|
403
548
|
executionId: string;
|
|
404
549
|
status: "running" | "completed" | "failed" | "cancelled";
|
|
405
550
|
steps: StepView[];
|
|
551
|
+
/** True when this run was served entirely by stub capabilities (an offline
|
|
552
|
+
* local run). Absent for real (prod / local-backend) runs. Drives the
|
|
553
|
+
* per-step "stubbed" chip. */
|
|
554
|
+
stubbed?: boolean;
|
|
555
|
+
/** Supplied stub keys that matched no capability call this run (likely a typo
|
|
556
|
+
* / wrong path). Absent when none — never an empty array. */
|
|
557
|
+
unusedStubs?: UnusedStubView[];
|
|
558
|
+
/** Human-readable warnings about stub values that matched a key but had the
|
|
559
|
+
* wrong shape (the silent-wrong-data trap). Absent when none. */
|
|
560
|
+
stubWarnings?: string[];
|
|
406
561
|
}
|
|
407
562
|
/** One entry in the adapter registry, as the SPA sees it (GET /api/harnesses). */
|
|
408
563
|
export interface HarnessEntry {
|
|
@@ -429,7 +584,7 @@ export interface HarnessEntry {
|
|
|
429
584
|
* See the collector README section in packages/harness/src/core/collector/
|
|
430
585
|
* for the full surface-"ui" contract.
|
|
431
586
|
*/
|
|
432
|
-
export type UiEventName = "prompt.submitted" | "session.switched" | "macro.invoked" | "visualize.triggered" | "consent.changed" | "session.created" | "
|
|
587
|
+
export type UiEventName = "prompt.submitted" | "session.switched" | "macro.invoked" | "visualize.triggered" | "consent.changed" | "session.created" | "mcp.install";
|
|
433
588
|
export interface UiTrackRequest {
|
|
434
589
|
/** Dot-canonical event name — one of the UiEventName literals. */
|
|
435
590
|
event: UiEventName;
|
|
@@ -442,7 +597,7 @@ export interface UiTrackRequest {
|
|
|
442
597
|
harnessSessionId?: string;
|
|
443
598
|
}
|
|
444
599
|
export declare const ANALYTICS_SCHEMA_VERSION = 1;
|
|
445
|
-
export type AnalyticsEventType = "session.start" | "prompt.submitted" | "tool.call" | "turn.completed" | "session.end" | "session.switched" | "macro.invoked" | "visualize.triggered" | "consent.changed" | "session.created" | "
|
|
600
|
+
export type AnalyticsEventType = "session.start" | "prompt.submitted" | "tool.call" | "turn.completed" | "session.end" | "session.switched" | "macro.invoked" | "visualize.triggered" | "consent.changed" | "session.created" | "mcp.install";
|
|
446
601
|
/**
|
|
447
602
|
* The normalized event — the shape that (with opt-in) is batched to the
|
|
448
603
|
* remote collector and always appended to events.ndjson locally.
|
|
@@ -450,9 +605,17 @@ export type AnalyticsEventType = "session.start" | "prompt.submitted" | "tool.ca
|
|
|
450
605
|
*/
|
|
451
606
|
export interface AnalyticsEvent {
|
|
452
607
|
eventId: string;
|
|
453
|
-
/**
|
|
608
|
+
/**
|
|
609
|
+
* Per-harnessSessionId counter from 1, for loss detection *within one
|
|
610
|
+
* process epoch* — NOT a session-lifetime ordering key. The counter lives
|
|
611
|
+
* in the server's memory (core/collector/seq.ts), so it restarts at 1 on
|
|
612
|
+
* every harness boot and on every fresh pty for the same session: a real
|
|
613
|
+
* resumed session reads 1, 2 then 1, 1, 1. A reset means "new epoch", never
|
|
614
|
+
* "events lost". Anything that orders events must sort by `(ts, seq)` —
|
|
615
|
+
* see core/session-record.ts.
|
|
616
|
+
*/
|
|
454
617
|
seq: number;
|
|
455
|
-
/** ISO-8601, client clock.
|
|
618
|
+
/** ISO-8601, client clock. Primary ordering key; `seq` only breaks ties. */
|
|
456
619
|
ts: string;
|
|
457
620
|
/** Sapiom user id from auth; null when not logged in. */
|
|
458
621
|
userId: string | null;
|
|
@@ -487,11 +650,158 @@ export interface CollectorBatch {
|
|
|
487
650
|
context: CollectorContext;
|
|
488
651
|
events: AnalyticsEvent[];
|
|
489
652
|
}
|
|
653
|
+
/** One tool invocation inside a turn, from a `tool.call` event. */
|
|
654
|
+
export interface SessionRecordToolCall {
|
|
655
|
+
/** Tool name as the agent reported it; null when the event omitted one. */
|
|
656
|
+
name: string | null;
|
|
657
|
+
/** Stringified tool input (JSON for structured inputs), possibly truncated. */
|
|
658
|
+
input: string | null;
|
|
659
|
+
/** Truncated stringified tool result — see {@link responseTruncated}. */
|
|
660
|
+
responseSummary: string | null;
|
|
661
|
+
/** True when the stored response hit the collector's size cap and the real
|
|
662
|
+
* output was longer. The missing bytes are not recoverable from our log. */
|
|
663
|
+
responseTruncated: boolean;
|
|
664
|
+
/** ISO-8601 timestamp of the `tool.call` event. */
|
|
665
|
+
at: string;
|
|
666
|
+
}
|
|
667
|
+
/** One prompt→response turn. A turn opens on `prompt.submitted` and closes on
|
|
668
|
+
* `turn.completed`; tool calls in between attach to it. */
|
|
669
|
+
export interface SessionRecordTurn {
|
|
670
|
+
/** 1-based ordinal in the record — stable for deep links and test asserts. */
|
|
671
|
+
index: number;
|
|
672
|
+
/**
|
|
673
|
+
* The human prompt that opened the turn. Null for a turn our events imply
|
|
674
|
+
* but never saw a prompt for: tool calls or a completion that arrived with
|
|
675
|
+
* no open turn (a session whose recording started mid-turn, or an agent-
|
|
676
|
+
* initiated turn). Rendered as an explicit "no recorded prompt", never faked.
|
|
677
|
+
*/
|
|
678
|
+
prompt: string | null;
|
|
679
|
+
/** ISO-8601 timestamp of the opening `prompt.submitted`; null when absent. */
|
|
680
|
+
promptAt: string | null;
|
|
681
|
+
toolCalls: SessionRecordToolCall[];
|
|
682
|
+
/**
|
|
683
|
+
* The assistant's final message for the turn. This is the Stop hook's LAST
|
|
684
|
+
* assistant message only — narration *between* tool calls is not in our
|
|
685
|
+
* event stream (see core/collector/transcript.ts). Null when the harness
|
|
686
|
+
* never captured one (Codex's rollout has no equivalent field).
|
|
687
|
+
*/
|
|
688
|
+
assistantText: string | null;
|
|
689
|
+
/** Model that produced the turn, when the transcript backfill supplied it. */
|
|
690
|
+
model: string | null;
|
|
691
|
+
usage: {
|
|
692
|
+
inputTokens: number | null;
|
|
693
|
+
outputTokens: number | null;
|
|
694
|
+
} | null;
|
|
695
|
+
/** ISO-8601 timestamp of the closing `turn.completed`; null when open. */
|
|
696
|
+
completedAt: string | null;
|
|
697
|
+
/** True when no `turn.completed` ever closed this turn — the session was
|
|
698
|
+
* killed mid-turn, or a new prompt superseded it. */
|
|
699
|
+
incomplete: boolean;
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* A known, structural gap in a reconstructed record. Codes (not prose) so the
|
|
703
|
+
* UI owns the wording and the set can grow without breaking older clients.
|
|
704
|
+
*
|
|
705
|
+
* - `truncated-tool-output`: at least one tool result hit the collector's size
|
|
706
|
+
* cap; the full output is not in our log.
|
|
707
|
+
* - `assistant-narration-gap`: a turn has tool calls plus a final assistant
|
|
708
|
+
* message, so whatever the agent said *between* those calls is missing.
|
|
709
|
+
* - `missing-assistant-text`: a turn completed with no assistant text at all
|
|
710
|
+
* (Codex, whose rollout carries no equivalent of the Stop hook's field).
|
|
711
|
+
* - `incomplete-final-turn`: the last turn never completed — the session
|
|
712
|
+
* ended mid-turn.
|
|
713
|
+
* - `compacted-archive`: the record was read from its archived copy, whose
|
|
714
|
+
* tool inputs and results are clipped to keep it bounded (see
|
|
715
|
+
* core/record-archive.ts). The conversation is whole; the tool payloads
|
|
716
|
+
* inside it are excerpts.
|
|
717
|
+
* - `dropped-early-turns`: the archived copy kept only the most recent turns —
|
|
718
|
+
* `turns` holds fewer of them than `turnCount` says happened.
|
|
719
|
+
*/
|
|
720
|
+
export type SessionRecordLimitation = "truncated-tool-output" | "assistant-narration-gap" | "missing-assistant-text" | "incomplete-final-turn" | "compacted-archive" | "dropped-early-turns";
|
|
721
|
+
/** `GET /api/sessions/:id/record` response. */
|
|
722
|
+
export interface SessionRecord {
|
|
723
|
+
/** The harness session this record was folded from. When several harness
|
|
724
|
+
* sessions share one agent session (a resumed conversation), this is the
|
|
725
|
+
* first of them and `mergedSessionIds` lists them all. */
|
|
726
|
+
harnessSessionId: string;
|
|
727
|
+
/** Every harnessSessionId folded into this record, in first-seen order. */
|
|
728
|
+
mergedSessionIds: string[];
|
|
729
|
+
/** The agent's own session id, when our events carried one. */
|
|
730
|
+
agentSessionId: string | null;
|
|
731
|
+
harness: HarnessKind;
|
|
732
|
+
/** cwd from the `session.start` event; null when it never recorded one. */
|
|
733
|
+
cwd: string | null;
|
|
734
|
+
/** ISO-8601 of the earliest event in the record. */
|
|
735
|
+
startedAt: string | null;
|
|
736
|
+
/** ISO-8601 of the `session.end` event; null for a session that never
|
|
737
|
+
* reported ending (killed, or crashed). */
|
|
738
|
+
endedAt: string | null;
|
|
739
|
+
turns: SessionRecordTurn[];
|
|
740
|
+
/**
|
|
741
|
+
* Human prompts in the CONVERSATION — `turns.filter(t => t.prompt != null)
|
|
742
|
+
* .length` for a record folded from events, and still that same count for an
|
|
743
|
+
* archived record whose oldest turns were dropped to fit its size cap. It
|
|
744
|
+
* describes what happened, not what survived, so a history row's turn count
|
|
745
|
+
* doesn't change when its events get swept; `dropped-early-turns` in
|
|
746
|
+
* {@link limitations} is what says `turns` holds fewer than this.
|
|
747
|
+
*/
|
|
748
|
+
turnCount: number;
|
|
749
|
+
/** Events folded into this record (including ones no turn field shows).
|
|
750
|
+
* Like {@link turnCount}, counted before any archive compaction. */
|
|
751
|
+
eventCount: number;
|
|
752
|
+
/** Always true. Present on the wire so no client can mistake a record for a
|
|
753
|
+
* verbatim replay of what the user saw in their terminal. */
|
|
754
|
+
reconstructed: true;
|
|
755
|
+
/**
|
|
756
|
+
* ISO-8601 of when this record was written to the durable archive
|
|
757
|
+
* (`~/.sapiom/harness/records/`), or null when it was folded from the live
|
|
758
|
+
* event log. Non-null therefore means "this is the archived copy": bounded,
|
|
759
|
+
* compacted, and — unlike the events — still here after the analytics sink's
|
|
760
|
+
* retention sweep. The UI says so where the user reads it.
|
|
761
|
+
*/
|
|
762
|
+
archivedAt: string | null;
|
|
763
|
+
limitations: SessionRecordLimitation[];
|
|
764
|
+
}
|
|
490
765
|
export interface CreateSessionRequest {
|
|
491
766
|
cwd: string;
|
|
492
767
|
harness: HarnessKind;
|
|
493
768
|
/** Profile id; omit for default. */
|
|
494
769
|
profile?: string;
|
|
770
|
+
/**
|
|
771
|
+
* Portable continue: seed this fresh session with a reconstruction of a
|
|
772
|
+
* prior one instead of asking the vendor to reattach. Accepts either a
|
|
773
|
+
* `harnessSessionId` or the agent's own session id — whichever the history
|
|
774
|
+
* row carries — and is what a `resumeMode: "rehydrate"` row posts.
|
|
775
|
+
*
|
|
776
|
+
* Best-effort by contract: an id our event log holds nothing for still
|
|
777
|
+
* creates the session, with `HarnessSession.rehydratedFrom` left null so the
|
|
778
|
+
* caller can tell that no context came across. Refusing instead would block
|
|
779
|
+
* the only thing still possible for that row (a fresh session in the same
|
|
780
|
+
* directory) on a summary that was never going to exist.
|
|
781
|
+
*/
|
|
782
|
+
rehydrateFrom?: string;
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* `POST /api/sessions/adopt` body — takes a transcript-only history row (one
|
|
786
|
+
* the registry never tracked, `SessionSummary.harnessSessionId` absent) into
|
|
787
|
+
* the registry and immediately resumes it, so a row whose transcript really is
|
|
788
|
+
* there reattaches to the agent instead of quietly starting a fresh session.
|
|
789
|
+
*
|
|
790
|
+
* The server re-verifies `resumeMode` itself via `HarnessAdapter.canResume`
|
|
791
|
+
* before registering anything — a client claim is never taken on trust, and a
|
|
792
|
+
* `rehydrate` row 409s with `SESSION_NOT_RESUMEABLE` rather than leaving a
|
|
793
|
+
* phantom record behind.
|
|
794
|
+
*/
|
|
795
|
+
export interface AdoptSessionRequest {
|
|
796
|
+
/** The agent's own conversation id, as reported by `GET /sessions/history`. */
|
|
797
|
+
agentSessionId: string;
|
|
798
|
+
harness: HarnessKind;
|
|
799
|
+
cwd: string;
|
|
800
|
+
/** Display title carried over from the history row. */
|
|
801
|
+
title: string;
|
|
802
|
+
/** When the transcript was last touched — becomes the adopted record's
|
|
803
|
+
* `createdAt`/`lastActiveAt` so history keeps sorting it where it was. */
|
|
804
|
+
lastActiveAt: string;
|
|
495
805
|
}
|
|
496
806
|
/** Inject text into the session pty (used by macros and the Visualize button). */
|
|
497
807
|
export interface InjectInputRequest {
|
|
@@ -585,6 +895,18 @@ export interface AppState {
|
|
|
585
895
|
/** The directory the CLI was launched against — the SPA prefills the
|
|
586
896
|
* new-session modal with this instead of recentDirs[0]. */
|
|
587
897
|
launchDir: string;
|
|
898
|
+
/**
|
|
899
|
+
* The HOST's default parent directory for NEW agent projects, before the
|
|
900
|
+
* user's `projectRoot` setting overrides it. The server supplies it because
|
|
901
|
+
* only the server knows which host it is running under: the Electron app
|
|
902
|
+
* passes `<launchDir>/projects` (keeping user code out of the state
|
|
903
|
+
* directory's own listing), while the CLI leaves it as `launchDir` — the
|
|
904
|
+
* developer `cd`'d somewhere on purpose.
|
|
905
|
+
*
|
|
906
|
+
* Optional so existing AppState constructors (tests, mocks) stay valid; the
|
|
907
|
+
* SPA falls back to `launchDir`, which is the CLI behaviour anyway.
|
|
908
|
+
*/
|
|
909
|
+
defaultProjectRoot?: string;
|
|
588
910
|
/** Harness kinds with a working binary on PATH at CLI boot (from doctor()),
|
|
589
911
|
* in default-preference order — `[0]` is what the auto-created boot
|
|
590
912
|
* session used. Optional: omitted by callers that construct AppState
|
|
@@ -613,14 +935,152 @@ export interface AppState {
|
|
|
613
935
|
* then falls back to the SDK's own default host. */
|
|
614
936
|
agentsBaseUrl?: string;
|
|
615
937
|
}
|
|
616
|
-
/**
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
938
|
+
/**
|
|
939
|
+
* The counts a complexity band was derived from, so a surface can explain the
|
|
940
|
+
* band instead of just asserting it. Mirrors core's `TemplateComplexityBasisDto`.
|
|
941
|
+
*/
|
|
942
|
+
export interface TemplateComplexityBasis {
|
|
943
|
+
/** Steps declared `kind: 'llm'` — each is a judgment point. */
|
|
944
|
+
llmSteps: number;
|
|
945
|
+
/** Model steps feeding directly into another model step (compounding variance). */
|
|
946
|
+
chainedLlmSteps: number;
|
|
947
|
+
/** Media-generation capabilities (image/video). */
|
|
948
|
+
mediaCapabilities: number;
|
|
949
|
+
capabilityCount: number;
|
|
950
|
+
stepCount: number;
|
|
951
|
+
/** Largest number of outgoing targets on any one step. */
|
|
952
|
+
maxFanOut: number;
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* How involved a template is, on a 1–5 band derived by core from the template's
|
|
956
|
+
* declared shape. `score` and `label` are redundant on purpose: the score orders
|
|
957
|
+
* and the label reads. A surface renders one of them, never a raw weighted sum —
|
|
958
|
+
* that would invite false precision on what is explicitly a rough estimate.
|
|
959
|
+
*
|
|
960
|
+
* Owned by core's `workflows/template-complexity.ts`. The Studio computes
|
|
961
|
+
* nothing: whether the band is derived (today) or authored (SAP-2086/2087), this
|
|
962
|
+
* type is unchanged and this surface reads whatever core serves.
|
|
963
|
+
*/
|
|
964
|
+
export interface TemplateComplexity {
|
|
965
|
+
/** 1–5, monotonic in `label`. */
|
|
966
|
+
score: number;
|
|
967
|
+
/** `Minimal` | `Simple` | `Moderate` | `Involved` | `Advanced`. A loose string
|
|
968
|
+
* for the same reason `category` is: the band vocabulary is owned upstream. */
|
|
969
|
+
label: string;
|
|
970
|
+
basis: TemplateComplexityBasis;
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* A gallery card, relayed verbatim from core's `GET /v1/workflows/templates`
|
|
974
|
+
* (`TemplateSummaryDto`). The Studio does NOT own this taxonomy — the registry
|
|
975
|
+
* in the public sapiom-js repo does, and core derives the complexity band from
|
|
976
|
+
* each template's declared shape. Fields are carried through untouched so the
|
|
977
|
+
* Studio's list and the dashboard's Template library can never disagree.
|
|
978
|
+
*/
|
|
979
|
+
export interface TemplateSummary {
|
|
980
|
+
id: string;
|
|
981
|
+
name: string;
|
|
982
|
+
description: string;
|
|
983
|
+
tags: string[];
|
|
984
|
+
/** Registry outcome-axis id (`starter`, `revenue-marketing`, …), or null for a
|
|
985
|
+
* template predating the field. A loose string on purpose: the taxonomy is
|
|
986
|
+
* owned upstream, so an unrecognised id buckets as uncategorised rather than
|
|
987
|
+
* dropping the card. */
|
|
988
|
+
category: string | null;
|
|
989
|
+
/** What starts a run (`on-demand` | `scheduled` | `on-webhook` | `on-event`),
|
|
990
|
+
* or null when undeclared. */
|
|
991
|
+
cadence: string | null;
|
|
992
|
+
stepCount: number;
|
|
993
|
+
/** Dotted catalog capability ids (e.g. `web.search`). */
|
|
994
|
+
capabilities: string[];
|
|
995
|
+
/**
|
|
996
|
+
* How involved the template is. Replaced an estimated per-run cost that core
|
|
997
|
+
* could only compute for 5 of 26 templates; a band is defined for every one.
|
|
998
|
+
*
|
|
999
|
+
* NULLABLE HERE THOUGH CORE TYPES IT REQUIRED, and that is not belt-and-braces.
|
|
1000
|
+
* The Studio is a published npm package: an old copy can point at any backend,
|
|
1001
|
+
* and a fresh copy can point at a backend that predates the field (a local
|
|
1002
|
+
* stack, a self-hosted one, prod before the promotion). Typing it required
|
|
1003
|
+
* would put an unguarded dereference in the row renderer and take out the whole
|
|
1004
|
+
* dialog. One row degrading to an em dash is the right failure.
|
|
1005
|
+
*
|
|
1006
|
+
* Note the em dash means something new: it used to be "no cost estimate
|
|
1007
|
+
* exists", the majority case; it now means "this response predates the band" —
|
|
1008
|
+
* a different claim wearing the same glyph, and one nobody should ever see
|
|
1009
|
+
* against a current backend.
|
|
1010
|
+
*/
|
|
1011
|
+
complexity: TemplateComplexity | null;
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* One step of a template's declared graph (core's `DefinitionStepDto` subset the
|
|
1015
|
+
* detail pane renders).
|
|
1016
|
+
*
|
|
1017
|
+
* `kind`/`sublabel` come from `classifyStepKind` — the SAME precedence the canvas
|
|
1018
|
+
* applies to a real definition — so the preview and the post-clone canvas agree.
|
|
1019
|
+
* There is deliberately no `terminal` boolean: the four transition kinds
|
|
1020
|
+
* (`continue`/`pause`/`terminate`/`fail`) do not collapse into one, and pretending
|
|
1021
|
+
* they do renders a fail-only sink as a green success exit.
|
|
1022
|
+
*/
|
|
1023
|
+
export interface TemplateStepView {
|
|
1024
|
+
name: string;
|
|
1025
|
+
description: string | null;
|
|
1026
|
+
capabilities: string[];
|
|
1027
|
+
/** Canvas node kind: `entry` | `step` | `pause` | `terminal-success` | `terminal-warn`. */
|
|
1028
|
+
kind: string;
|
|
1029
|
+
/** One-line role, e.g. `entry`, `step · can also fail`, `terminal · success`. */
|
|
1030
|
+
sublabel: string;
|
|
1031
|
+
}
|
|
1032
|
+
/** One edge of a template's declared graph (core's `DefinitionTransitionDto`). */
|
|
1033
|
+
export interface TemplateTransitionView {
|
|
1034
|
+
from: string;
|
|
1035
|
+
to: string;
|
|
1036
|
+
/** The signal a `pause` edge waits for; null for a plain `continue`. */
|
|
1037
|
+
label: string | null;
|
|
1038
|
+
/** `cross` for a pause edge (as the canvas draws it), else continue flow. */
|
|
1039
|
+
kind: "continue" | "pause";
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* `GET /api/templates/:id` — the summary plus the rich manifest the detail pane
|
|
1043
|
+
* renders. Core expands the registry's hand-authored step list into the same
|
|
1044
|
+
* graph shapes a real definition uses, so the preview and the post-clone canvas
|
|
1045
|
+
* are the same vocabulary.
|
|
1046
|
+
*/
|
|
1047
|
+
export interface TemplateDetailView extends TemplateSummary {
|
|
1048
|
+
/** Prose from the co-located `template.json`; null when the manifest omits it. */
|
|
1049
|
+
whatItDoes: string | null;
|
|
1050
|
+
/** Path inside the sapiom-js repo the fork is seeded from. */
|
|
1051
|
+
sourcePath: string | null;
|
|
1052
|
+
steps: TemplateStepView[];
|
|
1053
|
+
transitions: TemplateTransitionView[];
|
|
1054
|
+
author: {
|
|
1055
|
+
name: string;
|
|
1056
|
+
url: string | null;
|
|
1057
|
+
} | null;
|
|
1058
|
+
useCases: string[];
|
|
1059
|
+
/** Markdown. */
|
|
1060
|
+
notes: string | null;
|
|
1061
|
+
examples: Array<{
|
|
1062
|
+
title: string | null;
|
|
1063
|
+
input: unknown;
|
|
1064
|
+
output: unknown;
|
|
1065
|
+
}>;
|
|
1066
|
+
/** Credentials the template needs supplied before a deployed run works. */
|
|
1067
|
+
requiredSecrets: Array<{
|
|
1068
|
+
key: string;
|
|
1069
|
+
label: string;
|
|
1070
|
+
description: string | null;
|
|
1071
|
+
}>;
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* `GET /api/templates` response. `source` tells the SPA whether it is looking at
|
|
1075
|
+
* the live catalog or the offline fallback, so the dialog can say so instead of
|
|
1076
|
+
* silently presenting two entries as the whole gallery — the failure mode this
|
|
1077
|
+
* endpoint exists to fix.
|
|
1078
|
+
*/
|
|
1079
|
+
export interface TemplateListResponse {
|
|
1080
|
+
templates: TemplateSummary[];
|
|
1081
|
+
source: "live" | "fallback";
|
|
1082
|
+
/** Why the live fetch was not used, when `source` is `fallback`. */
|
|
1083
|
+
reason?: "signed-out" | "unreachable";
|
|
624
1084
|
}
|
|
625
1085
|
export interface HarnessSettings {
|
|
626
1086
|
telemetryOptIn: boolean;
|
|
@@ -632,10 +1092,43 @@ export interface HarnessSettings {
|
|
|
632
1092
|
* Persisted so the notice never appears again after the first dismiss.
|
|
633
1093
|
*/
|
|
634
1094
|
telemetryNoticeDismissed?: boolean;
|
|
1095
|
+
/**
|
|
1096
|
+
* Where NEW agent projects are created (the add-workspace template and idea
|
|
1097
|
+
* doors). Absent until the user changes it, in which case the host default
|
|
1098
|
+
* (`AppState.defaultProjectRoot`) applies.
|
|
1099
|
+
*
|
|
1100
|
+
* Deliberately the same value the door itself edits: changing the root while
|
|
1101
|
+
* creating a project saves it as the default, so there is one place to set it
|
|
1102
|
+
* rather than a door value that silently diverges from a settings value.
|
|
1103
|
+
*/
|
|
1104
|
+
projectRoot?: string;
|
|
1105
|
+
/**
|
|
1106
|
+
* Opt-in: periodically fold a live session's record into a ≤500-word rolling
|
|
1107
|
+
* summary (see core/rolling-summary.ts), which a later portable continue
|
|
1108
|
+
* reads to explain what the session was *for* rather than only what it last
|
|
1109
|
+
* did. Off by default because it spends tokens on a background LLM call the
|
|
1110
|
+
* user never asked for. With it off, briefs degrade to last-N-turns.
|
|
1111
|
+
*/
|
|
1112
|
+
rollingSummary?: boolean;
|
|
635
1113
|
}
|
|
636
1114
|
export interface FsDirEntry {
|
|
637
1115
|
name: string;
|
|
638
1116
|
path: string;
|
|
1117
|
+
/**
|
|
1118
|
+
* Whether this directory directly contains AGENT_PROJECT_MARKER.
|
|
1119
|
+
*
|
|
1120
|
+
* Load-bearing, not decorative: without it a picker cannot tell an agent
|
|
1121
|
+
* project from any other folder, so it has to offer every escape hatch
|
|
1122
|
+
* (register / scaffold / template / bulk-scan / install-MCP) at all times —
|
|
1123
|
+
* which is exactly what made the old add-workspace dialog unusable. With it,
|
|
1124
|
+
* those become outcomes of what we found rather than permanent options.
|
|
1125
|
+
*
|
|
1126
|
+
* Only ONE level deep, matching this endpoint's contract. A `false` here does
|
|
1127
|
+
* not mean the subtree is empty of projects — a container folder whose
|
|
1128
|
+
* children are projects reports `false` (the rail's recursive scan is the
|
|
1129
|
+
* thing that answers "anything under here?").
|
|
1130
|
+
*/
|
|
1131
|
+
hasAgentProject: boolean;
|
|
639
1132
|
}
|
|
640
1133
|
/**
|
|
641
1134
|
* GET /api/fs/list?path= response — directories only, one level deep.
|
|
@@ -684,11 +1177,10 @@ export interface MacroDef {
|
|
|
684
1177
|
kind: "open-url";
|
|
685
1178
|
url: string;
|
|
686
1179
|
}
|
|
687
|
-
/**
|
|
688
|
-
*
|
|
689
|
-
*
|
|
690
|
-
*
|
|
691
|
-
* session is unbound. */
|
|
1180
|
+
/** Refresh of the bound workflow's canvas: invalidates the extraction
|
|
1181
|
+
* cache and re-renders the fully deterministic diagram (structure +
|
|
1182
|
+
* derived annotations, no LLM, no user token) — no pty involved. A cheap
|
|
1183
|
+
* no-op when the session is unbound. */
|
|
692
1184
|
| {
|
|
693
1185
|
kind: "render-canvas";
|
|
694
1186
|
};
|