@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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,95 @@
|
|
|
1
1
|
# @sapiom/harness
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 3f96e37: Canvas board redesign + deterministic step/workflow metadata.
|
|
8
|
+
|
|
9
|
+
- **Board redesign** matching the new design: in-drawer step navigation removed (the chart is beside it), chat split into a standalone toggleable panel independent of the info panel, the board subheader dropped with the deployed pill and expand relocated to the tab bar, and the manual "Render diagram" button replaced by auto-render on bind/session-start.
|
|
10
|
+
- **Deterministic metadata:** the renderer surfaces per-step `description` / `inputSchema` / `capabilities` / `timeoutMs` and a workflow Overview payload, all read from the manifest (no LLM in the render path). When a step doesn't call a capability or declare a description, the field is simply absent — the shape summary still renders.
|
|
11
|
+
- **Capability auto-detect** from `sapiom.*` call sites in the workflow source, attributed to the `defineStep` block the call sits in (calls in shared helpers are left unattributed rather than mis-billed to the nearest step).
|
|
12
|
+
|
|
13
|
+
Reads the new optional `description` / `capabilities` fields from `@sapiom/agent`; workflows on an older SDK render with those fields blank.
|
|
14
|
+
|
|
15
|
+
- 7b98507: Studio: show a template's complexity band where the per-run cost estimate used to be.
|
|
16
|
+
|
|
17
|
+
The Templates dialog rendered `estCostPerRunUsd`, relayed from the same core endpoint the dashboard's Template library reads. Core stopped serving that field: it could only price capabilities metered per `call`, so the estimate was `null` for 21 of 26 templates, and a number that honest for 5 of them was not worth a slot on every card. Core now derives a **complexity band** — `Minimal` through `Advanced`, 1–5 — from each template's declared shape.
|
|
18
|
+
|
|
19
|
+
Without this change nothing errored, which is why it would have gone unnoticed: `template-catalog.ts`'s defensive narrowing turned the missing field into `null` and the formatter turned that into an em dash, so **every** template read `—` where a cost used to be.
|
|
20
|
+
|
|
21
|
+
`TemplateSummary.estCostPerRunUsd` is replaced by `TemplateSummary.complexity`, and the new `TemplateComplexity` / `TemplateComplexityBasis` types are exported alongside it. **Breaking for embedders** (hence `minor`): `src/index.ts` re-exports `./shared/types.js`, so code reading `estCostPerRunUsd` off a summary stops compiling.
|
|
22
|
+
|
|
23
|
+
The band is read, never computed. Whether core derives it (today) or serves an authored one later, this surface is unchanged.
|
|
24
|
+
|
|
25
|
+
`complexity` is typed nullable here even though core types it required, and that is deliberate rather than belt-and-braces. This is a published npm package: an old copy can point at any backend, and a fresh copy can point at a backend that predates the field — a local stack, a self-hosted one, production before a promotion. An unguarded dereference in the row renderer would take out the whole dialog, so a band that isn't there degrades that one row to an em dash instead. Note the glyph's meaning has changed: it used to mean "no cost estimate exists", the majority case; it now means "this response predates the band", and nobody should ever see it against a current backend.
|
|
26
|
+
|
|
27
|
+
On the card the band rides beside the step count, with the counts behind it in the tooltip. In the detail pane, the section retitles to "Capabilities and complexity" and the old three-state cost note collapses to a single line — the band plus what produced it ("2 model steps, 1 chained, 5 steps, 1 capability"), so it reads as an estimate of shape rather than an opaque verdict. Text only, no meter or dots, matching the dashboard's gallery.
|
|
28
|
+
|
|
29
|
+
- b199f93: Studio: read the template gallery live, and stop pitching the product at returning users.
|
|
30
|
+
|
|
31
|
+
**Templates come from the real catalog.** `web/src/lib/templates.ts` shipped a hardcoded copy of two registry entries (pinned at harness 0.1.4 / `f0e3406`) because, as its header said, no listing API exposed the gallery to any client. One exists now, so the Studio showed 2 templates while the dashboard's Template library showed 26. New `GET /api/templates` and `GET /api/templates/:id` relay core's `GET /v1/workflows/templates{,/:id}` — the same endpoint the dashboard renders, so the two surfaces can no longer drift. The dialog gains category grouping, search, and a per-template complexity band. Two contract details, both verified against a running backend: the core surface authenticates with `Authorization: Bearer` (the `x-sapiom-api-key` header the _agents_ surface takes returns 401 here), and the path carries the `/v1` prefix. The API key stays server-side, as with the runs router; a 401/403 triggers one credential refresh and retry. Signed out or with core unreachable, the dialog falls back to the bundled offline starters **and says which** — silence is what let a two-entry list read as the whole gallery.
|
|
32
|
+
|
|
33
|
+
The detail pane now projects the graph core actually serves — the engine's `DefinitionStepDto`/`DefinitionTransitionDto` shapes, where a step carries `stepName` and a singular `capabilityId`, edges reference steps by namespaced `id`, and a step's role is decided by its transition kinds rather than array order. Node kinds come from `classifyStepKind`, extracted from `canvas-graph.ts`'s `classifyNode` and now shared: the preview claims parity with the canvas, so it must not own a second copy of that precedence. All four kinds (`continue`/`pause`/`terminate`/`fail`) survive the projection — a fail-only sink renders amber "needs attention" rather than a green success exit, a `continue`-plus-`terminate` gate stays a mid-flow step, and a pause step shows its signal.
|
|
34
|
+
|
|
35
|
+
**Overview is a working surface, not a pitch.** `showWelcome` was `overviewSelected || (firstRun && !hasLiveSession)`, so the first-run hero rendered whenever the Overview tab was selected — including for someone with a rail full of workspaces. The hero is now genuine-first-run only; returning users get their recent workspaces, with the Docs / Templates / New workspace action band shared by both states.
|
|
36
|
+
|
|
37
|
+
**Workspace terminology.** A workspace is a folder, matching the rail and the editor convention users arrive with: the rail header reads "Workspaces", and "New project" / "Add project" / "Project directory" become their workspace equivalents. "Agent project" is left alone deliberately — that is the SDK's own term for a `sapiom.json` directory, and `sapiom agents init` and `AGENTS.md` both use it.
|
|
38
|
+
|
|
39
|
+
**The Sample project action is gone**, along with `POST /api/sample-project` and the exported `SampleProjectSeedResponse` type (nothing else in the repo referenced either). `core/example-seed.ts` remains — `scripts/seed-example.mjs` still uses it for demo prep.
|
|
40
|
+
|
|
41
|
+
**Breaking for embedders** (hence `minor`, not `patch`): `src/index.ts` re-exports `./shared/types.js`, so `SampleProjectSeedResponse` was part of the published surface, and `HarnessServerOptions` loses its `sampleProjectRoot` field. Code typed against either stops compiling. `TemplateStepView` also carries `kind`/`sublabel` rather than a `terminal` boolean — see above for why that collapse was wrong. No in-repo consumer is affected.
|
|
42
|
+
|
|
43
|
+
### Patch Changes
|
|
44
|
+
|
|
45
|
+
- c32f818: Session history now outlives the event log it is rebuilt from (SAP-2060).
|
|
46
|
+
|
|
47
|
+
H2 made past sessions readable by folding them out of `events.ndjson`. That file is an analytics sink with an analytics sink's retention — 50 MB / 30 days, truncated oldest-first — so a session a user could read today would quietly stop existing next month, with no way to tell "never recorded" from "swept".
|
|
48
|
+
|
|
49
|
+
- New `src/core/record-archive.ts`: at session end the folded `SessionRecord` is compacted and written to `~/.sapiom/harness/records/<harnessSessionId>.json`. Tool inputs and results are clipped hard (they are most of the bytes and the least useful part of a months-old record); prompts and assistant text — the conversation — are kept whole. Over ~64 KB it drops whole turns oldest-first, never below one. Both losses are declared as new `SessionRecord.limitations` codes (`compacted-archive`, `dropped-early-turns`) and the transcript view spells them out, alongside a note naming the record as an archived copy (`SessionRecord.archivedAt`).
|
|
50
|
+
- Bounded, not merely durable: the store enforces a 16 MB total and a 365-day age cap, oldest-first, swept after every write and once at boot. `turnCount` deliberately keeps the conversation's count even when turns were dropped, so a history row's count doesn't change when its events are swept.
|
|
51
|
+
- Written on the normal end of a session (the `SessionEnd` hook's event, via a new `onEventPersisted` ingest seam — fired from `onNormalizedEvent` it would race the append and store a record with no `endedAt`) and on an abnormal one (the session's transition to `exited`, which is all a killed pty gives us). A boot pass archives conversations the log still holds but the archive doesn't, which is what keeps history that predates this feature — and any session lost to a force-kill — from disappearing at its 30-day mark.
|
|
52
|
+
- Reads prefer whichever source still holds the WHOLE conversation. The ticket asked for "prefer the archive, fall back to events"; taken literally that serves a compacted excerpt for a session that ended a minute ago, and freezes a resumed conversation's record at its first exit. Since retention truncates oldest-first, an intact first event means the log lost nothing, so: the log when it still holds the beginning or has events newer than the archive, the archive otherwise, null when neither has anything (still an honest 404).
|
|
53
|
+
- Records live in their own root, NOT under `<generated>/<sessionId>/` as the ticket suggested: that directory is deleted the moment a session's pty exits and swept 7 days after going stale, both shorter than the 30 days of events the archive exists to outlive.
|
|
54
|
+
|
|
55
|
+
- 460bfc1: Expose the embedding surface so a second host (the Electron desktop app) can reuse the harness instead of forking it: re-export `startServer`/`HarnessServer`/`HarnessServerOptions` plus the setup helpers (`runDoctor`, `pickDefaultHarness`, `ensureAuthenticated`, `getOrCreateMachineId`, `ensureSpawnHelperExecutable`, settings, install-command constants) from the package entry. `saveSettings` is part of that surface: a host that prompts for telemetry consent natively (instead of through the TTY-shaped `ensureConsent`) must persist the answer itself, or the settings file — which the UI's analytics indicator and the next launch both read — never learns about it. No CLI behavior change.
|
|
56
|
+
|
|
57
|
+
Also run the Canvas step-graph check subprocess correctly when embedded in Electron: it spawns `process.execPath` (the Electron binary when embedded), so it now passes `ELECTRON_RUN_AS_NODE=1` — guarded by `process.versions.electron`, a no-op under the CLI's real Node. And `packageRoot()` (used as the subprocess `cwd`) now translates an `app.asar` path to its `app.asar.unpacked` twin, since a `cwd` inside the asar archive fails with `spawn ENOTDIR` (the host must `asarUnpack` the harness + its deps).
|
|
58
|
+
|
|
59
|
+
- c32f818: Portable continue: a session the agent can no longer reattach to is now continuable, by seeding a fresh session with our own reconstruction of it (SAP-2059).
|
|
60
|
+
|
|
61
|
+
H1 stopped offering Resume on rows that would fail; that left them with nowhere to go — a disabled button and "start a new session instead", even when our event log held the whole conversation. Resume-as-reattach can only ever work for the vendor that wrote the transcript, on the machine that wrote it. This adds the other half: `POST /api/sessions` takes `rehydrateFrom`, and the new session launches with a bounded markdown briefing about the old one.
|
|
62
|
+
|
|
63
|
+
- New `src/core/resume-brief.ts`: `buildResumeBrief(record, opts)` renders a `SessionRecord` as ~6k tokens of markdown — what the session was (title, cwd, branch, bound workflow + `definitionId`), the rolling summary when present, the last N turns with tool calls collapsed to name + target, and files written / commands run derived from `tool.call` inputs. Over budget it drops turns oldest-first, then the (hard-capped) digests, and clamps the summary only as a last resort.
|
|
64
|
+
- It leads with an honesty header and spells out each of the record's `limitations` in prose. A brief that reads like restored memory is worse than no brief: the agent would assert file contents and command results it never saw.
|
|
65
|
+
- Delivery is per-adapter, declared not inferred (`HarnessAdapter.systemPromptDelivery`). Both shipped adapters use `launch-flag` — the brief is appended to the generated system-prompt file, which claude-code reads via `--append-system-prompt` and codex inlines as `developer_instructions`, so one code path serves both with no adapter change. A harness with no prompt flag declares `post-ready-injection` and gets the brief through the ordinary input path once the session reports `ready`, never into a TUI sitting on a trust prompt.
|
|
66
|
+
- New `src/core/rolling-summary.ts`: opt-in via `HarnessSettings.rollingSummary` (off by default; toggle in Settings). Every 10 completed turns and once at session end, a bounded headless run (`launchTask`, cheap model, `--max-turns 1`) folds the record into `<generated>/<sessionId>/summary.md`. Fully detached from the ingest path — a turn is never slower or riskier for it. Codex has no `launchTask`, so its briefs degrade to last-N-turns, as does everyone's with the setting off.
|
|
67
|
+
- `HarnessSession.rehydratedFrom` records what actually happened, not what was asked for: a `rehydrateFrom` id our log holds nothing for still creates the session, with the field null and the UI saying the continue carried no context.
|
|
68
|
+
- UI: `resumeFromHistory` branches on the server-verified `resumeMode` instead of guessing, and the dead-session pane offers "Continue here" (with what will and won't carry over) wherever it has a record to seed from, instead of a disabled Resume.
|
|
69
|
+
|
|
70
|
+
- b7f5b02: Verify resumability before offering Resume, so no past-session row is a button that's guaranteed to fail (SAP-2057).
|
|
71
|
+
|
|
72
|
+
A row's Resume badge was `agentSessionId != null` — "our SessionStart hook fired once", not "the agent still has this conversation". Since neither Claude Code nor Codex writes any transcript for a session that never received a prompt, one in three registry rows on a real machine (16 of 49 measured) offered Resume and answered with `No conversation found with session ID: …`, exit 1, and a dead pane offering Resume again. Transcript-only rows had the inverse bug: hardcoded un-resumable even when the transcript was right there, so opening one silently started a fresh session and dropped the conversation.
|
|
73
|
+
|
|
74
|
+
- `HarnessAdapter` gains `canResume(agentSessionId, cwd)` (never throws): one `stat` on the encoded transcript path for claude-code, a `session_meta` id+cwd match for codex.
|
|
75
|
+
- Both adapters now resolve symlinked cwds. Claude Code encodes the cwd's **realpath**, so a session in `/tmp/foo` (macOS: `/tmp` → `/private/tmp`) stores its transcript under a different encoding than the registry's cwd string — history discovery silently missed those rows before, and a resumability probe would have gone further and refused a resume that works.
|
|
76
|
+
- `SessionSummary` gains `resumeMode: "agent-resume" | "rehydrate"`, resolved server-side in `GET /api/sessions/history` for both row sources. Adapters now return `PastSessionRecord`, so they can't decide it themselves.
|
|
77
|
+
- `SessionManager.resume()` pre-flights `canResume()` and throws `SessionNotResumeableError` (409, with a reason naming the agent) instead of spawning a doomed pty.
|
|
78
|
+
- New `POST /api/sessions/adopt` wires up `registerHistorical()`: a transcript-only row whose conversation the agent really holds is adopted into the registry and genuinely resumed. The server re-verifies resumability itself, and the route is idempotent.
|
|
79
|
+
- Truthful durations: a resume that never reaches a live pty no longer stamps `lastActiveAt`, so an idle session stops reporting "Ran for 6h 25m" after a failed Resume, and `formatDuration` returns null on a zero span instead of inventing "under a minute".
|
|
80
|
+
- UI: rows render the verified `resumeMode` (`resumable` / `archived`, `checking…` until known); the dead pane and past-session pane disable Resume with the real reason instead of a generic one.
|
|
81
|
+
|
|
82
|
+
- 460bfc1: Install the seeded sample project's dependencies on first creation, so the Canvas step-graph renders on first view instead of failing with "Could not resolve @sapiom/agent / zod". `seedExampleProject` now runs a best-effort `npm install` right after scaffolding (before the initial commit, keeping the gitignored `node_modules` out of history); it's non-fatal (missing/offline npm falls back to the existing "ask your agent to fix it" Canvas prompt) and skippable via the new `installDependencies` option (default true; tests pass false to stay offline). Adds `dependenciesInstalled` to `SeedExampleProjectResult`.
|
|
83
|
+
- 2d25205: Fix starting a session on Windows. An agent installed by npm is `claude.cmd`, and node-pty spawns via `CreateProcess`, which performs no `PATHEXT` resolution and cannot execute a `.cmd` at all — so every session failed with `Cannot create process, error code: 2` while `doctor` reported the agent present (detection shells `where`, which _does_ resolve `PATHEXT`). Background tasks and macros failed the same way through `child_process.spawn`.
|
|
84
|
+
|
|
85
|
+
Both paths now resolve the shim to what it really runs — Claude Code ships a native `bin\claude.exe`, other packages a `cli.js` run under node — and spawn that directly. Deliberately **not** via `cmd.exe`: node-pty escapes `"` as `\"` for `CreateProcess`, but cmd only counts raw quotes, so one embedded quote desynchronises its parser and any following `&`/`|` becomes a command separator (CVE-2024-27980's class, reachable on every session since the codex adapter passes `JSON.stringify(prompt)` as an argument). Resolving the target keeps arguments in exactly one quoting layer, with no shell involved.
|
|
86
|
+
|
|
87
|
+
Also exports `resolveSpawnTarget` and `createClaudeCodeAdapter` for hosts that spawn a pty themselves or need to point the adapter at a different binary. No change on macOS or Linux.
|
|
88
|
+
|
|
89
|
+
- Updated dependencies [3f96e37]
|
|
90
|
+
- @sapiom/agent@0.7.0
|
|
91
|
+
- @sapiom/agent-core@0.9.10
|
|
92
|
+
|
|
3
93
|
## 0.1.6
|
|
4
94
|
|
|
5
95
|
### Patch Changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface CliOptions {
|
|
2
|
+
dir: string;
|
|
3
|
+
port: number;
|
|
4
|
+
login: boolean;
|
|
5
|
+
noAuth: boolean;
|
|
6
|
+
noTelemetry: boolean;
|
|
7
|
+
noOpen: boolean;
|
|
8
|
+
noSession: boolean;
|
|
9
|
+
dev: boolean;
|
|
10
|
+
/** Override the state root (default `~/.sapiom/harness`). Lets a throwaway
|
|
11
|
+
* root stand in for a fresh install — the first-run flow becomes testable
|
|
12
|
+
* without moving, renaming, or losing the real one. */
|
|
13
|
+
stateRoot?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function parseArgs(argv: string[]): CliOptions;
|
|
16
|
+
//# sourceMappingURL=args.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,GAAG,EAAE,OAAO,CAAC;IACb;;4DAEwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAoEpD"}
|
package/dist/cli/args.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI argument parsing, split out of bin.ts so it can be TESTED.
|
|
3
|
+
*
|
|
4
|
+
* bin.ts self-executes (`main().catch(...)`), so importing it from a test would
|
|
5
|
+
* boot the whole CLI. That is why bin-boot.test.ts used to carry a hand-copied
|
|
6
|
+
* MIRROR of this function — a copy that could (and did) drift from the real
|
|
7
|
+
* one, and which meant a new flag could ship with passing tests that never
|
|
8
|
+
* exercised it. This module is the single implementation both use.
|
|
9
|
+
*/
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
import { DEFAULT_PORT } from "../shared/types.js";
|
|
12
|
+
export function parseArgs(argv) {
|
|
13
|
+
let dir;
|
|
14
|
+
let port = DEFAULT_PORT;
|
|
15
|
+
let login = false;
|
|
16
|
+
let noAuth = false;
|
|
17
|
+
let noTelemetry = false;
|
|
18
|
+
let noOpen = false;
|
|
19
|
+
let noSession = false;
|
|
20
|
+
let dev = false;
|
|
21
|
+
let stateRoot;
|
|
22
|
+
for (let i = 0; i < argv.length; i++) {
|
|
23
|
+
const arg = argv[i];
|
|
24
|
+
switch (arg) {
|
|
25
|
+
case "--port": {
|
|
26
|
+
const value = argv[++i];
|
|
27
|
+
if (!value || Number.isNaN(Number(value))) {
|
|
28
|
+
throw new Error("--port requires a numeric value");
|
|
29
|
+
}
|
|
30
|
+
port = Number(value);
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
case "--login":
|
|
34
|
+
login = true;
|
|
35
|
+
break;
|
|
36
|
+
case "--no-auth":
|
|
37
|
+
noAuth = true;
|
|
38
|
+
break;
|
|
39
|
+
case "--no-telemetry":
|
|
40
|
+
noTelemetry = true;
|
|
41
|
+
break;
|
|
42
|
+
case "--state-root": {
|
|
43
|
+
const value = argv[++i];
|
|
44
|
+
if (!value)
|
|
45
|
+
throw new Error("--state-root requires a directory path");
|
|
46
|
+
stateRoot = value;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
case "--no-open":
|
|
50
|
+
noOpen = true;
|
|
51
|
+
break;
|
|
52
|
+
case "--no-session":
|
|
53
|
+
noSession = true;
|
|
54
|
+
break;
|
|
55
|
+
case "--dev":
|
|
56
|
+
dev = true;
|
|
57
|
+
break;
|
|
58
|
+
default:
|
|
59
|
+
if (arg.startsWith("--")) {
|
|
60
|
+
throw new Error(`Unknown flag: ${arg}`);
|
|
61
|
+
}
|
|
62
|
+
if (dir !== undefined) {
|
|
63
|
+
throw new Error(`Unexpected extra argument: ${arg}`);
|
|
64
|
+
}
|
|
65
|
+
dir = arg;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
dir: path.resolve(dir ?? process.cwd()),
|
|
70
|
+
port,
|
|
71
|
+
login,
|
|
72
|
+
noAuth,
|
|
73
|
+
noTelemetry,
|
|
74
|
+
noOpen,
|
|
75
|
+
noSession,
|
|
76
|
+
dev,
|
|
77
|
+
...(stateRoot ? { stateRoot } : {}),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=args.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAiBlD,MAAM,UAAU,SAAS,CAAC,IAAc;IACtC,IAAI,GAAuB,CAAC;IAC5B,IAAI,IAAI,GAAG,YAAY,CAAC;IACxB,IAAI,KAAK,GAAG,KAAK,CAAC;IAClB,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,WAAW,GAAG,KAAK,CAAC;IACxB,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,IAAI,GAAG,GAAG,KAAK,CAAC;IAChB,IAAI,SAA6B,CAAC;IAElC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,QAAQ,GAAG,EAAE,CAAC;YACZ,KAAK,QAAQ,CAAC,CAAC,CAAC;gBACd,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;gBACxB,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;oBAC1C,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;gBACrD,CAAC;gBACD,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;gBACrB,MAAM;YACR,CAAC;YACD,KAAK,SAAS;gBACZ,KAAK,GAAG,IAAI,CAAC;gBACb,MAAM;YACR,KAAK,WAAW;gBACd,MAAM,GAAG,IAAI,CAAC;gBACd,MAAM;YACR,KAAK,gBAAgB;gBACnB,WAAW,GAAG,IAAI,CAAC;gBACnB,MAAM;YACR,KAAK,cAAc,CAAC,CAAC,CAAC;gBACpB,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;gBACxB,IAAI,CAAC,KAAK;oBAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;gBACtE,SAAS,GAAG,KAAK,CAAC;gBAClB,MAAM;YACR,CAAC;YACD,KAAK,WAAW;gBACd,MAAM,GAAG,IAAI,CAAC;gBACd,MAAM;YACR,KAAK,cAAc;gBACjB,SAAS,GAAG,IAAI,CAAC;gBACjB,MAAM;YACR,KAAK,OAAO;gBACV,GAAG,GAAG,IAAI,CAAC;gBACX,MAAM;YACR;gBACE,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;oBACzB,MAAM,IAAI,KAAK,CAAC,iBAAiB,GAAG,EAAE,CAAC,CAAC;gBAC1C,CAAC;gBACD,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;oBACtB,MAAM,IAAI,KAAK,CAAC,8BAA8B,GAAG,EAAE,CAAC,CAAC;gBACvD,CAAC;gBACD,GAAG,GAAG,GAAG,CAAC;QACd,CAAC;IACH,CAAC;IAED,OAAO;QACL,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QACvC,IAAI;QACJ,KAAK;QACL,MAAM;QACN,WAAW;QACX,MAAM;QACN,SAAS;QACT,GAAG;QACH,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACpC,CAAC;AACJ,CAAC"}
|
package/dist/cli/bin.js
CHANGED
|
@@ -11,77 +11,18 @@
|
|
|
11
11
|
* browser OAuth before the server starts.
|
|
12
12
|
*
|
|
13
13
|
* Flags: [dir] (default cwd), --port, --login, --no-auth, --no-telemetry,
|
|
14
|
-
* --no-open, --no-session, --dev
|
|
14
|
+
* --no-open, --no-session, --dev, --state-root <dir>.
|
|
15
15
|
*/
|
|
16
|
-
import * as path from "node:path";
|
|
17
16
|
import * as crypto from "node:crypto";
|
|
18
17
|
import open from "open";
|
|
19
|
-
import { DEFAULT_PORT } from "../shared/types.js";
|
|
20
18
|
import { runDoctor, printDoctorReport, pickDefaultHarness, CLAUDE_INSTALL_COMMAND, CODEX_INSTALL_COMMAND, } from "./doctor.js";
|
|
21
19
|
import { ensureAuthenticated } from "./auth.js";
|
|
22
20
|
import { ensureConsent } from "./consent.js";
|
|
23
21
|
import { loadSettings, recordRecentDir } from "./settings.js";
|
|
24
22
|
import { getOrCreateMachineId } from "./machine-id.js";
|
|
23
|
+
import { resolveStatePaths } from "../core/paths.js";
|
|
24
|
+
import { parseArgs } from "./args.js";
|
|
25
25
|
import { startServer } from "../server/index.js";
|
|
26
|
-
function parseArgs(argv) {
|
|
27
|
-
let dir;
|
|
28
|
-
let port = DEFAULT_PORT;
|
|
29
|
-
let login = false;
|
|
30
|
-
let noAuth = false;
|
|
31
|
-
let noTelemetry = false;
|
|
32
|
-
let noOpen = false;
|
|
33
|
-
let noSession = false;
|
|
34
|
-
let dev = false;
|
|
35
|
-
for (let i = 0; i < argv.length; i++) {
|
|
36
|
-
const arg = argv[i];
|
|
37
|
-
switch (arg) {
|
|
38
|
-
case "--port": {
|
|
39
|
-
const value = argv[++i];
|
|
40
|
-
if (!value || Number.isNaN(Number(value))) {
|
|
41
|
-
throw new Error("--port requires a numeric value");
|
|
42
|
-
}
|
|
43
|
-
port = Number(value);
|
|
44
|
-
break;
|
|
45
|
-
}
|
|
46
|
-
case "--login":
|
|
47
|
-
login = true;
|
|
48
|
-
break;
|
|
49
|
-
case "--no-auth":
|
|
50
|
-
noAuth = true;
|
|
51
|
-
break;
|
|
52
|
-
case "--no-telemetry":
|
|
53
|
-
noTelemetry = true;
|
|
54
|
-
break;
|
|
55
|
-
case "--no-open":
|
|
56
|
-
noOpen = true;
|
|
57
|
-
break;
|
|
58
|
-
case "--no-session":
|
|
59
|
-
noSession = true;
|
|
60
|
-
break;
|
|
61
|
-
case "--dev":
|
|
62
|
-
dev = true;
|
|
63
|
-
break;
|
|
64
|
-
default:
|
|
65
|
-
if (arg.startsWith("--")) {
|
|
66
|
-
throw new Error(`Unknown flag: ${arg}`);
|
|
67
|
-
}
|
|
68
|
-
if (dir !== undefined) {
|
|
69
|
-
throw new Error(`Unexpected extra argument: ${arg}`);
|
|
70
|
-
}
|
|
71
|
-
dir = arg;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return {
|
|
75
|
-
dir: path.resolve(dir ?? process.cwd()),
|
|
76
|
-
port,
|
|
77
|
-
login,
|
|
78
|
-
noAuth,
|
|
79
|
-
noTelemetry,
|
|
80
|
-
noOpen,
|
|
81
|
-
noSession,
|
|
82
|
-
dev,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
26
|
function printBanner(opts) {
|
|
86
27
|
const authLine = opts.identity
|
|
87
28
|
? `${opts.identity.organizationName} (${opts.identity.userId})${opts.identity.source === "cached" ? " — cached" : ""}`
|
|
@@ -114,7 +55,12 @@ const main = async () => {
|
|
|
114
55
|
console.log(`\n⚠ Claude Code not found — install with: ${CLAUDE_INSTALL_COMMAND}\n` +
|
|
115
56
|
" Continuing with the Codex harness.");
|
|
116
57
|
}
|
|
117
|
-
|
|
58
|
+
// Every piece of harness state resolves through ONE root, so --state-root
|
|
59
|
+
// relocates all of it together. Without this, firstRun and recentDirs would
|
|
60
|
+
// still read and WRITE the real settings file while everything else used the
|
|
61
|
+
// throwaway root — a half-isolated run that silently mutates real state.
|
|
62
|
+
const statePaths = resolveStatePaths(options.stateRoot);
|
|
63
|
+
const machineId = await getOrCreateMachineId(statePaths.machineId);
|
|
118
64
|
// Auth is non-blocking at boot: use a cached credential if one exists, but
|
|
119
65
|
// never open a browser at startup. The Studio launches unauthenticated and
|
|
120
66
|
// the web app drives sign-in (D4/D5). Pass --login to run an interactive
|
|
@@ -137,8 +83,8 @@ const main = async () => {
|
|
|
137
83
|
// and suppresses the auto-created boot session, so a brand-new user lands on
|
|
138
84
|
// the welcome panel rather than a bare terminal in whatever directory they
|
|
139
85
|
// happened to launch from.
|
|
140
|
-
const firstRun = (await loadSettings()).recentDirs.length === 0;
|
|
141
|
-
await recordRecentDir(options.dir);
|
|
86
|
+
const firstRun = (await loadSettings(statePaths.settings)).recentDirs.length === 0;
|
|
87
|
+
await recordRecentDir(options.dir, statePaths.settings);
|
|
142
88
|
const bootToken = crypto.randomBytes(32).toString("hex");
|
|
143
89
|
let server = null;
|
|
144
90
|
try {
|
|
@@ -151,6 +97,7 @@ const main = async () => {
|
|
|
151
97
|
identity,
|
|
152
98
|
machineId,
|
|
153
99
|
launchDir: options.dir,
|
|
100
|
+
...(options.stateRoot ? { stateRoot: options.stateRoot } : {}),
|
|
154
101
|
autoCreateSession: !options.noSession && !firstRun,
|
|
155
102
|
defaultHarnessKind,
|
|
156
103
|
availableHarnesses: doctorReport.availableHarnesses,
|
package/dist/cli/bin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../../src/cli/bin.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../../src/cli/bin.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/cli/settings.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/cli/settings.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AA2DzE;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAWhF;AAED,4EAA4E;AAC5E,wBAAsB,iBAAiB,CAAC,YAAY,GAAE,MAAkC,GAAG,OAAO,CAAC,OAAO,CAAC,CAO1G;AAED,wBAAsB,YAAY,CAAC,YAAY,GAAE,MAAkC,GAAG,OAAO,CAAC,eAAe,CAAC,CAe7G;AAED,wBAAsB,YAAY,CAChC,QAAQ,EAAE,eAAe,EACzB,YAAY,GAAE,MAAkC,GAC/C,OAAO,CAAC,IAAI,CAAC,CASf;AAED,6FAA6F;AAC7F,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,GAAE,MAAkC,GAAG,OAAO,CAAC,IAAI,CAAC,CAGlH;AAED;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CAAC,YAAY,GAAE,MAAkC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAkB7G"}
|
package/dist/cli/settings.js
CHANGED
|
@@ -5,6 +5,10 @@ import { expandHome } from "./paths.js";
|
|
|
5
5
|
const DEFAULT_SETTINGS = {
|
|
6
6
|
telemetryOptIn: false,
|
|
7
7
|
recentDirs: [],
|
|
8
|
+
// Opt-in: the rolling summary spends tokens on a background LLM call the
|
|
9
|
+
// user never asked for. Off, portable continue still works — its brief just
|
|
10
|
+
// degrades to the last N turns. See core/rolling-summary.ts.
|
|
11
|
+
rollingSummary: false,
|
|
8
12
|
};
|
|
9
13
|
const MAX_RECENT_DIRS = 8;
|
|
10
14
|
/** The real settings file. Every function below accepts an explicit path so
|
|
@@ -35,6 +39,25 @@ async function normalizeRecentDir(candidate) {
|
|
|
35
39
|
}
|
|
36
40
|
return resolved;
|
|
37
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Normalize a `projectRoot`: expand `~`, require absolute, resolve.
|
|
44
|
+
*
|
|
45
|
+
* Deliberately does NOT require the directory to exist, unlike
|
|
46
|
+
* normalizeRecentDir. A recent dir is a place something already happened; a
|
|
47
|
+
* project root is a place things WILL happen, and it is created when the first
|
|
48
|
+
* project lands there. Requiring existence would silently reset the user's
|
|
49
|
+
* chosen default the moment they picked a folder they hadn't made yet.
|
|
50
|
+
*
|
|
51
|
+
* Returns undefined for junk so the field simply falls back to the host default.
|
|
52
|
+
*/
|
|
53
|
+
function normalizeProjectRoot(candidate) {
|
|
54
|
+
if (typeof candidate !== "string" || !candidate.trim())
|
|
55
|
+
return undefined;
|
|
56
|
+
const expanded = expandHome(candidate.trim());
|
|
57
|
+
if (!path.isAbsolute(expanded))
|
|
58
|
+
return undefined;
|
|
59
|
+
return path.resolve(expanded);
|
|
60
|
+
}
|
|
38
61
|
/**
|
|
39
62
|
* Validates, normalizes, case-sensitively dedupes (first occurrence wins —
|
|
40
63
|
* callers order newest-first), and caps a candidate recent-dirs list.
|
|
@@ -70,16 +93,25 @@ export async function loadSettings(settingsPath = defaultSettingsFilePath()) {
|
|
|
70
93
|
try {
|
|
71
94
|
const raw = await fs.readFile(settingsPath, "utf-8");
|
|
72
95
|
const parsed = { ...DEFAULT_SETTINGS, ...JSON.parse(raw) };
|
|
73
|
-
|
|
96
|
+
const projectRoot = normalizeProjectRoot(parsed.projectRoot);
|
|
97
|
+
return {
|
|
98
|
+
...parsed,
|
|
99
|
+
recentDirs: await sanitizeRecentDirs(parsed.recentDirs),
|
|
100
|
+
// Omit rather than store undefined, so JSON.stringify on the way back out
|
|
101
|
+
// doesn't leave a dangling `"projectRoot": null`-shaped hole.
|
|
102
|
+
...(projectRoot ? { projectRoot } : { projectRoot: undefined }),
|
|
103
|
+
};
|
|
74
104
|
}
|
|
75
105
|
catch {
|
|
76
106
|
return { ...DEFAULT_SETTINGS };
|
|
77
107
|
}
|
|
78
108
|
}
|
|
79
109
|
export async function saveSettings(settings, settingsPath = defaultSettingsFilePath()) {
|
|
110
|
+
const normalizedRoot = normalizeProjectRoot(settings.projectRoot);
|
|
80
111
|
const sanitized = {
|
|
81
112
|
...settings,
|
|
82
113
|
recentDirs: await sanitizeRecentDirs(settings.recentDirs),
|
|
114
|
+
...(normalizedRoot ? { projectRoot: normalizedRoot } : { projectRoot: undefined }),
|
|
83
115
|
};
|
|
84
116
|
await fs.mkdir(path.dirname(settingsPath), { recursive: true });
|
|
85
117
|
await fs.writeFile(settingsPath, JSON.stringify(sanitized, null, 2) + "\n");
|
package/dist/cli/settings.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/cli/settings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,aAAa,EAAwB,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,MAAM,gBAAgB,GAAoB;IACxC,cAAc,EAAE,KAAK;IACrB,UAAU,EAAE,EAAE;
|
|
1
|
+
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/cli/settings.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,7 +4,43 @@
|
|
|
4
4
|
* flags (settings/mcp-config/system-prompt); nothing here mutates the user's
|
|
5
5
|
* own `~/.claude` config.
|
|
6
6
|
*/
|
|
7
|
-
import type { DoctorCheck, HarnessAdapter, LaunchOpts,
|
|
7
|
+
import type { DoctorCheck, HarnessAdapter, LaunchOpts, PastSessionRecord, SpawnSpec } from "../../shared/types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Maps a project cwd to the directory name Claude Code uses for its transcript
|
|
10
|
+
* store under `~/.claude/projects/`. Claude Code applies this encoding before
|
|
11
|
+
* creating the directory — see its own source for the canonical definition.
|
|
12
|
+
*
|
|
13
|
+
* Exported so tests assert against the real encoder rather than a copy of it:
|
|
14
|
+
* a private duplicate in the test file is exactly the thing that would let
|
|
15
|
+
* encoder drift pass the tests written to catch it.
|
|
16
|
+
*
|
|
17
|
+
* Encodes the path as given. Callers starting from a user-supplied cwd want
|
|
18
|
+
* {@link projectDirsFor}, which handles the symlink case this encoder can't
|
|
19
|
+
* see on its own — see its docstring.
|
|
20
|
+
*/
|
|
21
|
+
export declare function encodeProjectPath(cwd: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Every directory under `~/.claude/projects` that could hold `cwd`'s
|
|
24
|
+
* transcripts, realpath-resolved form first.
|
|
25
|
+
*
|
|
26
|
+
* Claude Code encodes the cwd's **realpath**, not the path the session was
|
|
27
|
+
* opened through — so a session in `/tmp/foo` on macOS stores its transcripts
|
|
28
|
+
* under the encoding of `/private/tmp/foo` (`/tmp` is a symlink), and one in
|
|
29
|
+
* `os.tmpdir()` under `/private/var/folders/…`. Both shapes are present in a
|
|
30
|
+
* real `~/.claude` on this machine. Encoding the registry's raw `cwd` string
|
|
31
|
+
* therefore stats a path that does not exist, which for a resumability probe
|
|
32
|
+
* is worse than a miss: it reports "the agent has no conversation" for a
|
|
33
|
+
* conversation the agent has, and refuses a resume that would have worked.
|
|
34
|
+
*
|
|
35
|
+
* The raw form is kept as a second candidate (when it differs) so nothing that
|
|
36
|
+
* resolved before can stop resolving, and a cwd that no longer exists on disk
|
|
37
|
+
* — realpath fails — still falls back to it.
|
|
38
|
+
*
|
|
39
|
+
* Exported for the session-record reader's optional vendor enrichment
|
|
40
|
+
* (core/session-record.ts), which reads the same transcript files. Claude's
|
|
41
|
+
* directory layout — symlink handling included — is defined here once.
|
|
42
|
+
*/
|
|
43
|
+
export declare function projectDirsFor(homeDir: string, cwd: string): Promise<string[]>;
|
|
8
44
|
export interface ClaudeCodeAdapterOptions {
|
|
9
45
|
/** Overridable for tests (e.g. spawn `bash` instead of a real, auth-gated `claude`). */
|
|
10
46
|
binary?: string;
|
|
@@ -17,6 +53,10 @@ export interface ClaudeCodeAdapterOptions {
|
|
|
17
53
|
export declare class ClaudeCodeAdapter implements HarnessAdapter {
|
|
18
54
|
readonly id: "claude-code";
|
|
19
55
|
readonly eventSource: "hooks";
|
|
56
|
+
/** `--append-system-prompt` carries `systemPromptFile`'s contents on every
|
|
57
|
+
* launch/resume path below, so a rehydration brief composed into that file
|
|
58
|
+
* needs no separate delivery. */
|
|
59
|
+
readonly systemPromptDelivery: "launch-flag";
|
|
20
60
|
private readonly binary;
|
|
21
61
|
private readonly homeDir;
|
|
22
62
|
private readonly fullScanMaxBytes;
|
|
@@ -39,7 +79,25 @@ export declare class ClaudeCodeAdapter implements HarnessAdapter {
|
|
|
39
79
|
* inheriting the user's interactive defaults.
|
|
40
80
|
*/
|
|
41
81
|
launchTask(opts: LaunchOpts): SpawnSpec;
|
|
42
|
-
|
|
82
|
+
/**
|
|
83
|
+
* See `HarnessAdapter.canResume`. One `stat` per candidate project dir on the
|
|
84
|
+
* transcript `claude --resume <id>` would read — `<project dir>/<id>.jsonl`
|
|
85
|
+
* — sharing `projectDirsFor` with `listPastSessions` so the vendor's
|
|
86
|
+
* directory layout, symlink handling included, stays defined in one place.
|
|
87
|
+
*
|
|
88
|
+
* A zero-byte file counts as absent: the transcript is created before the
|
|
89
|
+
* first turn is written, so an empty one is precisely the never-prompted
|
|
90
|
+
* session that `--resume` rejects with "No conversation found".
|
|
91
|
+
*
|
|
92
|
+
* Deliberately a weaker test than `listPastSessions`', which additionally
|
|
93
|
+
* requires a line OUR parser understands. A non-empty transcript we can't
|
|
94
|
+
* parse is still Claude's to interpret, and refusing the resume on the
|
|
95
|
+
* strength of our own parser's limits would be the same fail-closed mistake
|
|
96
|
+
* as the symlink bug above. So this can say `agent-resume` for a transcript
|
|
97
|
+
* that never shows up as a history row of its own.
|
|
98
|
+
*/
|
|
99
|
+
canResume(agentSessionId: string, cwd: string): Promise<boolean>;
|
|
100
|
+
listPastSessions(cwd: string): Promise<PastSessionRecord[]>;
|
|
43
101
|
}
|
|
44
102
|
export declare function createClaudeCodeAdapter(options?: ClaudeCodeAdapterOptions): HarnessAdapter;
|
|
45
103
|
//# sourceMappingURL=claude-code.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-code.d.ts","sourceRoot":"","sources":["../../../src/core/adapters/claude-code.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,OAAO,KAAK,EACV,WAAW,EACX,cAAc,EACd,UAAU,EACV,
|
|
1
|
+
{"version":3,"file":"claude-code.d.ts","sourceRoot":"","sources":["../../../src/core/adapters/claude-code.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,OAAO,KAAK,EACV,WAAW,EACX,cAAc,EACd,UAAU,EACV,iBAAiB,EACjB,SAAS,EACV,MAAM,uBAAuB,CAAC;AAC/B;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGrD;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAMpF;AA4BD,MAAM,WAAW,wBAAwB;IACvC,wFAAwF;IACxF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;qEACiE;IACjE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAwMD,qBAAa,iBAAkB,YAAW,cAAc;IACtD,QAAQ,CAAC,EAAE,EAAG,aAAa,CAAU;IACrC,QAAQ,CAAC,WAAW,EAAG,OAAO,CAAU;IACxC;;sCAEkC;IAClC,QAAQ,CAAC,oBAAoB,EAAG,aAAa,CAAU;IACvD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;gBAE9B,OAAO,GAAE,wBAA6B;IAM5C,MAAM,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAetC,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS;IAgBnC,MAAM,CAAC,cAAc,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,SAAS;IAa3D;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS;IAmBvC;;;;;;;;;;;;;;;;OAgBG;IACG,SAAS,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAShE,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;CA+ClE;AAcD,wBAAgB,uBAAuB,CAAC,OAAO,CAAC,EAAE,wBAAwB,GAAG,cAAc,CAE1F"}
|