@useshifu/coding-harness 0.3.36 → 0.3.37

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/README.md CHANGED
@@ -1,49 +1,44 @@
1
1
  # Shifu coding-harness connector
2
2
 
3
- `@useshifu/coding-harness` connects Codex, Claude Code, OpenCode, or Cursor to Shifu and watches one local input folder. Session sync includes coding work and non-coding work chats: research, planning, writing, marketing, career development, leadership, and learning discussions. It prepares version-five context from bounded original human messages and completed assistant reports through the matching native CLI, then sends only validated redacted activity to Shifu. Folder files are sent over TLS for server-side parsing, sanitization, and user-attribution checks; the local runner does not redact file contents.
3
+ `@useshifu/coding-harness` connects Codex, Claude Code, OpenCode, or Cursor to Shifu. Local capture requires reviewed history/project scope; one optional input folder can be enabled separately. Session sync includes coding work and non-coding work chats: research, planning, writing, marketing, career development, leadership, and learning discussions. It prepares version-five context from bounded original human messages and completed assistant reports through the matching native CLI, then sends only validated redacted activity to Shifu. Folder files are sent over TLS for server-side parsing, sanitization, and user-attribution checks; the local runner does not redact file contents.
4
4
 
5
5
  ## Connect
6
6
 
7
+ Use the bundled local wizard for capture setup:
8
+
7
9
  ```sh
8
- npx --yes @useshifu/coding-harness connect --harness <codex|claude_code|opencode|cursor>
10
+ npx --yes @useshifu/coding-harness onboard --harness codex
9
11
  ```
10
12
 
11
- Connect installs the local instruction and scheduled runner, saves the connection key with owner-only permissions, and shows a branded guide. Interactive setup asks for initial history (15 days recommended, 30 days, custom days, or start from now), helpful growth feedback, and decision alignment. Both feedback options are recommended and independent; historical context stays on after MCP setup. Sync defaults remain:
12
-
13
- - sync interval: one hour, configurable from 0.25 to 720 hours;
14
- - automatic coding-session sync (manual approval remains available for coding sessions);
15
- - one input folder created at `~/Desktop/Shifu` on macOS, with a `README.md` explaining what it is for and shortcuts at `~/Downloads/Shifu` and `~/Documents/Shifu` when those parent folders exist.
16
-
17
- Place `.txt`, `.md`, `.rtf`, or `.docx` files up to 20 MB directly in the input folder. Its `README.md` is never synced and an existing one is not overwritten. Screenshots, images, links, subfolders, and unknown formats are not uploaded; unsupported regular files are counted in sync status. Existing files are picked up on the next scheduled run; unchanged files are not sent again. A failed file stays pending for retry. Folder files never require a claim-review step, even when coding-session sync is in manual mode. Shifu creates claims only when the file supports a specific contribution by the connected user; meeting action items or another person's work are not treated as the user's claims.
13
+ Choose `codex`, `claude_code`, `opencode` or `cursor` on the local device. The wizard reuses saved settings and credentials, collects history (15 days recommended, 30, custom or zero), selected projects or an explicit all-project choice, optional files/folder, schedule and independent feedback preferences. Review shows metadata counts and configured-model usage before Start grants scope. Selected source uses the coding tool’s configured provider; Shifu receives redacted work records. Existing retained context can be retrieved independently of local capture.
18
14
 
19
- For named meeting transcripts, set your display name in Shifu Profile so your speaker turns can be identified. If the speaker cannot be matched, uncertain contributions are skipped.
15
+ Files are off until enabled for the reviewed canonical directory. Once enabled, `.txt`, `.md`, `.rtf` and `.docx` files up to 20 MB upload as provided for server-side parsing and sanitization, separately from manual session approval. The folder scanner ignores its `README.md`, subfolders and symlinks; images and screenshots are unsupported. Changing the folder disables file approval until reviewed again. Named transcripts use the Shifu Profile display name for attribution; unsupported ownership does not become a personal claim.
20
16
 
21
- The shortcuts are symlinks to the same folder, not additional watched folders. An existing conflicting `Shifu` item is left untouched and reported. To choose another existing, readable folder, use `--input-folder <path>` with `connect` or `config update`; Shifu checks the path before saving the change. No macOS permission flow is required by the CLI.
17
+ Start authorizes the reviewed history/projects/file scope and freezes the initial backfill manifest. Zero days skips historical work. The recommended future policy is hourly automatic session sync, with manual approval available. Closing the wizard does not revoke an accepted Start; pause when processing should stop. Missing or ambiguous legacy grants require wizard review, preserving checkpoints and frozen retry bodies.
22
18
 
23
- No content is sent during setup. Every `connect` asks for a connection key using a normal terminal prompt, so the key is visible as you type or paste it. The key is verified with Shifu for the chosen coding tool before saving it or reporting success. An invalid key or unreachable server leaves the existing connection unchanged. Replacing a key resets local checkpoints so existing work can be reconsidered for the new connection; the selected sync policy remains. If a coding tool cannot run `npx` or accept input in its tool terminal, run the same command in your own interactive terminal and check the confirmation output. An assistant using a noninteractive terminal collects these choices first and passes `--backfill-days N --growth-feedback on|off --decision-alignment on|off`. New noninteractive connections without flags use 15 days and both options on; existing choices are preserved. `--interactive` requires a terminal; `--non-interactive` disables questions. Change the choices later with:
24
-
25
- ```sh
26
- npx --yes @useshifu/coding-harness config update --harness <codex|claude_code|opencode> --interval-hours 1 --approval-mode automatic --input-folder ~/Desktop/Shifu
27
- ```
19
+ `connect --harness <harness>` is the lower-level credential/preferences command. It verifies the tool-specific key entered through the local wizard or terminal; do not paste it in chat. A new connection does not activate capture or a schedule. Missing noninteractive history does not silently become 15 days. An interval, approval mode, history preference or `syncPolicy.onboardingComplete` is not a capture grant. Key or endpoint replacement requires new review before capture, with old state quarantined rather than silently replayed.
28
20
 
29
- Older configuration files remain on-demand and manual until either command completes onboarding; upgrading the package alone never enables background upload.
21
+ MCP-only `mcp-setup --harness <harness>` connects existing Shifu context tools. A fresh connection leaves local capture disabled, and history flags cannot expand capture. Existing approved sync settings are preserved. Work capture requires the wizard’s own reviewed Start.
30
22
 
31
- To change feedback without touching sync:
23
+ To pause or resume the existing grant:
32
24
 
33
25
  ```sh
34
- npx --yes @useshifu/coding-harness config update --harness codex --growth-feedback off --decision-alignment on
26
+ npx --yes @useshifu/coding-harness pause --harness codex
27
+ npx --yes @useshifu/coding-harness resume --harness codex
35
28
  ```
36
29
 
37
- The installed context skill and managed global instructions reflect these choices. Reload the harness or start a new chat after changes. Feedback stays brief, constructive and grounded in observable work; goal-aware coaching requires an explicitly stated goal. Alignment highlights relevant historical support or differences while following current instructions. Empty-context messages and repetitive praise are skipped. Turning off proactive feedback still allows answers to explicit questions about it.
30
+ Pause blocks new preparation, uploads and backfill and cancels active local work. Accepted requests and checkpoints remain. `config update --schedule off` also pauses capture. Resume does not grant missing scope or broaden projects/history. Explicit prepare, recover and force requests also remain within the grant; use the wizard to expand it.
38
31
 
39
- `--backfill-days N` saves a fixed initial-history cutoff when selected. New scheduled preparation and `pending` respect it; `0` starts from that moment. Conversations with older unsynced turns remain blocked instead of skipping those turns or importing them silently, even when they have newer messages. `status.initialHistoryBlocked` reports these cases from local checkpoints; explicit review/sync can unblock them. An unconfirmed legacy checkpoint may require manual recovery even when the server accepted the older prefix. Previously prepared queues keep their prior authorization and are not removed by changing the window. Explicit manual selections remain available. Upgrades preserve saved choices; legacy installations without a saved window keep their previous eligibility behavior.
32
+ Change interval, manual/automatic session approval or independent feedback with requested `config update` flags. A changed history preference cannot widen the authorized cutoff. Feedback changes update installed guidance without altering capture; reload the coding tool or start a new chat afterwards. Historical retrieval remains available even when proactive feedback is off.
40
33
 
41
- Setup selects history without uploading, queuing, or claiming it was captured. First work-sync setup can then review and submit the selected window under its existing authorization. MCP-only setup never syncs. Upgrades and key replacement do not trigger new initial backfill. The receipt shows authentication and MCP discovery separately, and reports tools ready only after verified discovery.
34
+ `purge-local --harness <harness>` previews Shifu-managed caches, captured journals, dictionaries and outboxes while capture is paused. Only `--approved` after review removes those artifacts. It preserves original coding-tool history, input documents and hosted work. Purge is not part of ordinary setup or an upgrade.
42
35
 
43
- To disconnect this device, run `npx --yes @useshifu/coding-harness disconnect --harness <codex|claude_code|opencode>`. This removes the local key, schedule, and checkpoints. It does not revoke the key on Shifu; revoke the old key there if it should no longer work.
36
+ `disconnect --harness <harness>` removes the local connection and configured schedule; revoke the key in Shifu if it should no longer be usable. Disconnect and local purge are separate from hosted deletion requests.
44
37
 
45
38
  ## Operate
46
39
 
40
+ Current scope and pause rules above apply to all commands. Release notes below describe earlier changes; they do not grant upload authority.
41
+
47
42
  Version `0.3.32` preserves failed and pending source status during preparation, distinguishes people by their original source occurrences, and keeps the two-attempt review limit across metadata changes. Existing queues, V5 payloads and receipts remain compatible.
48
43
 
49
44
  Version `0.3.30` adds separately versioned episode and entity continuity without changing the V5 sync body or older queued payloads. Preparation uses bounded prior-turn references, source coverage checks and durable two-attempt review holds. Base acceptance and attachment completion have separate status. See [V5 continuity](#continuity-beside-version-five) below for privacy, recovery and mixed-version operation.
@@ -71,7 +66,7 @@ Version `0.3.12` makes force sync independent of local and server checkpoints, c
71
66
 
72
67
  Version `0.3.13` requires reviewed evidence for force sync. Empty replacements fail before changing existing work, and the harness instructions require semantic review of final notes instead of the scheduled keyword projection.
73
68
 
74
- Version `0.3.14` introduced first-connection backfill. Setup now asks for the initial window, with 15 days recommended, before configuration. Interrupted initial backfills resume from accepted checkpoints; upgrades and key replacement alone do not trigger another backfill. The saved approval policy and schedule stay unchanged.
69
+ Historical version `0.3.14` introduced first-connection backfill. Current capture instead requires the wizard’s history/project/file preview and explicit Start; 15 days is only a recommended choice. Interrupted initial backfills resume from accepted checkpoints; upgrades and key replacement alone do not trigger another backfill. The saved approval policy and schedule stay unchanged.
75
70
 
76
71
  ```sh
77
72
  npx --yes @useshifu/coding-harness status --harness codex
@@ -84,7 +79,7 @@ npx --yes @useshifu/coding-harness sync-files --harness codex
84
79
 
85
80
  To select by session count instead of a time window, run `sessions --harness codex --count 5`. It selects the five most recently updated eligible sessions, newest first, without an hours cutoff. Current and setup sessions are excluded. `selectedSessionRefs` fixes that selection; `sessions` contains pending segments and `skippedSessions` reports already-synced sessions. Skipped sessions are not replaced with older ones. Inspect each pending range with `review-source --harness codex --session-ref <sessionRef> --from-turn <fromTurn> --to-turn <toTurn>`, then prepare its redacted version-five payload in the current native harness. After an accepted segment, request the next with `sessions --harness codex --session-ref <sessionRef> --all` until that selected session is exhausted. Keep the original references instead of selecting another five.
86
81
 
87
- Time-window selection accepts any positive whole number of days or hours. `sessions --harness codex --days 3` uses the same window as `--hours 72`; pass the same window to `review`. `--days 7` also works. Mixed, missing, invalid, or unsupported options fail explicitly. A recently active session with older unsynced turns appears in `blockedSessions`: sync cannot skip those turns. Explicitly select that session with `--session-ref <sessionRef> --all` to backfill in chronological segments of at most 12 turns.
82
+ Time-window selection accepts any positive whole number of days or hours. `sessions --harness codex --days 3` uses the same window as `--hours 72`; pass the same window to `review`. `--days 7` also works. Mixed, missing, invalid, or unsupported options fail explicitly. A recently active session with older unsynced turns appears in `blockedSessions`: sync cannot skip those turns. Use `--session-ref <sessionRef> --all` only within the existing approved history/projects; wider prefixes require wizard re-review. Backfill stays chronological in segments of at most 12 turns.
88
83
 
89
84
  Manual session sync runs immediately, independently of the hourly schedule. If a segment fails, report it as failed/pending; a checkpoint advances only after a matching server receipt. Report completed, already-synced, and failed session counts separately.
90
85
 
@@ -98,13 +93,15 @@ OpenCode 2 account finalization has a separate 10-second recovery budget after m
98
93
 
99
94
  Each preparation invocation handles at most four ranges, each covering up to 12 original turns, 128 source messages, and 256 KiB. Oversized automatic ranges are shortened before preparation rather than silently truncating text. A native preparation has a 180-second deadline. Unfinished trailing turns stay pending regardless of age; partial assistant text and intermediate tool calls do not count as final reports. An unsupported native CLI, missing required isolation flags, unavailable account, timeout, or invalid model output leaves the range pending. Update or repair the matching CLI and retry; the runner does not fall back to the old keyword projection.
100
95
 
101
- Before queueing, invalid structured preparation receives at most one isolated correction attempt using only privacy-safe validator diagnostics and the same source packet, within the original 180-second deadline. Reference errors identify the evidence position and decision or verification field without exposing source text. Native invocation failures are not retried through this correction path. If correction still fails, the range remains pending.
96
+ Codex extraction normally inherits the native model and reasoning settings. `config update --harness codex --background-reasoning medium` saves a Medium reasoning override for isolated background extraction only; it leaves Codex chat configuration, the history cutoff, sync schedule and frozen payloads unchanged. The flag also accepts `high`. Both overrides use a ten-minute preparation limit inside a fifteen-minute run budget; changing reasoning does not shorten that allowance. Inherited settings retain their three-minute preparation and five-minute run limits. Use `--background-reasoning inherit` to remove the override. Safe `status` output reports the saved choice. A longer allowance does not guarantee completion.
97
+
98
+ Before queueing, invalid structured preparation receives at most one isolated correction attempt using only privacy-safe validator diagnostics and the same source packet. Both attempts share the invocation deadline. If too little time remains for a useful correction, safe guidance is saved and the remaining attempt resumes in a later bounded invocation. Reference errors identify the evidence position and decision or verification field without exposing source text. Native invocation failures are not retried through this correction path. If correction still fails, the range remains pending.
102
99
 
103
100
  The runner saves each validated redacted payload in its owner-readable pending queue before the first upload. Transport retries reuse that exact payload instead of asking the model to prepare another version. Previously queued payloads retain their version and are drained before the same session switches to version five. Accepted legacy ranges are reconciled with the server before new preparation; upgrades do not replay them.
104
101
 
105
102
  Automatic mode sends prepared payloads and advances checkpoints only after matching server receipts. Manual mode prepares the same redacted queue without uploading its work content or advancing checkpoints; `pending` can prepare another bounded batch. Show the complete returned queue and approve it once with `approve --approved`. Approval sends only already-prepared payloads, in order, and does not create unseen ones. Scheduled notifications are content-free. Preparation failures, unprepared ranges, and failed uploads remain pending rather than counting as completed work.
106
103
 
107
- The same scheduled run checks the input folder. `sync-files` checks it immediately without changing the schedule. Before sending new or changed files, the runner polls unfinished jobs from its local checkpoints. It sends supported file bytes over the configured HTTPS connection (or localhost HTTP for development). The server accepts each file as a background job and processes its text in chunks; upload does not wait for claim extraction. A later scheduled or manual sync polls the job's result. Only `processed`, `no_claims`, or `duplicate` finalizes the local checkpoint; `partial` reopens failed chunks on the next sync. On this upgrade, completed file checkpoints are replayed once so the new extraction can retain suggestions; in-flight jobs are polled first and replayed after finishing. Server-side idempotency prevents already published claims from being duplicated. Files larger than 20 MB remain pending with an error. The scanner reads regular files directly in the chosen folder and ignores symlinks and subfolders. Unknown formats are reported as unsupported rather than treated as successful syncs.
104
+ When file uploads are enabled for the approved canonical folder and capture is unpaused, the same scheduled run checks it. `sync-files` checks it immediately inside that grant without changing the schedule. Before sending new or changed files, the runner polls unfinished jobs from its local checkpoints. It sends supported file bytes over the configured HTTPS connection (or localhost HTTP for development). The server accepts each file as a background job and processes its text in chunks; upload does not wait for claim extraction. A later scheduled or manual sync polls the job's result. Only `processed`, `no_claims`, or `duplicate` finalizes the local checkpoint; `partial` reopens failed chunks on the next sync. On this upgrade, completed file checkpoints are replayed once so the new extraction can retain suggestions; in-flight jobs are polled first and replayed after finishing. Server-side idempotency prevents already published claims from being duplicated. Files larger than 20 MB remain pending with an error. The scanner reads regular files directly in the chosen folder and ignores symlinks and subfolders. Unknown formats are reported as unsupported rather than treated as successful syncs.
108
105
 
109
106
  The file-job result reports published claims separately from needs-evidence suggestions. A `no_claims` result can still contain suggestions: these are possible contributions awaiting evidence or attribution, not verified claims. The runner reports the suggestion count but never labels those suggestions as established work. It does not print the file's text, candidate statements, or source excerpts to logs. Use Shifu to inspect the suggestions and their evidence status. A result with zero claims and zero suggestions means no user-attributable contribution was extracted; it does not mean the upload failed.
110
107
 
@@ -130,11 +127,11 @@ The AI Profile page shows detected categories, model and thinking labels, and Je
130
127
 
131
128
  The connection endpoint returns `recommendedPackageVersion`, pinned to a published release. Before `sync`, `sync-files`, `approve`, or `scheduled-sync`, the runner compares its embedded release version with that property. An older runner installs the exact recommended release through npm and resumes the original command with the updated runner. Equal or newer runners continue unchanged. Explicitly recommended major releases follow the same comparison rule. Missing or invalid recommendations, connection failures, and npm failures do not block sync. The connection, pending work, checkpoints, and schedule are preserved.
132
129
 
133
- This update mechanism first ships in `0.3.10`. Runners from earlier releases need a one-time installation of a release containing it (`npx --yes @useshifu/coding-harness@0.3.34 install --harness codex`). Raise the server recommendation only after the target release is published and its backend requirements are deployed.
130
+ This update mechanism first ships in `0.3.10`. Runners from earlier releases need a one-time installation of a release containing it (`npx --yes @useshifu/coding-harness install --harness codex`). Raise the server recommendation only after the target release is published and its backend requirements are deployed.
134
131
 
135
132
  ## Force sync local work
136
133
 
137
- An explicit force request selects local work independently of both local and server checkpoints. It includes already synced sessions and ranges the server has never accepted. It creates missing ranges or replaces their current work evidence and claims while retaining history. Force never resets or lowers checkpoints and does not change scheduled sync or AI Profile observations.
134
+ An explicit force request selects local work independently of checkpoints, within the approved projects and history. A stored replacement range crossing the approved cutoff requires wider wizard review before force; do not trim the stored range. It includes already synced sessions and ranges the server has never accepted. It creates missing ranges or replaces their current work evidence and claims while retaining history. Force never resets or lowers checkpoints and does not change scheduled sync or AI Profile observations.
138
135
 
139
136
  1. Discover ranges with `sessions --harness codex --days 3 --force` (or a requested hours/count selection). Keep that initial target list fixed. Complete stored ranges touching selected turns are preserved, including older turns across a window boundary; uncovered selected turns are split into ranges of at most 12.
140
137
  2. Re-review each range with `review --harness codex --session-ref <sessionRef> --days 3 --force --from-turn <fromTurn> --to-turn <toTurn>`. The original local turns must exist. Explicit backfill/count selections use `--all`. No old receipt ID is required.
@@ -165,7 +162,7 @@ Setup preserves other instruction text and MCP settings. Package installation re
165
162
  The context branch adds a remote MCP connection using the same saved Codex key:
166
163
 
167
164
  ```sh
168
- npx --yes @useshifu/coding-harness@0.3.34 mcp-setup --harness codex --mcp-url https://YOUR-BRANCH-HOST/mcp
165
+ npx --yes @useshifu/coding-harness mcp-setup --harness codex --mcp-url https://YOUR-BRANCH-HOST/mcp
169
166
  ```
170
167
 
171
168
  For development, install dependencies and run from the checkout root:
@@ -196,7 +193,7 @@ Official configuration reference: https://learn.chatgpt.com/docs/config-file/con
196
193
  OpenCode uses the same `/mcp` endpoint and three tools, authenticated with its own saved OpenCode connection key:
197
194
 
198
195
  ```sh
199
- npx --yes @useshifu/coding-harness@0.3.34 mcp-setup --harness opencode --mcp-url https://YOUR-SHIFU-HOST/mcp
196
+ npx --yes @useshifu/coding-harness mcp-setup --harness opencode --mcp-url https://YOUR-SHIFU-HOST/mcp
200
197
  ```
201
198
 
202
199
  For checkout usage, install dependencies with `npm ci --prefix packages/shifu-coding-harness --ignore-scripts` from the checkout root, then run the checkout CLI with the same arguments. Setup preserves existing sync settings and other MCP servers, checks authenticated initialization and tool discovery, and installs the `shifu-context` usage instructions. A missing OpenCode connection prompts for its key in the terminal. A Codex key cannot authorize an OpenCode connection.
@@ -216,7 +213,7 @@ For the optional native integration gate, set `SHIFU_TEST_OPENCODE_BIN` to a tem
216
213
  Claude Code connects to the same `/mcp` endpoint and three tools with its own saved connection key:
217
214
 
218
215
  ```sh
219
- npx --yes @useshifu/coding-harness@0.3.34 mcp-setup --harness claude_code --mcp-url https://YOUR-SHIFU-HOST/mcp
216
+ npx --yes @useshifu/coding-harness mcp-setup --harness claude_code --mcp-url https://YOUR-SHIFU-HOST/mcp
220
217
  ```
221
218
 
222
219
  For checkout usage, install dependencies as above and run the checkout CLI with these arguments. Setup checks the existing Claude Code key, authenticated initialization and tool discovery, and installs the `shifu-context` skill. It preserves the sync endpoint, schedule, checkpoints and other MCP servers. A missing connection prompts for a Claude Code key in the terminal; do not substitute a Codex or OpenCode key.
@@ -236,11 +233,11 @@ Cursor support ships in `0.3.31` and requires backend migration `071_cursor_harn
236
233
  Install [Cursor Agent CLI](https://cursor.com/docs/cli/installation) and sign in with `agent login`. The desktop app's login does not necessarily authenticate the CLI. Then create a dedicated Cursor key in Shifu and run:
237
234
 
238
235
  ```sh
239
- npx --yes @useshifu/coding-harness@0.3.34 connect --harness cursor
240
- npx --yes @useshifu/coding-harness@0.3.34 mcp-setup --harness cursor
236
+ npx --yes @useshifu/coding-harness connect --harness cursor
237
+ npx --yes @useshifu/coding-harness mcp-setup --harness cursor
241
238
  ```
242
239
 
243
- For a checkout before publication, run `npm ci --prefix packages/shifu-coding-harness --ignore-scripts` and replace `npx --yes @useshifu/coding-harness@0.3.34` with `node packages/shifu-coding-harness/bin/shifu-harness.js`. `connect` installs the runner, skills and work-capture hooks, verifies the Cursor-specific key, saves history and feedback choices, and configures the existing sync schedule. MCP setup verifies authenticated initialization and discovery, preserves those choices and other MCP entries, and adds a local stdio bridge in `~/.cursor/mcp.json`. The bridge reads the protected Shifu connection file; no key is copied into Cursor configuration or process arguments. A conflicting existing `shifu` entry is preserved with a rename instruction. Other harness keys cannot authorize Cursor.
240
+ For a checkout before publication, run `npm ci --prefix packages/shifu-coding-harness --ignore-scripts` and replace `npx --yes @useshifu/coding-harness` with `node packages/shifu-coding-harness/bin/shifu-harness.js`. `connect` installs the runner, skills and work-capture hooks and verifies the Cursor-specific key. Capture remains disabled until the wizard’s reviewed Start; installing hooks is not capture authorization. MCP setup verifies authenticated initialization and discovery, preserves those choices and other MCP entries, and adds a local stdio bridge in `~/.cursor/mcp.json`. The bridge reads the protected Shifu connection file; no key is copied into Cursor configuration or process arguments. A conflicting existing `shifu` entry is preserved with a rename instruction. Other harness keys cannot authorize Cursor.
244
241
 
245
242
  Reload Cursor and start a new chat. Enable the Shifu MCP server if Cursor requests approval; CLI users can run `agent mcp enable shifu` and `agent mcp list-tools shifu`. Normal tool approvals still apply. Setup's HTTP probe is separate from native-client discovery and a model-driven tool call.
246
243
 
@@ -274,6 +271,10 @@ coding-harness retry-preparation --harness codex --session-ref <opaque-session>
274
271
 
275
272
  That command cannot regenerate a frozen or accepted payload. Historical recovery remains separate.
276
273
 
274
+ For explicitly authorized missed history, select each session with `sessions --harness codex --session-ref <opaque-session> --all`, inspect its exact range with `review-source`, then run `prepare --harness codex --session-ref <opaque-session> --from-turn <first> --to-turn <last> --all`. Preparation uses the same isolated native extraction and privacy checks and queues only the next completed contiguous range, at most 12 turns. It does not upload or change saved history settings. Existing queued payloads are reused exactly; exhausted holds require a separately reviewed `retry-preparation` with the same selected session, original start and `--all`. Omit `--all` to keep the saved history cutoff. The same commands support `claude_code` and `opencode`.
275
+
276
+ For multi-session recovery inside an existing approved project/history grant, freeze the selected opaque references and upper turn bounds in a private JSON selection with `harness`, `selectedAt`, and `sessions` containing `sessionRef`, `fromTurn`, and `throughTurn`. Run `recover --harness codex --selection <file>` once under saved automatic approval, then `recover --harness codex` to resume bounded batches. The runner saves a connection-bound recovery manifest; scheduled runs resume that exact selection without changing the initial-history cutoff or adding new sessions or turns. New ranges contain at most four completed turns; frozen queued payloads and existing hold boundaries remain exact. Existing unqueued holds receive at most one explicitly authorized retry reset, recorded before extraction. Unfinished tails, exhausted retries, rejected receipts and pending continuity attachments prevent completion. `status` reports recovery progress separately from ordinary sync.
277
+
277
278
  Continuity uses separate `*-continuity.json` and `*-entity-dictionary.json` files; the version-five body, hash, receipt and base queue stay unchanged. Semantic metadata is frozen before the base POST. The exact matching receipt ID is durably saved before checkpoint advancement or base queue removal. A lost response retries the frozen base payload, including after an older runner has advanced the local checkpoint; range equality is never used to pick an attachment target.
278
279
 
279
280
  The companion endpoint is used only when the authenticated connection advertises `capabilities.continuityAttachment: 1`. An older backend can still accept the base upload while metadata waits locally. Attachment retries reuse one frozen request ID and body, back off, and stop for review after ten attempts. Authorization, target and body conflicts stop immediately. Replacing either the connection key or API endpoint quarantines old checkpoints, base queue, outbox and dictionary before committing the new configuration. An interrupted replacement holds sync until connect resumes with the same selected target. Source text and private names never enter the companion request or status output. Atomic file replacement and file flushing apply on every platform; directory flushing and owner permission enforcement apply on POSIX.
@@ -285,25 +286,25 @@ Use only compatible preparation writers on a shared configuration directory. An
285
286
  Use the `shifu-onboarding` skill in your coding assistant, or open Shifu setup yourself:
286
287
 
287
288
  ```sh
288
- npx --yes @useshifu/coding-harness@0.3.36 onboard --harness codex
289
+ npx --yes @useshifu/coding-harness onboard --harness codex
289
290
  ```
290
291
 
291
292
  Choose `codex`, `claude_code`, `opencode`, or `cursor`. Run this on the local macOS/Linux device whose coding history you want to connect. A remote/headless assistant cannot open this device's setup page: run the command in your own local terminal instead. The launcher prints a local URL if opening the browser fails. That URL carries a per-launch capability in its fragment; do not share it. It is not your Shifu connection key.
292
293
 
293
- The setup page collects initial history (15 days, 30 days, custom 1–36500 days, or start from now), independent growth-feedback and decision-alignment choices, and shows examples. Saved settings and the original history cutoff are reused. Keys go directly from the setup page to the local runner, never through chat. Existing connections are verified and reused. Review explicitly authorizes preparation and upload of the selected coding history; source text uses your configured coding-model provider. The one-time backfill does not change an existing manual approval policy for future work. Folder-file uploads are separate from this coding-history backfill.
294
+ The setup page collects history, projects, optional file uploads/folder, schedule and independent feedback choices. Files start off. Saved choices and the original cutoff are reused; keys go directly to the local runner. Review shows counts and model-account usage before explicit Start authorizes that scope. Merely connecting or saving choices cannot capture work. Manual session approval remains separate from the enabled folder-file path.
294
295
 
295
- Accepted ranges use existing checkpoints and exact frozen payloads. Initial discovery freezes session boundaries for this run; later activity follows ordinary sync. Closing the browser does not stop the detached worker. The scheduled runner resumes unfinished ranges on its next tick; **Resume unfinished backfill** retries immediately. Older unsynced turns outside the saved window remain blocked rather than silently expanding scope. A zero-day selection skips historical work. Empty history is reported honestly, not counted as captured context.
296
+ Accepted ranges use existing checkpoints and exact frozen payloads. Initial discovery freezes session boundaries for this run; later activity follows ordinary sync. Closing the browser does not stop the detached worker after Start; pause stops further processing. The scheduled runner resumes unfinished ranges on its next tick; **Resume unfinished backfill** retries immediately. Older unsynced turns outside the saved window remain blocked rather than silently expanding scope. A zero-day selection skips historical work. Empty history is reported honestly, not counted as captured context.
296
297
 
297
298
  Setup checks saved-work availability before offering **Copy a question**. Reload the assistant as instructed and paste that short prompt. The non-secret run reference only identifies local state; it never grants account access. The skill checks status without reinstalling, reasking choices or replaying accepted history:
298
299
 
299
300
  ```sh
300
- npx --yes @useshifu/coding-harness@0.3.36 onboard --harness codex --status --run <reference>
301
+ npx --yes @useshifu/coding-harness onboard --harness codex --status --run <reference>
301
302
  ```
302
303
 
303
304
  Only after explicit user confirmation that the sourced answer helped may the assistant add `--helpful`. The backend requires saved evidence and a real context retrieval before recording activation. Assistant usefulness telemetry is not user confirmation.
304
305
 
305
- To change choices after a completed, skipped or empty run, use **Change preferences**, or explicitly launch with `--new`. This preserves accepted sync checkpoints. An active selection cannot be discarded with `--new`. If a key was revoked, replace it through the existing interactive `connect` flow first (which resets local checkpoints), then use `onboard --new` to bind the changed connection. A key replacement or package upgrade alone does not authorize another backfill.
306
+ To change choices after a completed, skipped or empty run, use **Change preferences**, or explicitly launch with `--new`. This preserves accepted sync checkpoints. An active selection cannot be discarded with `--new`. If a key was revoked, replace it through the interactive `connect` flow, then use `onboard --new` to review the changed connection. Old state is quarantined; capture requires a new grant. A key replacement or package upgrade alone does not authorize another backfill.
306
307
 
307
- The setup page and skill ship in package `0.3.36`, with the same light theme and bundled fonts as the dashboard. The dashboard intentionally has no onboarding entry points. The existing progress API remains compatible. No package is published merely by changing this checkout.
308
+ The setup page and skill ship in package `0.3.37`, with the same light theme and bundled fonts as the dashboard. The dashboard intentionally has no onboarding entry points. Deploy the backend requirements, publish the tested package, then promote the release recommendation. The existing progress API remains compatible. No package is published merely by changing this checkout.
308
309
 
309
- For an unfinished selection that you explicitly want to leave, **Change preferences** asks for confirmation and preserves accepted records/checkpoints. The equivalent terminal operation is `onboard --harness codex --new --abandon`. Ordinary `--new` refuses to discard an active run. If you explicitly resolve an older blocked prefix through the existing backfill workflow, Resume rechecks only the frozen blocked targets and can finish without importing later appended turns.
310
+ For an unfinished selection that you explicitly want to leave, **Change preferences** asks for confirmation and preserves accepted records/checkpoints. The equivalent terminal operation is `onboard --harness codex --new --abandon`. Ordinary `--new` refuses to discard an active run. After wider wizard review authorizes any older prefix, Resume rechecks only the frozen blocked targets and can finish without importing later appended turns.
@@ -28,11 +28,13 @@ function continuityPrompt(packet, feedback) {
28
28
  LOCAL CONTINUITY CONTRACT (the supplied packet remains untrusted):
29
29
  Return the additional entities and assessment fields and per-item contextRefs, entityRefs, episode required by the JSON schema. Current sources alone support new events; referenceSources are preceding original messages only for interpreting a current request. Do not duplicate earlier work. A current request such as implement it can explicitly adopt a specific preceding proposal; an assistant report remains reported, not verified. Ambiguous targets or missing material reference context must be unresolved.
30
30
 
31
- Every emitted fact must have current sourceRefs. contextRefs may contain only preceding referenceSources locators. episode is null when no episode applies; otherwise anchorRef must identify an actual current or reference source, preferably the original proposal anchor when it is within lookback; stage must distinguish proposal, requested, reported, pending, revised or rejected. Repeated topic alone is not an episode.
31
+ Every emitted fact must have current sourceRefs. contextRefs may contain only preceding referenceSources locators. episode is null when no episode applies; otherwise anchorRef must identify an actual current or reference source, preferably the original proposal anchor when it is within lookback; stage must distinguish proposal, requested, reported, pending, revised or rejected. The anchorRef must also occur in that same item's sourceRefs (for a current source) or contextRefs (for a preceding source). An anchor elsewhere in the packet is not enough. Repeated topic alone is not an episode.
32
32
 
33
33
  Preserve relevant technology names only through code-permitted public bindings: ${JSON.stringify(CATALOG)}. Every material selected named entity gets an entities entry with a short unique ref, kind, visibility, sourceRef, its exact source mention and mentionIndex (zero-based occurrence of that exact whole-word mention in that source). publicKey must be an allowed key for public technology and bindingRef empty; otherwise publicKey is empty and visibility private. For private identity, match packet.entityBindings sourceAnchors against the original sourceRef and mentionIndex, using the original surrounding words to distinguish roles and people. Reuse that explicit bindingRef for a known original occurrence; an empty bindingRef is only for a genuinely new occurrence. Two people called Alex in one message have different mentionIndex values and must keep their original bindings. Equal names alone do not establish identity; ambiguous identities are unresolved. When the current message refers to their earlier roles, identify the person through that preceding original source occurrence rather than creating a new binding from the repeated name. Confidential employer/customer relationships remain private even when the same word names public technology. The code, not you, assigns public labels and private aliases. Use {{ref}} placeholders for every entity mention in title, summary, decisions, verification, statement and workContext; never output raw private names there. Private placeholders expand to about fifty characters; keep enough room under all text limits after binding. Each evidence item lists pertinent entityRefs only. Entity source mention and raw identity are local-only and must not appear anywhere else in the result. Source role is authorship, never personal ownership.
34
34
 
35
- Assessment coverage accounts for every current source locator exactly once. represented entries identify distinct evidence indexes actually citing that source; irrelevant and privacy_withheld entries have no evidence indexes. A material omitted decision, pending check, constraint or ambiguity is unresolved. For useful output every decision and verification entry must have a pertinent evidence reference. Empty evidence cannot coexist with decisions or verification. no_safe_context requires an explicit reason and a neutral nonfactual title and summary; do not erase meaningful work merely to produce an empty result. unresolved is valid and keeps this range pending. Never create filler or require one claim per message.\n`;
35
+ For every entityRef on an evidence item, that entity's sourceRef must also occur in that same item's sourceRefs (for a current source) or contextRefs (for a preceding source). An entity source elsewhere in the packet is not enough. Prefer the actual mention in the item's cited source when available; do not invent a cross-source identity link. Ordinary descriptive phrases and anonymous project labels are not named identities and do not need entity bindings.
36
+
37
+ Assessment coverage accounts for every current source locator exactly once. represented entries identify distinct zero-based evidence indexes actually citing that source in their sourceRefs, not turns or source positions. Include every evidence index citing that source; irrelevant and privacy_withheld entries have no evidence indexes and no evidence item citing that source. A material omitted decision, pending check, constraint or ambiguity is unresolved. For useful output every decision and verification entry must have a pertinent evidence reference. Empty evidence cannot coexist with decisions or verification. no_safe_context requires an explicit reason and exactly "No safe reusable context" for both title and summary; do not erase meaningful work merely to produce an empty result. unresolved is valid and keeps this range pending. Never create filler or require one claim per message.\n`;
36
38
  }
37
39
 
38
40
  function exact(value, fields) {
@@ -2,6 +2,74 @@ const fs = require("node:fs");
2
2
  const path = require("node:path");
3
3
  const { randomUUID } = require("node:crypto");
4
4
  const { digest, POLICY_VERSION } = require("./continuity-preparation.js");
5
+ const { requestTimeout } = require("./run-deadline.js");
6
+
7
+ const PREPARATION_DIAGNOSTICS = {
8
+ native_setup: ["native_account_unavailable", "native_cli_incompatible", "native_start_failed"],
9
+ native_execution: ["native_process_failed", "native_timeout", "native_output_limit", "native_start_failed", "native_provider_unavailable", "native_rate_limited", "native_authentication_failed", "native_schema_rejected"],
10
+ native_parse: ["native_invalid_result"],
11
+ continuity_validation: ["continuity_validation_failed"],
12
+ payload_validation: ["payload_validation_failed"],
13
+ preparation_persistence: ["preparation_persistence_failed"],
14
+ };
15
+
16
+ const VALIDATION_MESSAGES = new Set([
17
+ "Continuity preparation returned missing or unsupported fields.",
18
+ "Continuity preparation returned an invalid array.",
19
+ "Continuity preparation returned invalid references.",
20
+ "Invalid preceding source metadata.",
21
+ "Entity binding is not grounded in a supplied source mention.",
22
+ "Entity binding selected an invalid source mention occurrence.",
23
+ "Entity binding requested an unpermitted public label.",
24
+ "Private identity needs an unambiguous source mention occurrence.",
25
+ "Private identity needs an explicit local binding selection.",
26
+ "Private identity selected an unavailable or conflicting source binding.",
27
+ "This private source occurrence already has a binding; select it explicitly or leave the identity unresolved.",
28
+ "Entity binding requested an invalid visibility.",
29
+ "Continuity preparation returned invalid text.",
30
+ "Continuity preparation invented an entity placeholder.",
31
+ "Continuity preparation returned an invalid entity placeholder.",
32
+ "Continuity preparation retained a private entity mention.",
33
+ "Item entity placeholders and typed bindings disagree.",
34
+ "Item entity binding is not linked to its source.",
35
+ "Item entity label was replaced by generic wording.",
36
+ "Episode anchor is not bound to the item sources.",
37
+ "Continuity preparation left a selected entity unrepresented.",
38
+ "Invalid continuity assessment.",
39
+ "Continuity preparation omitted current source coverage.",
40
+ "Invalid source coverage disposition.",
41
+ "Continuity preparation returned contradictory source coverage.",
42
+ "Continuity source coverage remains unresolved.",
43
+ "Continuity preparation remains unresolved.",
44
+ "Continuity preparation stranded a decision or verification entry.",
45
+ "Continuity preparation returned a contradictory empty result.",
46
+ "An empty result must use a neutral summary rather than discard substantive context.",
47
+ "Preparation returned missing or unsupported fields.",
48
+ "Preparation returned invalid or oversized text.",
49
+ "Preparation returned an invalid or oversized array.",
50
+ "Preparation referenced missing, duplicate, or invented sources.",
51
+ "containsPersonal must be an explicit boolean when supplied.",
52
+ "Recheck this proposed empty result against the original sources; preserve meaningful context or confirm a legitimate neutral empty result.",
53
+ "Preparation failed strict local validation; recheck schema, text limits and references.",
54
+ ]);
55
+ const VALIDATION_FIELD = /^(?:title|summary|(?:decisions|verification)\[(?:[0-9]|1[0-5])\]|evidence\[(?:[0-9]|1[01])\]\.(?:title|statement|workContext))$/;
56
+ const VALIDATION_REASONS = new Set(["empty or oversized text", "control characters or line breaks", "a phone-like numeric sequence", "a code-like declaration", "a credential-like pattern", "a forbidden literal fragment"]);
57
+ const VALIDATION_REFERENCES = /^Preparation evidence\[(?:[0-9]|1[01])\]\.(?:decisionRefs|verificationRefs) (?:contains duplicate indexes|must contain zero-based indexes into (?:decisions|verification) \(count (?:[0-9]|1[0-6])\))\.$/;
58
+ const VALIDATION_FRAGMENTS = new Set(["-----begin", "authorization", "sk-", "sk_", "ghp_", "gho_", "ghu_", "ghs_", "ghr_", "xox", "github_pat_", "glpat-", "ignore previous instructions", "ignore all previous instructions", "system prompt", "@", "://", "`", "/"]);
59
+
60
+ function safeValidationFeedback(value) {
61
+ if (!value || typeof value.rule !== "string" || value.rule.length > 200 || !(VALIDATION_MESSAGES.has(value.rule) || VALIDATION_REFERENCES.test(value.rule)) || !Array.isArray(value.fields) || value.fields.length > 64 ||
62
+ !value.fields.every((field) => typeof field?.path === "string" && VALIDATION_FIELD.test(field.path) && VALIDATION_REASONS.has(field.reason) &&
63
+ (field.reason !== "a forbidden literal fragment" || VALIDATION_FRAGMENTS.has(field.fragment)))) return undefined;
64
+ return { rule: value.rule, fields: value.fields.map(({ path, reason, fragment }) => ({ path, reason, ...(reason === "a forbidden literal fragment" ? { fragment } : {}) })) };
65
+ }
66
+
67
+ function validationFeedbackText(value) {
68
+ const feedback = safeValidationFeedback(value);
69
+ if (!feedback) return undefined;
70
+ const fields = feedback.fields.map(({ path, reason, fragment }) => `Field ${path} fails the privacy or length rule for ${reason === "a forbidden literal fragment" ? `the forbidden literal fragment ${JSON.stringify(fragment)}` : reason}.`).join(" ");
71
+ return `${feedback.rule}${fields ? ` ${fields} Rephrase those fields without copying the rejected content.` : ""} Recheck every text field before returning. Describe a user's go-ahead as approval, permission or adoption; the literal word authorization is rejected by the privacy filter. Preserve supported results, failed or pending checks, unresolved problems and release status while correcting the validation error.`;
72
+ }
5
73
 
6
74
  function readPrivate(file, fallback) {
7
75
  try {
@@ -97,17 +165,38 @@ function continuityStore(root, harness, config) {
97
165
  save(state);
98
166
  throw new Error("Continuity preparation needs review; this range exhausted two native attempts. Retry it explicitly after review.");
99
167
  }
100
- const attempt = prior?.fingerprint === fingerprint ? prior : { fingerprint, fromTurn: packet.fromTurn, toTurn: packet.toTurn, attempts: prior?.attempts || 0 };
168
+ const attempt = prior?.fingerprint === fingerprint ? prior : { fingerprint, sessionRef: packet.sessionRef, fromTurn: packet.fromTurn, toTurn: packet.toTurn, attempts: prior?.attempts || 0 };
101
169
  attempt.attempts += 1;
102
170
  attempt.status = "preparing";
103
171
  state.attempts[identity] = attempt;
104
172
  save(state); // Reservation precedes any native process, including after a crash.
105
- return { attempt: attempt.attempts, fingerprint };
173
+ return { attempt: attempt.attempts, fingerprint, validationFeedback: safeValidationFeedback(attempt.validationFeedback) };
106
174
  }
107
- function failure(packet, reason = "validation_failed") {
175
+ function failure(packet, diagnostic, validationFeedback) {
108
176
  const state = load();
109
177
  const attempt = state.attempts[key(packet)];
110
- if (attempt) { attempt.status = attempt.attempts >= 2 ? "needs_review" : "unresolved"; attempt.reason = reason; save(state); }
178
+ if (attempt) {
179
+ if (diagnostic?.category === "native_cancelled") {
180
+ attempt.attempts = Math.max(0, attempt.attempts - 1);
181
+ attempt.status = "unresolved";
182
+ attempt.reason = "native_cancelled";
183
+ delete attempt.diagnostic;
184
+ save(state);
185
+ return;
186
+ }
187
+ attempt.status = attempt.attempts >= 2 ? "needs_review" : "unresolved";
188
+ // Only code-owned classifications survive; messages and provider output may contain source or credentials.
189
+ if (Object.hasOwn(PREPARATION_DIAGNOSTICS, diagnostic?.stage) && PREPARATION_DIAGNOSTICS[diagnostic.stage].includes(diagnostic.category)) {
190
+ attempt.diagnostic = { stage: diagnostic.stage, category: diagnostic.category };
191
+ attempt.reason = diagnostic.category;
192
+ } else {
193
+ attempt.reason = "preparation_failed";
194
+ delete attempt.diagnostic;
195
+ }
196
+ const feedback = safeValidationFeedback(validationFeedback);
197
+ if (feedback) attempt.validationFeedback = feedback;
198
+ save(state);
199
+ }
111
200
  }
112
201
  function freeze(packet, result) {
113
202
  const state = load();
@@ -124,7 +213,7 @@ function continuityStore(root, harness, config) {
124
213
  companion: { schemaVersion: 1, requestId: randomUUID(), items } };
125
214
  state.outbox.push(entry);
126
215
  const attempt = state.attempts[key(packet)];
127
- if (attempt) { attempt.status = "prepared"; attempt.payloadKey = payloadKey; }
216
+ if (attempt) { attempt.status = "prepared"; attempt.payloadKey = payloadKey; delete attempt.validationFeedback; }
128
217
  save(state); // Semantic content and base bytes are durable before entering the old queue.
129
218
  return entry;
130
219
  }
@@ -163,26 +252,29 @@ function continuityStore(root, harness, config) {
163
252
  return { load, save, dictionary, saveDictionary: (value) => writePrivate(dictionaryFile, value), reservation, failure, freeze, find, update, bind, reserveBase,
164
253
  summary() {
165
254
  const state = load();
166
- return { preparation: Object.values(state.attempts).map(({ status, attempts, fromTurn, toTurn, reason }) => ({ status, attempts, fromTurn, toTurn, reason })),
255
+ return { preparation: Object.entries(state.attempts).map(([identity, { sessionRef, status, attempts, fromTurn, toTurn, reason, diagnostic }]) => ({ sessionRef: sessionRef || identity.slice(0, identity.lastIndexOf(":")), status, attempts, fromTurn, toTurn, reason, diagnostic })),
167
256
  base: state.outbox.map(({ sessionRef, fromTurn, toTurn, baseStatus, continuityStatus, attempts, nextAttemptAt, reason }) => ({ sessionRef, fromTurn, toTurn, baseStatus, continuityStatus, attempts, nextAttemptAt, reason })) };
168
257
  } };
169
258
  }
170
259
 
171
- async function flushContinuity(store, config, capable, fetcher = fetch, now = Date.now()) {
260
+ async function flushContinuity(store, config, capable, fetcher = fetch, now = Date.now(), deadlineAt = Infinity, beforeAttempt) {
172
261
  const excluded = new Set(config.syncPolicy?.excludedSessionRefs || []);
173
262
  for (const snapshot of store.load().outbox) {
263
+ if (Date.now() >= deadlineAt) break;
174
264
  if (excluded.has(snapshot.sessionRef)) continue;
175
265
  if (snapshot.baseStatus !== "accepted" || ["attached", "not_applicable", "needs_review"].includes(snapshot.continuityStatus)) continue;
266
+ if (beforeAttempt && beforeAttempt(snapshot) === false) continue;
176
267
  if (!capable) { store.update(snapshot.key, (entry) => { entry.continuityStatus = "waiting_for_capability"; }); continue; }
177
268
  if (snapshot.nextAttemptAt > now) continue;
178
269
  if (snapshot.attempts >= 10) { store.update(snapshot.key, (entry) => { entry.continuityStatus = "needs_review"; entry.reason = "transport_budget"; }); continue; }
270
+ requestTimeout(deadlineAt);
179
271
  const entry = store.update(snapshot.key, (item) => { item.attempts += 1; item.continuityStatus = "pending";
180
272
  item.nextAttemptAt = now + Math.min(15 * 60_000 * (2 ** (item.attempts - 1)), 24 * 3_600_000); });
181
273
  let response;
182
274
  try {
183
275
  response = await fetcher(`${config.apiUrl}/v1/connectors/coding-sessions/syncs/${entry.receipt.id}/continuity`, {
184
276
  method: "POST", redirect: "error", headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.token}` },
185
- body: JSON.stringify(entry.companion), signal: AbortSignal.timeout(30_000),
277
+ body: JSON.stringify(entry.companion), signal: AbortSignal.timeout(requestTimeout(deadlineAt)),
186
278
  });
187
279
  const result = await response.json().catch(() => undefined);
188
280
  const ack = result?.data;
@@ -198,4 +290,4 @@ async function flushContinuity(store, config, capable, fetcher = fetch, now = Da
198
290
  }
199
291
  }
200
292
 
201
- module.exports = { continuityStore, flushContinuity, readPrivate, writePrivate, commitConnection, assertConnectionReady };
293
+ module.exports = { continuityStore, flushContinuity, readPrivate, writePrivate, commitConnection, assertConnectionReady, safeValidationFeedback, validationFeedbackText };
@@ -162,7 +162,8 @@ function localSessions(options = {}) {
162
162
  if (!messages.length) return [];
163
163
  const knownTimes = records.map((record) => eventTime(record.timestamp)).filter(Boolean);
164
164
  return [{ sessionRef, turnCount: messages.length, turnTimes: messages.map((turn) => eventTime(turn.user.timestamp)),
165
- startedAt: knownTimes.at(0) || null, updatedAt: knownTimes.at(-1) || null }];
165
+ startedAt: knownTimes.at(0) || null, updatedAt: knownTimes.at(-1) || null,
166
+ ...(records.find((record) => record.workspace)?.workspace ? { workspace: records.find((record) => record.workspace).workspace } : {}) }];
166
167
  });
167
168
  }
168
169