@sapiom/harness 0.1.6 → 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 +90 -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 +12 -65
- package/dist/cli/bin.js.map +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/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/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 +41 -0
- package/dist/core/canvas-run-state.d.ts.map +1 -1
- package/dist/core/canvas-run-state.js +136 -0
- 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/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/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/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/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/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 +321 -50
- 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 -48
- package/dist/server/rest.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 +477 -29
- 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/index.html +2 -2
- package/package.json +5 -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/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/web/assets/index-DCtyI4VW.css +0 -32
- package/dist/web/assets/index-WSw5RvYR.js +0 -445
- package/dist/web/welcome-hero-dark.png +0 -0
- package/dist/web/welcome-hero-light.png +0 -0
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
|
|
@@ -494,9 +605,17 @@ export type AnalyticsEventType = "session.start" | "prompt.submitted" | "tool.ca
|
|
|
494
605
|
*/
|
|
495
606
|
export interface AnalyticsEvent {
|
|
496
607
|
eventId: string;
|
|
497
|
-
/**
|
|
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
|
+
*/
|
|
498
617
|
seq: number;
|
|
499
|
-
/** ISO-8601, client clock.
|
|
618
|
+
/** ISO-8601, client clock. Primary ordering key; `seq` only breaks ties. */
|
|
500
619
|
ts: string;
|
|
501
620
|
/** Sapiom user id from auth; null when not logged in. */
|
|
502
621
|
userId: string | null;
|
|
@@ -531,11 +650,158 @@ export interface CollectorBatch {
|
|
|
531
650
|
context: CollectorContext;
|
|
532
651
|
events: AnalyticsEvent[];
|
|
533
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
|
+
}
|
|
534
765
|
export interface CreateSessionRequest {
|
|
535
766
|
cwd: string;
|
|
536
767
|
harness: HarnessKind;
|
|
537
768
|
/** Profile id; omit for default. */
|
|
538
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;
|
|
539
805
|
}
|
|
540
806
|
/** Inject text into the session pty (used by macros and the Visualize button). */
|
|
541
807
|
export interface InjectInputRequest {
|
|
@@ -629,6 +895,18 @@ export interface AppState {
|
|
|
629
895
|
/** The directory the CLI was launched against — the SPA prefills the
|
|
630
896
|
* new-session modal with this instead of recentDirs[0]. */
|
|
631
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;
|
|
632
910
|
/** Harness kinds with a working binary on PATH at CLI boot (from doctor()),
|
|
633
911
|
* in default-preference order — `[0]` is what the auto-created boot
|
|
634
912
|
* session used. Optional: omitted by callers that construct AppState
|
|
@@ -657,14 +935,152 @@ export interface AppState {
|
|
|
657
935
|
* then falls back to the SDK's own default host. */
|
|
658
936
|
agentsBaseUrl?: string;
|
|
659
937
|
}
|
|
660
|
-
/**
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
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";
|
|
668
1084
|
}
|
|
669
1085
|
export interface HarnessSettings {
|
|
670
1086
|
telemetryOptIn: boolean;
|
|
@@ -676,10 +1092,43 @@ export interface HarnessSettings {
|
|
|
676
1092
|
* Persisted so the notice never appears again after the first dismiss.
|
|
677
1093
|
*/
|
|
678
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;
|
|
679
1113
|
}
|
|
680
1114
|
export interface FsDirEntry {
|
|
681
1115
|
name: string;
|
|
682
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;
|
|
683
1132
|
}
|
|
684
1133
|
/**
|
|
685
1134
|
* GET /api/fs/list?path= response — directories only, one level deep.
|
|
@@ -728,11 +1177,10 @@ export interface MacroDef {
|
|
|
728
1177
|
kind: "open-url";
|
|
729
1178
|
url: string;
|
|
730
1179
|
}
|
|
731
|
-
/**
|
|
732
|
-
*
|
|
733
|
-
*
|
|
734
|
-
*
|
|
735
|
-
* 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. */
|
|
736
1184
|
| {
|
|
737
1185
|
kind: "render-canvas";
|
|
738
1186
|
};
|