@kanyun-ai-infra/agenthub 1.6.0 → 1.7.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/dist/index.cjs +164 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +50 -15
- package/dist/index.d.ts +50 -15
- package/dist/index.js +164 -48
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -4935,9 +4935,18 @@ var DeliveryResultArtifactItemSchema = import_zod42.z.object({
|
|
|
4935
4935
|
/** Artifact category. Workers MAY add new kinds; storage is opaque. */
|
|
4936
4936
|
kind: import_zod42.z.enum(["diff", "log", "general"]),
|
|
4937
4937
|
/**
|
|
4938
|
-
* Logical path inside the sandbox workspace
|
|
4939
|
-
*
|
|
4940
|
-
*
|
|
4938
|
+
* Logical path inside the sandbox workspace, and the idempotency key of
|
|
4939
|
+
* the artifact row: `(runId, path)` on the run plane, `(sessionId, path)`
|
|
4940
|
+
* on the session plane. Two writes with the same pair collapse to a single
|
|
4941
|
+
* row — the run plane keeps the first (`ON CONFLICT DO NOTHING`), the
|
|
4942
|
+
* session plane keeps the newest bytes and moves `turn_id` to the newest
|
|
4943
|
+
* ATTRIBUTED writer (`ON CONFLICT DO UPDATE`, T-INV-5).
|
|
4944
|
+
*
|
|
4945
|
+
* Two items with the SAME path inside one request body are legal wire but
|
|
4946
|
+
* a single artifact: the session plane de-duplicates them before the
|
|
4947
|
+
* insert, last one wins, and `artifactsInserted` counts the de-duplicated
|
|
4948
|
+
* items. Not a 400 — see `turnId` below on why an artifact-shaped problem
|
|
4949
|
+
* must not be the reason a delivery result is lost.
|
|
4941
4950
|
*/
|
|
4942
4951
|
path: import_zod42.z.string().min(1).max(1024),
|
|
4943
4952
|
/** Object key inside the OSS bucket where the artifact was uploaded. */
|
|
@@ -4955,6 +4964,26 @@ var DeliveryResultArtifactItemSchema = import_zod42.z.object({
|
|
|
4955
4964
|
contentType: import_zod42.z.string().min(1).max(255)
|
|
4956
4965
|
}).strict();
|
|
4957
4966
|
var DeliveryResultUpdateSchema = import_zod42.z.object({
|
|
4967
|
+
/**
|
|
4968
|
+
* The turn this delivery result belongs to — `ctx.turnId` on the worker
|
|
4969
|
+
* side (`deriveWorkerTurnId`, uuid v5). Drives `artifacts.turn_id`; see
|
|
4970
|
+
* `specs/features/pilot-v2-session-turns.md` §Projection 6.
|
|
4971
|
+
*
|
|
4972
|
+
* Optional through the migration window: a sandbox image predating P2
|
|
4973
|
+
* omits it, and the control plane then INFERS the turn and records an
|
|
4974
|
+
* anomaly rather than rejecting the write. Delivery attribution must not
|
|
4975
|
+
* become a reason a delivery fails — which is also why an id that names no
|
|
4976
|
+
* turn of THIS session degrades to the same inference (plus a warn log and
|
|
4977
|
+
* the anomaly naming the rejected id) instead of failing the request. The
|
|
4978
|
+
* id is still never honoured across sessions: `artifacts.turn_id` has no
|
|
4979
|
+
* foreign key, so a foreign id would attach these artifacts to a turn
|
|
4980
|
+
* another session's `GET /turns` serves.
|
|
4981
|
+
*
|
|
4982
|
+
* Deliberately NOT part of the `.refine()` below: it qualifies the other
|
|
4983
|
+
* fields rather than mutating anything, so a body carrying only `turnId`
|
|
4984
|
+
* is still the empty request it looks like, and still a 400.
|
|
4985
|
+
*/
|
|
4986
|
+
turnId: import_zod42.z.string().uuid().optional(),
|
|
4958
4987
|
/**
|
|
4959
4988
|
* The PR / MR URL the agent created. First-write-wins per spec
|
|
4960
4989
|
* (`UPDATE ... WHERE pull_request_url IS NULL`); subsequent writes
|
|
@@ -4963,10 +4992,11 @@ var DeliveryResultUpdateSchema = import_zod42.z.object({
|
|
|
4963
4992
|
*/
|
|
4964
4993
|
pullRequestUrl: import_zod42.z.string().url().max(1024).optional(),
|
|
4965
4994
|
/**
|
|
4966
|
-
* Append-set of artifacts uploaded for this run. Each item is
|
|
4967
|
-
*
|
|
4968
|
-
* the response indicates how many of the submitted items
|
|
4969
|
-
* in a new row.
|
|
4995
|
+
* Append-set of artifacts uploaded for this run. Each item is upserted
|
|
4996
|
+
* independently via the `unique(run_id, path)` / `unique(session_id, path)`
|
|
4997
|
+
* constraint; the response indicates how many of the submitted items
|
|
4998
|
+
* resulted in a new row. Items are keyed by `path` — see the field's own
|
|
4999
|
+
* doc for what a repeated path inside one body means.
|
|
4970
5000
|
*/
|
|
4971
5001
|
artifacts: import_zod42.z.array(DeliveryResultArtifactItemSchema).max(64).optional(),
|
|
4972
5002
|
/**
|
|
@@ -5652,7 +5682,28 @@ var RecordWorkspaceSnapshotRequestSchema = import_zod50.z.object({
|
|
|
5652
5682
|
* parts may be null ("nothing to restore" — a valid no-op snapshot, NOT
|
|
5653
5683
|
* a failure, per 已定方案 决策②).
|
|
5654
5684
|
*/
|
|
5655
|
-
snapshot: WorkspaceSnapshotSchema
|
|
5685
|
+
snapshot: WorkspaceSnapshotSchema,
|
|
5686
|
+
/**
|
|
5687
|
+
* The turn whose end produced this snapshot (`ctx.turnId`, uuid v5 from
|
|
5688
|
+
* `deriveWorkerTurnId`). The control plane stamps
|
|
5689
|
+
* `session_turns.workspace_snapshot_captured_at` on that row; the stored
|
|
5690
|
+
* snapshot JSON and its session-level latest-wins semantics are unchanged.
|
|
5691
|
+
*
|
|
5692
|
+
* **It lives on the ENVELOPE, not inside `snapshot`** (T-INV-6).
|
|
5693
|
+
* `WorkspaceSnapshotSchema` is `.strict()` and is re-used verbatim as
|
|
5694
|
+
* `PodAgentStartWorkerRequestSchema.workspaceSnapshot`, which travels
|
|
5695
|
+
* control-plane → pod-agent: a key added there makes every pod-agent
|
|
5696
|
+
* older than the change reject `POST /pilot/worker/start`. The contract
|
|
5697
|
+
* test in `__tests__/pilot-v2-workspace-snapshot.test.ts` pins this.
|
|
5698
|
+
*
|
|
5699
|
+
* Optional for one migration window. This envelope is `.strict()`, so a
|
|
5700
|
+
* worker that sends `turnId` to a control plane predating this field gets
|
|
5701
|
+
* a 400 and loses the snapshot entirely — **control-plane and agent-hub
|
|
5702
|
+
* MUST ship before the sandbox image**. Until the window closes the
|
|
5703
|
+
* control plane infers the turn (most recent completed one) when it is
|
|
5704
|
+
* absent; after it, this becomes required.
|
|
5705
|
+
*/
|
|
5706
|
+
turnId: import_zod50.z.string().uuid().optional()
|
|
5656
5707
|
}).strict();
|
|
5657
5708
|
var RecordWorkspaceSnapshotResponseSchema = import_zod50.z.object({
|
|
5658
5709
|
success: import_zod50.z.literal(true),
|
|
@@ -5685,44 +5736,54 @@ var WorkspaceExportPartSchema = WorkspaceSnapshotPartSchema.extend({
|
|
|
5685
5736
|
var WorkspaceExportDataSchema = import_zod50.z.object({
|
|
5686
5737
|
version: import_zod50.z.literal("v1"),
|
|
5687
5738
|
/**
|
|
5688
|
-
*
|
|
5739
|
+
* **Internal ordering cursor. Prefer {@link WorkspaceExportDataSchema.shape.turnId}.**
|
|
5740
|
+
*
|
|
5741
|
+
* Opaque monotonic value identifying this capture. **Not a turn ordinal**
|
|
5689
5742
|
* despite the name: the capture side sets it to `capturedAt.getTime()`
|
|
5690
5743
|
* (epoch milliseconds, sandbox clock). The name is fixed by the stored
|
|
5691
5744
|
* `WorkspaceSnapshot` envelope. Never render it as "turn N".
|
|
5692
5745
|
*
|
|
5693
|
-
* Its
|
|
5694
|
-
*
|
|
5695
|
-
*
|
|
5696
|
-
*
|
|
5697
|
-
*
|
|
5698
|
-
*
|
|
5699
|
-
* the
|
|
5700
|
-
*
|
|
5701
|
-
*
|
|
5702
|
-
*
|
|
5703
|
-
*
|
|
5704
|
-
*
|
|
5705
|
-
*
|
|
5706
|
-
*
|
|
5707
|
-
*
|
|
5708
|
-
*
|
|
5709
|
-
*
|
|
5710
|
-
*
|
|
5711
|
-
*
|
|
5712
|
-
* concurrent turn is refused with `SESSION_TURN_IN_PROGRESS`). Equality is
|
|
5713
|
-
* only reachable within one turn, between its own terminal and its own
|
|
5714
|
-
* capture.
|
|
5715
|
-
*
|
|
5716
|
-
* Residual limit, deliberately not papered over: if the sandbox clock steps
|
|
5717
|
-
* BACKWARDS between the terminal append and the capture (NTP correction),
|
|
5718
|
-
* `capturedAtTurn` lands before `completedAt` and neither `>` nor `>=`
|
|
5719
|
-
* accepts it. The poll then times out, and the caller cannot distinguish that
|
|
5720
|
-
* from "nothing was produced". Anchoring the snapshot to a turn identity
|
|
5721
|
-
* rather than a timestamp is the real fix — see the spec's follow-ups.
|
|
5746
|
+
* Its remaining job is the platform's own latest-wins comparison on the
|
|
5747
|
+
* recording path. The freshness question it used to answer for callers —
|
|
5748
|
+
* "is this snapshot my turn's or the previous turn's?" — is now answered
|
|
5749
|
+
* directly by `turnId`: compare it against the turn you dispatched instead
|
|
5750
|
+
* of comparing timestamps. The old recipe (poll until
|
|
5751
|
+
* `capturedAtTurn >= Date.parse(completedAt)`, `>=` and not `>` because the
|
|
5752
|
+
* two `ctx.now()` readings can land on the same millisecond) still works and
|
|
5753
|
+
* is the only option against a sandbox image predating P2, but it carries a
|
|
5754
|
+
* failure mode `turnId` does not: a BACKWARD sandbox clock step (NTP
|
|
5755
|
+
* correction) between the terminal append and the capture puts
|
|
5756
|
+
* `capturedAtTurn` before `completedAt`, and neither `>` nor `>=` accepts
|
|
5757
|
+
* it — the poll then times out and looks exactly like "nothing was
|
|
5758
|
+
* produced".
|
|
5759
|
+
*
|
|
5760
|
+
* The race itself is unchanged either way: the worker emits the turn's
|
|
5761
|
+
* terminal event BEFORE it captures and uploads, so an export issued the
|
|
5762
|
+
* moment a turn completes returns either 404 or the PRIOR turn's snapshot.
|
|
5763
|
+
* Poll with a bounded timeout; with `turnId` the predicate is equality
|
|
5764
|
+
* against your own turn, so there is nothing to reason about clocks.
|
|
5722
5765
|
*/
|
|
5723
5766
|
capturedAtTurn: import_zod50.z.number().int().nonnegative(),
|
|
5724
5767
|
/** ISO-8601 capture time — the field to show a human. */
|
|
5725
5768
|
capturedAt: import_zod50.z.string().min(1),
|
|
5769
|
+
/**
|
|
5770
|
+
* The turn whose end produced this snapshot, when the platform knows it.
|
|
5771
|
+
* This is the field a caller polls on: the export is this turn's iff
|
|
5772
|
+
* `turnId` equals the turn id it dispatched — no clock comparison, and
|
|
5773
|
+
* immune to the backward-clock-step hole in `capturedAtTurn`.
|
|
5774
|
+
*
|
|
5775
|
+
* Absent (or attributed to an earlier turn than you expect) in three cases,
|
|
5776
|
+
* all of which mean "not yours yet", not "broken":
|
|
5777
|
+
* 1. the snapshot was recorded by a sandbox image predating the envelope
|
|
5778
|
+
* `turnId` — the platform then attributes it to the most recent
|
|
5779
|
+
* completed turn and flags the inference;
|
|
5780
|
+
* 2. the turn's projection had not materialized its `session_turns` row
|
|
5781
|
+
* when the snapshot landed, so there was no row to stamp;
|
|
5782
|
+
* 3. the session predates this field entirely.
|
|
5783
|
+
* Keep the `capturedAtTurn` fallback for those; do not treat a missing
|
|
5784
|
+
* `turnId` as an error.
|
|
5785
|
+
*/
|
|
5786
|
+
turnId: import_zod50.z.string().uuid().nullable().optional(),
|
|
5726
5787
|
parts: import_zod50.z.object({
|
|
5727
5788
|
repoBundle: WorkspaceExportPartSchema.nullable(),
|
|
5728
5789
|
externalArchive: WorkspaceExportPartSchema.nullable()
|
|
@@ -7536,7 +7597,35 @@ var TurnArtifactBundleSchema = import_zod58.z.object({
|
|
|
7536
7597
|
/** Absent for a `rejected` bundle — nothing was uploaded, so there is no row. */
|
|
7537
7598
|
artifactId: import_zod58.z.string().optional(),
|
|
7538
7599
|
state: ArtifactBundleStateSchema,
|
|
7600
|
+
/**
|
|
7601
|
+
* MUST STAY A PLAIN `number` — widening it to `.nullable()` is a wire break,
|
|
7602
|
+
* not an additive change. `@kanyun-ai-infra/agenthub` bundles contracts into
|
|
7603
|
+
* its published artifact (`noExternal: ["@pilot/contracts"]`), so SDK 1.6.0
|
|
7604
|
+
* carries a FROZEN copy of this schema with `entryCount` a required number.
|
|
7605
|
+
* Old readers tolerate keys they have never heard of (every envelope on the
|
|
7606
|
+
* turn read path is a non-strict `z.object`, which strips them) but not a
|
|
7607
|
+
* value outside the type they froze: a `null` here makes 1.6.0's
|
|
7608
|
+
* `sessions.turns.get` / `.list` throw `MALFORMED_PLATFORM_RESPONSE` for
|
|
7609
|
+
* every turn that has any artifact row at all.
|
|
7610
|
+
*
|
|
7611
|
+
* So "not counted" travels as `0` plus {@link entryCountKnown} `false`
|
|
7612
|
+
* rather than as `null`. The real count lives in the manifest inside the zip
|
|
7613
|
+
* in object storage, and the read path that produces a `Turn` does not fan
|
|
7614
|
+
* out to object storage to get it; to see the actual entries, follow
|
|
7615
|
+
* `TurnArtifacts.incrementalHref` / `cumulativeHref` on the parent object.
|
|
7616
|
+
*/
|
|
7539
7617
|
entryCount: import_zod58.z.number().int().nonnegative(),
|
|
7618
|
+
/**
|
|
7619
|
+
* Is {@link entryCount} a measurement? Absent or `false` means it is not —
|
|
7620
|
+
* the `0` above is a placeholder, NOT an empty bundle. Optional so the key
|
|
7621
|
+
* itself is additive: a reader frozen before it existed drops it and still
|
|
7622
|
+
* parses, and one that has it must treat "absent" as "unknown" too, because
|
|
7623
|
+
* that is exactly what a pre-P2 control plane sends.
|
|
7624
|
+
*
|
|
7625
|
+
* `true` is reserved for a build that really counts the manifest entries.
|
|
7626
|
+
* Nothing sets it today.
|
|
7627
|
+
*/
|
|
7628
|
+
entryCountKnown: import_zod58.z.boolean().optional(),
|
|
7540
7629
|
createdAt: import_zod58.z.string(),
|
|
7541
7630
|
reason: import_zod58.z.string().optional()
|
|
7542
7631
|
});
|
|
@@ -10654,14 +10743,14 @@ var SessionsResource = class {
|
|
|
10654
10743
|
* **`waitForTurn` resolving does NOT mean the snapshot is ready.** The worker
|
|
10655
10744
|
* emits the turn's terminal event and only then bundles, uploads and reports.
|
|
10656
10745
|
* Exporting the instant a turn completes therefore returns either a 404 or —
|
|
10657
|
-
* worse, because it is silent — the PREVIOUS turn's snapshot. Poll
|
|
10658
|
-
*
|
|
10746
|
+
* worse, because it is silent — the PREVIOUS turn's snapshot. Poll until the
|
|
10747
|
+
* export names your turn instead of calling once:
|
|
10659
10748
|
*
|
|
10660
10749
|
* ```ts
|
|
10661
10750
|
* const { terminalEvent } = await agenthub.sessions.waitForTurn(sessionId, turnId);
|
|
10662
|
-
* // `completedAt` exists ONLY on a success terminal
|
|
10663
|
-
* //
|
|
10664
|
-
* //
|
|
10751
|
+
* // `completedAt` exists ONLY on a success terminal. Its value is the
|
|
10752
|
+
* // fallback cursor below; its PRESENCE is what tells you the turn succeeded.
|
|
10753
|
+
* // `payload` is typed `unknown`, hence the cast.
|
|
10665
10754
|
* const completedAtRaw = (terminalEvent.payload as { completedAt?: string })?.completedAt;
|
|
10666
10755
|
* if (!completedAtRaw) {
|
|
10667
10756
|
* // Failed / aborted turn: nothing was captured, and polling would just
|
|
@@ -10674,11 +10763,24 @@ var SessionsResource = class {
|
|
|
10674
10763
|
* while (Date.now() < deadline) {
|
|
10675
10764
|
* try {
|
|
10676
10765
|
* const candidate = await agenthub.sessions.exportWorkspace(sessionId);
|
|
10677
|
-
* //
|
|
10678
|
-
*
|
|
10679
|
-
* //
|
|
10680
|
-
* //
|
|
10681
|
-
*
|
|
10766
|
+
* // The snapshot names its own turn — compare ids, no clock reasoning.
|
|
10767
|
+
* if (candidate.turnId === turnId) { exported = candidate; break; }
|
|
10768
|
+
* // Fallback: `turnId` didn't match — either it's absent (session
|
|
10769
|
+
* // predates the field, or the turn's `session_turns` row hadn't
|
|
10770
|
+
* // materialized yet when the snapshot landed) or it's present but
|
|
10771
|
+
* // WRONG: a sandbox image older than this field makes the platform
|
|
10772
|
+
* // infer "most recent completed turn", which can land on a turn you
|
|
10773
|
+
* // didn't dispatch. Either way `turnId` cannot tell you "not mine" —
|
|
10774
|
+
* // only the timestamp can, so any non-match falls through to it, not
|
|
10775
|
+
* // just a null one. `>=`, not `>`: both timestamps are `ctx.now()` in
|
|
10776
|
+
* // the same sandbox process, one `await` apart, at millisecond
|
|
10777
|
+
* // resolution — so they can be EQUAL, and a strict `>` would never
|
|
10778
|
+
* // accept this turn's own snapshot, reporting "no files" after the
|
|
10779
|
+
* // full timeout.
|
|
10780
|
+
* if (candidate.capturedAtTurn >= completedAt) {
|
|
10781
|
+
* exported = candidate;
|
|
10782
|
+
* break;
|
|
10783
|
+
* }
|
|
10682
10784
|
* } catch (err) {
|
|
10683
10785
|
* // Discriminate on `code`, NOT on status: SESSION_NOT_FOUND is a 404 too,
|
|
10684
10786
|
* // and a bad id / deleted session / lost access must surface immediately
|
|
@@ -10695,6 +10797,20 @@ var SessionsResource = class {
|
|
|
10695
10797
|
* // exported === null → this turn produced no files. Normal, not a failure.
|
|
10696
10798
|
* ```
|
|
10697
10799
|
*
|
|
10800
|
+
* **`turnId` is the primary check; the timestamp branch is only a fallback.**
|
|
10801
|
+
* They are not equivalent. `turnId` is an equality test against an id you
|
|
10802
|
+
* already hold — but a non-matching `turnId` does not mean "not my turn": it
|
|
10803
|
+
* can be null, or it can be a DIFFERENT turn the platform inferred during the
|
|
10804
|
+
* migration window, and nothing in the response distinguishes the two. Treat
|
|
10805
|
+
* every non-match the same way and fall back to the timestamp rather than
|
|
10806
|
+
* special-casing "absent." `capturedAtTurn` is an internal epoch-ms cursor
|
|
10807
|
+
* with a failure mode `turnId` does not have: a BACKWARD sandbox clock step
|
|
10808
|
+
* (NTP correction) between the terminal append and the capture puts it before
|
|
10809
|
+
* `completedAt`, no comparison accepts it, and the poll times out —
|
|
10810
|
+
* reporting "no files" for a turn that produced them, which reads like a
|
|
10811
|
+
* normal result. Never display either value; `capturedAt` is the
|
|
10812
|
+
* human-facing field.
|
|
10813
|
+
*
|
|
10698
10814
|
* **404 `WORKSPACE_SNAPSHOT_NOT_FOUND` is not by itself an error.** It throws
|
|
10699
10815
|
* (a `PilotPlatformApiError`, so "this session never ran" is never silently
|
|
10700
10816
|
* flattened to an empty result), and you MUST catch it inside the polling
|