@intentic/sandbox-contract 1.315.0 → 1.317.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/agent.contract.d.ts +562 -0
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.d.ts +402 -0
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +3 -3
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/capabilities.contract.d.ts +16 -0
- package/dist/contracts/capabilities.contract.d.ts.map +1 -1
- package/dist/contracts/capabilities.contract.js +10 -1
- package/dist/contracts/capabilities.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/device.contract.d.ts +1 -0
- package/dist/contracts/device.contract.d.ts.map +1 -1
- package/dist/contracts/needs.contract.d.ts +532 -0
- package/dist/contracts/needs.contract.d.ts.map +1 -0
- package/dist/contracts/needs.contract.js +80 -0
- package/dist/contracts/needs.contract.js.map +1 -0
- package/dist/contracts/runner.contract.d.ts +294 -122
- package/dist/contracts/runner.contract.d.ts.map +1 -1
- package/dist/contracts/secrets.contract.d.ts +16 -0
- package/dist/contracts/secrets.contract.d.ts.map +1 -1
- package/dist/contracts/secrets.contract.js +12 -2
- package/dist/contracts/secrets.contract.js.map +1 -1
- package/dist/contracts/sessions.contract.d.ts +79 -0
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.d.ts +2 -3
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.d.ts +163 -0
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.js +11 -0
- package/dist/contracts/system.contract.js.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 +309 -0
- package/dist/events/agent-events.d.ts.map +1 -1
- package/dist/events/agent-events.js +2 -0
- 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 +2 -1
- 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/need-wake.d.ts +17 -0
- package/dist/events/need-wake.d.ts.map +1 -0
- package/dist/events/need-wake.js +34 -0
- package/dist/events/need-wake.js.map +1 -0
- package/dist/events/system-events.d.ts +40 -0
- package/dist/events/system-events.d.ts.map +1 -1
- package/dist/events/transcript.d.ts +484 -0
- package/dist/events/transcript.d.ts.map +1 -1
- package/dist/events/transcript.js +9 -0
- package/dist/events/transcript.js.map +1 -1
- package/dist/index.d.ts +1956 -284
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -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 +2 -1
- package/dist/models/plan-pools.d.ts.map +1 -1
- package/dist/models/plan-pools.js +8 -4
- package/dist/models/plan-pools.js.map +1 -1
- package/dist/protocol/raw-routes.d.ts +9 -5
- package/dist/protocol/raw-routes.d.ts.map +1 -1
- package/dist/protocol/raw-routes.js +2 -1
- 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 +58 -1
- package/dist/schemas/agents.d.ts.map +1 -1
- package/dist/schemas/agents.js +11 -2
- package/dist/schemas/agents.js.map +1 -1
- package/dist/schemas/automations.d.ts +19 -0
- package/dist/schemas/automations.d.ts.map +1 -1
- package/dist/schemas/capabilities.d.ts +19 -0
- package/dist/schemas/capabilities.d.ts.map +1 -1
- package/dist/schemas/capabilities.js +16 -0
- package/dist/schemas/capabilities.js.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 +68 -1
- package/dist/schemas/devices.d.ts.map +1 -1
- package/dist/schemas/devices.js +10 -4
- package/dist/schemas/devices.js.map +1 -1
- package/dist/schemas/hosts.d.ts +2 -0
- package/dist/schemas/hosts.d.ts.map +1 -1
- package/dist/schemas/hosts.js +2 -1
- package/dist/schemas/hosts.js.map +1 -1
- package/dist/schemas/needs.d.ts +661 -0
- package/dist/schemas/needs.d.ts.map +1 -0
- package/dist/schemas/needs.js +196 -0
- package/dist/schemas/needs.js.map +1 -0
- package/dist/schemas/personas.js +2 -2
- package/dist/schemas/personas.js.map +1 -1
- package/dist/schemas/providers/plan-limits.d.ts +2 -0
- package/dist/schemas/providers/plan-limits.d.ts.map +1 -1
- package/dist/schemas/providers/plan-limits.js +4 -0
- package/dist/schemas/providers/plan-limits.js.map +1 -1
- package/dist/schemas/secrets.d.ts +18 -0
- package/dist/schemas/secrets.d.ts.map +1 -1
- package/dist/schemas/secrets.js +20 -0
- package/dist/schemas/secrets.js.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 +2 -4
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +10 -13
- package/dist/schemas/settings.js.map +1 -1
- package/dist/schemas/state-plan.d.ts +1 -0
- package/dist/schemas/state-plan.d.ts.map +1 -1
- package/dist/schemas/state-plan.js +3 -1
- package/dist/schemas/state-plan.js.map +1 -1
- package/dist/schemas/system.d.ts +20 -0
- package/dist/schemas/system.d.ts.map +1 -1
- package/dist/schemas/system.js +7 -0
- package/dist/schemas/system.js.map +1 -1
- package/dist/schemas/updates.d.ts +39 -0
- package/dist/schemas/updates.d.ts.map +1 -0
- package/dist/schemas/updates.js +31 -0
- package/dist/schemas/updates.js.map +1 -0
- 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 +16 -4
- package/dist/state/definition.d.ts.map +1 -1
- package/dist/state/history-state.d.ts.map +1 -1
- package/dist/state/history-state.js +3 -0
- package/dist/state/history-state.js.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 +5 -11
- package/dist/state/workspace-state.d.ts.map +1 -1
- package/dist/state/workspace-state.js +2 -13
- package/dist/state/workspace-state.js.map +1 -1
- package/dist/text/transcript-fold.d.ts.map +1 -1
- package/dist/text/transcript-fold.js +3 -0
- package/dist/text/transcript-fold.js.map +1 -1
- package/package.json +5 -5
- package/src/contracts/agents.contract.ts +3 -3
- package/src/contracts/capabilities.contract.ts +14 -1
- package/src/contracts/needs.contract.ts +103 -0
- package/src/contracts/secrets.contract.ts +17 -3
- package/src/contracts/system.contract.ts +14 -0
- package/src/contracts/workspace.contract.ts +35 -21
- package/src/events/agent-events.ts +4 -0
- package/src/events/agent-words.ts +2 -1
- package/src/events/errands.test.ts +4 -4
- package/src/events/land-breakage.ts +5 -9
- package/src/events/need-wake.test.ts +55 -0
- package/src/events/need-wake.ts +71 -0
- package/src/events/transcript.ts +16 -0
- package/src/events/verify-nudge.ts +3 -3
- package/src/index.ts +8 -2
- package/src/models/agent-runtimes.ts +5 -4
- package/src/models/plan-pools.test.ts +15 -0
- package/src/models/plan-pools.ts +20 -4
- package/src/protocol/ingress-contract.fixture.json +2 -0
- package/src/protocol/raw-routes.ts +5 -3
- package/src/protocol/request-id.ts +0 -3
- package/src/schemas/agent.ts +1 -1
- package/src/schemas/agents.ts +19 -8
- package/src/schemas/automations.ts +2 -2
- package/src/schemas/capabilities.ts +28 -0
- package/src/schemas/ci.ts +25 -0
- package/src/schemas/devices.ts +25 -12
- package/src/schemas/hosts.ts +4 -1
- package/src/schemas/needs.ts +292 -0
- package/src/schemas/personas.ts +2 -2
- package/src/schemas/providers/plan-limits.ts +8 -0
- package/src/schemas/secrets.ts +23 -0
- package/src/schemas/settings-history.ts +3 -1
- package/src/schemas/settings.test.ts +4 -3
- package/src/schemas/settings.ts +25 -31
- package/src/schemas/speaker.ts +4 -4
- package/src/schemas/state-plan.ts +8 -1
- package/src/schemas/system.ts +15 -0
- package/src/schemas/updates.ts +56 -0
- 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/history-state.ts +5 -0
- 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 +5 -13
- package/src/text/transcript-fold.ts +4 -0
- 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
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { unspokenPromptRow } from "./agent-words.js";
|
|
2
|
+
import { needRowText, needWakeOf, needWakePrompt, needWakeRow, type NeedWakeFields, type NeedWakeOutcome } from "./need-wake.js";
|
|
3
|
+
|
|
4
|
+
const fields = (over: Partial<NeedWakeFields> = {}): NeedWakeFields => ({
|
|
5
|
+
outcome: "met",
|
|
6
|
+
id: "need-7k2q",
|
|
7
|
+
title: "Connect GitHub",
|
|
8
|
+
why: "to open the pull request for the fix",
|
|
9
|
+
result: 'GitHub is connected as "github".',
|
|
10
|
+
use: ["In this turn, prefix a command with GH_TOKEN={{secret:github/token}}."],
|
|
11
|
+
...over,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const OUTCOMES: NeedWakeOutcome[] = ["met", "declined"];
|
|
15
|
+
|
|
16
|
+
describe("need wake", () => {
|
|
17
|
+
// The composer and the parser are the same piece of knowledge; this holds them together when either is reworded.
|
|
18
|
+
it.each(OUTCOMES)("round-trips a %s wake back to its fields", (outcome) => {
|
|
19
|
+
const prompt = needWakePrompt(fields({ outcome }));
|
|
20
|
+
expect(needWakeOf(prompt)).toEqual({ outcome, title: "Connect GitHub", id: "need-7k2q", sent: prompt });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("tells a met need to carry on and a declined one not to wait", () => {
|
|
24
|
+
expect(needWakePrompt(fields()).split("\n").at(-1)).toBe("Continue the task that needed it.");
|
|
25
|
+
expect(needWakePrompt(fields({ outcome: "declined", use: [] }))).not.toContain("Continue the task");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("leaves the agent's reason out when it gave none, rather than printing an empty label", () => {
|
|
29
|
+
expect(needWakePrompt(fields({ why: undefined }))).not.toContain("You asked because:");
|
|
30
|
+
expect(needWakePrompt(fields())).toContain("You asked because: to open the pull request for the fix");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("is not a wake once its labelled lines are gone", () => {
|
|
34
|
+
const prompt = needWakePrompt(fields());
|
|
35
|
+
expect(needWakeOf(prompt.replace("Need id: need-7k2q\n", ""))).toBeUndefined();
|
|
36
|
+
expect(needWakeOf(prompt.replace("Need: Connect GitHub\n", ""))).toBeUndefined();
|
|
37
|
+
expect(needWakeOf("please connect github")).toBeUndefined();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("becomes a notice row that says what happened, and every transcript reader finds it", () => {
|
|
41
|
+
const prompt = needWakePrompt(fields());
|
|
42
|
+
const row = needWakeRow(prompt);
|
|
43
|
+
expect(row).toEqual({
|
|
44
|
+
role: "notice",
|
|
45
|
+
text: "Connect GitHub: done, and the agent carries on.",
|
|
46
|
+
needWake: { outcome: "met", title: "Connect GitHub", id: "need-7k2q", sent: prompt },
|
|
47
|
+
});
|
|
48
|
+
expect(unspokenPromptRow(prompt)).toEqual(row);
|
|
49
|
+
expect(needWakeRow(needWakePrompt(fields({ outcome: "declined" })))?.text).toBe("Connect GitHub: declined, and the agent was told.");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("words a raised need's row for a reader that draws no card", () => {
|
|
53
|
+
expect(needRowText({ title: "The OPENAI_API_KEY secret" })).toBe("Needs a person: The OPENAI_API_KEY secret");
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { TranscriptNeedWake, TranscriptRow } from "./transcript.js";
|
|
2
|
+
|
|
3
|
+
// What a need says when its answer reaches a conversation, and how that reads to a person. Delivered as an ordinary
|
|
4
|
+
// turn prompt (a steer into a live turn, or a turn of its own), so composing it and recognising it stay one piece of
|
|
5
|
+
// knowledge, exactly as a condition watch's wake does (watch-wake.ts): the daemon's record, a steered live turn and a
|
|
6
|
+
// provider's session store all turn this prompt back into the same row.
|
|
7
|
+
|
|
8
|
+
export type NeedWakeOutcome = TranscriptNeedWake["outcome"];
|
|
9
|
+
|
|
10
|
+
// Per-outcome opening sentence, which is also what the parser anchors on: keep each unique and stable across releases,
|
|
11
|
+
// or a reworded opening un-recognises wakes already in a record and they read as the user's own words.
|
|
12
|
+
const OPENINGS: Record<NeedWakeOutcome, string> = {
|
|
13
|
+
met: "Need met: a person gave you what you asked for.",
|
|
14
|
+
declined: "Need declined: a person said no to what you asked for. Carry on without it, and say plainly what it would have enabled.",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Labels the prompt writes and the parser reads back. `Need` and `Need id` are load-bearing on both sides.
|
|
18
|
+
const NEED = "Need: ";
|
|
19
|
+
const NEED_ID = "Need id: ";
|
|
20
|
+
const ASKED = "You asked because: ";
|
|
21
|
+
|
|
22
|
+
export interface NeedWakeFields {
|
|
23
|
+
readonly outcome: NeedWakeOutcome;
|
|
24
|
+
readonly id: string;
|
|
25
|
+
readonly title: string;
|
|
26
|
+
readonly why: string | undefined;
|
|
27
|
+
// The daemon's sentence on how it ended ("Connected as "github".", "Stored under OPENAI_API_KEY.").
|
|
28
|
+
readonly result: string;
|
|
29
|
+
// What the agent can do with it now, and what arrives on its next turn; empty for a decline.
|
|
30
|
+
readonly use: readonly string[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const needWakePrompt = (fields: NeedWakeFields): string =>
|
|
34
|
+
[
|
|
35
|
+
OPENINGS[fields.outcome],
|
|
36
|
+
`${NEED}${fields.title}`,
|
|
37
|
+
`${NEED_ID}${fields.id}`,
|
|
38
|
+
...(fields.why === undefined || fields.why === "" ? [] : [`${ASKED}${fields.why}`]),
|
|
39
|
+
fields.result,
|
|
40
|
+
...fields.use,
|
|
41
|
+
...(fields.outcome === "met" ? ["Continue the task that needed it."] : []),
|
|
42
|
+
].join("\n");
|
|
43
|
+
|
|
44
|
+
const headline = (outcome: NeedWakeOutcome, title: string): string =>
|
|
45
|
+
outcome === "met" ? `${title}: done, and the agent carries on.` : `${title}: declined, and the agent was told.`;
|
|
46
|
+
|
|
47
|
+
const valueOn = (lines: readonly string[], label: string): string | undefined => lines.find((line) => line.startsWith(label))?.slice(label.length);
|
|
48
|
+
|
|
49
|
+
// The words a raised need's row carries for a reader that draws no card (search, an export, an older client); the card
|
|
50
|
+
// itself is drawn from the need.
|
|
51
|
+
export const needRowText = (need: { readonly title: string }): string => `Needs a person: ${need.title}`;
|
|
52
|
+
|
|
53
|
+
// Which need wake a stored prompt is, if any; undefined for every prompt that isn't one, so any reader can ask without
|
|
54
|
+
// checking first. One that opens as a wake but lost its labelled lines is not one.
|
|
55
|
+
export const needWakeOf = (prompt: string): TranscriptNeedWake | undefined => {
|
|
56
|
+
const outcome = (Object.keys(OPENINGS) as NeedWakeOutcome[]).find((key) => prompt.startsWith(OPENINGS[key]));
|
|
57
|
+
if (outcome === undefined) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const lines = prompt.split("\n");
|
|
61
|
+
const title = valueOn(lines, NEED);
|
|
62
|
+
const id = valueOn(lines, NEED_ID);
|
|
63
|
+
return title === undefined || id === undefined ? undefined : { outcome, title, id, sent: prompt };
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// The row a wake becomes: a notice, since a need being answered happened to the conversation rather than being said by
|
|
67
|
+
// either side. Undefined when the prompt is not a need wake at all.
|
|
68
|
+
export const needWakeRow = (prompt: string): TranscriptRow | undefined => {
|
|
69
|
+
const wake = needWakeOf(prompt);
|
|
70
|
+
return wake === undefined ? undefined : { role: "notice", text: headline(wake.outcome, wake.title), needWake: wake };
|
|
71
|
+
};
|
package/src/events/transcript.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { AgentHarnessSchema, AgentProviderSchema } from "../schemas/agent.js";
|
|
|
3
3
|
import { ShareDetailSchema } from "../schemas/share.js";
|
|
4
4
|
import { TurnErrandSchema, TurnSpeakerSchema } from "../schemas/speaker.js";
|
|
5
5
|
import { SubagentKindSchema, SubagentStatusSchema, SubagentVerificationSchema } from "../schemas/terminal.js";
|
|
6
|
+
import { NeedSchema } from "../schemas/needs.js";
|
|
6
7
|
import { RetryLadderSchema } from "../schemas/turn-break.js";
|
|
7
8
|
import type { ToolCallContent, ToolCallLocation, ToolCallStatus, ToolKind} from "./requests.js";
|
|
8
9
|
import { browserHelpRequest, capabilityOfferRequest, CapabilityOutcomeSchema, credentialOfferRequest, CredentialReceiptSchema, paymentOfferRequest, PaymentReceiptSchema, PermissionAskSchema, permissionRequest, planRequest, questionRequest, terminalHelpRequest, TodoItemSchema, ToolCallContentSchema, ToolCallLocationSchema, ToolCallStatusSchema, ToolKindSchema } from "./requests.js";
|
|
@@ -155,6 +156,15 @@ export const TranscriptWatchWakeSchema = z.object({
|
|
|
155
156
|
});
|
|
156
157
|
export type TranscriptWatchWake = z.infer<typeof TranscriptWatchWakeSchema>;
|
|
157
158
|
|
|
159
|
+
// A need's answer that reached this conversation as a prompt (need-wake.ts); the row keeps the prompt and names the need.
|
|
160
|
+
export const TranscriptNeedWakeSchema = z.object({
|
|
161
|
+
outcome: z.enum(["met", "declined"]).describe("How the need ended: a person gave it, or said no."),
|
|
162
|
+
title: z.string().describe("The need, as its card leads with it."),
|
|
163
|
+
id: z.string().describe("The need's handle, which its live card is keyed by."),
|
|
164
|
+
sent: z.string().describe("The whole prompt the model received, disclosed under the row."),
|
|
165
|
+
});
|
|
166
|
+
export type TranscriptNeedWake = z.infer<typeof TranscriptNeedWakeSchema>;
|
|
167
|
+
|
|
158
168
|
// Another agent's words that reached this conversation as a prompt; the row keeps the prompt and names the sender.
|
|
159
169
|
export const TranscriptAgentWordsSchema = z.object({
|
|
160
170
|
kind: z.enum(["peer", "child"]).describe("Who sent it: another conversation in the workspace, or a subagent this one started."),
|
|
@@ -299,6 +309,12 @@ export const TranscriptRowSchema = z.object({
|
|
|
299
309
|
capabilityOffer: TranscriptCapabilityOfferSchema.optional().describe("The capability setup this row asked for, the decision, and the outcome."),
|
|
300
310
|
paymentOffer: TranscriptPaymentOfferSchema.optional().describe("The payment this row asked for, the decision, and the receipt."),
|
|
301
311
|
watchWake: TranscriptWatchWakeSchema.optional().describe("The condition watch that woke this conversation, and the prompt it was woken with."),
|
|
312
|
+
// A need is not a parked card: it holds no turn open, outlives the one that raised it, and its live state is the
|
|
313
|
+
// needs store's, keyed by `need.id`. What rides here is the need as it was raised, for a reader with no store.
|
|
314
|
+
need: NeedSchema.optional().describe(
|
|
315
|
+
"Something the agent asked a person for, as it was when raised. Its live state (answered, met) is read by its id, since it outlives the turn.",
|
|
316
|
+
),
|
|
317
|
+
needWake: TranscriptNeedWakeSchema.optional().describe("The answered need that reached this conversation, and the prompt it came as."),
|
|
302
318
|
agentWords: TranscriptAgentWordsSchema.optional().describe("Another agent's words that reached this conversation, whose they are, and the prompt they came as."),
|
|
303
319
|
backgroundJob: TranscriptBackgroundJobSchema.optional().describe("The background job this row marks the start of."),
|
|
304
320
|
credentialOffer: TranscriptCredentialOfferSchema.optional().describe(
|
|
@@ -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
|
@@ -32,6 +32,7 @@ import { netdiskContract } from "./contracts/netdisk.contract.js";
|
|
|
32
32
|
import { providersContract } from "./contracts/providers.contract.js";
|
|
33
33
|
import { pushContract } from "./contracts/push.contract.js";
|
|
34
34
|
import { safetyContract } from "./contracts/safety.contract.js";
|
|
35
|
+
import { needsContract } from "./contracts/needs.contract.js";
|
|
35
36
|
import { secretsContract } from "./contracts/secrets.contract.js";
|
|
36
37
|
import { sessionsContract } from "./contracts/sessions.contract.js";
|
|
37
38
|
import { settingsContract } from "./contracts/settings.contract.js";
|
|
@@ -83,7 +84,7 @@ export { intenticContract } from "./contracts/intentic.contract.js";
|
|
|
83
84
|
export { inventoryContract } from "./contracts/inventory.contract.js";
|
|
84
85
|
export { issuesContract } from "./contracts/issues.contract.js";
|
|
85
86
|
export { logsContract } from "./contracts/logs.contract.js";
|
|
86
|
-
export {
|
|
87
|
+
export { REQUEST_ID_HEADER } from "./protocol/request-id.js";
|
|
87
88
|
export * from "./protocol/edge-verdict.js";
|
|
88
89
|
export { loopsContract } from "./contracts/loops.contract.js";
|
|
89
90
|
export { panelsContract } from "./contracts/panels.contract.js";
|
|
@@ -92,6 +93,7 @@ export { publicContract } from "./contracts/public.contract.js";
|
|
|
92
93
|
export { providersContract, type RunnableProviders, RunnableProvidersSchema } from "./contracts/providers.contract.js";
|
|
93
94
|
export { pushContract } from "./contracts/push.contract.js";
|
|
94
95
|
export { safetyContract } from "./contracts/safety.contract.js";
|
|
96
|
+
export { needsContract } from "./contracts/needs.contract.js";
|
|
95
97
|
export { secretsContract } from "./contracts/secrets.contract.js";
|
|
96
98
|
export { sessionsContract } from "./contracts/sessions.contract.js";
|
|
97
99
|
export { settingsContract } from "./contracts/settings.contract.js";
|
|
@@ -115,6 +117,7 @@ export * from "./events/land-breakage.js";
|
|
|
115
117
|
export * from "./events/land-conflict.js";
|
|
116
118
|
export * from "./events/verify-nudge.js";
|
|
117
119
|
export * from "./events/errands.js";
|
|
120
|
+
export * from "./events/need-wake.js";
|
|
118
121
|
export * from "./events/watch-wake.js";
|
|
119
122
|
export * from "./policy/request-status.js";
|
|
120
123
|
export * from "./events/child-run.js";
|
|
@@ -214,6 +217,7 @@ export * from "./schemas/providers/provider-subscriptions.js";
|
|
|
214
217
|
export * from "./schemas/public.js";
|
|
215
218
|
export * from "./schemas/push.js";
|
|
216
219
|
export * from "./schemas/git/remote-refs.js";
|
|
220
|
+
export * from "./schemas/needs.js";
|
|
217
221
|
export * from "./schemas/secrets.js";
|
|
218
222
|
export * from "./schemas/netdisk.js";
|
|
219
223
|
export * from "./schemas/sessions.js";
|
|
@@ -224,6 +228,7 @@ export * from "./schemas/shared.js";
|
|
|
224
228
|
export * from "./schemas/system-prompt.js";
|
|
225
229
|
export * from "./schemas/system.js";
|
|
226
230
|
export * from "./schemas/state-plan.js";
|
|
231
|
+
export * from "./schemas/updates.js";
|
|
227
232
|
export * from "./schemas/terminal.js";
|
|
228
233
|
export * from "./schemas/turn-break.js";
|
|
229
234
|
export * from "./schemas/keep-warm.js";
|
|
@@ -234,7 +239,7 @@ export * from "./schemas/webext.js";
|
|
|
234
239
|
export * from "./schemas/workflows.js";
|
|
235
240
|
export * from "./schemas/workspace/workspace-repos.js";
|
|
236
241
|
export * from "./schemas/workspace/workspace-search.js";
|
|
237
|
-
export * from "./schemas/workspace/
|
|
242
|
+
export * from "./schemas/workspace/push-checks.js";
|
|
238
243
|
export * from "./schemas/workspace/workspace-setup.js";
|
|
239
244
|
export * from "./schemas/workspace/workspace-tree.js";
|
|
240
245
|
export * from "./state/arrival.js";
|
|
@@ -285,6 +290,7 @@ export const sandboxContract = {
|
|
|
285
290
|
public: publicContract,
|
|
286
291
|
providers: providersContract,
|
|
287
292
|
push: pushContract,
|
|
293
|
+
needs: needsContract,
|
|
288
294
|
secrets: secretsContract,
|
|
289
295
|
system: systemContract,
|
|
290
296
|
translator: translatorContract,
|
|
@@ -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,
|
|
@@ -209,6 +209,21 @@ test("a refusal stands until a later reading contradicts it, and never past its
|
|
|
209
209
|
expect(refusalVerdict(refusal({ kind: "limit", account: "a" }), [])).toBe("standing");
|
|
210
210
|
});
|
|
211
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
|
+
|
|
212
227
|
test("a provider's accounts are judged together: a standing refusal lands only on the account it names", () => {
|
|
213
228
|
const room = at(500, window({ kind: "five_hour", utilization: 40 }));
|
|
214
229
|
const states = serviceStates([{ account: "a", usage: room }, { account: "b", usage: room }], refusal({ kind: "entitlement", account: "a" }));
|
package/src/models/plan-pools.ts
CHANGED
|
@@ -147,6 +147,9 @@ export interface ServiceFacts {
|
|
|
147
147
|
readonly seatRefusal?: string | undefined;
|
|
148
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
|
/**
|
|
@@ -262,7 +278,7 @@ export const serviceStates = (
|
|
|
262
278
|
model?: ModelRef,
|
|
263
279
|
now: number = Date.now(),
|
|
264
280
|
): ReadonlyMap<string, AccountState> => {
|
|
265
|
-
const standing = refusal !== undefined && refusalVerdict(refusal, accounts) === "standing" ? refusal : undefined;
|
|
281
|
+
const standing = refusal !== undefined && refusalVerdict(refusal, accounts, now) === "standing" ? refusal : undefined;
|
|
266
282
|
return new Map(
|
|
267
283
|
accounts.map((facts) => [
|
|
268
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": {},
|
|
@@ -81,9 +83,8 @@ export const RAW_ROUTES = {
|
|
|
81
83
|
"GET /extensions/{id}/bundle": { lane: "bulk" },
|
|
82
84
|
// An extension backend's own namespace, proxied verbatim.
|
|
83
85
|
"ALL /x/*": {},
|
|
84
|
-
// The `capabilities` CLI
|
|
86
|
+
// The `capabilities` CLI's discovery by name; its ask is the needs door (contracts/needs.contract.ts).
|
|
85
87
|
"GET /capabilities/connectable": { floor: "maintainer", agent: true, control: "never" },
|
|
86
|
-
"POST /capabilities/ask": { floor: "maintainer", agent: true, control: "never" },
|
|
87
88
|
// The `sandboxes` CLI; every create parks on a card in the owner's chat first.
|
|
88
89
|
"GET /sandboxes": { agent: true },
|
|
89
90
|
"POST /sandboxes": { agent: true },
|
|
@@ -91,12 +92,13 @@ export const RAW_ROUTES = {
|
|
|
91
92
|
"GET /wallet/status": { agent: true, control: "never" },
|
|
92
93
|
"POST /wallet/fetch": { agent: true, control: "never" },
|
|
93
94
|
"GET /wallet/history": { agent: true, control: "never" },
|
|
94
|
-
// The `agents` CLI's children: start, follow up, answer (never a consent card), list.
|
|
95
|
+
// The `agents` CLI's children: start, follow up, answer (never a consent card), cancel, list.
|
|
95
96
|
"POST /children/spawn": { agent: true, control: "never" },
|
|
96
97
|
"GET /children/providers": { agent: true, control: "never" },
|
|
97
98
|
"POST /children/wait": { agent: true, control: "never" },
|
|
98
99
|
"POST /children/send": { agent: true, control: "never" },
|
|
99
100
|
"POST /children/answer": { agent: true, control: "never" },
|
|
101
|
+
"POST /children/cancel": { agent: true, control: "never" },
|
|
100
102
|
"GET /children": { agent: true, control: "never" },
|
|
101
103
|
// The fleet reads, and the one write: words in front of another conversation, which a person can do by typing.
|
|
102
104
|
"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
|
@@ -4,6 +4,7 @@ import { AgentHarnessSchema, AgentOriginSchema, AgentProviderSchema, Conversatio
|
|
|
4
4
|
import { LoopStateSchema } from "./loops.js";
|
|
5
5
|
import { LimitPolicySchema, RetryPolicySchema, TurnBreakPolicySchema, TurnBreakSchema } from "./turn-break.js";
|
|
6
6
|
import { KeepWarmSchema } from "./keep-warm.js";
|
|
7
|
+
import { AgentNeedSchema } from "./needs.js";
|
|
7
8
|
import { EMOJI_MAX_LENGTH, isSingleEmoji } from "../text/emoji.js";
|
|
8
9
|
// A fleet agent is any conversation with a registry entry, keyed by conversationId. Isolated ones own a git worktree
|
|
9
10
|
// (branch agent/<id>); workspace conversations have none, but both share one status/activity/cost lifecycle.
|
|
@@ -56,7 +57,7 @@ export const AgentAttentionSchema = z.object({
|
|
|
56
57
|
plan: z.boolean().describe("It has proposed a plan and is waiting for a yes."),
|
|
57
58
|
question: z.boolean().describe("It has asked you something."),
|
|
58
59
|
permission: z.boolean().describe("It wants to use a tool it needs permission for."),
|
|
59
|
-
// A missing capability (
|
|
60
|
+
// A missing capability on a card from before needs (docs/architecture/needs.md); lets the lane say "setup needed" rather than a generic
|
|
60
61
|
// pause.
|
|
61
62
|
capability: z.boolean().describe("It needs something connected that is not connected yet."),
|
|
62
63
|
// A gated credential parked on a named person's click; the one pause the board's reader may not be able to clear
|
|
@@ -65,6 +66,11 @@ export const AgentAttentionSchema = z.object({
|
|
|
65
66
|
.boolean()
|
|
66
67
|
.describe("It is waiting for a named person to release a credential. The one pause that may not be yours to clear, whatever your role."),
|
|
67
68
|
conflict: z.boolean().describe("Its work cannot be merged without somebody resolving a clash."),
|
|
69
|
+
// A need outlives its turn (docs/architecture/needs.md), so this can stand on a card that is not running at all.
|
|
70
|
+
need: z
|
|
71
|
+
.boolean()
|
|
72
|
+
.optional()
|
|
73
|
+
.describe("It asked a person for something it still needs: a connection, a secret, wider reach, a tool. Absent from a daemon older than needs."),
|
|
68
74
|
});
|
|
69
75
|
export type AgentAttention = z.infer<typeof AgentAttentionSchema>;
|
|
70
76
|
// What the last turn left open, measured at the moment it ended, unlike AgentAttentionSchema's live parked waits.
|
|
@@ -88,11 +94,11 @@ export const UnfinishedWorkSchema = z.object({
|
|
|
88
94
|
check: z
|
|
89
95
|
.string()
|
|
90
96
|
.optional()
|
|
91
|
-
.describe("The end-of-turn check that was still failing when the turn ended, by name. No longer written: checks
|
|
97
|
+
.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
98
|
});
|
|
93
99
|
export type UnfinishedWork = z.infer<typeof UnfinishedWorkSchema>;
|
|
94
100
|
// 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.
|
|
101
|
+
// badges instead of sending the turn back to prove anything. CI checks what the owner pushes (push-checks.ts).
|
|
96
102
|
export const TurnProofSchema = z.object({
|
|
97
103
|
at: z.number().describe("When the turn that left this ended, in milliseconds."),
|
|
98
104
|
verification: z
|
|
@@ -137,7 +143,7 @@ export const LandedMessageSchema = z.object({
|
|
|
137
143
|
.optional()
|
|
138
144
|
.describe("Why a test this change weakened is meant to be weaker, in the conversation's own words. Nearly always absent."),
|
|
139
145
|
// Present only when the conversation declared an exception a check should grant this change (`Allow: <check> — <reason>`);
|
|
140
|
-
// the
|
|
146
|
+
// the push reads it off the commit.
|
|
141
147
|
allows: z
|
|
142
148
|
.array(z.string())
|
|
143
149
|
.optional()
|
|
@@ -527,6 +533,13 @@ export const AgentSummarySchema = z.object({
|
|
|
527
533
|
.describe(
|
|
528
534
|
"Outside conditions this conversation is parked on, each of which will wake it. Absent means none, which is nearly every conversation: an armed watch is why a finished-looking agent starts working by itself, and why a hosted machine will not go idle.",
|
|
529
535
|
),
|
|
536
|
+
// Kept in the needs store, so unlike `jobs` this survives a restart and stands while the conversation is idle.
|
|
537
|
+
needs: z
|
|
538
|
+
.array(AgentNeedSchema)
|
|
539
|
+
.optional()
|
|
540
|
+
.describe(
|
|
541
|
+
"What it is waiting on people for and has not got yet, oldest first. Absent means nothing: an open need is why an idle-looking agent still needs you.",
|
|
542
|
+
),
|
|
530
543
|
// In memory only: a daemon restart leaves transcript rows naming jobs this list no longer carries.
|
|
531
544
|
jobs: z
|
|
532
545
|
.array(
|
|
@@ -574,10 +587,8 @@ export const AgentSummarySchema = z.object({
|
|
|
574
587
|
),
|
|
575
588
|
});
|
|
576
589
|
export type AgentSummary = z.infer<typeof AgentSummarySchema>;
|
|
577
|
-
// Whether a conversation runs again by itself: the daemon's own reading,
|
|
578
|
-
|
|
579
|
-
export const awaitsWake = (agent: { readonly awaitingWake?: boolean | undefined; readonly watches?: readonly unknown[] | undefined }): boolean =>
|
|
580
|
-
agent.awaitingWake ?? (agent.watches?.length ?? 0) > 0;
|
|
590
|
+
// Whether a conversation runs again by itself: the daemon's own reading, never guessed from its armed watches.
|
|
591
|
+
export const awaitsWake = (agent: { readonly awaitingWake?: boolean | undefined }): boolean => agent.awaitingWake === true;
|
|
581
592
|
// One armed watch as a card carries it; derived from AgentSummarySchema.watches so the two shapes can't drift apart.
|
|
582
593
|
export type AgentWatch = NonNullable<AgentSummary["watches"]>[number];
|
|
583
594
|
// One background job as a card carries it.
|
|
@@ -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.
|
|
@@ -104,6 +104,15 @@ export const SshConfigSchema = z.discriminatedUnion("auth", [
|
|
|
104
104
|
user: z.string().min(1).describe("Which user to connect as."),
|
|
105
105
|
privateKey: z.string().min(1).describe("The private key, whole. Stored with tight permissions and never echoed back."),
|
|
106
106
|
}),
|
|
107
|
+
// The sandbox made this key itself (capabilities.sshKey), so its private half never crossed the wire; stored and
|
|
108
|
+
// written exactly like a pasted one.
|
|
109
|
+
z.object({
|
|
110
|
+
auth: z.literal("generated").describe("Sign in with a key the sandbox generated. Its private half never left the sandbox."),
|
|
111
|
+
host: z.string().min(1).describe("The machine's address."),
|
|
112
|
+
port: z.coerce.number().default(22).describe("Which port it listens on."),
|
|
113
|
+
user: z.string().min(1).describe("Which user to connect as."),
|
|
114
|
+
privateKey: z.string().min(1).describe("The private half of the generated key. Stored with tight permissions and never echoed back."),
|
|
115
|
+
}),
|
|
107
116
|
z.object({
|
|
108
117
|
auth: z.literal("password").describe("Sign in with a password."),
|
|
109
118
|
host: z.string().min(1).describe("The machine's address."),
|
|
@@ -112,6 +121,14 @@ export const SshConfigSchema = z.discriminatedUnion("auth", [
|
|
|
112
121
|
password: z.string().min(1).describe("The password. Stored, never echoed back."),
|
|
113
122
|
}),
|
|
114
123
|
]);
|
|
124
|
+
// What a form sends where a generated key's private half goes: the token capabilities.sshKey answered with, which the
|
|
125
|
+
// add swaps for the key the daemon kept. VAULTED's sibling (policy/capability-secrets.ts) for a value not stored yet; a
|
|
126
|
+
// marker with nothing held behind it is refused, never written.
|
|
127
|
+
const STASHED_PREFIX = "__intentic_stashed__:";
|
|
128
|
+
export const stashedMarker = (token: string): string => `${STASHED_PREFIX}${token}`;
|
|
129
|
+
// The token a marker names; undefined for anything else (a real key, VAULTED, a blank).
|
|
130
|
+
export const stashedToken = (value: unknown): string | undefined =>
|
|
131
|
+
typeof value === "string" && value.startsWith(STASHED_PREFIX) && value.length > STASHED_PREFIX.length ? value.slice(STASHED_PREFIX.length) : undefined;
|
|
115
132
|
// What is optional about the in-sandbox Docker Engine: `gpu` is an IMAGE option (rides the Dockerfile overlay, needs a
|
|
116
133
|
// rebuild), everything else is an ENGINE option (rewrites daemon.json, restarts dockerd, no rebuild but stops running
|
|
117
134
|
// containers). Flat strings, not nested booleans, to match the manifest's own two-state convention.
|
|
@@ -460,3 +477,14 @@ export const CapabilityProbeSchema = z.object({
|
|
|
460
477
|
who: z.string().optional().describe("Who the service said the credential belongs to, when it said."),
|
|
461
478
|
});
|
|
462
479
|
export type CapabilityProbe = z.infer<typeof CapabilityProbeSchema>;
|
|
480
|
+
// POST /capabilities/ssh-key: a key pair made inside the sandbox for a connection being set up. The public half is the
|
|
481
|
+
// whole answer; the private half stays in the daemon until an add sends `stashedMarker(token)` where it goes.
|
|
482
|
+
export const SshKeySchema = z.object({
|
|
483
|
+
publicKey: z.string().describe("The public half, as the one line a server's authorized_keys holds: `ssh-ed25519 AAAA… intentic-<sandbox>`."),
|
|
484
|
+
token: z
|
|
485
|
+
.string()
|
|
486
|
+
.describe(
|
|
487
|
+
"Stands for the private half, which never leaves the sandbox. Sent wrapped as a marker in the private key's place, it installs that key once, and it lapses after thirty minutes.",
|
|
488
|
+
),
|
|
489
|
+
});
|
|
490
|
+
export type SshKey = z.infer<typeof SshKeySchema>;
|
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.
|