@sema-agent/sdk 4.1.0 → 4.3.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/README.md +34 -1
- package/dist/client.d.ts +10 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +7 -2
- package/dist/client.js.map +1 -1
- package/dist/control-types.d.ts +22 -4
- package/dist/control-types.d.ts.map +1 -1
- package/dist/control-types.js +3 -1
- package/dist/control-types.js.map +1 -1
- package/dist/errors.d.ts +42 -5
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +81 -7
- package/dist/errors.js.map +1 -1
- package/dist/events.d.ts +63 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js.map +1 -1
- package/dist/health.d.ts +76 -7
- package/dist/health.d.ts.map +1 -1
- package/dist/health.js +40 -2
- package/dist/health.js.map +1 -1
- package/dist/idempotency.d.ts +11 -1
- package/dist/idempotency.d.ts.map +1 -1
- package/dist/idempotency.js +11 -1
- package/dist/idempotency.js.map +1 -1
- package/dist/index.d.ts +18 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -2
- package/dist/index.js.map +1 -1
- package/dist/registry/health.d.ts +6 -1
- package/dist/registry/health.d.ts.map +1 -1
- package/dist/registry/health.js +6 -4
- package/dist/registry/health.js.map +1 -1
- package/dist/registry/index.d.ts +7 -1
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/index.js +7 -1
- package/dist/registry/index.js.map +1 -1
- package/dist/resources/approvals.d.ts +43 -13
- package/dist/resources/approvals.d.ts.map +1 -1
- package/dist/resources/approvals.js +35 -12
- package/dist/resources/approvals.js.map +1 -1
- package/dist/resources/assistant.d.ts +21 -4
- package/dist/resources/assistant.d.ts.map +1 -1
- package/dist/resources/assistant.js +10 -3
- package/dist/resources/assistant.js.map +1 -1
- package/dist/resources/attachments.d.ts +9 -2
- package/dist/resources/attachments.d.ts.map +1 -1
- package/dist/resources/attachments.js.map +1 -1
- package/dist/resources/control/secrets.d.ts +10 -1
- package/dist/resources/control/secrets.d.ts.map +1 -1
- package/dist/resources/control/secrets.js +10 -1
- package/dist/resources/control/secrets.js.map +1 -1
- package/dist/resources/fleet.d.ts +11 -5
- package/dist/resources/fleet.d.ts.map +1 -1
- package/dist/resources/fleet.js +2 -1
- package/dist/resources/fleet.js.map +1 -1
- package/dist/resources/images.d.ts +10 -1
- package/dist/resources/images.d.ts.map +1 -1
- package/dist/resources/images.js +20 -5
- package/dist/resources/images.js.map +1 -1
- package/dist/resources/leader.d.ts +7 -3
- package/dist/resources/leader.d.ts.map +1 -1
- package/dist/resources/leader.js +7 -3
- package/dist/resources/leader.js.map +1 -1
- package/dist/resources/ops.d.ts +29 -2
- package/dist/resources/ops.d.ts.map +1 -1
- package/dist/resources/ops.js +6 -1
- package/dist/resources/ops.js.map +1 -1
- package/dist/resources/runs.d.ts +89 -18
- package/dist/resources/runs.d.ts.map +1 -1
- package/dist/resources/runs.js +53 -17
- package/dist/resources/runs.js.map +1 -1
- package/dist/resources/session-sync.d.ts +77 -17
- package/dist/resources/session-sync.d.ts.map +1 -1
- package/dist/resources/session-sync.js +81 -20
- package/dist/resources/session-sync.js.map +1 -1
- package/dist/resources/sessions.d.ts +82 -13
- package/dist/resources/sessions.d.ts.map +1 -1
- package/dist/resources/sessions.js +39 -7
- package/dist/resources/sessions.js.map +1 -1
- package/dist/resources/tool-approvals.d.ts +8 -5
- package/dist/resources/tool-approvals.d.ts.map +1 -1
- package/dist/resources/tool-approvals.js +2 -1
- package/dist/resources/tool-approvals.js.map +1 -1
- package/dist/resources/trace.d.ts +17 -3
- package/dist/resources/trace.d.ts.map +1 -1
- package/dist/resources/trace.js +25 -5
- package/dist/resources/trace.js.map +1 -1
- package/dist/resources/workflows.d.ts +42 -10
- package/dist/resources/workflows.d.ts.map +1 -1
- package/dist/resources/workflows.js +46 -9
- package/dist/resources/workflows.js.map +1 -1
- package/dist/resources/workspace.d.ts +36 -11
- package/dist/resources/workspace.d.ts.map +1 -1
- package/dist/resources/workspace.js.map +1 -1
- package/dist/sse.d.ts +58 -14
- package/dist/sse.d.ts.map +1 -1
- package/dist/sse.js +88 -15
- package/dist/sse.js.map +1 -1
- package/dist/sync.d.ts +37 -8
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +24 -8
- package/dist/sync.js.map +1 -1
- package/dist/transport.d.ts +31 -4
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +38 -6
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +156 -51
- package/dist/types.d.ts.map +1 -1
- package/openapi.yaml +377 -59
- package/package.json +2 -2
- package/registry-openapi.yaml +34 -3
package/dist/resources/runs.js
CHANGED
|
@@ -83,14 +83,26 @@ export class RunsResource {
|
|
|
83
83
|
/** design/80 D-A — STEER a RUNNING durable run: inject mid-flight direction (the core of *supervision* vs mere
|
|
84
84
|
* approve/deny). AT-MOST-ONCE — steer is NOT idempotent, so NO submit/retry. `trusted` is NOT a body field:
|
|
85
85
|
* the server derives it from the authenticated operator (the BFF's operator role), never a client-set flag.
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
86
|
+
*
|
|
87
|
+
* 🔴 STEER ITSELF IS LIVE/SHIPPED (service §K-6, runStore-gated). Branch on {@link SteerReceipt.delivery},
|
|
88
|
+
* NOT on the status code — the three legs are (`routes/runs.ts:641`/`:648`/`:734`):
|
|
89
|
+
* · 200 `applied` — injected into the live run on this replica.
|
|
90
|
+
* · 202 `queued` — the run is durably **suspended**: the steer is PARKED on its pending checkpoint
|
|
91
|
+
* and injected on resume. ⚠️ [2400] TR-3 correction: the previous note here said a
|
|
92
|
+
* `suspended` run yields `steering.not_running` (409). It does not — the server
|
|
93
|
+
* parks it. A 409 only comes from a run that is TERMINAL-with-no-park-path, or
|
|
94
|
+
* running on ANOTHER replica (cross-replica live-steer is not wired yet).
|
|
95
|
+
* · 202 `parked_for_wake` — the run already ENDED; the message sits on a freshly-minted `task_done`
|
|
96
|
+
* checkpoint and is delivered ONLY by `POST /v1/sessions/:id/wake`.
|
|
97
|
+
* Typed errors: `SteeringNotRunningError` (`steering.not_running`, 409) / `SteeringInvalidContentError`
|
|
98
|
+
* (`steering.invalid_content`, 422) / `CapabilityUnavailableError` (`capability.checkpoint_store_required`,
|
|
99
|
+
* 501 — steering a suspended run needs the checkpoint store).
|
|
100
|
+
*
|
|
101
|
+
* 🔴 ONE narrow caveat (NOT "steer is a draft"): the per-call `mode` arg is currently a NO-OP — core's steer is
|
|
102
|
+
* HARNESS-LEVEL (`setSteeringMode`), set globally on the runner, not switched per steer call; the `mode`
|
|
103
|
+
* mapping is pending D-A core (search [76]). Sending `mode` is harmless (ignored); steer delivery itself works
|
|
104
|
+
* today. `mode` semantics (when wired): "all" = drain ALL queued steers at the next turn boundary;
|
|
105
|
+
* "one-at-a-time" (default) = one per turn, FIFO. `priority` is likewise advisory — see {@link SteerPriority}. */
|
|
94
106
|
async steer(taskId, steer, opts) {
|
|
95
107
|
return this.t.request({
|
|
96
108
|
method: "POST",
|
|
@@ -146,7 +158,18 @@ export class RunsResource {
|
|
|
146
158
|
* (status/retrieval_status/partial flags verbatim). Non-blocking (no long-poll). server 1.250: no longer
|
|
147
159
|
* replica-local — a full in-process miss falls back to the durable agent record (cross-instance /
|
|
148
160
|
* post-restart): terminal = final snapshot served, still-running-elsewhere = honest "outcome unknown
|
|
149
|
-
* here".
|
|
161
|
+
* here".
|
|
162
|
+
* 🔴 [2400] TR-15 — the 404 is NOT one indistinguishable arm: it carries TWO codes and they mean different
|
|
163
|
+
* things (server `src/http/routes/runs.ts`, all `sendError(…, 404, …)` sites on this verb):
|
|
164
|
+
* · `not_found.run` (:869/:873) — the RUN is unknown, or you are not its owner. The owner-gate and the
|
|
165
|
+
* unknown-run arm deliberately share this code+text (no existence oracle across tenants) — that pair, and
|
|
166
|
+
* only that pair, is indistinguishable by design.
|
|
167
|
+
* · `not_found.subagent` (:937/:982) — the run is yours and visible, but THIS HANDLE is not one of its
|
|
168
|
+
* background children: unknown handle, not this run's child, already reaped (bg children live in the
|
|
169
|
+
* replica-local registry for the parent's lifetime), or a `wa…` workflow-agent row (read those via the
|
|
170
|
+
* workflow journal instead).
|
|
171
|
+
* Consumer value: the first says "check the taskId / your identity", the second says "the parent is fine,
|
|
172
|
+
* the child handle is wrong or gone" — a UI that collapsed them sent users to re-check the wrong thing.
|
|
150
173
|
*
|
|
151
174
|
* 🔴 server 1.245 [1493]: this is a CONVERSATION-CONTENT read, so it is SESSION-scoped, not just
|
|
152
175
|
* principal-scoped — pass `session` (your own conversation's sessionId, the one POST /v1/runs returned). A
|
|
@@ -166,7 +189,9 @@ export class RunsResource {
|
|
|
166
189
|
* spooled handles re-read in full, cursor-only handles return only new bytes; trust the projection's own
|
|
167
190
|
* flags), monitor (batches), and background_agent (final report). A workflow handle 404s (its read face is
|
|
168
191
|
* the workflow journal). 🔴 `target` is the CANONICAL task handle only (b…/m…/a… task_id from the spawn) —
|
|
169
|
-
* the in-engine tools' agent-name / legacy-id resolution is NOT on the wire (404
|
|
192
|
+
* the in-engine tools' agent-name / legacy-id resolution is NOT on the wire (404 `not_found.task_handle`,
|
|
193
|
+
* `:1076` — DISTINCT from `not_found.run`, [2400] TR-15: the run is visible, the HANDLE is not one of its
|
|
194
|
+
* tasks / was reaped / is a workflow handle). 🔴 NO server-side
|
|
170
195
|
* filter: `?filter=` is REFUSED with 400 on both verbs (wire-supplied regexes don't run on the server) —
|
|
171
196
|
* fetch the output and apply your regex locally, knowing the wire serves the CLIPPED projection (matches
|
|
172
197
|
* inside a clipped middle are not recoverable, and a cursor-only bash leg consumes what it serves; the
|
|
@@ -187,12 +212,21 @@ export class RunsResource {
|
|
|
187
212
|
}
|
|
188
213
|
/** server 1.246 ([1499]) — stop a background task handle (CC TaskStop 对位): bash kill / monitor stop /
|
|
189
214
|
* agent abort, attributed "user" (`output.stoppedBy`). Idempotent on a terminal handle — the 200
|
|
190
|
-
* `output.status` tells you what actually happened. 🔴
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
215
|
+
* `output.status` tells you what actually happened. 🔴 Treat ONLY 200 as stopped — every other outcome is a
|
|
216
|
+
* 409, and [2400] TR-14 lists the family in full (server `src/http/routes/runs.ts:1111-1118`, one ternary
|
|
217
|
+
* chain; all five are {@link import("../errors.js").TaskStopConflictError}, discriminate on `.errorCode`):
|
|
218
|
+
* · `stop.not_local` — the handle lives on ANOTHER replica (cross-instance / post-restart):
|
|
219
|
+
* **no kill was attempted**. Distinct from not_landed. Retry via the
|
|
220
|
+
* owning replica, or read the durable record.
|
|
221
|
+
* · `stop.not_landed` — a kill WAS attempted and did not land; the body carries the honest
|
|
222
|
+
* projection (`output.status` stays "running"). Retry.
|
|
223
|
+
* · `stop.parked` — the row is PARKED (no live process to kill). Resolve its gate instead.
|
|
224
|
+
* · `stop.park_resume_won` — stop RACED the "approval landed → resume" path and the resume won;
|
|
225
|
+
* the task is running again. Re-read, then stop again if still wanted.
|
|
226
|
+
* · `stop.park_arbiter_unreachable`— the arbiter store is unreachable, so the truth is UNKNOWN (core 1.397's
|
|
227
|
+
* three-way split). Neither "stopped" nor "still running" — retry later.
|
|
228
|
+
* Open set: a future `stop.*` code still lands on TaskStopConflictError, so keep a `default` in your switch.
|
|
229
|
+
* Same session scoping + kind set + canonical-handle
|
|
196
230
|
* addressing as {@link taskOutput} (a workflow handle 404s — stop workflows via their cancel face). Gate
|
|
197
231
|
* on `capabilities.taskHandles`. */
|
|
198
232
|
async taskStop(taskId, target, opts) {
|
|
@@ -244,7 +278,9 @@ export class RunsResource {
|
|
|
244
278
|
}
|
|
245
279
|
}
|
|
246
280
|
/** Resumable event stream. Internally: SSE + Last-Event-ID auto-reconnect; on a 416 (evicted past retention,
|
|
247
|
-
* body `{error,retainedFrom}` —
|
|
281
|
+
* body `{error, errorCode:"limit.retention_evicted", retainedFrom}` — [2400] CB-7: the machine code IS on the
|
|
282
|
+
* wire (server `src/http/sse-log.ts:41`); this loop keys on the STATUS only because what it needs is
|
|
283
|
+
* `retainedFrom`) → full-sync via trace.turns then resume
|
|
248
284
|
* from `retainedFrom` (sse.ts). Established-stream drops do NOT count as retries. */
|
|
249
285
|
async *events(taskId, opts) {
|
|
250
286
|
let first = opts?.lastEventId;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runs.js","sourceRoot":"","sources":["../../src/resources/runs.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"runs.js","sourceRoot":"","sources":["../../src/resources/runs.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,9 +9,17 @@
|
|
|
9
9
|
* PLAN (dry run): plan() classifies what a PUSH WOULD do, no write.
|
|
10
10
|
*
|
|
11
11
|
* Owner-gated exactly like fork/delete (§9): a PULL 404s a non-owner (no existence oracle); a PUSH is own-or-FRESH
|
|
12
|
-
* (a session that does not exist yet, the caller is about to import it, is allowed). A
|
|
12
|
+
* (a session that does not exist yet, the caller is about to import it, is allowed). A
|
|
13
13
|
* PUSH/PLAN that would lose dst history (fork/stale) without `resolution:"overwrite-dst"` → a typed
|
|
14
|
-
* {@link import("../errors.js").SyncConflictError} (409).
|
|
14
|
+
* {@link import("../errors.js").SyncConflictError} (409).
|
|
15
|
+
*
|
|
16
|
+
* 🔴 ID 形状(2026-08-02 更正,server `routes/session-sync.ts:135-138` 亲读):这一族的 id 门是
|
|
17
|
+
* `isUuidShape` = **任意 uuid 版本**,NOT `isUuidV7`. server 的旁注逐字说明了为什么这是**故意**的:
|
|
18
|
+
* 「a v4-keyed legacy shell session must be pushable (it was creatable via the lenient submit face)」——
|
|
19
|
+
* crafted-id 类(LIKE 元字符、超长)仍被挡在门外。此前 SDK 与 spec 三处都写「400 on a non-uuidv7 id」,
|
|
20
|
+
* 会让持 uuidv4 legacy 会话的消费方在客户端先自判「本会话不支持 sync」而根本不发请求,把 server 明确
|
|
21
|
+
* 为迁移开的通道自己封死。对照组:`sessions.fork`/`delete`/`policy` 用的**确实是** `isUuidV7`(那三处
|
|
22
|
+
* 的同款说法是对的)—— 差别只出在 sync 这一族。 */
|
|
15
23
|
import type { Transport } from "../transport.js";
|
|
16
24
|
import type { SessionManifest, SyncEntry, SyncSnapshot, SessionRulesRecord, SyncAnchor, SyncRelation, ImportStaged, ImportCommitted } from "../types.js";
|
|
17
25
|
export declare class SessionSyncResource {
|
|
@@ -21,8 +29,9 @@ export declare class SessionSyncResource {
|
|
|
21
29
|
* policy + anchors + leaf), NO entry payloads and NO blob bytes (those stream / are content-addressed). The
|
|
22
30
|
* server wraps it as `{ manifest }`; this unwraps to the bare {@link SessionManifest}. Feed `manifest.entryIds`
|
|
23
31
|
* to `classifySyncRelationshipByIds` to decide fast-forward/fork BEFORE pulling the entry stream + only the
|
|
24
|
-
* blob hashes the local peer lacks. Owner-scoped (non-owner → 404, no oracle); 400 on
|
|
25
|
-
*
|
|
32
|
+
* blob hashes the local peer lacks. Owner-scoped (non-owner → 404, no oracle); 400 on an id that is not
|
|
33
|
+
* uuid-SHAPED (any uuid version passes — see the module header; a v4 legacy session is deliberately
|
|
34
|
+
* pushable); 501 without a durable session store. */
|
|
26
35
|
manifest(sessionId: string, opts?: {
|
|
27
36
|
signal?: AbortSignal;
|
|
28
37
|
}): Promise<{
|
|
@@ -51,16 +60,24 @@ export declare class SessionSyncResource {
|
|
|
51
60
|
/** PUSH one content-addressed blob INTO the cloud's snapshot store — `PUT …/sync/blobs/:hash` (the bare path; the
|
|
52
61
|
* PULL is scoped under `snapshots/:key`). RAW bytes body, octet-stream. The cloud verifies sha256(body)===:hash
|
|
53
62
|
* (the content-address integrity guard) — a mismatch → 400 `blob hash mismatch`; the SDK validates the hash
|
|
54
|
-
* shape locally first. own-or-FRESH gate.
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
63
|
+
* shape locally first. own-or-FRESH gate.
|
|
64
|
+
*
|
|
65
|
+
* 🔴 **两种 413,语义相反**(server `routes/session-sync.ts:250` vs `:253-263`,2026-08-02 亲读):
|
|
66
|
+
* - 通用超 cap(`readRawBody` 的 64 MiB per-blob 帽)—— 换小 blob / 分片是唯一出路;
|
|
67
|
+
* - `blob_too_large_for_sql`(SQL 店的行宽帽,body 带 `sizeBytes`/`limit`)—— 这块字节在**这个后端上
|
|
68
|
+
* 永远落不了地**,重试是纯浪费;运维要么换对象存储轨,要么这条 blob 不同步。
|
|
69
|
+
* 两者都是 413,只有机器码能区分 —— 别对 413 统一写「重试」。
|
|
70
|
+
*
|
|
71
|
+
* A backend write failure (e.g. a MinIO non-2xx) → 502 `blob store write failed`. `openRaw` does **NOT** retry;
|
|
72
|
+
* the caller re-PUTs (the blob is content-addressed ⇒ idempotent). 204 on success (no body).
|
|
73
|
+
* Call this for every blob hash the manifest references that the cloud lacks BEFORE import Phase A. */
|
|
58
74
|
putBlob(sessionId: string, hash: string, bytes: Uint8Array, opts?: {
|
|
59
75
|
signal?: AbortSignal;
|
|
60
76
|
}): Promise<void>;
|
|
61
77
|
/** PLAN (dry-run) — `POST …/sync/plan` body `{ entryIds }` (the LOCAL peer's entry-id list, oldest-first) →
|
|
62
78
|
* classify what a PUSH WOULD do against the cloud's current log, WITHOUT writing. Returns the §7 {@link SyncRelation}
|
|
63
|
-
* (`fresh` for a fresh cloud session). own-or-FRESH gate; 400 on a non-string-array body / a non-
|
|
79
|
+
* (`fresh` for a fresh cloud session). own-or-FRESH gate; 400 on a non-string-array body / a non-uuid-SHAPED id
|
|
80
|
+
* (any uuid version passes — see the module header).
|
|
64
81
|
* NB: unlike import, plan never 409s — it is a pure read-side classify (a fork/stale is REPORTED in the relation,
|
|
65
82
|
* not raised). Use it to surface the keep-local/keep-cloud divergence before committing to a PUSH. */
|
|
66
83
|
plan(sessionId: string, body: {
|
|
@@ -68,21 +85,50 @@ export declare class SessionSyncResource {
|
|
|
68
85
|
}, opts?: {
|
|
69
86
|
signal?: AbortSignal;
|
|
70
87
|
}): Promise<SyncRelation>;
|
|
71
|
-
/** PUSH Phase A — `POST …/sync/import` body `{ entryIds, snapshots, policy, anchors, resolution? }`
|
|
72
|
-
* metadata; entries stream in Phase B). The cloud classifies (§7), pre-checks blob presence (every
|
|
73
|
-
* hash must have been PUT — a missing one → 422 `missing_blob`), guards the import lease + any active
|
|
74
|
-
* then mints a stagingId. Returns {@link ImportStaged}:
|
|
75
|
-
* - `identical` → `{ relation:"identical" }` with NO `stagingId` (
|
|
88
|
+
/** PUSH Phase A — `POST …/sync/import` body `{ entryIds, snapshots, policy, anchors, resolution?, logDigest? }`
|
|
89
|
+
* (the SMALL metadata; entries stream in Phase B). The cloud classifies (§7), pre-checks blob presence (every
|
|
90
|
+
* referenced hash must have been PUT — a missing one → 422 `missing_blob`), guards the import lease + any active
|
|
91
|
+
* run (409), then mints a stagingId. Returns {@link ImportStaged}:
|
|
92
|
+
* - `identical` → `{ relation:"identical", basis, payloadVerified }` with NO `stagingId` (skip Phase B).
|
|
76
93
|
* - `fresh`/`fast_forward` → `{ stagingId, relation }` — drive Phase B with the `stagingId`.
|
|
77
94
|
* 🔴 a `fork`/`stale` WITHOUT `resolution:"overwrite-dst"` → a typed SyncConflictError (409, carrying the relation).
|
|
78
|
-
* An active run on the session → ConflictError (409, `session_active`, `activeTaskId`)
|
|
79
|
-
*
|
|
95
|
+
* An active run on the session → ConflictError (409, `session_active`, `activeTaskId`).
|
|
96
|
+
*
|
|
97
|
+
* ⚠️ `identical` is **not** a bare short-circuit: the cloud idempotently REPLAYS snapshots/policy/anchors on this
|
|
98
|
+
* arm (server `routes/session-sync.ts:367-384`) — that is the self-heal for "the conversation synced but the file
|
|
99
|
+
* tree did not" (a previous run's post-commit replay 422'd). So it can still answer 422 `missing_blob` /
|
|
100
|
+
* `restore_failed`; re-PUT the blob and retry. Do NOT read `identical` as "zero side effects".
|
|
101
|
+
*
|
|
102
|
+
* ⚠️ `import_in_flight` (409) is **not** a permanent state: the cloud carries `retryAfterSec` on the error body
|
|
103
|
+
* and TAKES OVER a stale lease itself (default 600 s without activity — server `:405-434`). Wait the advertised
|
|
104
|
+
* seconds and retry; never surface it to the user as "this session is locked".
|
|
105
|
+
*
|
|
106
|
+
* Not a submit (it mints a distinct staging token) → no SDK retry. */
|
|
80
107
|
import(sessionId: string, body: {
|
|
81
108
|
entryIds: string[];
|
|
82
109
|
snapshots: SyncSnapshot[];
|
|
83
110
|
policy: SessionRulesRecord[];
|
|
84
111
|
anchors: SyncAnchor[];
|
|
85
112
|
resolution?: "overwrite-dst";
|
|
113
|
+
/** 🔴 OPTIONAL content digest of the SOURCE's whole entry log — the ONE key that makes
|
|
114
|
+
* {@link ImportStaged.payloadVerified `payloadVerified:true`} reachable (方案 B,黑板 [1701]→[1707] 三端对齐;
|
|
115
|
+
* server `routes/session-sync.ts:295,310,357-366`).
|
|
116
|
+
*
|
|
117
|
+
* Why it matters: `identical` is classified from the **entry-id SETS** alone, and an entry id is a uuidv7 —
|
|
118
|
+
* NOT content-addressed. "Same ids, different payloads" is structurally possible, and without a digest the
|
|
119
|
+
* cloud answers `{relation:"identical", basis:"entry-ids", payloadVerified:false}` and Phase B is skipped —
|
|
120
|
+
* a genuinely diverged session reads as "already synced" while the destination keeps its own content.
|
|
121
|
+
* Supply this and the cloud recomputes the digest over ITS log: equal ⇒ `basis:"entry-ids+digest"` +
|
|
122
|
+
* `payloadVerified:true` (a real short-circuit); different or not comparable ⇒ it does **not** short-circuit
|
|
123
|
+
* and opens staging for a full sync (the safe direction).
|
|
124
|
+
*
|
|
125
|
+
* 🔴 The SDK does NOT compute this — it has **zero runtime dependencies** by constitution, and the digest
|
|
126
|
+
* algorithm belongs to core (`sessionLogDigest`, scheme `sema-log-v3`; core ≥ 1.416.0 is the server's own
|
|
127
|
+
* floor for this leg). Callers that already hold core pass its value through; callers that don't simply omit
|
|
128
|
+
* the key and get byte-identical pre-4.3.0 behavior. A malformed value is NOT rejected — the cloud treats it
|
|
129
|
+
* as absent (a digest mismatch must never become an error / a 409 / a refusal to sync; it only means "do the
|
|
130
|
+
* work"). */
|
|
131
|
+
logDigest?: string;
|
|
86
132
|
}, opts?: {
|
|
87
133
|
signal?: AbortSignal;
|
|
88
134
|
}): Promise<ImportStaged>;
|
|
@@ -93,9 +139,23 @@ export declare class SessionSyncResource {
|
|
|
93
139
|
* (NOT JSON.stringify of an array — the cloud line-buffers). Re-POSTing the same stagingId is idempotent
|
|
94
140
|
* (appendBatch dedups; the commit re-runs). Typed errors: a per-entry/structural violation → 422 `invalid_entries`;
|
|
95
141
|
* a line too large → 413; a commit-time TOCTOU flip to fork/stale → a typed SyncConflictError (409); a refused
|
|
96
|
-
* policy loosen → 403 `loosen_forbidden`; a wrong/foreign stagingId → 404.
|
|
142
|
+
* policy loosen → 403 `loosen_forbidden`; a wrong/foreign stagingId → 404.
|
|
143
|
+
*
|
|
144
|
+
* ⚠️ Idempotency is HALF true: re-POSTing the same stagingId after a *transport* drop is fine (appendBatch dedups,
|
|
145
|
+
* the commit re-runs), but any PrecheckError / SessionError path server-side `abort()`s and cleans up the staging
|
|
146
|
+
* (server `:561,:565,:573-574`) ⇒ after a typed 4xx the SAME stagingId is DEAD — reopen Phase A.
|
|
147
|
+
*
|
|
148
|
+
* 🔴 **Memory** (`stream`, 4.3.0): by DEFAULT the SDK serializes the whole iterator into one NDJSON string before
|
|
149
|
+
* sending — peak memory is O(entire log). Until 4.3.0 three JSDocs here and in `sync.ts` claimed the opposite
|
|
150
|
+
* ("pipe a PULL stream straight into a PUSH without materializing the whole log"); that was false, and it is what
|
|
151
|
+
* {@link PushSessionOptions} sized its own trade-off note against. Pass `stream: true` to send a real
|
|
152
|
+
* `ReadableStream` request body (constant memory, one entry at a time — the server side has always been truly
|
|
153
|
+
* streaming: it reads chunk-wise with an 8 MiB per-line cap and a 500-entry append batch). It is OPT-IN because a
|
|
154
|
+
* streaming request body needs `duplex: "half"` support: Node ≥18 undici and Deno have it, but a polyfilled /
|
|
155
|
+
* proxied `fetch` may not, and silently switching the default would break those callers. */
|
|
97
156
|
importEntries(sessionId: string, stagingId: string, entries: Iterable<SyncEntry> | AsyncIterable<SyncEntry>, opts?: {
|
|
98
157
|
signal?: AbortSignal;
|
|
158
|
+
stream?: boolean;
|
|
99
159
|
}): Promise<ImportCommitted>;
|
|
100
160
|
}
|
|
101
161
|
//# sourceMappingURL=session-sync.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-sync.d.ts","sourceRoot":"","sources":["../../src/resources/session-sync.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"session-sync.d.ts","sourceRoot":"","sources":["../../src/resources/session-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;kCAqBkC;AAClC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAGjD,OAAO,KAAK,EACV,eAAe,EACf,SAAS,EACT,YAAY,EACZ,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,eAAe,EAChB,MAAM,aAAa,CAAC;AAMrB,qBAAa,mBAAmB;IAClB,OAAO,CAAC,QAAQ,CAAC,CAAC;gBAAD,CAAC,EAAE,SAAS;IAEzC;;;;;;0DAMsD;IAChD,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,eAAe,CAAA;KAAE,CAAC;IAS1G;;;;;;mGAM+F;IACxF,OAAO,CACZ,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,EAC9B,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAC9B,cAAc,CAAC,SAAS,CAAC;IAQ5B;;;;+GAI2G;IACrG,OAAO,CACX,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAC9B,OAAO,CAAC,UAAU,CAAC;IAYtB;;;;;;;;;;;;;4GAawG;IAClG,OAAO,CACX,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,UAAU,EACjB,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAC9B,OAAO,CAAC,IAAI,CAAC;IAahB;;;;;2GAKuG;IACjG,IAAI,CACR,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE,EAC5B,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAC9B,OAAO,CAAC,YAAY,CAAC;IASxB;;;;;;;;;;;;;;;;;;2EAkBuE;IACjE,MAAM,CACV,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE;QACJ,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,SAAS,EAAE,YAAY,EAAE,CAAC;QAC1B,MAAM,EAAE,kBAAkB,EAAE,CAAC;QAC7B,OAAO,EAAE,UAAU,EAAE,CAAC;QACtB,UAAU,CAAC,EAAE,eAAe,CAAC;QAC7B;;;;;;;;;;;;;;;;;sBAiBc;QACd,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,EACD,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAC9B,OAAO,CAAC,YAAY,CAAC;IASxB;;;;;;;;;;;;;;;;;;;;iGAoB6F;IACvF,aAAa,CACjB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,QAAQ,CAAC,SAAS,CAAC,GAAG,aAAa,CAAC,SAAS,CAAC,EACvD,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAChD,OAAO,CAAC,eAAe,CAAC;CAY5B"}
|
|
@@ -12,8 +12,9 @@ export class SessionSyncResource {
|
|
|
12
12
|
* policy + anchors + leaf), NO entry payloads and NO blob bytes (those stream / are content-addressed). The
|
|
13
13
|
* server wraps it as `{ manifest }`; this unwraps to the bare {@link SessionManifest}. Feed `manifest.entryIds`
|
|
14
14
|
* to `classifySyncRelationshipByIds` to decide fast-forward/fork BEFORE pulling the entry stream + only the
|
|
15
|
-
* blob hashes the local peer lacks. Owner-scoped (non-owner → 404, no oracle); 400 on
|
|
16
|
-
*
|
|
15
|
+
* blob hashes the local peer lacks. Owner-scoped (non-owner → 404, no oracle); 400 on an id that is not
|
|
16
|
+
* uuid-SHAPED (any uuid version passes — see the module header; a v4 legacy session is deliberately
|
|
17
|
+
* pushable); 501 without a durable session store. */
|
|
17
18
|
async manifest(sessionId, opts) {
|
|
18
19
|
// 🔴 BREAKING(clay 拍 2026-07-28,信封归一):wire 原样 `{manifest}`,不再拆封。迁移:`(...).manifest`。
|
|
19
20
|
return this.t.request({
|
|
@@ -59,10 +60,17 @@ export class SessionSyncResource {
|
|
|
59
60
|
/** PUSH one content-addressed blob INTO the cloud's snapshot store — `PUT …/sync/blobs/:hash` (the bare path; the
|
|
60
61
|
* PULL is scoped under `snapshots/:key`). RAW bytes body, octet-stream. The cloud verifies sha256(body)===:hash
|
|
61
62
|
* (the content-address integrity guard) — a mismatch → 400 `blob hash mismatch`; the SDK validates the hash
|
|
62
|
-
* shape locally first. own-or-FRESH gate.
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
63
|
+
* shape locally first. own-or-FRESH gate.
|
|
64
|
+
*
|
|
65
|
+
* 🔴 **两种 413,语义相反**(server `routes/session-sync.ts:250` vs `:253-263`,2026-08-02 亲读):
|
|
66
|
+
* - 通用超 cap(`readRawBody` 的 64 MiB per-blob 帽)—— 换小 blob / 分片是唯一出路;
|
|
67
|
+
* - `blob_too_large_for_sql`(SQL 店的行宽帽,body 带 `sizeBytes`/`limit`)—— 这块字节在**这个后端上
|
|
68
|
+
* 永远落不了地**,重试是纯浪费;运维要么换对象存储轨,要么这条 blob 不同步。
|
|
69
|
+
* 两者都是 413,只有机器码能区分 —— 别对 413 统一写「重试」。
|
|
70
|
+
*
|
|
71
|
+
* A backend write failure (e.g. a MinIO non-2xx) → 502 `blob store write failed`. `openRaw` does **NOT** retry;
|
|
72
|
+
* the caller re-PUTs (the blob is content-addressed ⇒ idempotent). 204 on success (no body).
|
|
73
|
+
* Call this for every blob hash the manifest references that the cloud lacks BEFORE import Phase A. */
|
|
66
74
|
async putBlob(sessionId, hash, bytes, opts) {
|
|
67
75
|
if (!SHA256_HEX_RE.test(hash))
|
|
68
76
|
throw toApiError(400, { error: "blob hash must be 64 lowercase-hex chars" });
|
|
@@ -79,7 +87,8 @@ export class SessionSyncResource {
|
|
|
79
87
|
}
|
|
80
88
|
/** PLAN (dry-run) — `POST …/sync/plan` body `{ entryIds }` (the LOCAL peer's entry-id list, oldest-first) →
|
|
81
89
|
* classify what a PUSH WOULD do against the cloud's current log, WITHOUT writing. Returns the §7 {@link SyncRelation}
|
|
82
|
-
* (`fresh` for a fresh cloud session). own-or-FRESH gate; 400 on a non-string-array body / a non-
|
|
90
|
+
* (`fresh` for a fresh cloud session). own-or-FRESH gate; 400 on a non-string-array body / a non-uuid-SHAPED id
|
|
91
|
+
* (any uuid version passes — see the module header).
|
|
83
92
|
* NB: unlike import, plan never 409s — it is a pure read-side classify (a fork/stale is REPORTED in the relation,
|
|
84
93
|
* not raised). Use it to surface the keep-local/keep-cloud divergence before committing to a PUSH. */
|
|
85
94
|
async plan(sessionId, body, opts) {
|
|
@@ -90,15 +99,25 @@ export class SessionSyncResource {
|
|
|
90
99
|
signal: opts?.signal,
|
|
91
100
|
});
|
|
92
101
|
}
|
|
93
|
-
/** PUSH Phase A — `POST …/sync/import` body `{ entryIds, snapshots, policy, anchors, resolution? }`
|
|
94
|
-
* metadata; entries stream in Phase B). The cloud classifies (§7), pre-checks blob presence (every
|
|
95
|
-
* hash must have been PUT — a missing one → 422 `missing_blob`), guards the import lease + any active
|
|
96
|
-
* then mints a stagingId. Returns {@link ImportStaged}:
|
|
97
|
-
* - `identical` → `{ relation:"identical" }` with NO `stagingId` (
|
|
102
|
+
/** PUSH Phase A — `POST …/sync/import` body `{ entryIds, snapshots, policy, anchors, resolution?, logDigest? }`
|
|
103
|
+
* (the SMALL metadata; entries stream in Phase B). The cloud classifies (§7), pre-checks blob presence (every
|
|
104
|
+
* referenced hash must have been PUT — a missing one → 422 `missing_blob`), guards the import lease + any active
|
|
105
|
+
* run (409), then mints a stagingId. Returns {@link ImportStaged}:
|
|
106
|
+
* - `identical` → `{ relation:"identical", basis, payloadVerified }` with NO `stagingId` (skip Phase B).
|
|
98
107
|
* - `fresh`/`fast_forward` → `{ stagingId, relation }` — drive Phase B with the `stagingId`.
|
|
99
108
|
* 🔴 a `fork`/`stale` WITHOUT `resolution:"overwrite-dst"` → a typed SyncConflictError (409, carrying the relation).
|
|
100
|
-
* An active run on the session → ConflictError (409, `session_active`, `activeTaskId`)
|
|
101
|
-
*
|
|
109
|
+
* An active run on the session → ConflictError (409, `session_active`, `activeTaskId`).
|
|
110
|
+
*
|
|
111
|
+
* ⚠️ `identical` is **not** a bare short-circuit: the cloud idempotently REPLAYS snapshots/policy/anchors on this
|
|
112
|
+
* arm (server `routes/session-sync.ts:367-384`) — that is the self-heal for "the conversation synced but the file
|
|
113
|
+
* tree did not" (a previous run's post-commit replay 422'd). So it can still answer 422 `missing_blob` /
|
|
114
|
+
* `restore_failed`; re-PUT the blob and retry. Do NOT read `identical` as "zero side effects".
|
|
115
|
+
*
|
|
116
|
+
* ⚠️ `import_in_flight` (409) is **not** a permanent state: the cloud carries `retryAfterSec` on the error body
|
|
117
|
+
* and TAKES OVER a stale lease itself (default 600 s without activity — server `:405-434`). Wait the advertised
|
|
118
|
+
* seconds and retry; never surface it to the user as "this session is locked".
|
|
119
|
+
*
|
|
120
|
+
* Not a submit (it mints a distinct staging token) → no SDK retry. */
|
|
102
121
|
async import(sessionId, body, opts) {
|
|
103
122
|
return this.t.request({
|
|
104
123
|
method: "POST",
|
|
@@ -114,13 +133,26 @@ export class SessionSyncResource {
|
|
|
114
133
|
* (NOT JSON.stringify of an array — the cloud line-buffers). Re-POSTing the same stagingId is idempotent
|
|
115
134
|
* (appendBatch dedups; the commit re-runs). Typed errors: a per-entry/structural violation → 422 `invalid_entries`;
|
|
116
135
|
* a line too large → 413; a commit-time TOCTOU flip to fork/stale → a typed SyncConflictError (409); a refused
|
|
117
|
-
* policy loosen → 403 `loosen_forbidden`; a wrong/foreign stagingId → 404.
|
|
136
|
+
* policy loosen → 403 `loosen_forbidden`; a wrong/foreign stagingId → 404.
|
|
137
|
+
*
|
|
138
|
+
* ⚠️ Idempotency is HALF true: re-POSTing the same stagingId after a *transport* drop is fine (appendBatch dedups,
|
|
139
|
+
* the commit re-runs), but any PrecheckError / SessionError path server-side `abort()`s and cleans up the staging
|
|
140
|
+
* (server `:561,:565,:573-574`) ⇒ after a typed 4xx the SAME stagingId is DEAD — reopen Phase A.
|
|
141
|
+
*
|
|
142
|
+
* 🔴 **Memory** (`stream`, 4.3.0): by DEFAULT the SDK serializes the whole iterator into one NDJSON string before
|
|
143
|
+
* sending — peak memory is O(entire log). Until 4.3.0 three JSDocs here and in `sync.ts` claimed the opposite
|
|
144
|
+
* ("pipe a PULL stream straight into a PUSH without materializing the whole log"); that was false, and it is what
|
|
145
|
+
* {@link PushSessionOptions} sized its own trade-off note against. Pass `stream: true` to send a real
|
|
146
|
+
* `ReadableStream` request body (constant memory, one entry at a time — the server side has always been truly
|
|
147
|
+
* streaming: it reads chunk-wise with an 8 MiB per-line cap and a 500-entry append batch). It is OPT-IN because a
|
|
148
|
+
* streaming request body needs `duplex: "half"` support: Node ≥18 undici and Deno have it, but a polyfilled /
|
|
149
|
+
* proxied `fetch` may not, and silently switching the default would break those callers. */
|
|
118
150
|
async importEntries(sessionId, stagingId, entries, opts) {
|
|
119
|
-
const
|
|
151
|
+
const rawBody = opts?.stream === true ? toNdjsonStream(entries) : await toNdjson(entries);
|
|
120
152
|
const res = await this.t.openRaw({
|
|
121
153
|
method: "POST",
|
|
122
154
|
path: `/v1/sessions/${encodeURIComponent(sessionId)}/sync/import/${encodeURIComponent(stagingId)}/entries`,
|
|
123
|
-
rawBody
|
|
155
|
+
rawBody,
|
|
124
156
|
contentType: "application/x-ndjson",
|
|
125
157
|
signal: opts?.signal,
|
|
126
158
|
});
|
|
@@ -129,13 +161,42 @@ export class SessionSyncResource {
|
|
|
129
161
|
return (await res.json());
|
|
130
162
|
}
|
|
131
163
|
}
|
|
132
|
-
/** Serialize entries to
|
|
133
|
-
*
|
|
134
|
-
*
|
|
164
|
+
/** Serialize entries to ONE NDJSON string (one JSON value per `\n`-terminated line) — the whole-block Phase-B body.
|
|
165
|
+
* 🔴 This MATERIALIZES the whole log in memory. It is kept as the default because it works on every `fetch`
|
|
166
|
+
* implementation; {@link toNdjsonStream} is the constant-memory form. */
|
|
135
167
|
async function toNdjson(entries) {
|
|
136
168
|
let body = "";
|
|
137
169
|
for await (const e of entries)
|
|
138
170
|
body += `${JSON.stringify(e)}\n`;
|
|
139
171
|
return body;
|
|
140
172
|
}
|
|
173
|
+
/** Serialize entries to a `ReadableStream<Uint8Array>` NDJSON body — one line enqueued per `pull()`, so a
|
|
174
|
+
* `PULL …/sync/entries` async iterator really can be piped straight into a PUSH at CONSTANT memory. Requires the
|
|
175
|
+
* runtime `fetch` to accept a stream body (`duplex: "half"`, set by {@link import("../transport.js").Transport.openRaw}).
|
|
176
|
+
* A throw from the source iterator `error()`s the stream, which aborts the request — the caller sees the source's
|
|
177
|
+
* error (e.g. `SyncTruncatedError`), never a silently short body. */
|
|
178
|
+
function toNdjsonStream(entries) {
|
|
179
|
+
const it = entries[Symbol.asyncIterator]
|
|
180
|
+
? entries[Symbol.asyncIterator]()
|
|
181
|
+
: entries[Symbol.iterator]();
|
|
182
|
+
const enc = new TextEncoder();
|
|
183
|
+
return new ReadableStream({
|
|
184
|
+
async pull(controller) {
|
|
185
|
+
try {
|
|
186
|
+
const next = await it.next();
|
|
187
|
+
if (next.done === true) {
|
|
188
|
+
controller.close();
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
controller.enqueue(enc.encode(`${JSON.stringify(next.value)}\n`));
|
|
192
|
+
}
|
|
193
|
+
catch (e) {
|
|
194
|
+
controller.error(e);
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
async cancel(reason) {
|
|
198
|
+
await it.return?.(reason);
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
}
|
|
141
202
|
//# sourceMappingURL=session-sync.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-sync.js","sourceRoot":"","sources":["../../src/resources/session-sync.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"session-sync.js","sourceRoot":"","sources":["../../src/resources/session-sync.ts"],"names":[],"mappings":"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"}
|