@intentic/sandbox-contract 1.314.0 → 1.316.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 +5 -5
- package/dist/contracts/accounts.contract.d.ts +6 -0
- package/dist/contracts/accounts.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.d.ts +2 -0
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.js +1 -1
- package/dist/contracts/agent.contract.js.map +1 -1
- package/dist/contracts/agents.contract.d.ts +293 -0
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +14 -4
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/ci.contract.d.ts +22 -0
- package/dist/contracts/ci.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.d.ts +0 -3
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.d.ts +6 -0
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.js +1 -1
- package/dist/contracts/system.contract.js.map +1 -1
- package/dist/contracts/translator.contract.d.ts +12 -0
- package/dist/contracts/translator.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.d.ts +15 -137
- package/dist/contracts/workspace.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.js +25 -15
- package/dist/contracts/workspace.contract.js.map +1 -1
- package/dist/events/agent-events.d.ts.map +1 -1
- package/dist/events/agent-events.js.map +1 -1
- package/dist/events/agent-words.d.ts.map +1 -1
- package/dist/events/agent-words.js +5 -4
- package/dist/events/agent-words.js.map +1 -1
- package/dist/events/land-breakage.d.ts +0 -2
- package/dist/events/land-breakage.d.ts.map +1 -1
- package/dist/events/land-breakage.js +0 -2
- package/dist/events/land-breakage.js.map +1 -1
- package/dist/events/requests.js +1 -1
- package/dist/events/requests.js.map +1 -1
- package/dist/events/system-events.d.ts +4 -0
- package/dist/events/system-events.d.ts.map +1 -1
- package/dist/events/transcript.d.ts +1 -0
- package/dist/events/transcript.d.ts.map +1 -1
- package/dist/events/transcript.js +5 -1
- package/dist/events/transcript.js.map +1 -1
- package/dist/index.d.ts +358 -142
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/models/agent-runtimes.d.ts.map +1 -1
- package/dist/models/agent-runtimes.js +1 -1
- package/dist/models/agent-runtimes.js.map +1 -1
- package/dist/models/plan-pools.d.ts +3 -1
- package/dist/models/plan-pools.d.ts.map +1 -1
- package/dist/models/plan-pools.js +11 -4
- package/dist/models/plan-pools.js.map +1 -1
- package/dist/protocol/raw-routes.d.ts +9 -0
- package/dist/protocol/raw-routes.d.ts.map +1 -1
- package/dist/protocol/raw-routes.js +2 -0
- package/dist/protocol/raw-routes.js.map +1 -1
- package/dist/protocol/request-id.d.ts +0 -1
- package/dist/protocol/request-id.d.ts.map +1 -1
- package/dist/protocol/request-id.js +0 -1
- package/dist/protocol/request-id.js.map +1 -1
- package/dist/schemas/agent.js +1 -1
- package/dist/schemas/agent.js.map +1 -1
- package/dist/schemas/agents.d.ts +7 -1
- package/dist/schemas/agents.d.ts.map +1 -1
- package/dist/schemas/agents.js +14 -3
- package/dist/schemas/agents.js.map +1 -1
- package/dist/schemas/automations.d.ts +1 -0
- package/dist/schemas/automations.d.ts.map +1 -1
- package/dist/schemas/ci.d.ts +45 -0
- package/dist/schemas/ci.d.ts.map +1 -1
- package/dist/schemas/ci.js +18 -0
- package/dist/schemas/ci.js.map +1 -1
- package/dist/schemas/devices.d.ts +0 -1
- package/dist/schemas/devices.d.ts.map +1 -1
- package/dist/schemas/devices.js +3 -4
- package/dist/schemas/devices.js.map +1 -1
- package/dist/schemas/personas.js +3 -3
- package/dist/schemas/personas.js.map +1 -1
- package/dist/schemas/providers/plan-limits.d.ts +21 -0
- package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
- package/dist/schemas/providers/plan-limits.js +13 -3
- package/dist/schemas/providers/plan-limits.js.map +1 -1
- package/dist/schemas/providers/provider-oauth.d.ts +6 -0
- package/dist/schemas/providers/provider-oauth.d.ts.map +1 -1
- package/dist/schemas/settings-history.d.ts +1 -1
- package/dist/schemas/settings-history.d.ts.map +1 -1
- package/dist/schemas/settings-history.js +2 -1
- package/dist/schemas/settings-history.js.map +1 -1
- package/dist/schemas/settings.d.ts +0 -4
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +6 -13
- package/dist/schemas/settings.js.map +1 -1
- package/dist/schemas/terminal.d.ts.map +1 -1
- package/dist/schemas/terminal.js +3 -3
- package/dist/schemas/terminal.js.map +1 -1
- package/dist/schemas/workspace/push-checks.d.ts +222 -0
- package/dist/schemas/workspace/push-checks.d.ts.map +1 -0
- package/dist/schemas/workspace/push-checks.js +97 -0
- package/dist/schemas/workspace/push-checks.js.map +1 -0
- package/dist/schemas/workspace/workspace-tree.d.ts +9 -0
- package/dist/schemas/workspace/workspace-tree.d.ts.map +1 -1
- package/dist/schemas/workspace/workspace-tree.js +12 -0
- package/dist/schemas/workspace/workspace-tree.js.map +1 -1
- package/dist/state/definition.d.ts +0 -4
- package/dist/state/definition.d.ts.map +1 -1
- package/dist/state/runtime-state.d.ts +2 -2
- package/dist/state/runtime-state.d.ts.map +1 -1
- package/dist/state/runtime-state.js +1 -1
- package/dist/state/runtime-state.js.map +1 -1
- package/dist/state/workspace-state.d.ts +1 -11
- package/dist/state/workspace-state.d.ts.map +1 -1
- package/dist/state/workspace-state.js +1 -13
- package/dist/state/workspace-state.js.map +1 -1
- package/dist/text/transcript-fold.d.ts +8 -0
- package/dist/text/transcript-fold.d.ts.map +1 -1
- package/dist/text/transcript-fold.js +67 -14
- package/dist/text/transcript-fold.js.map +1 -1
- package/package.json +5 -5
- package/src/contracts/agent.contract.ts +1 -1
- package/src/contracts/agents.contract.ts +15 -3
- package/src/contracts/system.contract.ts +1 -1
- package/src/contracts/workspace.contract.ts +35 -21
- package/src/events/agent-events.ts +4 -2
- package/src/events/agent-words.test.ts +8 -1
- package/src/events/agent-words.ts +8 -5
- package/src/events/errands.test.ts +4 -4
- package/src/events/land-breakage.ts +5 -9
- package/src/events/requests.ts +1 -1
- package/src/events/transcript.ts +9 -3
- package/src/events/verify-nudge.ts +3 -3
- package/src/index.ts +2 -2
- package/src/models/agent-runtimes.ts +5 -4
- package/src/models/plan-pools.test.ts +22 -0
- package/src/models/plan-pools.ts +26 -5
- package/src/protocol/ingress-contract.fixture.json +2 -0
- package/src/protocol/raw-routes.ts +4 -1
- package/src/protocol/request-id.ts +0 -3
- package/src/schemas/agent.ts +1 -1
- package/src/schemas/agents.ts +21 -8
- package/src/schemas/automations.ts +2 -2
- package/src/schemas/ci.ts +25 -0
- package/src/schemas/devices.ts +8 -12
- package/src/schemas/personas.ts +3 -3
- package/src/schemas/providers/plan-limits.ts +21 -3
- package/src/schemas/settings-history.ts +3 -1
- package/src/schemas/settings.test.ts +4 -3
- package/src/schemas/settings.ts +17 -31
- package/src/schemas/speaker.ts +4 -4
- package/src/schemas/terminal.ts +12 -10
- package/src/schemas/workspace/{mainline.test.ts → push-checks.test.ts} +5 -5
- package/src/schemas/workspace/push-checks.ts +155 -0
- package/src/schemas/workspace/workspace-tree.ts +15 -0
- package/src/state/fix-stance.test.ts +2 -1
- package/src/state/landed-commit.ts +1 -1
- package/src/state/red-streak.ts +4 -4
- package/src/state/runtime-state.ts +2 -3
- package/src/state/workspace-state.ts +1 -13
- package/src/text/transcript-fold.test.ts +107 -0
- package/src/text/transcript-fold.ts +95 -16
- package/dist/schemas/workspace/mainline.d.ts +0 -513
- package/dist/schemas/workspace/mainline.d.ts.map +0 -1
- package/dist/schemas/workspace/mainline.js +0 -182
- package/dist/schemas/workspace/mainline.js.map +0 -1
- package/src/schemas/workspace/mainline.ts +0 -268
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { errandOfPrompt, errandOfRow } from "./errands.js";
|
|
2
|
-
import {
|
|
2
|
+
import { LAND_BREAKAGE_OPENING, LAND_FIX_OPENING } from "./land-breakage.js";
|
|
3
3
|
import { LAND_CONFLICT_OPENING } from "./land-conflict.js";
|
|
4
4
|
import { verifyNudgePrompt } from "./verify-nudge.js";
|
|
5
5
|
|
|
@@ -11,12 +11,12 @@ describe(`which errand a message is`, () => {
|
|
|
11
11
|
});
|
|
12
12
|
|
|
13
13
|
test(`is read by its opening on a row written before rows said, and on nothing but a message`, () => {
|
|
14
|
-
expect(errandOfPrompt(
|
|
15
|
-
expect(errandOfPrompt(
|
|
14
|
+
expect(errandOfPrompt(`${LAND_BREAKAGE_OPENING}\n\n- a failure`)).toBe(`land-breakage`);
|
|
15
|
+
expect(errandOfPrompt(`${LAND_FIX_OPENING}\n\n- a failure`)).toBe(`land-fix`);
|
|
16
16
|
expect(errandOfPrompt(verifyNudgePrompt([`x`]))).toBe(`verify-nudge`);
|
|
17
17
|
expect(errandOfPrompt(`${LAND_CONFLICT_OPENING}\n\nthe files`)).toBe(`land-conflict`);
|
|
18
18
|
expect(errandOfPrompt(`Please fix the parser`)).toBeUndefined();
|
|
19
|
-
expect(errandOfRow({ role: `assistant`, text:
|
|
19
|
+
expect(errandOfRow({ role: `assistant`, text: LAND_FIX_OPENING })).toBeUndefined();
|
|
20
20
|
expect(errandOfRow({ role: `user`, text: `hello` })).toBeUndefined();
|
|
21
21
|
});
|
|
22
22
|
});
|
|
@@ -1,23 +1,19 @@
|
|
|
1
|
-
// The follow-
|
|
1
|
+
// The follow-ups the daemon USED to send when the check it ran after every land went red: back to the conversation
|
|
2
|
+
// that landed the work, or to a fresh conversation started on it. Nothing sends either any more: nothing checks work
|
|
3
|
+
// after it lands, CI checks what the owner pushes (schemas/workspace/push-checks.ts). They stay on the wire because
|
|
4
|
+
// records already hold them, and the chat has to go on recognising them: a prompt nobody typed must not reach a reader
|
|
5
|
+
// as their own words.
|
|
2
6
|
|
|
3
7
|
// Anchored on by the chat, so it must stay unique and stable across releases.
|
|
4
8
|
export const LAND_BREAKAGE_OPENING =
|
|
5
9
|
"What this conversation landed turned the main tree's own check red after it arrived. Each failure below appeared with that land and was not failing before it. Fix them on this branch and check the fix as you would any change; it lands like any other turn.";
|
|
6
10
|
|
|
7
|
-
// The prompt a breakage actually sends: its opening, then the failures, each already worded for the model.
|
|
8
|
-
export const landBreakagePrompt = (parts: readonly string[]): string => [LAND_BREAKAGE_OPENING, ...parts].join("\n\n");
|
|
9
|
-
|
|
10
11
|
// Whether a stored prompt is one.
|
|
11
12
|
export const isLandBreakage = (prompt: string): boolean => prompt.startsWith(LAND_BREAKAGE_OPENING);
|
|
12
13
|
|
|
13
|
-
// The opening of a FRESH conversation the daemon starts on a red main-line check that the conversation that landed the
|
|
14
|
-
// work could not take: nobody could be named, several could, or the one named had gone cold or run out of room.
|
|
15
14
|
// Anchored on by the chat like the follow-up above, so it must stay unique and stable across releases.
|
|
16
15
|
export const LAND_FIX_OPENING =
|
|
17
16
|
"The main tree's own check went red after work landed, and no conversation still holding that work could take it, so this one was started to fix it. The failures, the lands they arrived with and where to read those conversations are below. Fix them in this isolated worktree and check the fix as you would any change; your work lands like any other turn.";
|
|
18
17
|
|
|
19
|
-
// The prompt a fresh fix-up is started with: its opening, then the evidence, each part already worded for the model.
|
|
20
|
-
export const landFixPrompt = (parts: readonly string[]): string => [LAND_FIX_OPENING, ...parts].join("\n\n");
|
|
21
|
-
|
|
22
18
|
// Whether a stored prompt is one.
|
|
23
19
|
export const isLandFix = (prompt: string): boolean => prompt.startsWith(LAND_FIX_OPENING);
|
package/src/events/requests.ts
CHANGED
|
@@ -99,7 +99,7 @@ export const PermissionAskSchema = z.object({
|
|
|
99
99
|
"The program this request is holding, when the request is about one. Present on a command gate's request and absent on every other permission ask.",
|
|
100
100
|
),
|
|
101
101
|
child: ChildAgentAskSchema.optional().describe(
|
|
102
|
-
"The
|
|
102
|
+
"The subagent this request would start or reach, and what it runs on. Present on the request to start or reach a subagent and absent on every other permission ask.",
|
|
103
103
|
),
|
|
104
104
|
// The judge's own sentence, present only when the title can't say it; never written by the agent being gated.
|
|
105
105
|
explain: z
|
package/src/events/transcript.ts
CHANGED
|
@@ -97,9 +97,15 @@ export const TranscriptToolSchema: z.ZodType<TranscriptTool> = z.lazy(() =>
|
|
|
97
97
|
),
|
|
98
98
|
}),
|
|
99
99
|
);
|
|
100
|
-
// The
|
|
101
|
-
//
|
|
100
|
+
// The subagent a call started, on that call's card whichever mechanism started it (transcript-fold.ts places it).
|
|
101
|
+
// Identifying fields arrive once; status/spend/activity replace on each update.
|
|
102
102
|
export const TranscriptSubagentSchema = z.object({
|
|
103
|
+
id: z
|
|
104
|
+
.string()
|
|
105
|
+
.optional()
|
|
106
|
+
.describe(
|
|
107
|
+
"Its own id, where that is not the card's: a spawned subagent is named by its own conversation, which is what the roster, `wait` and its page call it. Absent, the card's id is its id, as it is for one the runtime started in-process.",
|
|
108
|
+
),
|
|
103
109
|
kind: SubagentKindSchema,
|
|
104
110
|
agentType: z.string().optional(),
|
|
105
111
|
description: z.string().optional(),
|
|
@@ -151,7 +157,7 @@ export type TranscriptWatchWake = z.infer<typeof TranscriptWatchWakeSchema>;
|
|
|
151
157
|
|
|
152
158
|
// Another agent's words that reached this conversation as a prompt; the row keeps the prompt and names the sender.
|
|
153
159
|
export const TranscriptAgentWordsSchema = z.object({
|
|
154
|
-
kind: z.enum(["peer", "child"]).describe("Who sent it: another conversation in the workspace, or a
|
|
160
|
+
kind: z.enum(["peer", "child"]).describe("Who sent it: another conversation in the workspace, or a subagent this one started."),
|
|
155
161
|
from: z.string().describe("The sending conversation's id."),
|
|
156
162
|
title: z.string().optional().describe("The sender's title, when it had one."),
|
|
157
163
|
failed: z.boolean().optional().describe("A child's report on a turn that failed rather than finished."),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The follow-up the daemon USED to send when a turn ended with work it could not confirm. Nothing sends it any more:
|
|
2
|
-
// checks
|
|
3
|
-
// records already hold it, and the chat has to go on recognising it: a prompt nobody typed must not
|
|
4
|
-
// their own words.
|
|
2
|
+
// nothing checks inside a conversation, CI checks what the owner pushes (schemas/workspace/push-checks.ts). It stays on
|
|
3
|
+
// the wire because records already hold it, and the chat has to go on recognising it: a prompt nobody typed must not
|
|
4
|
+
// reach a reader as their own words.
|
|
5
5
|
|
|
6
6
|
// Anchored on by the reader, so it must stay unique and stable across releases — a reworded opening un-recognises every
|
|
7
7
|
// nudge already in a record, and they read as the user's own typing again.
|
package/src/index.ts
CHANGED
|
@@ -83,7 +83,7 @@ export { intenticContract } from "./contracts/intentic.contract.js";
|
|
|
83
83
|
export { inventoryContract } from "./contracts/inventory.contract.js";
|
|
84
84
|
export { issuesContract } from "./contracts/issues.contract.js";
|
|
85
85
|
export { logsContract } from "./contracts/logs.contract.js";
|
|
86
|
-
export {
|
|
86
|
+
export { REQUEST_ID_HEADER } from "./protocol/request-id.js";
|
|
87
87
|
export * from "./protocol/edge-verdict.js";
|
|
88
88
|
export { loopsContract } from "./contracts/loops.contract.js";
|
|
89
89
|
export { panelsContract } from "./contracts/panels.contract.js";
|
|
@@ -234,7 +234,7 @@ export * from "./schemas/webext.js";
|
|
|
234
234
|
export * from "./schemas/workflows.js";
|
|
235
235
|
export * from "./schemas/workspace/workspace-repos.js";
|
|
236
236
|
export * from "./schemas/workspace/workspace-search.js";
|
|
237
|
-
export * from "./schemas/workspace/
|
|
237
|
+
export * from "./schemas/workspace/push-checks.js";
|
|
238
238
|
export * from "./schemas/workspace/workspace-setup.js";
|
|
239
239
|
export * from "./schemas/workspace/workspace-tree.js";
|
|
240
240
|
export * from "./state/arrival.js";
|
|
@@ -181,7 +181,7 @@ export const PI: AgentCapabilities = {
|
|
|
181
181
|
secrets: "none",
|
|
182
182
|
};
|
|
183
183
|
|
|
184
|
-
// Cursor's own runtime, driven through `@cursor/sdk`
|
|
184
|
+
// Cursor's own runtime, driven through `@cursor/sdk` from this daemon's process; an embedding surface, so most seams
|
|
185
185
|
// other runtimes lack are function arguments here. The harness axis doesn't apply: the SDK is the only door.
|
|
186
186
|
export const CURSOR: AgentCapabilities = {
|
|
187
187
|
runtime: "cursor",
|
|
@@ -199,11 +199,12 @@ export const CURSOR: AgentCapabilities = {
|
|
|
199
199
|
// Cursor publishes effort as model parameters, not one scale; true here just means it's forwardable at all.
|
|
200
200
|
effort: true,
|
|
201
201
|
fastMode: false,
|
|
202
|
-
//
|
|
203
|
-
|
|
202
|
+
// The SDK agent of an anchored turn runs in a runtime process born in the turn's mount namespace (the daemon keeps
|
|
203
|
+
// the loop, the tools and the hook gate), so its shell and its edits see the worktree at /work.
|
|
204
|
+
isolation: "namespace",
|
|
204
205
|
// Cursor's commands are files on disk the SDK loads but doesn't publish back, so there's no list to offer.
|
|
205
206
|
commands: false,
|
|
206
|
-
// The SDK runs its shell
|
|
207
|
+
// The SDK runs its shell itself, as a plain child; there is no tmux session for the terminal panel to attach to.
|
|
207
208
|
terminals: false,
|
|
208
209
|
// The SDK throws typed errors instead of dissolving a refusal into prose, so the adapter files coded frames.
|
|
209
210
|
recovery: true,
|
|
@@ -167,6 +167,13 @@ test("a revoked sign-in outranks a lost seat, which outranks a bench, whatever t
|
|
|
167
167
|
reason: "cooling down",
|
|
168
168
|
until: 2_000,
|
|
169
169
|
});
|
|
170
|
+
// One Google wants its owner to verify: neither waiting nor reconnecting lifts it, and the row carries the page.
|
|
171
|
+
expect(serviceState({ ...facts, cooling: { reason: "Verify your account to continue.", verify: "https://accounts.google.com/v" } })).toEqual({
|
|
172
|
+
kind: "blocked",
|
|
173
|
+
fix: "verify",
|
|
174
|
+
reason: "Verify your account to continue.",
|
|
175
|
+
url: "https://accounts.google.com/v",
|
|
176
|
+
});
|
|
170
177
|
// A bench whose instant has passed is over.
|
|
171
178
|
expect(serviceState({ ...facts, cooling: { until: 999 } }, undefined, undefined, 1_000_000)).toEqual({ kind: "ready", room: 100 });
|
|
172
179
|
});
|
|
@@ -202,6 +209,21 @@ test("a refusal stands until a later reading contradicts it, and never past its
|
|
|
202
209
|
expect(refusalVerdict(refusal({ kind: "limit", account: "a" }), [])).toBe("standing");
|
|
203
210
|
});
|
|
204
211
|
|
|
212
|
+
// Codex's usage endpoint kept reading room while every turn was refused "try again at 3:38 PM": the provider's own
|
|
213
|
+
// instant is what ends the refusal, where it is known which account it refused.
|
|
214
|
+
test("a spent allowance with the provider's own reset stands until that instant, whatever a later reading says", () => {
|
|
215
|
+
const room = at(2_000, window({ kind: "five_hour", utilization: 0 }));
|
|
216
|
+
const until = { kind: "limit", resetsAt: 10 } as const;
|
|
217
|
+
expect(refusalVerdict(refusal({ ...until, account: "a" }), [{ account: "a", usage: room }], 9_999)).toBe("standing");
|
|
218
|
+
expect(refusalVerdict(refusal({ ...until, account: "a" }), [{ account: "a", usage: room }], 10_000)).toBe("answered");
|
|
219
|
+
// A routed refusal names no account, but with one account there is no doubt whose it was.
|
|
220
|
+
expect(refusalVerdict(refusal(until), [{ account: "a", usage: room }], 9_999)).toBe("standing");
|
|
221
|
+
// Over several, the translator benches the one it refused and routes on, so another's room still answers it.
|
|
222
|
+
expect(refusalVerdict(refusal(until), [{ account: "a", usage: room }, { account: "b", usage: room }], 9_999)).toBe("answered");
|
|
223
|
+
// Spent until the instant the provider named, not the pool's own reset.
|
|
224
|
+
expect(serviceStates([{ account: "a", usage: room }], refusal({ ...until, account: "a" }), undefined, 9_999).get("a")).toEqual({ kind: "spent", reopensAt: 10 });
|
|
225
|
+
});
|
|
226
|
+
|
|
205
227
|
test("a provider's accounts are judged together: a standing refusal lands only on the account it names", () => {
|
|
206
228
|
const room = at(500, window({ kind: "five_hour", utilization: 40 }));
|
|
207
229
|
const states = serviceStates([{ account: "a", usage: room }, { account: "b", usage: room }], refusal({ kind: "entitlement", account: "a" }));
|
package/src/models/plan-pools.ts
CHANGED
|
@@ -145,8 +145,11 @@ export interface ServiceFacts {
|
|
|
145
145
|
// Why the sign-in needs renewing, where the provider said.
|
|
146
146
|
readonly detail?: string | undefined;
|
|
147
147
|
readonly seatRefusal?: string | undefined;
|
|
148
|
-
readonly cooling?: { readonly until?: number | undefined; readonly reason?: string | undefined } | undefined;
|
|
148
|
+
readonly cooling?: { readonly until?: number | undefined; readonly reason?: string | undefined; readonly verify?: string | undefined } | undefined;
|
|
149
149
|
readonly usage?: AccountUsage | undefined;
|
|
150
|
+
// How the account is named to a person (its label, a routed credential's address); a surface that quotes a figure
|
|
151
|
+
// says whose it is.
|
|
152
|
+
readonly label?: string | undefined;
|
|
150
153
|
}
|
|
151
154
|
|
|
152
155
|
// A spent allowance is over once a reading with room lands after it; a refused credential once any reading does (the
|
|
@@ -168,11 +171,22 @@ const answers = (refusal: ProviderRefusal, facts: ServiceFacts): boolean => {
|
|
|
168
171
|
* the account it named is disconnected, `answered` once that account (or, for a refusal naming none, any account) has a
|
|
169
172
|
* later reading that contradicts it, else `standing`. An empty list is one not loaded yet, so the refusal stands.
|
|
170
173
|
*/
|
|
171
|
-
export const refusalVerdict = (
|
|
174
|
+
export const refusalVerdict = (
|
|
175
|
+
refusal: ProviderRefusal,
|
|
176
|
+
accounts: readonly ServiceFacts[],
|
|
177
|
+
now: number = Date.now(),
|
|
178
|
+
): "standing" | "answered" | "gone" => {
|
|
172
179
|
const named = accounts.filter((facts) => facts.account === refusal.account);
|
|
173
180
|
if (refusal.account !== undefined && named.length === 0) {
|
|
174
181
|
return accounts.length === 0 ? "standing" : "gone";
|
|
175
182
|
}
|
|
183
|
+
// Where the provider named when to try again, and it is known which account it refused (it named one, or there is
|
|
184
|
+
// only one), its own words outrank a usage endpoint that kept showing room while it refused: over at that instant,
|
|
185
|
+
// and not before. A nameless refusal over several accounts is still answered by any of them reading room, since the
|
|
186
|
+
// translator benches the one it refused and routes on.
|
|
187
|
+
if (refusal.kind === "limit" && refusal.resetsAt !== undefined && (refusal.account !== undefined || accounts.length <= 1)) {
|
|
188
|
+
return refusal.resetsAt * 1000 <= now ? "answered" : "standing";
|
|
189
|
+
}
|
|
176
190
|
return (refusal.account === undefined ? accounts : named).some((facts) => answers(refusal, facts)) ? "answered" : "standing";
|
|
177
191
|
};
|
|
178
192
|
|
|
@@ -215,10 +229,12 @@ const refusedPool = (usage: AccountUsage | undefined, refusal: ProviderRefusal,
|
|
|
215
229
|
if (usage === undefined || binding === undefined) {
|
|
216
230
|
// No pool to pin: the refusal itself is the evidence, unless it was about a different model than this question.
|
|
217
231
|
const sameModel = refusal.model === undefined || model === undefined || refusal.model === model.id;
|
|
218
|
-
return sameModel ?
|
|
232
|
+
return sameModel ? spent(refusal.resetsAt) : headroomState(usage, model);
|
|
219
233
|
}
|
|
220
234
|
const pinned = usage.windows.map((window) => (window === binding ? { ...window, utilization: Math.max(window.utilization, SPENT_UTILIZATION) } : window));
|
|
221
|
-
|
|
235
|
+
const state = headroomState({ ...usage, windows: pinned }, model);
|
|
236
|
+
// The provider's own "try again at" is when it reopens, over whatever the polled pool last said.
|
|
237
|
+
return state.kind === "spent" && refusal.resetsAt !== undefined ? spent(refusal.resetsAt) : state;
|
|
222
238
|
};
|
|
223
239
|
|
|
224
240
|
/**
|
|
@@ -240,6 +256,11 @@ export const serviceState = (facts: ServiceFacts, refusal?: ProviderRefusal, mod
|
|
|
240
256
|
return { kind: "blocked", fix: "admin", reason: refusal.message };
|
|
241
257
|
}
|
|
242
258
|
const cooling = facts.cooling;
|
|
259
|
+
// A bench the provider wants a person for: the account's owner confirms it on the provider's page, and no wait or
|
|
260
|
+
// reconnect here lifts it. Outranks the retry instant, which is only when the proxy will ask again.
|
|
261
|
+
if (cooling?.verify !== undefined) {
|
|
262
|
+
return { kind: "blocked", fix: "verify", reason: cooling.reason ?? "the provider wants this account verified", url: cooling.verify };
|
|
263
|
+
}
|
|
243
264
|
// A bench with no instant is one no wait lifts (a Google account with no project): somebody has to connect again.
|
|
244
265
|
if (cooling !== undefined && cooling.until === undefined) {
|
|
245
266
|
return { kind: "blocked", fix: "reconnect", reason: cooling.reason ?? "benched by the translator" };
|
|
@@ -257,7 +278,7 @@ export const serviceStates = (
|
|
|
257
278
|
model?: ModelRef,
|
|
258
279
|
now: number = Date.now(),
|
|
259
280
|
): ReadonlyMap<string, AccountState> => {
|
|
260
|
-
const standing = refusal !== undefined && refusalVerdict(refusal, accounts) === "standing" ? refusal : undefined;
|
|
281
|
+
const standing = refusal !== undefined && refusalVerdict(refusal, accounts, now) === "standing" ? refusal : undefined;
|
|
261
282
|
return new Map(
|
|
262
283
|
accounts.map((facts) => [
|
|
263
284
|
facts.account,
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"GET /diff/raw",
|
|
49
49
|
"GET /workspace/raw",
|
|
50
50
|
"GET /workspace/media",
|
|
51
|
+
"GET /workspace/download",
|
|
51
52
|
"POST /workspace/upload",
|
|
52
53
|
"POST /workspace/upload-archive",
|
|
53
54
|
"GET /system/sync/ssh",
|
|
@@ -59,6 +60,7 @@
|
|
|
59
60
|
],
|
|
60
61
|
"lanes": [
|
|
61
62
|
{ "host": "sandbox-abcdef012345.sbx.example.test", "method": "GET", "path": "/workspace/media?path=a.mp4", "lane": "bulk" },
|
|
63
|
+
{ "host": "sandbox-abcdef012345.sbx.example.test", "method": "GET", "path": "/workspace/download?ticket=t", "lane": "bulk" },
|
|
62
64
|
{ "host": "sandbox-abcdef012345.sbx.example.test", "method": "POST", "path": "/workspace/upload?path=a", "lane": "bulk" },
|
|
63
65
|
{ "host": "sandbox-abcdef012345.sbx.example.test:443", "method": "GET", "path": "/system/runners/git/app/info/refs?service=git-upload-pack", "lane": "bulk" },
|
|
64
66
|
{ "host": "sandbox-abcdef012345.sbx.example.test", "method": "POST", "path": "/system/runners/git/app/git-receive-pack", "lane": "bulk" },
|
|
@@ -18,6 +18,8 @@ export const RAW_ROUTES = {
|
|
|
18
18
|
"GET /workspace/thumb": { guest: true },
|
|
19
19
|
// Fetched by a <video>/<audio> tag, which sends no header: its scoped ticket is checked in the handler.
|
|
20
20
|
"GET /workspace/media": { auth: "door", guest: true, lane: "bulk" },
|
|
21
|
+
// Navigated to for a ZIP of a selection, so no header either: the ticket it carries names the selection.
|
|
22
|
+
"GET /workspace/download": { auth: "door", guest: true, lane: "bulk" },
|
|
21
23
|
// An attachment is part of the message it rides with; any other target edits the shared tree.
|
|
22
24
|
"POST /workspace/upload": { floor: "writer", attachmentFloor: "collaborator", lane: "bulk" },
|
|
23
25
|
"POST /workspace/upload-diff": {},
|
|
@@ -91,12 +93,13 @@ export const RAW_ROUTES = {
|
|
|
91
93
|
"GET /wallet/status": { agent: true, control: "never" },
|
|
92
94
|
"POST /wallet/fetch": { agent: true, control: "never" },
|
|
93
95
|
"GET /wallet/history": { agent: true, control: "never" },
|
|
94
|
-
// The `agents` CLI's children: start, follow up, answer (never a consent card), list.
|
|
96
|
+
// The `agents` CLI's children: start, follow up, answer (never a consent card), cancel, list.
|
|
95
97
|
"POST /children/spawn": { agent: true, control: "never" },
|
|
96
98
|
"GET /children/providers": { agent: true, control: "never" },
|
|
97
99
|
"POST /children/wait": { agent: true, control: "never" },
|
|
98
100
|
"POST /children/send": { agent: true, control: "never" },
|
|
99
101
|
"POST /children/answer": { agent: true, control: "never" },
|
|
102
|
+
"POST /children/cancel": { agent: true, control: "never" },
|
|
100
103
|
"GET /children": { agent: true, control: "never" },
|
|
101
104
|
// The fleet reads, and the one write: words in front of another conversation, which a person can do by typing.
|
|
102
105
|
"GET /fleet": { agent: true },
|
|
@@ -1,5 +1,2 @@
|
|
|
1
1
|
// Correlates a browser call to the daemon request that served it; a token only, never a security boundary.
|
|
2
2
|
export const REQUEST_ID_HEADER = "x-intentic-request-id";
|
|
3
|
-
|
|
4
|
-
// Confirms daemon CORS accepts this header before sending; no evidence here means don't send, unlike supportsRoute.
|
|
5
|
-
export const REQUEST_ID_EVIDENCE_ROUTE = "logs.report";
|
package/src/schemas/agent.ts
CHANGED
|
@@ -131,7 +131,7 @@ export type ForkedFrom = z.infer<typeof ForkedFromSchema>;
|
|
|
131
131
|
const AgentTurnFieldsSchema = z.object({
|
|
132
132
|
prompt: z.string().describe("What to say to the agent. May be empty if you are only attaching files."),
|
|
133
133
|
errand: TurnErrandSchema.optional().describe(
|
|
134
|
-
"What the words are for, when the app or the sandbox composed them rather than a person typing them: a land conflict to resolve, a red
|
|
134
|
+
"What the words are for, when the app or the sandbox composed them rather than a person typing them: a land conflict to resolve, a red CI run to fix. Shown as the sandbox's words, not yours. Leave it out for your own words.",
|
|
135
135
|
),
|
|
136
136
|
// Minted by the sender so a send whose answer was lost can be sent again without being delivered twice.
|
|
137
137
|
messageId: z
|
package/src/schemas/agents.ts
CHANGED
|
@@ -88,11 +88,11 @@ export const UnfinishedWorkSchema = z.object({
|
|
|
88
88
|
check: z
|
|
89
89
|
.string()
|
|
90
90
|
.optional()
|
|
91
|
-
.describe("The end-of-turn check that was still failing when the turn ended, by name. No longer written: checks
|
|
91
|
+
.describe("The end-of-turn check that was still failing when the turn ended, by name. No longer written: nothing checks inside a turn."),
|
|
92
92
|
});
|
|
93
93
|
export type UnfinishedWork = z.infer<typeof UnfinishedWorkSchema>;
|
|
94
94
|
// What the last turn showed of its own work, read off its tool calls and never asked of the model: the record a card
|
|
95
|
-
// badges instead of sending the turn back to prove anything.
|
|
95
|
+
// badges instead of sending the turn back to prove anything. CI checks what the owner pushes (push-checks.ts).
|
|
96
96
|
export const TurnProofSchema = z.object({
|
|
97
97
|
at: z.number().describe("When the turn that left this ended, in milliseconds."),
|
|
98
98
|
verification: z
|
|
@@ -137,7 +137,7 @@ export const LandedMessageSchema = z.object({
|
|
|
137
137
|
.optional()
|
|
138
138
|
.describe("Why a test this change weakened is meant to be weaker, in the conversation's own words. Nearly always absent."),
|
|
139
139
|
// Present only when the conversation declared an exception a check should grant this change (`Allow: <check> — <reason>`);
|
|
140
|
-
// the
|
|
140
|
+
// the push reads it off the commit.
|
|
141
141
|
allows: z
|
|
142
142
|
.array(z.string())
|
|
143
143
|
.optional()
|
|
@@ -418,6 +418,13 @@ export const AgentSummarySchema = z.object({
|
|
|
418
418
|
.describe(
|
|
419
419
|
"When somebody last opened it, in milliseconds. Newer activity than this is what makes it unread. Kept by the sandbox rather than by a browser, so clearing site data or picking up a phone does not resurrect every badge.",
|
|
420
420
|
),
|
|
421
|
+
// Browser-side words the daemon cannot read, reported so the unattended archive sweep never files them away.
|
|
422
|
+
unsentAt: z
|
|
423
|
+
.number()
|
|
424
|
+
.optional()
|
|
425
|
+
.describe(
|
|
426
|
+
"Since when somebody's composer has held a message for it that they have not sent yet, in milliseconds. While set, the sandbox never archives it on its own for being idle.",
|
|
427
|
+
),
|
|
421
428
|
attention: AgentAttentionSchema.describe("Which kinds of waiting-for-you it is doing."),
|
|
422
429
|
// Only the causes that still hold, re-read live (conversations/land/standing.ts), never the stored report's own list: a
|
|
423
430
|
// blocker the user has since cleared is not something to offer them an action about.
|
|
@@ -448,7 +455,7 @@ export const AgentSummarySchema = z.object({
|
|
|
448
455
|
})
|
|
449
456
|
.optional()
|
|
450
457
|
.describe(
|
|
451
|
-
"Subagents
|
|
458
|
+
"Subagents this one started, in-process and spawned alike. Absent means it never has, which is most conversations. Their spend is their own and is not folded into this conversation's cost.",
|
|
452
459
|
),
|
|
453
460
|
// Cumulative output across every repo (base → branch tip), refreshed on each land; independent of what has actually
|
|
454
461
|
// landed.
|
|
@@ -567,10 +574,8 @@ export const AgentSummarySchema = z.object({
|
|
|
567
574
|
),
|
|
568
575
|
});
|
|
569
576
|
export type AgentSummary = z.infer<typeof AgentSummarySchema>;
|
|
570
|
-
// Whether a conversation runs again by itself: the daemon's own reading,
|
|
571
|
-
|
|
572
|
-
export const awaitsWake = (agent: { readonly awaitingWake?: boolean | undefined; readonly watches?: readonly unknown[] | undefined }): boolean =>
|
|
573
|
-
agent.awaitingWake ?? (agent.watches?.length ?? 0) > 0;
|
|
577
|
+
// Whether a conversation runs again by itself: the daemon's own reading, never guessed from its armed watches.
|
|
578
|
+
export const awaitsWake = (agent: { readonly awaitingWake?: boolean | undefined }): boolean => agent.awaitingWake === true;
|
|
574
579
|
// One armed watch as a card carries it; derived from AgentSummarySchema.watches so the two shapes can't drift apart.
|
|
575
580
|
export type AgentWatch = NonNullable<AgentSummary["watches"]>[number];
|
|
576
581
|
// One background job as a card carries it.
|
|
@@ -740,6 +745,14 @@ export const AgentAutoLandSchema = z.object({
|
|
|
740
745
|
"Whether its work merges automatically when a turn finishes. Null clears the override and goes back to following the sandbox-wide setting, so a conversation does not sit holding a frozen copy of a default it has quietly stopped following.",
|
|
741
746
|
),
|
|
742
747
|
});
|
|
748
|
+
// A composer's unsent words, reported by the editor that holds them: the words themselves stay in the browser.
|
|
749
|
+
export const AgentUnsentSchema = z.object({
|
|
750
|
+
id: z.string().min(1).describe("Which conversation."),
|
|
751
|
+
at: z
|
|
752
|
+
.number()
|
|
753
|
+
.nullable()
|
|
754
|
+
.describe("When the composer started holding the unsent message, in milliseconds. Null says it no longer holds one: it was sent or cleared."),
|
|
755
|
+
});
|
|
743
756
|
// Same `null`-clears-the-override shape as autoLand, for this conversation's own answer to one ending's question.
|
|
744
757
|
// One route rather than one per ending: they are the same decision asked about different walls, and three near-identical
|
|
745
758
|
// verbs is how the surfaces drifted apart in the first place.
|
|
@@ -28,8 +28,8 @@ import { ZoneSchema } from "../time/zone.js";
|
|
|
28
28
|
// reads these.
|
|
29
29
|
// turn.settled: fires once per isolated turn regardless of outcome, while the diff is still on screen
|
|
30
30
|
// agent.landed: fires only once work reaches the main tree, including a later manual Land
|
|
31
|
-
// deps.broken/deps.fixed:
|
|
32
|
-
//
|
|
31
|
+
// deps.broken/deps.fixed: retired with the check that ran after every land (2026-09-27). Nothing emits them any more;
|
|
32
|
+
// they stay so an automation written for them still reads, and it never fires. Main's red CI is the `ci` listener's.
|
|
33
33
|
export const WorkspaceEventKindSchema = z.enum(["turn.settled", "agent.landed", "deps.broken", "deps.fixed"]);
|
|
34
34
|
export type WorkspaceEventKind = z.infer<typeof WorkspaceEventKindSchema>;
|
|
35
35
|
// Payload of a workspace-triggered wake, delivered as JSON in $AUTOMATION_PAYLOAD and appended to the prompt.
|
package/src/schemas/ci.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// ci: pipeline runs on the workspace repos' github/gitlab remotes
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { AgentRunPickSchema } from "./agent.js";
|
|
4
|
+
import { RedDecisionSchema } from "./workspace/push-checks.js";
|
|
4
5
|
// The daemon maps each workspace repo to its CI project via a connected github/gitlab capability, registers a webhook
|
|
5
6
|
// to fire `ci` listener automations instantly, and serves the Pipelines rail from a webhook-freshened, poll-backfilled
|
|
6
7
|
// cache. `host` names which provider API serves a repo; the listener provider is always `ci`, since a trigger narrows
|
|
@@ -39,6 +40,9 @@ export const PipelineRunSchema = z.object({
|
|
|
39
40
|
.describe(
|
|
40
41
|
"What set it off, in the forge's own word rather than flattened into a shared vocabulary, because the forge's word is the precise one.",
|
|
41
42
|
),
|
|
43
|
+
// github's workflow name: several workflows run on one push there, and one passing says nothing of another. Absent on
|
|
44
|
+
// gitlab, where a commit's pipeline is the project's one.
|
|
45
|
+
workflow: z.string().optional().describe("Which workflow it is a run of, where a push starts several. Absent where a commit has one pipeline."),
|
|
42
46
|
branch: z.string().describe("Which branch."),
|
|
43
47
|
sha: z.string().describe("Which commit."),
|
|
44
48
|
status: PipelineStatusSchema.describe(
|
|
@@ -108,10 +112,31 @@ export type CiRepo = z.infer<typeof CiRepoSchema>;
|
|
|
108
112
|
// Fallback poll interval when a repo's webhook couldn't register; lives here since both the poller and the automation
|
|
109
113
|
// editor need it to tell the owner what a `hookWarning` actually costs (minutes' delay, not the feature).
|
|
110
114
|
export const CI_POLL_INTERVAL_MS = 2 * 60_000;
|
|
115
|
+
// Main's CI while it is red, as the daemon's one fix agent on it sees it (ci/main-fixer.ts): from the first job that
|
|
116
|
+
// failed until a later run of every workflow that failed on it passes, whatever else failed along the way.
|
|
117
|
+
export const CiMainRedSchema = z.object({
|
|
118
|
+
repo: z.string().describe("Which workspace repository."),
|
|
119
|
+
branch: z.string().describe("Which main-line branch."),
|
|
120
|
+
since: z.number().describe("When its first job failed, in milliseconds."),
|
|
121
|
+
runId: z.number().describe("The newest run that failed on it."),
|
|
122
|
+
jobs: z.array(z.string()).describe("The jobs failing on it now, by name: the newest red run's failures, and any that failed since."),
|
|
123
|
+
fixer: z
|
|
124
|
+
.string()
|
|
125
|
+
.optional()
|
|
126
|
+
.describe("The one conversation working on it, which every failure goes to until the branch is green. Absent while nobody is on it."),
|
|
127
|
+
decision: RedDecisionSchema.optional().describe(
|
|
128
|
+
"The latest thing decided about it: `fix-up` while the fix agent has it, `spent` once it waits for you (its turns are used up, or it stopped without a fix), `reported` when repairs are off.",
|
|
129
|
+
),
|
|
130
|
+
});
|
|
131
|
+
export type CiMainRed = z.infer<typeof CiMainRedSchema>;
|
|
111
132
|
export const CiRunsResponseSchema = z.object({
|
|
112
133
|
repos: z.array(CiRepoSchema).describe("Which workspace repositories are wired to a forge, and how each one's notifications are set up."),
|
|
113
134
|
// Newest first, across all mapped repos.
|
|
114
135
|
runs: z.array(PipelineRunSchema).describe("Runs across all of them, newest first."),
|
|
136
|
+
reds: z
|
|
137
|
+
.array(CiMainRedSchema)
|
|
138
|
+
.optional()
|
|
139
|
+
.describe("Every main-line branch red right now, with the fix agent on it. Absent from a daemon that keeps none."),
|
|
115
140
|
});
|
|
116
141
|
export type CiRunsResponse = z.infer<typeof CiRunsResponseSchema>;
|
|
117
142
|
// Re-resolves repo → project + token on every call, so a stale card can't act on a mapping the workspace no longer has.
|
package/src/schemas/devices.ts
CHANGED
|
@@ -505,7 +505,7 @@ export const DeviceSchema = z.object({
|
|
|
505
505
|
// The host connection's key, when this machine is also a connected device. Absent otherwise.
|
|
506
506
|
hostId: z.string().optional(),
|
|
507
507
|
// The card that connection is of, beside the key rather than parsed out of it: the machine's name, a label. Absent
|
|
508
|
-
// with `hostId
|
|
508
|
+
// with `hostId`.
|
|
509
509
|
card: z.string().optional(),
|
|
510
510
|
// The computer this row is an environment of, from whichever door has said (MachineIdSchema): what `machinesOf`
|
|
511
511
|
// groups by. Absent when no door has, which leaves the row a computer of its own.
|
|
@@ -563,20 +563,15 @@ export const deviceDistro = (device: Device): string | undefined => {
|
|
|
563
563
|
const byEnvironment = (a: Device, b: Device): number =>
|
|
564
564
|
Number(isWslDevice(a)) - Number(isWslDevice(b)) || (deviceDistro(a) ?? a.label).localeCompare(deviceDistro(b) ?? b.label);
|
|
565
565
|
|
|
566
|
-
// The card a device's door hangs off: one card is one computer, so two doors naming the same card are one machine
|
|
567
|
-
// whatever either has said about itself — which is the whole of what an environment that has never connected says.
|
|
568
|
-
// The row's own label, else its key read by the one parser there is (a row from a daemon older than `card`).
|
|
569
|
-
export const deviceCard = (device: Pick<Device, "card" | "hostId">): string | undefined =>
|
|
570
|
-
device.card ?? (device.hostId === undefined ? undefined : parseHostConnection(device.hostId).card);
|
|
571
|
-
|
|
572
566
|
// The computer a device is on, by whichever door said: its row, its connect-time facts, its report, its sync enrollment.
|
|
573
567
|
export const deviceMachineId = (device: Device): string | undefined =>
|
|
574
568
|
device.machineId ?? device.facts?.machineId ?? device.report?.machineId ?? device.sync?.machineId;
|
|
575
569
|
|
|
576
|
-
// What makes two devices one computer: a shared card
|
|
577
|
-
//
|
|
570
|
+
// What makes two devices one computer: a shared card (the row's own `card`, never parsed back out of its key: one card
|
|
571
|
+
// is one computer, whatever either door has said about itself), or a shared machine id. Nothing else: a hostname is
|
|
572
|
+
// shared by a PC and its WSL distros and by two unrelated machines with the same name, so it joined things by accident.
|
|
578
573
|
const tokensOf = (device: Device): string[] => {
|
|
579
|
-
const card =
|
|
574
|
+
const { card } = device;
|
|
580
575
|
const machine = deviceMachineId(device);
|
|
581
576
|
return [...(card === undefined ? [] : [`card:${card.toLowerCase()}`]), ...(machine === undefined ? [] : [`machine:${machine}`])];
|
|
582
577
|
};
|
|
@@ -621,8 +616,9 @@ const machineOf = (environments: readonly Device[]): Machine => {
|
|
|
621
616
|
if (first === undefined) {
|
|
622
617
|
return { key: "", label: "", environments };
|
|
623
618
|
}
|
|
624
|
-
const named =
|
|
625
|
-
|
|
619
|
+
const named =
|
|
620
|
+
environments.map((device) => device.card).find((key) => key !== undefined) ?? (rest.length === 0 ? undefined : deviceMachineId(first));
|
|
621
|
+
const label = first.label === first.hostId ? (first.card ?? first.label) : first.label;
|
|
626
622
|
return { key: named ?? first.key, label, environments };
|
|
627
623
|
};
|
|
628
624
|
|
package/src/schemas/personas.ts
CHANGED
|
@@ -134,15 +134,15 @@ export const TURN_BRIEFING_NOTES: readonly TurnBriefingNote[] = [
|
|
|
134
134
|
},
|
|
135
135
|
{
|
|
136
136
|
id: "delegation",
|
|
137
|
-
label: "Spawning
|
|
137
|
+
label: "Spawning subagents",
|
|
138
138
|
when: "First message, on a shell-only runtime, for a card that may delegate.",
|
|
139
139
|
cost: "The agent does the work itself rather than handing parts of it to other conversations.",
|
|
140
140
|
},
|
|
141
141
|
{
|
|
142
142
|
id: "checks",
|
|
143
143
|
label: "Checks after landing",
|
|
144
|
-
when: "First message and after a compaction
|
|
145
|
-
cost: "The agent does not know
|
|
144
|
+
when: "First message and after a compaction: that nothing checks its work when it finishes or after it lands, which checks are its to run, and that CI checks what the owner pushes.",
|
|
145
|
+
cost: "The agent does not know what checks its work, so it may run the whole repository's suite on a shared machine, or chase a failure main already has.",
|
|
146
146
|
},
|
|
147
147
|
{
|
|
148
148
|
id: "dependencies",
|
|
@@ -47,7 +47,7 @@ export const AccountUsageSchema = z.object({
|
|
|
47
47
|
export type AccountUsage = z.infer<typeof AccountUsageSchema>;
|
|
48
48
|
// Who has to act for a blocked account, which is the only thing a surface chooses its instruction by: the reason is the
|
|
49
49
|
// words for a person, never a key.
|
|
50
|
-
export const AccountFixSchema = z.enum(["reconnect", "admin", "wait"]);
|
|
50
|
+
export const AccountFixSchema = z.enum(["reconnect", "admin", "verify", "wait"]);
|
|
51
51
|
export type AccountFix = z.infer<typeof AccountFixSchema>;
|
|
52
52
|
// Whether an account can serve a turn, decided once by the daemon (models/plan-pools.ts `serviceState`) from every fact
|
|
53
53
|
// it holds about it: a revoked sign-in, a lost seat, a translator bench, a refusal still standing, and its plan limits.
|
|
@@ -69,10 +69,11 @@ export const AccountStateSchema = z.discriminatedUnion("kind", [
|
|
|
69
69
|
z.object({
|
|
70
70
|
kind: z.literal("blocked"),
|
|
71
71
|
fix: AccountFixSchema.describe(
|
|
72
|
-
"Who can make it serve again: `reconnect` (sign in again on this sandbox), `admin` (an organisation admin hands the seat back), or `wait` (it lifts by itself, at `until` where known).",
|
|
72
|
+
"Who can make it serve again: `reconnect` (sign in again on this sandbox), `admin` (an organisation admin hands the seat back), `verify` (the account's owner confirms it on the provider's page, at `url`), or `wait` (it lifts by itself, at `until` where known).",
|
|
73
73
|
),
|
|
74
74
|
reason: z.string().describe("Why, in words a person can act on: the provider's own sentence where it gave one."),
|
|
75
75
|
until: z.number().optional().describe("When waiting lifts it, in epoch seconds, for `wait` only."),
|
|
76
|
+
url: z.string().optional().describe("The provider's page where the account's owner lifts it, for `verify` only."),
|
|
76
77
|
}),
|
|
77
78
|
z.object({ kind: z.literal("unknown").describe("Nothing blocks it and nothing has been measured: usable, never read as room.") }),
|
|
78
79
|
]);
|
|
@@ -122,6 +123,14 @@ export const ProviderRefusalSchema = z.object({
|
|
|
122
123
|
// Lets a `limit` refusal be read against the pool that model spends (`UsageWindow.gates`), not the account's
|
|
123
124
|
// fullest pool.
|
|
124
125
|
model: z.string().optional().describe("Which model the refused turn was on, where that is known."),
|
|
126
|
+
// The provider's own "try again at …", which a polled reading cannot contradict: a usage endpoint can keep showing
|
|
127
|
+
// room while the plan refuses every turn.
|
|
128
|
+
resetsAt: z
|
|
129
|
+
.number()
|
|
130
|
+
.optional()
|
|
131
|
+
.describe(
|
|
132
|
+
"When the provider said to try again, in epoch seconds, for a spent allowance where it named one. Until then the refusal stands whatever a reading says; after it, it is over.",
|
|
133
|
+
),
|
|
125
134
|
});
|
|
126
135
|
export type ProviderRefusal = z.infer<typeof ProviderRefusalSchema>;
|
|
127
136
|
export const ProviderRefusalsSchema = z.object({
|
|
@@ -151,6 +160,9 @@ export const TranslatorAccountSchema = z.object({
|
|
|
151
160
|
// Why, in the words of what is missing: the proxy's own sentence where it gave one short enough to print,
|
|
152
161
|
// else this sandbox's. Never a pasted upstream body.
|
|
153
162
|
reason: z.string().optional(),
|
|
163
|
+
// The provider's page where the account's owner confirms it (Google's VALIDATION_REQUIRED). Present means
|
|
164
|
+
// a person has to act there: the proxy's retry fails until they do.
|
|
165
|
+
verify: z.string().optional(),
|
|
154
166
|
})
|
|
155
167
|
.optional(),
|
|
156
168
|
state: AccountStateSchema.optional().describe(
|
|
@@ -205,7 +217,7 @@ export const AgentReplySchema = z.discriminatedUnion("kind", [
|
|
|
205
217
|
feedback: z.string().optional().describe("Why not, which goes back to the model as the reason."),
|
|
206
218
|
// Whole, not a patch: effort or an account named for one model means nothing on another.
|
|
207
219
|
child: ChildRunSchema.optional().describe(
|
|
208
|
-
"For a request to start a
|
|
220
|
+
"For a request to start a subagent: what to start it on instead of what the agent asked for. It replaces the whole run (model, account, effort and the rest), not only the fields it names. Ignored with a no, and on any other request.",
|
|
209
221
|
),
|
|
210
222
|
}),
|
|
211
223
|
z.object({
|
|
@@ -400,6 +412,12 @@ export const SwitchAccountSchema = z.object({
|
|
|
400
412
|
.describe(
|
|
401
413
|
"Keep the provider session across the move (the model keeps everything, and re-reads all of it once on the other account) rather than opening a fresh one seeded from the record.",
|
|
402
414
|
),
|
|
415
|
+
run: z
|
|
416
|
+
.boolean()
|
|
417
|
+
.optional()
|
|
418
|
+
.describe(
|
|
419
|
+
"Also run a turn that a spent allowance, a stop or a refusal is holding, at once on the new account. Leave it out to only move the conversation: a held turn stays held until something asks for it.",
|
|
420
|
+
),
|
|
403
421
|
});
|
|
404
422
|
export type SwitchAccount = z.infer<typeof SwitchAccountSchema>;
|
|
405
423
|
export const AccountSwitchedSchema = z.object({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { dropAll, fold, isJsonObject, mapValue, retype } from "../documents/conversions.js";
|
|
2
|
+
import { at, drop, dropAll, fold, isJsonObject, mapValue, retype } from "../documents/conversions.js";
|
|
3
3
|
import type { KeepWarmSettings, RuleSchema } from "./settings.js";
|
|
4
4
|
|
|
5
5
|
// The conversions the settings shape has had, oldest first: what brings any earlier release's settings to
|
|
@@ -105,4 +105,6 @@ export const SETTINGS_HISTORY = [
|
|
|
105
105
|
"drops rules at the retired turn.ending moment (and the verify-ui-edits built-in), which run nothing",
|
|
106
106
|
),
|
|
107
107
|
keepWarmObject,
|
|
108
|
+
// Where the check after landing ran (2026-09-27): nothing checks work after it lands any more, CI does.
|
|
109
|
+
at("offload", drop("landCheck")),
|
|
108
110
|
] as const;
|