@intentic/sandbox-contract 1.310.0 → 1.311.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 +38 -159
- package/dist/contracts/accounts.contract.d.ts +3 -0
- package/dist/contracts/accounts.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.d.ts +15 -0
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.d.ts +596 -1
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +13 -2
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/capabilities.contract.d.ts +2 -0
- package/dist/contracts/capabilities.contract.d.ts.map +1 -1
- package/dist/contracts/device.contract.d.ts +1 -0
- package/dist/contracts/device.contract.d.ts.map +1 -1
- package/dist/contracts/runner.contract.d.ts +45 -0
- package/dist/contracts/runner.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.d.ts +8 -0
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.d.ts +59 -0
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/events/agent-events.d.ts +20 -0
- package/dist/events/agent-events.d.ts.map +1 -1
- package/dist/events/agent-events.js +6 -1
- package/dist/events/agent-events.js.map +1 -1
- package/dist/events/system-events.d.ts +48 -0
- package/dist/events/system-events.d.ts.map +1 -1
- package/dist/events/transcript.d.ts +5 -0
- package/dist/events/transcript.d.ts.map +1 -1
- package/dist/events/transcript.js +9 -1
- package/dist/events/transcript.js.map +1 -1
- package/dist/front/generated/wire.d.ts +74 -0
- package/dist/front/generated/wire.d.ts.map +1 -0
- package/dist/front/generated/wire.js +2 -0
- package/dist/front/generated/wire.js.map +1 -0
- package/dist/index.d.ts +684 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/policy/command-run.d.ts +1 -1
- package/dist/policy/command-run.d.ts.map +1 -1
- package/dist/policy/command-run.js +7 -4
- package/dist/policy/command-run.js.map +1 -1
- package/dist/protocol/ingress-protocol.d.ts.map +1 -1
- package/dist/protocol/ingress-protocol.js +15 -0
- package/dist/protocol/ingress-protocol.js.map +1 -1
- package/dist/schemas/agents.d.ts +72 -3
- package/dist/schemas/agents.d.ts.map +1 -1
- package/dist/schemas/agents.js +10 -7
- package/dist/schemas/agents.js.map +1 -1
- package/dist/schemas/automations.d.ts +24 -0
- package/dist/schemas/automations.d.ts.map +1 -1
- package/dist/schemas/capabilities.d.ts +3 -0
- package/dist/schemas/capabilities.d.ts.map +1 -1
- package/dist/schemas/capabilities.js +4 -0
- package/dist/schemas/capabilities.js.map +1 -1
- package/dist/schemas/devices.d.ts +32 -6
- package/dist/schemas/devices.d.ts.map +1 -1
- package/dist/schemas/devices.js +8 -6
- package/dist/schemas/devices.js.map +1 -1
- package/dist/schemas/keep-warm.d.ts +68 -0
- package/dist/schemas/keep-warm.d.ts.map +1 -0
- package/dist/schemas/keep-warm.js +74 -0
- package/dist/schemas/keep-warm.js.map +1 -0
- package/dist/schemas/providers/provider-oauth.d.ts +3 -0
- package/dist/schemas/providers/provider-oauth.d.ts.map +1 -1
- package/dist/schemas/providers/provider-oauth.js +4 -0
- package/dist/schemas/providers/provider-oauth.js.map +1 -1
- package/dist/schemas/providers/usage.d.ts +6 -0
- package/dist/schemas/providers/usage.d.ts.map +1 -1
- package/dist/schemas/providers/usage.js +4 -0
- package/dist/schemas/providers/usage.js.map +1 -1
- package/dist/schemas/settings.d.ts +4 -0
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +23 -0
- package/dist/schemas/settings.js.map +1 -1
- package/dist/state/definition.d.ts +16 -0
- package/dist/state/definition.d.ts.map +1 -1
- package/dist/state/fix-stance.d.ts.map +1 -1
- package/dist/state/fix-stance.js +1 -0
- package/dist/state/fix-stance.js.map +1 -1
- package/dist/state/runtime-state.d.ts +3 -0
- package/dist/state/runtime-state.d.ts.map +1 -1
- package/dist/state/runtime-state.js +1 -0
- package/dist/state/runtime-state.js.map +1 -1
- package/dist/text/transcript-fold.d.ts +2 -0
- package/dist/text/transcript-fold.d.ts.map +1 -1
- package/dist/text/transcript-fold.js +20 -7
- package/dist/text/transcript-fold.js.map +1 -1
- package/dist/time/zone.d.ts +1 -0
- package/dist/time/zone.d.ts.map +1 -1
- package/dist/time/zone.js +5 -0
- package/dist/time/zone.js.map +1 -1
- package/package.json +16 -5
- package/src/contracts/agents.contract.ts +14 -2
- package/src/events/agent-events.ts +9 -1
- package/src/events/transcript.ts +14 -3
- package/src/front/generated/wire.ts +73 -0
- package/src/ids/hostnames.fixture.json +20 -0
- package/src/ids/hostnames.test.ts +20 -0
- package/src/index.ts +1 -0
- package/src/policy/command-run.test.ts +35 -0
- package/src/policy/command-run.ts +10 -5
- package/src/protocol/ingress-protocol.test.ts +45 -1
- package/src/protocol/ingress-protocol.ts +25 -4
- package/src/schemas/agents.ts +14 -7
- package/src/schemas/capabilities.ts +6 -0
- package/src/schemas/devices.ts +16 -9
- package/src/schemas/keep-warm.test.ts +68 -0
- package/src/schemas/keep-warm.ts +124 -0
- package/src/schemas/providers/provider-oauth.ts +6 -0
- package/src/schemas/providers/usage.ts +7 -0
- package/src/schemas/settings.ts +30 -0
- package/src/state/fix-stance.test.ts +14 -0
- package/src/state/fix-stance.ts +2 -0
- package/src/state/runtime-state.ts +4 -0
- package/src/text/transcript-fold.test.ts +20 -1
- package/src/text/transcript-fold.ts +39 -9
- package/src/time/zone.ts +7 -0
- package/src/webext/index.ts +1 -1
package/dist/time/zone.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export interface WallRule {
|
|
|
22
22
|
export declare const cronOptions: (tz: Zone) => {
|
|
23
23
|
timezone: string;
|
|
24
24
|
};
|
|
25
|
+
export declare const nextDayStartIn: (at: number, zone: Zone) => number;
|
|
25
26
|
export declare const sameClock: (a: Zone, b: Zone, at?: number) => boolean;
|
|
26
27
|
export declare const zoneLabel: (rule: Zone, reader: Zone, at?: number) => string | undefined;
|
|
27
28
|
//# sourceMappingURL=zone.d.ts.map
|
package/dist/time/zone.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zone.d.ts","sourceRoot":"","sources":["../../src/time/zone.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,MAAM,MAAM,IAAI,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAGzD,eAAO,MAAM,GAAG,EAAY,IAAI,CAAC;AASjC,eAAO,MAAM,MAAM,UAAW,MAAM,KAAG,KAAK,IAAI,IAS/C,CAAC;AAGF,eAAO,MAAM,MAAM,UAAW,MAAM,GAAG,SAAS,KAAG,IAAI,GAAG,SAAuE,CAAC;AAIlI,eAAO,MAAM,UAAU,+EAAkG,CAAC;AAM1H,eAAO,MAAM,SAAS,QAAO,IAAgE,CAAC;AAO9F,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAA;CAAE,CAAC;AAEjE,eAAO,MAAM,cAAc,aAA2E,CAAC;AAGvG,eAAO,MAAM,UAAU,OAAQ,MAAM,QAAQ,IAAI,KAAG,QAC8E,CAAC;AAGnI,eAAO,MAAM,QAAQ,OAAQ,MAAM,KAAG,QAA+D,CAAC;AAStG,eAAO,MAAM,WAAW,OAAQ,MAAM,QAAQ,IAAI,KAAG,MAG5B,CAAC;AAG1B,eAAO,MAAM,UAAU,UAAW,MAAM,GAAG,SAAS,KAAG,QAAQ,GAAG,SAAyG,CAAC;AAO5K,MAAM,WAAW,QAAQ;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;CACrB;AAGD,eAAO,MAAM,WAAW,OAAQ,IAAI,KAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAwB,CAAC;AAclF,eAAO,MAAM,SAAS,MAAO,IAAI,KAAK,IAAI,OAAM,MAAM,KAAgB,OAAmE,CAAC;AAM1I,eAAO,MAAM,SAAS,SAAU,IAAI,UAAU,IAAI,OAAM,MAAM,KAAgB,MAAM,GAAG,SAAgF,CAAC"}
|
|
1
|
+
{"version":3,"file":"zone.d.ts","sourceRoot":"","sources":["../../src/time/zone.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,MAAM,MAAM,IAAI,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAGzD,eAAO,MAAM,GAAG,EAAY,IAAI,CAAC;AASjC,eAAO,MAAM,MAAM,UAAW,MAAM,KAAG,KAAK,IAAI,IAS/C,CAAC;AAGF,eAAO,MAAM,MAAM,UAAW,MAAM,GAAG,SAAS,KAAG,IAAI,GAAG,SAAuE,CAAC;AAIlI,eAAO,MAAM,UAAU,+EAAkG,CAAC;AAM1H,eAAO,MAAM,SAAS,QAAO,IAAgE,CAAC;AAO9F,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAA;CAAE,CAAC;AAEjE,eAAO,MAAM,cAAc,aAA2E,CAAC;AAGvG,eAAO,MAAM,UAAU,OAAQ,MAAM,QAAQ,IAAI,KAAG,QAC8E,CAAC;AAGnI,eAAO,MAAM,QAAQ,OAAQ,MAAM,KAAG,QAA+D,CAAC;AAStG,eAAO,MAAM,WAAW,OAAQ,MAAM,QAAQ,IAAI,KAAG,MAG5B,CAAC;AAG1B,eAAO,MAAM,UAAU,UAAW,MAAM,GAAG,SAAS,KAAG,QAAQ,GAAG,SAAyG,CAAC;AAO5K,MAAM,WAAW,QAAQ;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;CACrB;AAGD,eAAO,MAAM,WAAW,OAAQ,IAAI,KAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAwB,CAAC;AAclF,eAAO,MAAM,cAAc,OAAQ,MAAM,QAAQ,IAAI,KAAG,MAIvD,CAAC;AAGF,eAAO,MAAM,SAAS,MAAO,IAAI,KAAK,IAAI,OAAM,MAAM,KAAgB,OAAmE,CAAC;AAM1I,eAAO,MAAM,SAAS,SAAU,IAAI,UAAU,IAAI,OAAM,MAAM,KAAgB,MAAM,GAAG,SAAgF,CAAC"}
|
package/dist/time/zone.js
CHANGED
|
@@ -30,6 +30,11 @@ const offsetMinutes = (at, zone) => {
|
|
|
30
30
|
}
|
|
31
31
|
return (match[1] === "-" ? -1 : 1) * (Number(match[2]) * 60 + Number(match[3]));
|
|
32
32
|
};
|
|
33
|
+
export const nextDayStartIn = (at, zone) => {
|
|
34
|
+
const [year = 0, month = 1, day = 1] = civilDayIn(at, zone).split("-").map(Number);
|
|
35
|
+
const midnightUtc = Date.UTC(year, month - 1, day + 1);
|
|
36
|
+
return midnightUtc - offsetMinutes(midnightUtc, zone) * 60_000;
|
|
37
|
+
};
|
|
33
38
|
export const sameClock = (a, b, at = Date.now()) => a === b || offsetMinutes(at, a) === offsetMinutes(at, b);
|
|
34
39
|
export const zoneLabel = (rule, reader, at = Date.now()) => (sameClock(rule, reader, at) ? undefined : rule.replace(/_/g, " "));
|
|
35
40
|
//# sourceMappingURL=zone.js.map
|
package/dist/time/zone.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zone.js","sourceRoot":"","sources":["../../src/time/zone.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"zone.js","sourceRoot":"","sources":["../../src/time/zone.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentic/sandbox-contract",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.311.0",
|
|
4
4
|
"description": "oRPC wire contract for the intentic sandbox daemon, shared by the daemon and its browser client",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -150,6 +150,17 @@
|
|
|
150
150
|
"default": "./dist/policy/owner-ticket.js"
|
|
151
151
|
}
|
|
152
152
|
},
|
|
153
|
+
"./front-wire": {
|
|
154
|
+
"types": "./dist/front/generated/wire.d.ts",
|
|
155
|
+
"import": {
|
|
156
|
+
"@intentic/src": "./src/front/generated/wire.ts",
|
|
157
|
+
"default": "./dist/front/generated/wire.js"
|
|
158
|
+
},
|
|
159
|
+
"require": {
|
|
160
|
+
"@intentic/src": "./src/front/generated/wire.ts",
|
|
161
|
+
"default": "./dist/front/generated/wire.js"
|
|
162
|
+
}
|
|
163
|
+
},
|
|
153
164
|
"./ingress-protocol": {
|
|
154
165
|
"types": "./dist/protocol/ingress-protocol.d.ts",
|
|
155
166
|
"import": {
|
|
@@ -240,10 +251,10 @@
|
|
|
240
251
|
}
|
|
241
252
|
},
|
|
242
253
|
"dependencies": {
|
|
243
|
-
"@intentic/base": "1.
|
|
244
|
-
"@intentic/constants": "1.
|
|
245
|
-
"@intentic/extension-manifest": "1.
|
|
246
|
-
"@intentic/registry": "1.
|
|
254
|
+
"@intentic/base": "1.311.0",
|
|
255
|
+
"@intentic/constants": "1.311.0",
|
|
256
|
+
"@intentic/extension-manifest": "1.311.0",
|
|
257
|
+
"@intentic/registry": "1.311.0",
|
|
247
258
|
"@orpc/contract": "1.14.13",
|
|
248
259
|
"tslib": "2.8.1",
|
|
249
260
|
"zod": "4.5.4"
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { procedure } from "../protocol/route-meta.js";
|
|
2
2
|
import { AgentToolChildrenSchema, AgentTranscriptSchema } from "../events/transcript.js";
|
|
3
3
|
import {
|
|
4
|
-
AgentArchiveSchema,
|
|
5
4
|
AgentAssignSchema,
|
|
6
5
|
AgentAutoLandSchema,
|
|
7
6
|
AgentBreakPolicySchema,
|
|
@@ -27,6 +26,7 @@ import { AgentsListSchema } from "../schemas/automations.js";
|
|
|
27
26
|
import { AgentChangesSchema, AgentScratchSchema, AgentHistorySchema } from "../schemas/git/git.js";
|
|
28
27
|
import { FileDiffSchema } from "../schemas/history.js";
|
|
29
28
|
import { OkSchema } from "../schemas/shared.js";
|
|
29
|
+
import { AgentKeepWarmSchema } from "../schemas/keep-warm.js";
|
|
30
30
|
import { ConversationPromptSchema } from "../schemas/system-prompt.js";
|
|
31
31
|
|
|
32
32
|
// Every registered conversation-agent (AgentSummarySchema); an unknown {id} is NOT_FOUND across this whole family.
|
|
@@ -151,6 +151,18 @@ export const agentsContract = {
|
|
|
151
151
|
.meta({ floor: "collaborator" })
|
|
152
152
|
.input(AgentBreakPolicySchema)
|
|
153
153
|
.output(AgentSummarySchema),
|
|
154
|
+
// Legal for a workspace conversation too, and only between turns in effect: a turn starting ends the hold.
|
|
155
|
+
keepWarm: procedure
|
|
156
|
+
.route({
|
|
157
|
+
method: "POST",
|
|
158
|
+
path: "/agents/{id}/keep-warm",
|
|
159
|
+
summary: "Keep this conversation's prompt cache warm while it sits idle",
|
|
160
|
+
description:
|
|
161
|
+
"Re-reads the conversation's cached context shortly before the provider would drop it, until the time asked for, so picking it back up costs a cache read instead of re-sending everything. Each refresh is a forked, unsaved request that adds nothing to the conversation. Stops by itself when the time runs out, when a turn starts, when the account nears its limit, or when the prompt the next turn would send has changed. Refused for a conversation whose cache is already cold or that this sandbox cannot replay. Null stops it.",
|
|
162
|
+
})
|
|
163
|
+
.meta({ floor: "collaborator" })
|
|
164
|
+
.input(AgentKeepWarmSchema)
|
|
165
|
+
.output(AgentSummarySchema),
|
|
154
166
|
seen: procedure
|
|
155
167
|
.route({
|
|
156
168
|
method: "POST",
|
|
@@ -306,7 +318,7 @@ export const agentsContract = {
|
|
|
306
318
|
"The gentle counterpart to discarding. Commits whatever the conversation still has in progress onto its own branch, releases its working copy, and keeps the entry and the record. Its scratch is not committed and goes with the copy. It leaves the live fleet and joins the archive. Refused for a conversation that is running.",
|
|
307
319
|
})
|
|
308
320
|
.meta({ floor: "collaborator", guest: true })
|
|
309
|
-
.input(
|
|
321
|
+
.input(AgentIdsSchema)
|
|
310
322
|
.output(AgentsArchivedSchema),
|
|
311
323
|
unarchive: procedure
|
|
312
324
|
.route({
|
|
@@ -4,6 +4,7 @@ import { LandConflictSchema } from "../schemas/agents.js";
|
|
|
4
4
|
import { ContextTrimSchema } from "../schemas/context-trim.js";
|
|
5
5
|
import { RateLimitInfoSchema } from "../schemas/providers/claude-gate.js";
|
|
6
6
|
import { FastModeStateSchema } from "../schemas/providers/fast-mode.js";
|
|
7
|
+
import { PromptCacheOpeningSchema, PromptFingerprintSchema } from "../schemas/keep-warm.js";
|
|
7
8
|
import { AgentReplySchema, UsageWindowSchema } from "../schemas/providers/plan-limits.js";
|
|
8
9
|
import { SubagentKindSchema, SubagentStatusSchema, SubagentVerificationSchema } from "../schemas/terminal.js";
|
|
9
10
|
import { RetryLadderSchema } from "../schemas/turn-break.js";
|
|
@@ -50,7 +51,8 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
|
|
|
50
51
|
// message to be drawn beside, so this is the only place the reader can learn the turn ran thin.
|
|
51
52
|
ContextTrimSchema.extend({ kind: z.literal("context_trim") }),
|
|
52
53
|
// The SDK's init handshake; carries the model it actually resolved for the turn.
|
|
53
|
-
|
|
54
|
+
// `prompt` names the parts the cache is keyed on, so a later turn can say which of them changed.
|
|
55
|
+
z.object({ kind: z.literal("init"), model: z.string(), prompt: PromptFingerprintSchema.optional() }),
|
|
54
56
|
// The pre-turn snapshot id, emitted once before the provider stream so the client can offer
|
|
55
57
|
// restore-to-before-this-message. Absent on an isolated turn or when the tree was already clean.
|
|
56
58
|
// `index` is the message's position in the transcript, which the rewind route addresses by; absent on a turn with
|
|
@@ -139,7 +141,13 @@ export const AgentEventSchema = z.discriminatedUnion("kind", [
|
|
|
139
141
|
cacheCreationTokens: z.number().optional(),
|
|
140
142
|
durationMs: z.number().optional(),
|
|
141
143
|
numTurns: z.number().optional(),
|
|
144
|
+
// The stream's first request only; a sum of frames keeps the first frame's, never adds them.
|
|
145
|
+
openingCacheReadTokens: z.number().optional(),
|
|
146
|
+
openingCacheCreationTokens: z.number().optional(),
|
|
147
|
+
promptFingerprint: z.string().optional(),
|
|
142
148
|
}),
|
|
149
|
+
// What the turn's first request found in the prompt cache, sent as soon as it is known.
|
|
150
|
+
PromptCacheOpeningSchema.extend({ kind: z.literal("prompt_cache") }),
|
|
143
151
|
// The provider's live answer to whether this turn may run, pushed mid-turn; drives the rate-limited notice, not the
|
|
144
152
|
// headroom readouts.
|
|
145
153
|
RateLimitInfoSchema.extend({ kind: z.literal("rate_limit_info"), account: z.string().optional() }),
|
package/src/events/transcript.ts
CHANGED
|
@@ -311,8 +311,9 @@ export const holdsRequest = (row: TranscriptRequests): boolean => REQUEST_FIELDS
|
|
|
311
311
|
// Whether a row is holding the turn open on a card nobody has answered.
|
|
312
312
|
export const isAwaitingDecision = (row: TranscriptRequests): boolean => REQUEST_FIELDS.some((field) => row[field]?.status === "pending");
|
|
313
313
|
|
|
314
|
-
// One change to a run's rows, so a client keeps rows rather than frames. Prose/thinking append to a row
|
|
315
|
-
// replaces
|
|
314
|
+
// One change to a run's rows, so a client keeps rows rather than frames. Prose/thinking append to a row or a card; a
|
|
315
|
+
// `tool` card replaces its own fields by id, never its nested calls or thinking, which arrive by their own patches;
|
|
316
|
+
// everything else replaces its row; `drop` removes a row that opened and never wrote.
|
|
316
317
|
export const TranscriptPatchSchema = z.discriminatedUnion("op", [
|
|
317
318
|
z.object({ op: z.literal("append").describe("A new row at the end."), row: TranscriptRowSchema }),
|
|
318
319
|
z.object({ op: z.literal("replace").describe("This row, whole, in place of the one at that index."), index: z.number().int().nonnegative(), row: TranscriptRowSchema }),
|
|
@@ -320,7 +321,17 @@ export const TranscriptPatchSchema = z.discriminatedUnion("op", [
|
|
|
320
321
|
z.object({ op: z.literal("text").describe("More of the agent's prose, onto that row's text."), index: z.number().int().nonnegative(), text: z.string() }),
|
|
321
322
|
z.object({ op: z.literal("thinking").describe("More of the agent's reasoning, onto that row's thinking."), index: z.number().int().nonnegative(), text: z.string() }),
|
|
322
323
|
z.object({
|
|
323
|
-
op: z.literal("
|
|
324
|
+
op: z.literal("toolThinking").describe("More of a delegated subagent's reasoning, onto the thinking of the card that started it."),
|
|
325
|
+
index: z.number().int().nonnegative(),
|
|
326
|
+
id: z.string().describe("The card's id, matched wherever it nests."),
|
|
327
|
+
text: z.string(),
|
|
328
|
+
}),
|
|
329
|
+
z.object({
|
|
330
|
+
op: z
|
|
331
|
+
.literal("tool")
|
|
332
|
+
.describe(
|
|
333
|
+
"A tool card's own fields: new, or the latest state of one already there, matched by id wherever it nests. Carries no `children` or `thinking`; a card already there keeps its own.",
|
|
334
|
+
),
|
|
324
335
|
index: z.number().int().nonnegative(),
|
|
325
336
|
tool: TranscriptToolSchema,
|
|
326
337
|
parent: z.string().optional().describe("The card this one nests under, when it is a delegated subagent's own call."),
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
2
|
+
|
|
3
|
+
export type Answer = { "answer": "preview", route: PreviewRoute, };
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A PEM certificate chain and its PEM private key.
|
|
7
|
+
*/
|
|
8
|
+
export type Certificate = { certificate: string, privateKey: string, };
|
|
9
|
+
|
|
10
|
+
export type Endpoint = { host: string, port: number, };
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Everything Node sends the front.
|
|
14
|
+
*/
|
|
15
|
+
export type FromNode = { "kind": "hello", build: string, pid: number, } | { "kind": "listen", config: ListenConfig, } | { "kind": "certificate", certificate?: Certificate, } | { "kind": "tunnel", tunnel?: TunnelConfig, } | { "kind": "answer", id: number, answer: Answer, } | { "kind": "refused", id: number, message: string, };
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Request headers only the front sets on what it forwards to Node; it strips them from everything arriving outside.
|
|
19
|
+
* Exported as a literal type, so a Node constant naming one that drifts from this spelling fails to compile.
|
|
20
|
+
*/
|
|
21
|
+
export type FrontHeader = "x-intentic-preview" | "x-intentic-preview-unreachable";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Every port the front binds, as Node's config names them; an absent one is not bound.
|
|
25
|
+
*/
|
|
26
|
+
export type ListenConfig = { daemon: Endpoint, preview?: Endpoint, loopback?: Endpoint,
|
|
27
|
+
/**
|
|
28
|
+
* The 12-hex id every public hostname ends in; without one, a label's prefix alone classifies it.
|
|
29
|
+
*/
|
|
30
|
+
sandboxId?: string,
|
|
31
|
+
/**
|
|
32
|
+
* Origins that may frame a preview besides the preview itself.
|
|
33
|
+
*/
|
|
34
|
+
frameAncestors: Array<string>,
|
|
35
|
+
/**
|
|
36
|
+
* A preview path Node answers whatever its host resolves to.
|
|
37
|
+
*/
|
|
38
|
+
previewProbePath: string, };
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Where a preview host's requests go, as Node resolved it.
|
|
42
|
+
*/
|
|
43
|
+
export type PreviewRoute = { "to": "upstream", upstream: Upstream, } | { "to": "node" };
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A question the front asks Node; the answer carries the same id.
|
|
47
|
+
*/
|
|
48
|
+
export type Question = { "question": "preview", host: string, };
|
|
49
|
+
|
|
50
|
+
export type Scheme = "http" | "https";
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Everything the front sends Node.
|
|
54
|
+
*/
|
|
55
|
+
export type ToNode = { "kind": "ask", id: number, question: Question, } | { "kind": "tunnel", connected: boolean, };
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The ingress tunnel's door and the reachability grant it presents there.
|
|
59
|
+
*/
|
|
60
|
+
export type TunnelConfig = { url: string, grant: string, };
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A local upstream a preview host relays to.
|
|
64
|
+
*/
|
|
65
|
+
export type Upstream = { host: string, port: number, scheme: Scheme,
|
|
66
|
+
/**
|
|
67
|
+
* Rewrites Host and Origin to `localhost:<port>`, for an app that never agreed to its preview name.
|
|
68
|
+
*/
|
|
69
|
+
localhost: boolean,
|
|
70
|
+
/**
|
|
71
|
+
* Framed by the editor: `frame-ancestors` is set and `x-frame-options` dropped.
|
|
72
|
+
*/
|
|
73
|
+
frameable: boolean, };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[
|
|
2
|
+
{ "host": "preview-web-abcdef012345.sbx.example.test", "sandboxId": "abcdef012345", "panel": "web" },
|
|
3
|
+
{ "host": "preview-my-app-abcdef012345.sbx.example.test:443", "sandboxId": "abcdef012345", "panel": "my-app" },
|
|
4
|
+
{ "host": "preview-shop--web-abcdef012345.example.com", "sandboxId": "abcdef012345", "panel": "shop--web" },
|
|
5
|
+
{ "host": "preview-web-0123456789ab.sbx.example.test", "sandboxId": "abcdef012345" },
|
|
6
|
+
{ "host": "preview--abcdef012345.sbx.example.test", "sandboxId": "abcdef012345" },
|
|
7
|
+
{ "host": "preview-x-abcdef012345.sbx.example.test", "sandboxId": "abcdef012345", "panel": "x" },
|
|
8
|
+
{ "host": "port-1a2b3c4d5e6f-abcdef012345.sbx.example.test:8443", "sandboxId": "abcdef012345", "port": "1a2b3c4d5e6f" },
|
|
9
|
+
{ "host": "public-9f8e7d6c5b4a-abcdef012345.sbx.example.test", "sandboxId": "abcdef012345", "public": "9f8e7d6c5b4a" },
|
|
10
|
+
{ "host": "sandbox-abcdef012345.sbx.example.test", "sandboxId": "abcdef012345" },
|
|
11
|
+
{ "host": "app.example.com", "sandboxId": "abcdef012345" },
|
|
12
|
+
{ "host": "preview-web.localhost:8788", "panel": "web" },
|
|
13
|
+
{ "host": "port-a.localhost", "port": "a" },
|
|
14
|
+
{ "host": "public-z.example.com", "public": "z" },
|
|
15
|
+
{ "host": "preview-.localhost" },
|
|
16
|
+
{ "host": "127.0.0.1:8788" },
|
|
17
|
+
{ "host": "[::1]:8788" },
|
|
18
|
+
{ "host": "" },
|
|
19
|
+
{ "host": "Preview-web-abcdef012345.sbx.example.test", "sandboxId": "abcdef012345" }
|
|
20
|
+
]
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
1
2
|
import {
|
|
2
3
|
labelHostname,
|
|
3
4
|
panelFromHost,
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
previewHostname,
|
|
9
10
|
previewLabel,
|
|
10
11
|
previewUrl,
|
|
12
|
+
publicSlotFromHost,
|
|
11
13
|
sandboxHostname,
|
|
12
14
|
sandboxIdFromUrl,
|
|
13
15
|
syncFolder,
|
|
@@ -15,6 +17,24 @@ import {
|
|
|
15
17
|
|
|
16
18
|
const ID = "abc123def456";
|
|
17
19
|
|
|
20
|
+
// The cases the front's Rust router is held to as well (_sandbox/front, route.rs), read from the one file both run.
|
|
21
|
+
interface HostCase {
|
|
22
|
+
readonly host: string;
|
|
23
|
+
readonly sandboxId?: string;
|
|
24
|
+
readonly panel?: string;
|
|
25
|
+
readonly port?: string;
|
|
26
|
+
readonly public?: string;
|
|
27
|
+
}
|
|
28
|
+
const HOST_CASES = JSON.parse(readFileSync(new URL("./hostnames.fixture.json", import.meta.url), "utf8")) as HostCase[];
|
|
29
|
+
|
|
30
|
+
test.each(HOST_CASES)("the shared host cases parse as the front parses them: $host", (hostCase) => {
|
|
31
|
+
expect({
|
|
32
|
+
panel: panelFromHost(hostCase.host, hostCase.sandboxId),
|
|
33
|
+
port: portSlotFromHost(hostCase.host, hostCase.sandboxId),
|
|
34
|
+
public: publicSlotFromHost(hostCase.host, hostCase.sandboxId),
|
|
35
|
+
}).toEqual({ panel: hostCase.panel, port: hostCase.port, public: hostCase.public });
|
|
36
|
+
});
|
|
37
|
+
|
|
18
38
|
test("preview and port hostnames round-trip through their Host-header parsers", () => {
|
|
19
39
|
expect(panelFromHost(`${previewHostname("shop--web", ID, "example.com")}:443`, ID)).toBe("shop--web");
|
|
20
40
|
expect(portSlotFromHost(`${portHostname("a", ID, "example.com")}:443`, ID)).toBe("a");
|
package/src/index.ts
CHANGED
|
@@ -216,6 +216,7 @@ export * from "./schemas/system-prompt.js";
|
|
|
216
216
|
export * from "./schemas/system.js";
|
|
217
217
|
export * from "./schemas/terminal.js";
|
|
218
218
|
export * from "./schemas/turn-break.js";
|
|
219
|
+
export * from "./schemas/keep-warm.js";
|
|
219
220
|
export * from "./schemas/providers/usage.js";
|
|
220
221
|
export * from "./schemas/vpn.js";
|
|
221
222
|
export * from "./schemas/webext.js";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { CommandRun } from "../schemas/ci.js";
|
|
2
|
+
import { followCommandRun } from "./command-run.js";
|
|
3
|
+
|
|
4
|
+
const RUNNING: CommandRun = { status: "running", command: "pnpm test", output: "" };
|
|
5
|
+
const PASSED: CommandRun = { status: "passed", command: "pnpm test", output: "ok" };
|
|
6
|
+
|
|
7
|
+
test("a poll that fails is reported and retried, and the run is still followed to its verdict", async () => {
|
|
8
|
+
const answers: Array<CommandRun | Error> = [new Error("daemon restarting"), RUNNING, PASSED];
|
|
9
|
+
const errors: unknown[] = [];
|
|
10
|
+
const settled = await followCommandRun(
|
|
11
|
+
async () => {
|
|
12
|
+
const next = answers.shift();
|
|
13
|
+
if (next instanceof Error) {
|
|
14
|
+
throw next;
|
|
15
|
+
}
|
|
16
|
+
return next as CommandRun;
|
|
17
|
+
},
|
|
18
|
+
{ intervalMs: 1, onError: (cause) => errors.push(cause) },
|
|
19
|
+
);
|
|
20
|
+
expect(settled).toEqual(PASSED);
|
|
21
|
+
expect(errors).toEqual([new Error("daemon restarting")]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("a throw from onState is the caller's bug and reaches the caller, not a poll failure retried forever", async () => {
|
|
25
|
+
const errors: unknown[] = [];
|
|
26
|
+
const following = followCommandRun(async () => RUNNING, {
|
|
27
|
+
intervalMs: 1,
|
|
28
|
+
onState: () => {
|
|
29
|
+
throw new TypeError("state.session is undefined");
|
|
30
|
+
},
|
|
31
|
+
onError: (cause) => errors.push(cause),
|
|
32
|
+
});
|
|
33
|
+
await expect(following).rejects.toThrow("state.session is undefined");
|
|
34
|
+
expect(errors).toEqual([]);
|
|
35
|
+
});
|
|
@@ -10,8 +10,9 @@ export interface FollowRunOptions<R extends CommandRun> {
|
|
|
10
10
|
readonly signal?: AbortSignal | undefined;
|
|
11
11
|
// Every state read, settled or not, for a surface that draws progress.
|
|
12
12
|
readonly onState?: ((run: R) => void) | undefined;
|
|
13
|
-
// A poll that failed, not a run that failed: the run is still going, and following continues.
|
|
14
|
-
|
|
13
|
+
// A poll that failed, not a run that failed: the run is still going, and following continues. Required, since a
|
|
14
|
+
// loop that retries a failing read forever with nobody told looks exactly like a run that is still going.
|
|
15
|
+
readonly onError: (cause: unknown) => void;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
// Follows a run to its verdict: reads state until it is no longer running. The one shared loop, so a dropped poll isn't
|
|
@@ -22,14 +23,18 @@ export const followCommandRun = async <R extends CommandRun>(read: () => Promise
|
|
|
22
23
|
if (signal?.aborted === true) {
|
|
23
24
|
return undefined;
|
|
24
25
|
}
|
|
26
|
+
// Only the read is a poll that may fail and be retried; a throw from `onState` is the caller's own bug.
|
|
27
|
+
let run: R | undefined;
|
|
25
28
|
try {
|
|
26
|
-
|
|
29
|
+
run = await read();
|
|
30
|
+
} catch (cause) {
|
|
31
|
+
onError(cause);
|
|
32
|
+
}
|
|
33
|
+
if (run !== undefined) {
|
|
27
34
|
onState?.(run);
|
|
28
35
|
if (run.status !== "running") {
|
|
29
36
|
return run;
|
|
30
37
|
}
|
|
31
|
-
} catch (cause) {
|
|
32
|
-
onError?.(cause);
|
|
33
38
|
}
|
|
34
39
|
await sleep(intervalMs, { signal });
|
|
35
40
|
}
|
|
@@ -148,9 +148,25 @@ const target = createServer((request: IncomingMessage, response: ServerResponse)
|
|
|
148
148
|
|
|
149
149
|
const cancelled = gate<string>();
|
|
150
150
|
|
|
151
|
+
// `/late` holds its handshake until released, so the session can die while the loopback upgrade is still pending.
|
|
152
|
+
const lateArrived = gate();
|
|
153
|
+
const lateReleased = gate();
|
|
154
|
+
let lateTornDown = false;
|
|
155
|
+
|
|
151
156
|
// Hand-written HTTP/1.1 upgrade carrying the 101 head plus the raw bytes after it. `/refuse` answers instead of
|
|
152
157
|
// upgrading.
|
|
153
158
|
target.on("upgrade", (request: IncomingMessage, socket: Duplex, head: Buffer) => {
|
|
159
|
+
if ((request.url ?? "") === "/late") {
|
|
160
|
+
socket.on("error", () => {});
|
|
161
|
+
socket.on("close", () => (lateTornDown = true));
|
|
162
|
+
lateArrived.open();
|
|
163
|
+
void lateReleased.opened.then(() => {
|
|
164
|
+
if (!socket.destroyed) {
|
|
165
|
+
socket.write("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n\r\n");
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
154
170
|
if ((request.url ?? "") === "/refuse") {
|
|
155
171
|
socket.end("HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nContent-Length: 7\r\n\r\nno dice");
|
|
156
172
|
return;
|
|
@@ -179,7 +195,7 @@ target.on("upgrade", (request: IncomingMessage, socket: Duplex, head: Buffer) =>
|
|
|
179
195
|
// One session; every request is routed through it.
|
|
180
196
|
const front = async (
|
|
181
197
|
targetPort: number,
|
|
182
|
-
): Promise<{ readonly server: Server; readonly poison: (bytes: Buffer) => void; readonly close: () => void }> => {
|
|
198
|
+
): Promise<{ readonly server: Server; readonly poison: (bytes: Buffer) => void; readonly sever: () => void; readonly close: () => void }> => {
|
|
183
199
|
const [edgeSide, daemonSide] = duplexPair();
|
|
184
200
|
const daemon = await serveIngressSession(daemonSide, { targetPort });
|
|
185
201
|
const session = await openIngressSession(edgeSide);
|
|
@@ -202,6 +218,8 @@ const front = async (
|
|
|
202
218
|
server,
|
|
203
219
|
// Writes garbage onto the wire the edge reads, simulating a wedged or corrupted peer.
|
|
204
220
|
poison: (bytes: Buffer) => void daemonSide.write(bytes),
|
|
221
|
+
// Cuts the tunnel under the daemon, which takes its h2 session and every stream on it down.
|
|
222
|
+
sever: () => void daemonSide.destroy(),
|
|
205
223
|
close: () => {
|
|
206
224
|
session.close();
|
|
207
225
|
daemon.close();
|
|
@@ -439,6 +457,32 @@ test("a browser that gives up cancels the stream all the way to the target", asy
|
|
|
439
457
|
await expect(cancelled.opened).resolves.toBe("aborted");
|
|
440
458
|
});
|
|
441
459
|
|
|
460
|
+
// The handshake lands after its stream is gone: answering it throws ERR_HTTP2_INVALID_STREAM from an event callback.
|
|
461
|
+
test("a session that dies mid-handshake tears the loopback upgrade down instead of answering into a dead stream", async () => {
|
|
462
|
+
const own = await front((target.address() as AddressInfo).port);
|
|
463
|
+
const ownPort = await listen(own.server);
|
|
464
|
+
const request = h1Request({
|
|
465
|
+
host: "127.0.0.1",
|
|
466
|
+
port: ownPort,
|
|
467
|
+
path: "/late",
|
|
468
|
+
headers: { host: HOST, connection: "Upgrade", upgrade: "websocket", "sec-websocket-key": "x" },
|
|
469
|
+
agent: false,
|
|
470
|
+
});
|
|
471
|
+
request.on("error", () => {});
|
|
472
|
+
request.end();
|
|
473
|
+
await lateArrived.opened;
|
|
474
|
+
|
|
475
|
+
own.sever();
|
|
476
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
477
|
+
lateReleased.open();
|
|
478
|
+
|
|
479
|
+
await waitFor(() => expect(lateTornDown).toBe(true));
|
|
480
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
481
|
+
expect(uncaught).toStrictEqual([]);
|
|
482
|
+
request.destroy();
|
|
483
|
+
own.close();
|
|
484
|
+
});
|
|
485
|
+
|
|
442
486
|
test("a tunnel whose target is not listening fails the exchange rather than answering for it", async () => {
|
|
443
487
|
const dead = createServer();
|
|
444
488
|
const deadPort = await listen(dead);
|
|
@@ -10,9 +10,9 @@ import {
|
|
|
10
10
|
import { type AddressInfo, createServer as createNetServer, connect as netConnect, type Socket } from "node:net";
|
|
11
11
|
import { Duplex } from "node:stream";
|
|
12
12
|
|
|
13
|
-
// Both
|
|
14
|
-
//
|
|
15
|
-
// interleave several sandboxes' hosts.
|
|
13
|
+
// Both halves of the ingress tunnel's h2 session over any node Duplex, in one file so the wire has one owner: the edge's,
|
|
14
|
+
// and a daemon half its suites dial (a sandbox runs the Rust one, _sandbox/front tunnel.rs, held to this by its test).
|
|
15
|
+
// Routing is per h2 stream, since one edge connection can interleave several sandboxes' hosts.
|
|
16
16
|
|
|
17
17
|
// Header hygiene
|
|
18
18
|
|
|
@@ -308,7 +308,7 @@ export const openIngressSession = async (duplex: Duplex): Promise<IngressSession
|
|
|
308
308
|
};
|
|
309
309
|
};
|
|
310
310
|
|
|
311
|
-
// The daemon half: an h2 server over the tunnel, onto
|
|
311
|
+
// The daemon half the edge's suites dial: an h2 server over the tunnel, onto a local listener
|
|
312
312
|
|
|
313
313
|
export interface ServeIngressSessionOptions {
|
|
314
314
|
// The daemon's own listener; every stream lands there as a plain HTTP/1.1 request or upgrade.
|
|
@@ -340,6 +340,10 @@ export const serveIngressSession = async (duplex: Duplex, options: ServeIngressS
|
|
|
340
340
|
server.on("error", () => duplex.destroy());
|
|
341
341
|
server.on("session", (session) => session.setLocalWindowSize(CONNECTION_WINDOW_SIZE));
|
|
342
342
|
|
|
343
|
+
// The loopback's answer can outlive its stream when the session dies, and respond/write on a dead stream throws
|
|
344
|
+
// ERR_HTTP2_INVALID_STREAM from an event callback, where nothing catches it.
|
|
345
|
+
const gone = (stream: ServerHttp2Stream): boolean => stream.destroyed || stream.closed;
|
|
346
|
+
|
|
343
347
|
const forwardToLoopback = (stream: ServerHttp2Stream, headers: IncomingHttpHeaders): void => {
|
|
344
348
|
const local = h1Request({
|
|
345
349
|
host: LOOPBACK,
|
|
@@ -353,6 +357,10 @@ export const serveIngressSession = async (duplex: Duplex, options: ServeIngressS
|
|
|
353
357
|
});
|
|
354
358
|
stream.pipe(local);
|
|
355
359
|
local.on("response", (response) => {
|
|
360
|
+
if (gone(stream)) {
|
|
361
|
+
response.destroy();
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
356
364
|
stream.respond({ [constants.HTTP2_HEADER_STATUS]: response.statusCode ?? 502, ...endToEnd(response.headers) });
|
|
357
365
|
response.pipe(stream);
|
|
358
366
|
});
|
|
@@ -364,6 +372,9 @@ export const serveIngressSession = async (duplex: Duplex, options: ServeIngressS
|
|
|
364
372
|
// means this — on a reset, node fires `aborted` before `finish` and `close`, so a
|
|
365
373
|
// `writableEnded`/`writableFinished` guard there would miss it.
|
|
366
374
|
stream.on("aborted", () => local.destroy());
|
|
375
|
+
// `aborted` fires only while the stream's writable side is open; `close` covers every other way it dies, and a
|
|
376
|
+
// request whose answer already completed ignores the destroy.
|
|
377
|
+
stream.on("close", () => local.destroy());
|
|
367
378
|
};
|
|
368
379
|
|
|
369
380
|
const spliceUpgrade = (stream: ServerHttp2Stream, headers: IncomingHttpHeaders): void => {
|
|
@@ -372,6 +383,10 @@ export const serveIngressSession = async (duplex: Duplex, options: ServeIngressS
|
|
|
372
383
|
// pool the request path above uses.
|
|
373
384
|
const local = h1Request({ host: LOOPBACK, port: options.targetPort, method: head.method, path: head.path, headers: head.headers, agent: false });
|
|
374
385
|
local.on("upgrade", (response, socket, first) => {
|
|
386
|
+
if (gone(stream)) {
|
|
387
|
+
socket.destroy();
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
375
390
|
stream.respond({ [constants.HTTP2_HEADER_STATUS]: constants.HTTP_STATUS_OK });
|
|
376
391
|
// Response head written verbatim (see serializeHead), then any bytes already sent past it, then the pipe.
|
|
377
392
|
stream.write(serializeHead(response, NOTHING_DROPPED, []));
|
|
@@ -383,11 +398,17 @@ export const serveIngressSession = async (duplex: Duplex, options: ServeIngressS
|
|
|
383
398
|
// The local server declined to upgrade; its answer goes to the browser with framing that matches the already
|
|
384
399
|
// de-chunked body.
|
|
385
400
|
local.on("response", (response) => {
|
|
401
|
+
if (gone(stream)) {
|
|
402
|
+
response.destroy();
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
386
405
|
stream.respond({ [constants.HTTP2_HEADER_STATUS]: constants.HTTP_STATUS_OK });
|
|
387
406
|
stream.write(serializeHead(response, DECLINED_UPGRADE_DROP, DECLINED_UPGRADE_ADD));
|
|
388
407
|
response.pipe(stream);
|
|
389
408
|
});
|
|
390
409
|
local.on("error", () => stream.close(constants.NGHTTP2_CONNECT_ERROR));
|
|
410
|
+
// Before the handshake answers, nothing else ties the loopback request to the stream; once spliced, a no-op.
|
|
411
|
+
stream.on("close", () => local.destroy());
|
|
391
412
|
// An upgrade request carries no body; the client is waiting on the handshake before it says anything else.
|
|
392
413
|
local.end();
|
|
393
414
|
};
|
package/src/schemas/agents.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import { AgentHarnessSchema, AgentOriginSchema, AgentProviderSchema, ConversationQueueSchema, ForkedFromSchema } from "./agent.js";
|
|
4
4
|
import { LoopStateSchema } from "./loops.js";
|
|
5
5
|
import { LimitPolicySchema, RetryPolicySchema, TurnBreakPolicySchema, TurnBreakSchema } from "./turn-break.js";
|
|
6
|
+
import { KeepWarmSchema } from "./keep-warm.js";
|
|
6
7
|
import { EMOJI_MAX_LENGTH, isSingleEmoji } from "../text/emoji.js";
|
|
7
8
|
// A fleet agent is any conversation with a registry entry, keyed by conversationId. Isolated ones own a git worktree
|
|
8
9
|
// (branch agent/<id>); workspace conversations have none, but both share one status/activity/cost lifecycle.
|
|
@@ -329,11 +330,24 @@ export const AgentSummarySchema = z.object({
|
|
|
329
330
|
.object({
|
|
330
331
|
at: z.number().describe("When its last request touched the provider's prompt cache, in milliseconds."),
|
|
331
332
|
ttlMs: z.number().describe("How long that entry lives from `at`, in milliseconds."),
|
|
333
|
+
rollsAt: z
|
|
334
|
+
.number()
|
|
335
|
+
.optional()
|
|
336
|
+
.describe(
|
|
337
|
+
"When the date written into the agent's prompt next changes, in milliseconds (midnight where the agent runs). Past it the next turn sends a different prompt, so nothing kept before it is read again.",
|
|
338
|
+
),
|
|
339
|
+
keepable: z
|
|
340
|
+
.boolean()
|
|
341
|
+
.optional()
|
|
342
|
+
.describe("Whether this sandbox can keep this cache warm: it holds the last turn's exact request to replay, on a Claude subscription account."),
|
|
332
343
|
})
|
|
333
344
|
.optional()
|
|
334
345
|
.describe(
|
|
335
346
|
"When this conversation's prompt cache was last kept alive and how long it lasts, which together say when picking the conversation up stops being cheap. Absent when the provider publishes nothing to ground it on.",
|
|
336
347
|
),
|
|
348
|
+
keepWarm: KeepWarmSchema.optional().describe(
|
|
349
|
+
"Whether the sandbox is keeping this conversation's prompt cache warm while it sits idle, how that is going, or why it stopped. Absent when nobody asked for it.",
|
|
350
|
+
),
|
|
337
351
|
activity: AgentActivitySchema.optional().describe("What it is doing at this moment."),
|
|
338
352
|
// Beside `activity` because both answer "where is it", one in a sentence and one as a fraction. Live while a turn
|
|
339
353
|
// runs, and left standing after it, so a settled card still says how far the work got.
|
|
@@ -517,13 +531,6 @@ export const AgentToolChildrenQuerySchema = AgentIdSchema.extend({
|
|
|
517
531
|
toolId: z.string().min(1).describe("Which tool call, by the id its card carries."),
|
|
518
532
|
});
|
|
519
533
|
// Absent `ids` archives every archivable finished agent (the lane's "Clear"); unarchive always names its own ids.
|
|
520
|
-
export const AgentArchiveSchema = z.object({
|
|
521
|
-
ids: z
|
|
522
|
-
.array(z.string().min(1))
|
|
523
|
-
.max(500)
|
|
524
|
-
.optional()
|
|
525
|
-
.describe("Which conversations to put away. Leave it out for every finished one that can be archived right now."),
|
|
526
|
-
});
|
|
527
534
|
export const AgentIdsSchema = z.object({ ids: z.array(z.string().min(1)).min(1).max(500).describe("Which conversations.") });
|
|
528
535
|
// What actually moved, not the roster afterward: two archives finishing at once would otherwise let a slower snapshot
|
|
529
536
|
// resurrect what a faster one just filed away. Whole summaries, since the receiving surfaces (archive list, detail
|
|
@@ -354,6 +354,12 @@ export const CapabilityStatusSchema = z.object({
|
|
|
354
354
|
state: CapabilityStateSchema.describe("Whether it is live, still coming up, broken, or switched off."),
|
|
355
355
|
detail: z.string().optional().describe("What is wrong, in words a person can act on."),
|
|
356
356
|
code: z.string().optional().describe("A short marker for that reason, for anything deciding what to do about it."),
|
|
357
|
+
settling: z
|
|
358
|
+
.boolean()
|
|
359
|
+
.optional()
|
|
360
|
+
.describe(
|
|
361
|
+
"True while something under way will move this on its own: a start, a download, a pairing code waiting to be typed. Absent when only a person can move it, or a pushed change will say when it moved.",
|
|
362
|
+
),
|
|
357
363
|
});
|
|
358
364
|
export type CapabilityStatus = z.infer<typeof CapabilityStatusSchema>;
|
|
359
365
|
// The list row: manifest entry plus live status, secrets never returned. `secrets` names which config keys hold a
|