@intentic/sandbox-contract 1.317.0 → 1.319.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 +16 -6
- package/dist/chores/chores.js +1 -1
- package/dist/chores/chores.js.map +1 -1
- package/dist/chores/probes.js +1 -1
- package/dist/chores/probes.js.map +1 -1
- package/dist/contracts/accounts.contract.d.ts +75 -0
- package/dist/contracts/accounts.contract.d.ts.map +1 -1
- package/dist/contracts/accounts.contract.js +10 -1
- package/dist/contracts/accounts.contract.js.map +1 -1
- package/dist/contracts/agent.contract.d.ts +62 -1
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.d.ts +830 -93
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +12 -12
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/ci.contract.d.ts +22 -4
- package/dist/contracts/ci.contract.d.ts.map +1 -1
- package/dist/contracts/device.contract.d.ts +38 -0
- package/dist/contracts/device.contract.d.ts.map +1 -1
- package/dist/contracts/device.contract.js +2 -0
- package/dist/contracts/device.contract.js.map +1 -1
- package/dist/contracts/endpoints.contract.d.ts +23 -0
- package/dist/contracts/endpoints.contract.d.ts.map +1 -1
- package/dist/contracts/endpoints.contract.js +6 -1
- package/dist/contracts/endpoints.contract.js.map +1 -1
- package/dist/contracts/extensions.contract.d.ts +12 -0
- package/dist/contracts/extensions.contract.d.ts.map +1 -1
- package/dist/contracts/needs.contract.d.ts +2 -0
- package/dist/contracts/needs.contract.d.ts.map +1 -1
- package/dist/contracts/providers.contract.d.ts +4 -0
- package/dist/contracts/providers.contract.d.ts.map +1 -1
- package/dist/contracts/runner.contract.d.ts +143 -124
- package/dist/contracts/runner.contract.d.ts.map +1 -1
- package/dist/contracts/secrets.contract.d.ts +37 -0
- package/dist/contracts/secrets.contract.d.ts.map +1 -1
- package/dist/contracts/secrets.contract.js +20 -1
- package/dist/contracts/secrets.contract.js.map +1 -1
- package/dist/contracts/sessions.contract.d.ts +4 -0
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.d.ts +31 -4
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.js +20 -1
- package/dist/contracts/settings.contract.js.map +1 -1
- package/dist/contracts/system.contract.d.ts +102 -464
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.js +2 -12
- package/dist/contracts/system.contract.js.map +1 -1
- package/dist/contracts/translator.contract.d.ts +7 -0
- package/dist/contracts/translator.contract.d.ts.map +1 -1
- package/dist/contracts/translator.contract.js.map +1 -1
- package/dist/contracts/webext.contract.d.ts +38 -0
- package/dist/contracts/webext.contract.d.ts.map +1 -1
- package/dist/contracts/webext.contract.js +3 -0
- package/dist/contracts/webext.contract.js.map +1 -1
- package/dist/contracts/workspace.contract.d.ts +1 -133
- package/dist/contracts/workspace.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.js +1 -37
- package/dist/contracts/workspace.contract.js.map +1 -1
- package/dist/events/agent-events.d.ts +21 -0
- package/dist/events/agent-events.d.ts.map +1 -1
- package/dist/events/agent-events.js +11 -0
- package/dist/events/agent-events.js.map +1 -1
- package/dist/events/agent-words.d.ts +1 -0
- package/dist/events/agent-words.d.ts.map +1 -1
- package/dist/events/agent-words.js +1 -1
- package/dist/events/agent-words.js.map +1 -1
- package/dist/events/land-breakage.d.ts.map +1 -1
- package/dist/events/land-breakage.js.map +1 -1
- package/dist/events/resume.d.ts +6 -1
- package/dist/events/resume.d.ts.map +1 -1
- package/dist/events/resume.js +17 -10
- package/dist/events/resume.js.map +1 -1
- package/dist/events/sandbox-notice.d.ts +183 -0
- package/dist/events/sandbox-notice.d.ts.map +1 -0
- package/dist/events/sandbox-notice.js +56 -0
- package/dist/events/sandbox-notice.js.map +1 -0
- package/dist/events/system-events.d.ts +50 -18
- package/dist/events/system-events.d.ts.map +1 -1
- package/dist/events/system-events.js +4 -2
- package/dist/events/system-events.js.map +1 -1
- package/dist/events/transcript.d.ts +24 -0
- package/dist/events/transcript.d.ts.map +1 -1
- package/dist/events/transcript.js +3 -1
- package/dist/events/transcript.js.map +1 -1
- package/dist/front/generated/browser-wire.d.ts +13 -0
- package/dist/front/generated/browser-wire.d.ts.map +1 -1
- package/dist/front/generated/wire.d.ts +4 -0
- package/dist/front/generated/wire.d.ts.map +1 -1
- package/dist/ids/conversation-ids.d.ts +1 -3
- package/dist/ids/conversation-ids.d.ts.map +1 -1
- package/dist/ids/conversation-ids.js +1 -3
- package/dist/ids/conversation-ids.js.map +1 -1
- package/dist/ids/project-dir.d.ts +6 -0
- package/dist/ids/project-dir.d.ts.map +1 -0
- package/dist/ids/project-dir.js +43 -0
- package/dist/ids/project-dir.js.map +1 -0
- package/dist/index.d.ts +11543 -11039
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -4
- package/dist/index.js.map +1 -1
- package/dist/models/agent-runtimes.d.ts.map +1 -1
- package/dist/models/agent-runtimes.js +2 -2
- package/dist/models/agent-runtimes.js.map +1 -1
- package/dist/models/model-roles.d.ts +1 -9
- package/dist/models/model-roles.d.ts.map +1 -1
- package/dist/models/model-roles.js +1 -9
- package/dist/models/model-roles.js.map +1 -1
- package/dist/policy/command-classes.d.ts.map +1 -1
- package/dist/policy/command-classes.js +52 -3
- package/dist/policy/command-classes.js.map +1 -1
- package/dist/policy/safety-policy.d.ts +7 -1
- package/dist/policy/safety-policy.d.ts.map +1 -1
- package/dist/policy/safety-policy.js +2 -0
- package/dist/policy/safety-policy.js.map +1 -1
- package/dist/policy/secret-hosts.d.ts +13 -0
- package/dist/policy/secret-hosts.d.ts.map +1 -0
- package/dist/policy/secret-hosts.js +41 -0
- package/dist/policy/secret-hosts.js.map +1 -0
- package/dist/policy/turned-away.d.ts.map +1 -1
- package/dist/policy/turned-away.js +1 -0
- package/dist/policy/turned-away.js.map +1 -1
- package/dist/protocol/raw-routes.d.ts +12 -0
- package/dist/protocol/raw-routes.d.ts.map +1 -1
- package/dist/protocol/raw-routes.js +5 -0
- package/dist/protocol/raw-routes.js.map +1 -1
- package/dist/protocol/routes.d.ts +2 -6
- package/dist/protocol/routes.d.ts.map +1 -1
- package/dist/protocol/routes.js +2 -3
- package/dist/protocol/routes.js.map +1 -1
- package/dist/protocol/stream-of.d.ts +6 -0
- package/dist/protocol/stream-of.d.ts.map +1 -0
- package/dist/protocol/stream-of.js +4 -0
- package/dist/protocol/stream-of.js.map +1 -0
- package/dist/protocol/vitals.d.ts +8 -0
- package/dist/protocol/vitals.d.ts.map +1 -0
- package/dist/protocol/vitals.js +16 -0
- package/dist/protocol/vitals.js.map +1 -0
- package/dist/schemas/activity.js +1 -1
- package/dist/schemas/activity.js.map +1 -1
- package/dist/schemas/agent.d.ts +25 -2
- package/dist/schemas/agent.d.ts.map +1 -1
- package/dist/schemas/agent.js +14 -3
- package/dist/schemas/agent.js.map +1 -1
- package/dist/schemas/agents.d.ts +67 -0
- package/dist/schemas/agents.d.ts.map +1 -1
- package/dist/schemas/agents.js +36 -0
- package/dist/schemas/agents.js.map +1 -1
- package/dist/schemas/automations.d.ts +20 -0
- package/dist/schemas/automations.d.ts.map +1 -1
- package/dist/schemas/capabilities.d.ts +2 -0
- package/dist/schemas/capabilities.d.ts.map +1 -1
- package/dist/schemas/capabilities.js +34 -4
- package/dist/schemas/capabilities.js.map +1 -1
- package/dist/schemas/ci.d.ts +69 -9
- package/dist/schemas/ci.d.ts.map +1 -1
- package/dist/schemas/ci.js +29 -8
- package/dist/schemas/ci.js.map +1 -1
- package/dist/schemas/context-trim.d.ts +5 -1
- package/dist/schemas/context-trim.d.ts.map +1 -1
- package/dist/schemas/context-trim.js +8 -4
- package/dist/schemas/context-trim.js.map +1 -1
- package/dist/schemas/devices.d.ts +23 -0
- package/dist/schemas/devices.d.ts.map +1 -1
- package/dist/schemas/devices.js +16 -1
- package/dist/schemas/devices.js.map +1 -1
- package/dist/schemas/environment.d.ts +48 -0
- package/dist/schemas/environment.d.ts.map +1 -1
- package/dist/schemas/environment.js +15 -0
- package/dist/schemas/environment.js.map +1 -1
- package/dist/schemas/extension-updates.d.ts +24 -0
- package/dist/schemas/extension-updates.d.ts.map +1 -1
- package/dist/schemas/git/git.d.ts +13 -0
- package/dist/schemas/git/git.d.ts.map +1 -1
- package/dist/schemas/git/git.js +11 -1
- package/dist/schemas/git/git.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/keep-warm.d.ts +4 -0
- package/dist/schemas/keep-warm.d.ts.map +1 -1
- package/dist/schemas/keep-warm.js +15 -5
- package/dist/schemas/keep-warm.js.map +1 -1
- package/dist/schemas/loopback-catch.d.ts +33 -0
- package/dist/schemas/loopback-catch.d.ts.map +1 -0
- package/dist/schemas/loopback-catch.js +20 -0
- package/dist/schemas/loopback-catch.js.map +1 -0
- package/dist/schemas/maintenance.js +1 -1
- package/dist/schemas/maintenance.js.map +1 -1
- package/dist/schemas/needs.d.ts +3 -0
- package/dist/schemas/needs.d.ts.map +1 -1
- package/dist/schemas/needs.js +10 -3
- package/dist/schemas/needs.js.map +1 -1
- package/dist/schemas/personas.d.ts.map +1 -1
- package/dist/schemas/personas.js +6 -3
- package/dist/schemas/personas.js.map +1 -1
- package/dist/schemas/providers/provider-oauth.d.ts +87 -0
- package/dist/schemas/providers/provider-oauth.d.ts.map +1 -1
- package/dist/schemas/providers/provider-oauth.js +20 -0
- package/dist/schemas/providers/provider-oauth.js.map +1 -1
- package/dist/schemas/secrets.d.ts +66 -0
- package/dist/schemas/secrets.d.ts.map +1 -1
- package/dist/schemas/secrets.js +50 -0
- package/dist/schemas/secrets.js.map +1 -1
- package/dist/schemas/settings-history.d.ts +2 -2
- package/dist/schemas/settings-history.d.ts.map +1 -1
- package/dist/schemas/settings-history.js +2 -0
- package/dist/schemas/settings-history.js.map +1 -1
- package/dist/schemas/settings.d.ts +42 -4
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +26 -8
- package/dist/schemas/settings.js.map +1 -1
- package/dist/schemas/terminal.d.ts +2 -3
- package/dist/schemas/terminal.d.ts.map +1 -1
- package/dist/schemas/terminal.js +12 -4
- package/dist/schemas/terminal.js.map +1 -1
- package/dist/schemas/webext.d.ts +3 -0
- package/dist/schemas/webext.d.ts.map +1 -1
- package/dist/schemas/webext.js +1 -0
- package/dist/schemas/webext.js.map +1 -1
- package/dist/schemas/workflows.js +3 -3
- package/dist/schemas/workflows.js.map +1 -1
- package/dist/schemas/workspace/workspace-tree.d.ts +2 -56
- package/dist/schemas/workspace/workspace-tree.d.ts.map +1 -1
- package/dist/schemas/workspace/workspace-tree.js +7 -15
- package/dist/schemas/workspace/workspace-tree.js.map +1 -1
- package/dist/state/definition.d.ts +12 -8
- 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 +7 -0
- package/dist/state/history-state.js.map +1 -1
- package/dist/state/runtime-state.d.ts +0 -3
- package/dist/state/runtime-state.d.ts.map +1 -1
- package/dist/state/runtime-state.js +0 -1
- package/dist/state/runtime-state.js.map +1 -1
- package/dist/state/workspace-state.d.ts +0 -5
- package/dist/state/workspace-state.d.ts.map +1 -1
- package/dist/state/workspace-state.js +0 -6
- package/dist/state/workspace-state.js.map +1 -1
- package/dist/text/documents.d.ts +3 -0
- package/dist/text/documents.d.ts.map +1 -1
- package/dist/text/documents.js +14 -0
- package/dist/text/documents.js.map +1 -1
- package/dist/text/transcript-fold.d.ts +4 -3
- package/dist/text/transcript-fold.d.ts.map +1 -1
- package/dist/text/transcript-fold.js +137 -31
- package/dist/text/transcript-fold.js.map +1 -1
- package/dist/webext/index.d.ts +1 -0
- package/dist/webext/index.d.ts.map +1 -1
- package/dist/webext/index.js +1 -0
- package/dist/webext/index.js.map +1 -1
- package/package.json +5 -5
- package/src/chores/chores.ts +2 -2
- package/src/chores/probes.ts +1 -1
- package/src/contracts/accounts.contract.ts +12 -0
- package/src/contracts/agents.contract.ts +13 -13
- package/src/contracts/device.contract.ts +5 -0
- package/src/contracts/endpoints.contract.ts +20 -4
- package/src/contracts/secrets.contract.ts +28 -0
- package/src/contracts/settings.contract.ts +29 -0
- package/src/contracts/system.contract.ts +5 -14
- package/src/contracts/translator.contract.ts +2 -1
- package/src/contracts/webext.contract.ts +7 -1
- package/src/contracts/workspace.contract.ts +0 -49
- package/src/events/agent-events.ts +18 -1
- package/src/events/agent-words.ts +4 -1
- package/src/events/land-breakage.ts +3 -4
- package/src/events/resume.test.ts +13 -2
- package/src/events/resume.ts +25 -12
- package/src/events/sandbox-notice.ts +92 -0
- package/src/events/system-events.ts +12 -5
- package/src/events/transcript.ts +7 -1
- package/src/events/verify-nudge.ts +3 -3
- package/src/front/generated/browser-wire.json +95 -0
- package/src/front/generated/browser-wire.ts +36 -0
- package/src/front/generated/front-wire.json +25 -0
- package/src/front/generated/wire.ts +2 -2
- package/src/front/wire-manifests.test.ts +7 -0
- package/src/ids/conversation-ids.test.ts +2 -26
- package/src/ids/conversation-ids.ts +6 -12
- package/src/ids/project-dir.fixture.json +65 -0
- package/src/ids/project-dir.test.ts +60 -0
- package/src/ids/project-dir.ts +70 -0
- package/src/index.ts +13 -4
- package/src/models/agent-catalog.test.ts +7 -1
- package/src/models/agent-runtimes.ts +7 -5
- package/src/models/model-roles.ts +1 -9
- package/src/policy/command-classes.test.ts +39 -0
- package/src/policy/command-classes.ts +70 -3
- package/src/policy/safety-policy.ts +10 -0
- package/src/policy/secret-hosts.test.ts +89 -0
- package/src/policy/secret-hosts.ts +73 -0
- package/src/policy/turned-away.ts +1 -0
- package/src/protocol/raw-routes.ts +8 -1
- package/src/protocol/routes.test.ts +6 -6
- package/src/protocol/routes.ts +2 -9
- package/src/protocol/stream-of.ts +13 -0
- package/src/protocol/vitals.test.ts +46 -0
- package/src/protocol/vitals.ts +38 -0
- package/src/schemas/activity.ts +1 -1
- package/src/schemas/agent.ts +20 -3
- package/src/schemas/agents.ts +58 -4
- package/src/schemas/automations.ts +2 -2
- package/src/schemas/capabilities.ts +44 -7
- package/src/schemas/ci.ts +56 -11
- package/src/schemas/context-trim.ts +11 -6
- package/src/schemas/devices.ts +38 -2
- package/src/schemas/environment.ts +39 -0
- package/src/schemas/git/git.ts +20 -2
- package/src/schemas/hosts.ts +4 -1
- package/src/schemas/keep-warm.ts +19 -6
- package/src/schemas/loopback-catch.ts +45 -0
- package/src/schemas/maintenance.ts +1 -1
- package/src/schemas/needs.ts +12 -3
- package/src/schemas/personas.ts +8 -3
- package/src/schemas/providers/provider-oauth.ts +35 -0
- package/src/schemas/secrets.ts +70 -0
- package/src/schemas/settings-history.ts +3 -0
- package/src/schemas/settings.test.ts +19 -1
- package/src/schemas/settings.ts +59 -16
- package/src/schemas/speaker.ts +5 -5
- package/src/schemas/terminal.ts +19 -6
- package/src/schemas/webext.ts +3 -0
- package/src/schemas/workflows.ts +5 -5
- package/src/schemas/workspace/workspace-tree.ts +12 -20
- package/src/state/fix-attempt-plan.test.ts +1 -1
- package/src/state/fix-stance.ts +1 -1
- package/src/state/history-state.ts +14 -0
- package/src/state/runtime-state.ts +0 -3
- package/src/state/workspace-state.ts +0 -6
- package/src/text/documents.test.ts +25 -1
- package/src/text/documents.ts +24 -0
- package/src/text/title.ts +1 -1
- package/src/text/transcript-fold.test.ts +218 -25
- package/src/text/transcript-fold.ts +166 -45
- package/src/webext/index.ts +1 -0
- package/dist/schemas/workspace/push-checks.d.ts +0 -222
- package/dist/schemas/workspace/push-checks.d.ts.map +0 -1
- package/dist/schemas/workspace/push-checks.js +0 -97
- package/dist/schemas/workspace/push-checks.js.map +0 -1
- package/dist/state/red-streak.d.ts +0 -7
- package/dist/state/red-streak.d.ts.map +0 -1
- package/dist/state/red-streak.js +0 -11
- package/dist/state/red-streak.js.map +0 -1
- package/src/schemas/workspace/push-checks.test.ts +0 -28
- package/src/schemas/workspace/push-checks.ts +0 -155
- package/src/state/red-streak.test.ts +0 -28
- package/src/state/red-streak.ts +0 -28
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
// The sandbox's own notice rows (a land, a hold, a renewal, a stop), as a code and the facts they were worded from. The
|
|
4
|
+
// row's `text` stays the English sentence, which older apps, stored transcripts and agents reading their own history
|
|
5
|
+
// go on reading; `noticeCode` beside it is what lets an app say the same thing in the reader's language and words.
|
|
6
|
+
|
|
7
|
+
// On the wire a code is any string and its facts a flat record: a transcript page carrying a code newer than its reader
|
|
8
|
+
// still parses, and that reader draws `text`. The codes this build writes, with their facts typed, are
|
|
9
|
+
// SandboxNoticeSchema below.
|
|
10
|
+
export const NoticeCodeSchema = z.object({
|
|
11
|
+
code: z.string().describe("Which of the sandbox's notices this row is, by name. A reader that does not know the name shows the row's text."),
|
|
12
|
+
params: z
|
|
13
|
+
.record(z.string(), z.union([z.string(), z.number(), z.boolean()]))
|
|
14
|
+
.optional()
|
|
15
|
+
.describe("The facts the notice was worded from, by name: counts, names, and the provider's own sentence where the notice quotes one."),
|
|
16
|
+
});
|
|
17
|
+
export type NoticeCode = z.infer<typeof NoticeCodeSchema>;
|
|
18
|
+
|
|
19
|
+
const count = z.number().int().nonnegative();
|
|
20
|
+
|
|
21
|
+
// A failure's own sentence, which the notice's clause follows: the provider's words, or the sandbox's under `error`, its
|
|
22
|
+
// failure code, for a reader that words a failure it knows itself.
|
|
23
|
+
const failure = {
|
|
24
|
+
message: z.string(),
|
|
25
|
+
error: z.string().optional(),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// The re-runs a resumed turn's row can name (events/resume.ts); a reason a reader does not know draws the row's text.
|
|
29
|
+
const ResumeNoticeReasonSchema = z.enum(["auth", "outage", "restart", "stopped", "limit", "switched", "carried", "refused", "door", "overflow"]);
|
|
30
|
+
export type ResumeNoticeReason = z.infer<typeof ResumeNoticeReasonSchema>;
|
|
31
|
+
|
|
32
|
+
// Every code this build writes, one per sentence shape, each with the facts that shape needs.
|
|
33
|
+
export const SandboxNoticeSchema = z.discriminatedUnion("code", [
|
|
34
|
+
z.object({ code: z.literal("compacted") }),
|
|
35
|
+
z.object({ code: z.literal("stopped") }),
|
|
36
|
+
// The branch was rebased onto `commits` new commits, and/or could not be in the repos named in `blocked`.
|
|
37
|
+
z.object({ code: z.literal("synced"), params: z.object({ commits: count, blocked: z.string().optional() }) }),
|
|
38
|
+
z.object({ code: z.literal("intoParent") }),
|
|
39
|
+
z.object({ code: z.literal("intoParentClash"), params: z.object({ files: count }) }),
|
|
40
|
+
z.object({ code: z.literal("landHeld") }),
|
|
41
|
+
z.object({ code: z.literal("landConflict"), params: z.object({ files: count, repos: z.string() }) }),
|
|
42
|
+
// `deps` dependencies the turn added or changed, installing now, or `queued` behind any other install.
|
|
43
|
+
z.object({ code: z.literal("landed"), params: z.object({ deps: count.optional(), queued: z.boolean().optional() }).optional() }),
|
|
44
|
+
z.object({ code: z.literal("retrying"), params: z.object({ ...failure, attempt: count, of: count }) }),
|
|
45
|
+
z.object({ code: z.literal("retried"), params: z.object({ ...failure, made: count, of: count }) }),
|
|
46
|
+
z.object({ code: z.literal("outageWaiting"), params: z.object(failure) }),
|
|
47
|
+
z.object({ code: z.literal("renewing"), params: z.object(failure) }),
|
|
48
|
+
z.object({ code: z.literal("renewalWithdrawn"), params: z.object(failure) }),
|
|
49
|
+
z.object({ code: z.literal("reconnect"), params: z.object(failure) }),
|
|
50
|
+
z.object({ code: z.literal("undelivered"), params: z.object({ ...failure, unattended: z.boolean().optional() }) }),
|
|
51
|
+
// A turn the sandbox kept whole, its message above; `memory` when a memory hold kept it.
|
|
52
|
+
z.object({ code: z.literal("kept"), params: z.object({ ...failure, memory: z.boolean().optional() }) }),
|
|
53
|
+
z.object({ code: z.literal("memoryHeld"), params: z.object(failure) }),
|
|
54
|
+
// A failure the sandbox names by code and adds nothing to.
|
|
55
|
+
z.object({ code: z.literal("failed"), params: z.object(failure) }),
|
|
56
|
+
z.object({ code: z.literal("questionDismissed") }),
|
|
57
|
+
z.object({ code: z.literal("planApproved") }),
|
|
58
|
+
z.object({ code: z.literal("keptPlanning") }),
|
|
59
|
+
// `note` is the agent's own words; `every` the interval, already worded ("5m").
|
|
60
|
+
z.object({ code: z.literal("watching"), params: z.object({ note: z.string(), every: z.string() }) }),
|
|
61
|
+
z.object({ code: z.literal("restartInterrupted") }),
|
|
62
|
+
// A turn that picked up a conversation the sandbox kept warm: `tokens` read from the cache ("305k"), or for
|
|
63
|
+
// keptCold sent again, after `span` ("2h 5m") and `refreshes` refreshes.
|
|
64
|
+
z.object({ code: z.literal("keptWarm"), params: z.object({ tokens: z.string(), span: z.string(), refreshes: count }) }),
|
|
65
|
+
z.object({ code: z.literal("keptCold"), params: z.object({ tokens: z.string(), span: z.string(), refreshes: count }) }),
|
|
66
|
+
// A turn sent thin for a small window ("16k"): what was left out, and whether the base instructions were swapped too.
|
|
67
|
+
z.object({ code: z.literal("contextTrim"), params: z.object({ window: z.string(), omitted: z.string().optional(), base: z.boolean() }) }),
|
|
68
|
+
z.object({ code: z.literal("resumed"), params: z.object({ reason: ResumeNoticeReasonSchema }) }),
|
|
69
|
+
// An agent's own dependency install starting: into its conversation's own copy (`ownCopy`) or the main tree; the
|
|
70
|
+
// workspace root (`root`) and up to three project folders named (`projects`, each with its slash, already joined),
|
|
71
|
+
// and how many `more` were not named.
|
|
72
|
+
z.object({
|
|
73
|
+
code: z.literal("installing"),
|
|
74
|
+
params: z.object({ ownCopy: z.boolean(), root: z.boolean().optional(), projects: z.string().optional(), more: count.optional() }),
|
|
75
|
+
}),
|
|
76
|
+
]);
|
|
77
|
+
export type SandboxNotice = z.infer<typeof SandboxNoticeSchema>;
|
|
78
|
+
|
|
79
|
+
/** The wire form of one of this build's notices, facts nobody gave left off rather than sent as nothing. */
|
|
80
|
+
export const noticeCode = (notice: SandboxNotice): NoticeCode => {
|
|
81
|
+
if (!("params" in notice) || notice.params === undefined) {
|
|
82
|
+
return { code: notice.code };
|
|
83
|
+
}
|
|
84
|
+
const params = Object.fromEntries(
|
|
85
|
+
Object.entries(notice.params).filter((entry): entry is [string, string | number | boolean] => entry[1] !== undefined),
|
|
86
|
+
);
|
|
87
|
+
return Object.keys(params).length === 0 ? { code: notice.code } : { code: notice.code, params };
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/** One of this build's notices, read off a row; undefined for a row without one, or with a code or facts this build does not know. */
|
|
91
|
+
export const sandboxNoticeOf = (row: { readonly noticeCode?: NoticeCode | undefined }): SandboxNotice | undefined =>
|
|
92
|
+
row.noticeCode === undefined ? undefined : SandboxNoticeSchema.safeParse(row.noticeCode).data;
|
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { AgentSummarySchema } from "../schemas/agents.js";
|
|
3
3
|
import { AccountUsageSchema, ProviderRefusalSchema } from "../schemas/providers/plan-limits.js";
|
|
4
4
|
import { MemberRoleSchema } from "../schemas/shared.js";
|
|
5
|
-
import {
|
|
5
|
+
import { WorkspaceTreeDeltaSchema } from "../schemas/workspace/workspace-tree.js";
|
|
6
6
|
|
|
7
7
|
// Frames about the sandbox rather than a turn: liveness, boot progress, what moved (repos, refs, running processes,
|
|
8
8
|
// presence, the fleet), and account headroom. One stream carries them all.
|
|
@@ -53,6 +53,13 @@ export const HelloSchema = z.object({
|
|
|
53
53
|
shapes: z.record(z.string(), z.string()).optional(),
|
|
54
54
|
build: z.string().optional(),
|
|
55
55
|
boot: BootProgressSchema.optional(),
|
|
56
|
+
// A project sandbox's folder, workspace-relative (`my-app` for /work/my-app): the owner's own folder, synced live
|
|
57
|
+
// with their computer, which the browser scopes the workspace to on arrival. Absent on every other sandbox.
|
|
58
|
+
projectDir: z.string().optional(),
|
|
59
|
+
// What answers: a sandbox's daemon, or the desktop app's sidecar serving a folder on the user's own computer
|
|
60
|
+
// (_devices/local-files), whose `routes` are the few a folder has rather than a daemon's missing ones. Absent reads
|
|
61
|
+
// as a sandbox, which every daemon before this field was.
|
|
62
|
+
surface: z.enum(["sandbox", "folder"]).optional(),
|
|
56
63
|
});
|
|
57
64
|
export type Hello = z.infer<typeof HelloSchema>;
|
|
58
65
|
|
|
@@ -66,10 +73,10 @@ export type ReposChanged = z.infer<typeof ReposChangedSchema>;
|
|
|
66
73
|
export const WorkspaceChangedSchema = z.object({ kind: z.literal("workspaceChanged"), paths: z.array(z.string()) });
|
|
67
74
|
export type WorkspaceChanged = z.infer<typeof WorkspaceChangedSchema>;
|
|
68
75
|
|
|
69
|
-
// Files whose
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
export const DerivedChangedSchema = z.object({ kind: z.literal("derivedChanged"), paths: z.array(z.string())
|
|
76
|
+
// Files whose rendered text just landed. Renderings live under the state directory the watcher ignores on purpose — a
|
|
77
|
+
// cache write must never read as a workspace change — so `workspaceChanged` structurally cannot carry this, and without
|
|
78
|
+
// it a second reader watching the same file never sees its text land.
|
|
79
|
+
export const DerivedChangedSchema = z.object({ kind: z.literal("derivedChanged"), paths: z.array(z.string()) });
|
|
73
80
|
export type DerivedChanged = z.infer<typeof DerivedChangedSchema>;
|
|
74
81
|
|
|
75
82
|
// Repos whose refs moved (commit, checkout, branch, rebase); a repo's git dir lives outside /work and the watcher
|
package/src/events/transcript.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { TurnErrandSchema, TurnSpeakerSchema } from "../schemas/speaker.js";
|
|
|
5
5
|
import { SubagentKindSchema, SubagentStatusSchema, SubagentVerificationSchema } from "../schemas/terminal.js";
|
|
6
6
|
import { NeedSchema } from "../schemas/needs.js";
|
|
7
7
|
import { RetryLadderSchema } from "../schemas/turn-break.js";
|
|
8
|
+
import { NoticeCodeSchema } from "./sandbox-notice.js";
|
|
8
9
|
import type { ToolCallContent, ToolCallLocation, ToolCallStatus, ToolKind} from "./requests.js";
|
|
9
10
|
import { browserHelpRequest, capabilityOfferRequest, CapabilityOutcomeSchema, credentialOfferRequest, CredentialReceiptSchema, paymentOfferRequest, PaymentReceiptSchema, PermissionAskSchema, permissionRequest, planRequest, questionRequest, terminalHelpRequest, TodoItemSchema, ToolCallContentSchema, ToolCallLocationSchema, ToolCallStatusSchema, ToolKindSchema } from "./requests.js";
|
|
10
11
|
|
|
@@ -105,7 +106,7 @@ export const TranscriptSubagentSchema = z.object({
|
|
|
105
106
|
.string()
|
|
106
107
|
.optional()
|
|
107
108
|
.describe(
|
|
108
|
-
"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
|
|
109
|
+
"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 own chat call it. Absent, the card's id is its id, as it is for one the runtime started in-process.",
|
|
109
110
|
),
|
|
110
111
|
kind: SubagentKindSchema,
|
|
111
112
|
agentType: z.string().optional(),
|
|
@@ -300,6 +301,11 @@ export const TranscriptRowSchema = z.object({
|
|
|
300
301
|
.string()
|
|
301
302
|
.optional()
|
|
302
303
|
.describe("Which instance of the wait this notice names, for a kind that can have several running at once."),
|
|
304
|
+
// The sandbox's own notice by name, beside its English `text` (sandbox-notice.ts); absent on every other row, and
|
|
305
|
+
// on the notices written before rows carried it, which a reader draws from `text` as it always did.
|
|
306
|
+
noticeCode: NoticeCodeSchema.optional().describe(
|
|
307
|
+
"Which of the sandbox's own notices this row is, and the facts it was worded from, so a reader can say it in the reader's own language. The text stays the English sentence.",
|
|
308
|
+
),
|
|
303
309
|
// At most one card per row; a card closes its bubble. One field per kind, so a reader reaches it by name.
|
|
304
310
|
plan: TranscriptPlanSchema.optional().describe("The plan this row asked approval for, and the answer."),
|
|
305
311
|
question: TranscriptQuestionSchema.optional().describe("The questions this row asked, and the picks that answered them."),
|
|
@@ -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
|
-
// nothing checks inside a conversation, CI checks what the owner pushes (schemas/
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
// nothing checks inside a conversation, CI checks what the owner pushes (schemas/ci.ts). It stays on the wire because
|
|
3
|
+
// records already hold it, and the chat has to go on recognising it: a prompt nobody typed must not reach a reader as
|
|
4
|
+
// 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.
|
|
@@ -131,6 +131,101 @@
|
|
|
131
131
|
},
|
|
132
132
|
"upgrade": "websocket"
|
|
133
133
|
},
|
|
134
|
+
"vitals": {
|
|
135
|
+
"body": {
|
|
136
|
+
"$defs": {
|
|
137
|
+
"NodeLink": {
|
|
138
|
+
"description": "Where the front's control link to Node stands.",
|
|
139
|
+
"oneOf": [
|
|
140
|
+
{
|
|
141
|
+
"const": "starting",
|
|
142
|
+
"description": "Node has not said hello since the container started.",
|
|
143
|
+
"type": "string"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"const": "up",
|
|
147
|
+
"description": "Node's control link is open and it said hello on it.",
|
|
148
|
+
"type": "string"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"const": "restarting",
|
|
152
|
+
"description": "Node was up or was restarted before, and is not up now: its link dropped, or its process exited and the front is\nstarting it again.",
|
|
153
|
+
"type": "string"
|
|
154
|
+
}
|
|
155
|
+
]
|
|
156
|
+
},
|
|
157
|
+
"Pressure": {
|
|
158
|
+
"description": "The share of the last 10 seconds, as a percentage, in which some of the container's tasks stalled waiting for each\nresource: cgroup v2's `some avg10` from `cpu.pressure`, `memory.pressure` and `io.pressure`.",
|
|
159
|
+
"properties": {
|
|
160
|
+
"cpu": {
|
|
161
|
+
"format": "float",
|
|
162
|
+
"type": "number"
|
|
163
|
+
},
|
|
164
|
+
"io": {
|
|
165
|
+
"format": "float",
|
|
166
|
+
"type": "number"
|
|
167
|
+
},
|
|
168
|
+
"memory": {
|
|
169
|
+
"format": "float",
|
|
170
|
+
"type": "number"
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
"required": [
|
|
174
|
+
"cpu",
|
|
175
|
+
"memory",
|
|
176
|
+
"io"
|
|
177
|
+
],
|
|
178
|
+
"type": "object"
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
"description": "The sandbox's proof of life, measured by the front and answered by it at [`VITALS_PATH`] whatever state Node is in:\nthe daemon's own heartbeat rides Node's event loop, so a sandbox busy enough to starve it would otherwise look dead.\nIt carries nothing of the workspace.",
|
|
182
|
+
"properties": {
|
|
183
|
+
"lagMs": {
|
|
184
|
+
"description": "How long Node's event loop takes to answer the front, in milliseconds: the round trip of the last ping it\nanswered, or how long the one outstanding has waited when that is longer. The front pings every 2 s, only once\nthe previous ping is answered. Null before the first ping, and while Node is not `up`.",
|
|
185
|
+
"format": "uint32",
|
|
186
|
+
"minimum": 0,
|
|
187
|
+
"type": [
|
|
188
|
+
"integer",
|
|
189
|
+
"null"
|
|
190
|
+
]
|
|
191
|
+
},
|
|
192
|
+
"node": {
|
|
193
|
+
"$ref": "#/$defs/NodeLink"
|
|
194
|
+
},
|
|
195
|
+
"pressure": {
|
|
196
|
+
"anyOf": [
|
|
197
|
+
{
|
|
198
|
+
"$ref": "#/$defs/Pressure"
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"type": "null"
|
|
202
|
+
}
|
|
203
|
+
],
|
|
204
|
+
"description": "The container's pressure stall; null where the cgroup's pressure files cannot be read."
|
|
205
|
+
},
|
|
206
|
+
"restarts": {
|
|
207
|
+
"description": "How many times the front restarted Node in the last 10 minutes.",
|
|
208
|
+
"format": "uint32",
|
|
209
|
+
"minimum": 0,
|
|
210
|
+
"type": "integer"
|
|
211
|
+
},
|
|
212
|
+
"uptimeS": {
|
|
213
|
+
"description": "Seconds since the front, and so the container, started.",
|
|
214
|
+
"format": "uint32",
|
|
215
|
+
"minimum": 0,
|
|
216
|
+
"type": "integer"
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
"required": [
|
|
220
|
+
"node",
|
|
221
|
+
"restarts",
|
|
222
|
+
"uptimeS"
|
|
223
|
+
],
|
|
224
|
+
"title": "SandboxVitals",
|
|
225
|
+
"type": "object"
|
|
226
|
+
},
|
|
227
|
+
"path": "/system/vitals"
|
|
228
|
+
},
|
|
134
229
|
"webTransport": {
|
|
135
230
|
"alpn": "h3",
|
|
136
231
|
"path": "/system/transport"
|
|
@@ -5,6 +5,42 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export type EdgeVerdict = "no-tunnel" | "unknown-sandbox" | "dropped";
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Where the front's control link to Node stands.
|
|
10
|
+
*/
|
|
11
|
+
export type NodeLink = "starting" | "up" | "restarting";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The share of the last 10 seconds, as a percentage, in which some of the container's tasks stalled waiting for each
|
|
15
|
+
* resource: cgroup v2's `some avg10` from `cpu.pressure`, `memory.pressure` and `io.pressure`.
|
|
16
|
+
*/
|
|
17
|
+
export type Pressure = { cpu: number, memory: number, io: number, };
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The sandbox's proof of life, measured by the front and answered by it at [`VITALS_PATH`] whatever state Node is in:
|
|
21
|
+
* the daemon's own heartbeat rides Node's event loop, so a sandbox busy enough to starve it would otherwise look dead.
|
|
22
|
+
* It carries nothing of the workspace.
|
|
23
|
+
*/
|
|
24
|
+
export type SandboxVitals = { node: NodeLink,
|
|
25
|
+
/**
|
|
26
|
+
* How long Node's event loop takes to answer the front, in milliseconds: the round trip of the last ping it
|
|
27
|
+
* answered, or how long the one outstanding has waited when that is longer. The front pings every 2 s, only once
|
|
28
|
+
* the previous ping is answered. Null before the first ping, and while Node is not `up`.
|
|
29
|
+
*/
|
|
30
|
+
lagMs: number | null,
|
|
31
|
+
/**
|
|
32
|
+
* How many times the front restarted Node in the last 10 minutes.
|
|
33
|
+
*/
|
|
34
|
+
restarts: number,
|
|
35
|
+
/**
|
|
36
|
+
* Seconds since the front, and so the container, started.
|
|
37
|
+
*/
|
|
38
|
+
uptimeS: number,
|
|
39
|
+
/**
|
|
40
|
+
* The container's pressure stall; null where the cgroup's pressure files cannot be read.
|
|
41
|
+
*/
|
|
42
|
+
pressure: Pressure | null, };
|
|
43
|
+
|
|
8
44
|
/**
|
|
9
45
|
* What the browser sends on a terminal socket, each a JSON text message.
|
|
10
46
|
*/
|
|
@@ -50,6 +50,18 @@
|
|
|
50
50
|
"plan"
|
|
51
51
|
],
|
|
52
52
|
"type": "object"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"properties": {
|
|
56
|
+
"answer": {
|
|
57
|
+
"const": "pong",
|
|
58
|
+
"type": "string"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"required": [
|
|
62
|
+
"answer"
|
|
63
|
+
],
|
|
64
|
+
"type": "object"
|
|
53
65
|
}
|
|
54
66
|
]
|
|
55
67
|
},
|
|
@@ -699,6 +711,19 @@
|
|
|
699
711
|
"query"
|
|
700
712
|
],
|
|
701
713
|
"type": "object"
|
|
714
|
+
},
|
|
715
|
+
{
|
|
716
|
+
"description": "Whether Node's event loop turns, and how fast: answered `pong` at once, by the link itself. The front sends one\nevery 2 s once the previous one is answered, and reports the round trip as the vitals' lag. An older Node answers\nwith a refusal, or with an answer the front cannot read, and either still counts as answered.",
|
|
717
|
+
"properties": {
|
|
718
|
+
"question": {
|
|
719
|
+
"const": "ping",
|
|
720
|
+
"type": "string"
|
|
721
|
+
}
|
|
722
|
+
},
|
|
723
|
+
"required": [
|
|
724
|
+
"question"
|
|
725
|
+
],
|
|
726
|
+
"type": "object"
|
|
702
727
|
}
|
|
703
728
|
]
|
|
704
729
|
}
|
|
@@ -4,7 +4,7 @@ export type Answer = { "answer": "preview", route: PreviewRoute, } | { "answer":
|
|
|
4
4
|
/**
|
|
5
5
|
* The member whose ticket opened it, lowercased; absent when the daemon runs without auth.
|
|
6
6
|
*/
|
|
7
|
-
member?: string, };
|
|
7
|
+
member?: string, } | { "answer": "pong" };
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* A PEM certificate chain and its PEM private key.
|
|
@@ -61,7 +61,7 @@ export type PreviewRoute = { "to": "upstream", upstream: Upstream, } | { "to": "
|
|
|
61
61
|
/**
|
|
62
62
|
* A question the front asks Node.
|
|
63
63
|
*/
|
|
64
|
-
export type Question = { "question": "preview", host: string, probe: boolean, } | { "question": "terminal", query: string, };
|
|
64
|
+
export type Question = { "question": "preview", host: string, probe: boolean, } | { "question": "terminal", query: string, } | { "question": "ping" };
|
|
65
65
|
|
|
66
66
|
export type Scheme = "http" | "https";
|
|
67
67
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { EDGE_VERDICT_HEADER, edgeVerdictOf } from "../protocol/edge-verdict.js";
|
|
3
3
|
import { INGRESS_GRANT_HEADER, INGRESS_TUNNEL_PATH } from "../protocol/ingress-contract.js";
|
|
4
|
+
import { VITALS_PATH } from "../protocol/vitals.js";
|
|
4
5
|
import { EDGE_TRANSPORTS, TERMINAL_PATH, WEBTRANSPORT_PATH } from "./browser-wire.js";
|
|
5
6
|
import { ASK_PATIENCE_MS, FRAME_LENGTH_BYTES, FRONT_SOCKET_ENV, NODE_SOCKET_ENV } from "./front-wire.js";
|
|
6
7
|
|
|
@@ -23,6 +24,7 @@ const socket = manifest("front-wire") as {
|
|
|
23
24
|
const browser = manifest("browser-wire") as {
|
|
24
25
|
edge: { verdictHeader: string; verdicts: { oneOf: { const: string }[] } };
|
|
25
26
|
terminal: { path: string; upgrade: string };
|
|
27
|
+
vitals: { path: string; body: { $defs: { NodeLink: { oneOf: { const: string }[] } } } };
|
|
26
28
|
webTransport: { path: string };
|
|
27
29
|
};
|
|
28
30
|
|
|
@@ -58,4 +60,9 @@ describe("the wire outside oRPC, as the Rust crates define it", () => {
|
|
|
58
60
|
upgrade: browser.terminal.upgrade,
|
|
59
61
|
});
|
|
60
62
|
});
|
|
63
|
+
|
|
64
|
+
it("finds the front's vitals where browser-wire says it answers them, in the states it names", () => {
|
|
65
|
+
expect(VITALS_PATH).toBe(browser.vitals.path);
|
|
66
|
+
expect(browser.vitals.body.$defs.NodeLink.oneOf.map((state) => state.const)).toEqual(["starting", "up", "restarting"]);
|
|
67
|
+
});
|
|
61
68
|
});
|
|
@@ -7,8 +7,6 @@ import {
|
|
|
7
7
|
latestFixAttempt,
|
|
8
8
|
newConversationId,
|
|
9
9
|
nextFixAttemptId,
|
|
10
|
-
PUSH_FIX_PREFIX,
|
|
11
|
-
pushFixConversationId,
|
|
12
10
|
} from "./conversation-ids.js";
|
|
13
11
|
import { ConversationIdSchema } from "../schemas/agent.js";
|
|
14
12
|
|
|
@@ -90,28 +88,6 @@ test("the prefix is carried by every fix id", () => {
|
|
|
90
88
|
expect(ciFixConversationId(`ci-fix`, 7)).toBe(`ci-fix-ci-fix-7`);
|
|
91
89
|
});
|
|
92
90
|
|
|
93
|
-
test("a push fix id is the same string every time the same failure derives it", () => {
|
|
94
|
-
expect(pushFixConversationId(`intentic`, `checkout gates,lint`)).toBe(pushFixConversationId(`intentic`, `checkout gates,lint`));
|
|
95
|
-
expect(pushFixConversationId(`intentic`, `checkout gates,lint`)).toMatch(/^push-fix-intentic-[0-9a-z]{7}$/);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test("a push fix id separates failures and scopes", () => {
|
|
99
|
-
expect(pushFixConversationId(`intentic`, `lint`)).not.toBe(pushFixConversationId(`intentic`, `checkout gates`));
|
|
100
|
-
expect(pushFixConversationId(`web`, `lint`)).not.toBe(pushFixConversationId(`api`, `lint`));
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
// Signatures cover raw gate output: control characters, unicode, path-like strings, and pathological lengths.
|
|
104
|
-
test("every derived push fix id passes the conversation-id guard", () => {
|
|
105
|
-
const signatures = [``, `checkout gates`, `✗ lint · pnpm lint`, `a`.repeat(4_000), `../../etc/passwd`, `a\nb\tc`, `résumé`];
|
|
106
|
-
for (const scope of [`root`, `apps/web`, `my repo`, `___`, `x`.repeat(80)]) {
|
|
107
|
-
for (const signature of signatures) {
|
|
108
|
-
const id = pushFixConversationId(scope, signature);
|
|
109
|
-
expect(ConversationIdSchema.safeParse(id).success).toBe(true);
|
|
110
|
-
expect(id.startsWith(PUSH_FIX_PREFIX)).toBe(true);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
|
|
115
91
|
// Attempt 1 is the bare derived id, so every fix conversation minted before attempts existed reads as attempt 1.
|
|
116
92
|
test("the first attempt wears the failure's own id, later ones carry their number", () => {
|
|
117
93
|
const base = ciFixConversationId(`web`, 41);
|
|
@@ -136,8 +112,8 @@ test("another failure's id is not an attempt at this one", () => {
|
|
|
136
112
|
});
|
|
137
113
|
|
|
138
114
|
test("attempts are read off the roster, earliest first, and the newest is the live answer", () => {
|
|
139
|
-
const base =
|
|
140
|
-
const roster = [{ id: `swift-otter-k9m2` }, { id: fixAttemptId(base, 3) }, { id: base }, { id:
|
|
115
|
+
const base = ciFixConversationId(`intentic`, 7);
|
|
116
|
+
const roster = [{ id: `swift-otter-k9m2` }, { id: fixAttemptId(base, 3) }, { id: base }, { id: ciFixConversationId(`intentic`, 8) }];
|
|
141
117
|
expect(fixAttemptsOf(base, roster).map((entry) => entry.attempt)).toEqual([1, 3]);
|
|
142
118
|
expect(latestFixAttempt(base, roster)?.agent.id).toBe(fixAttemptId(base, 3));
|
|
143
119
|
expect(latestFixAttempt(base, [])).toBeUndefined();
|
|
@@ -160,13 +160,9 @@ const repoSlug = (repo: string): string =>
|
|
|
160
160
|
.replace(/^-+|-+$/g, "") || "repo";
|
|
161
161
|
export const ciFixConversationId = (repo: string, runId: number): string => `${CI_FIX_PREFIX}${repoSlug(repo)}-${runId}`;
|
|
162
162
|
|
|
163
|
-
// Keyed by when the project's push red began (pushFixBase, `red:<since>`), not a run id (a push has none): a refused push
|
|
164
|
-
// is owed by that red too (push-checks-store.ts). Hashed rather than spelled into the branch name; the fix agent's first
|
|
165
|
-
// message names what it is for.
|
|
166
|
-
export const PUSH_FIX_PREFIX = "push-fix-";
|
|
167
|
-
|
|
168
163
|
// FNV-1a, not a cryptographic hash: nothing here is secret, the only requirement is the same failure yields the same
|
|
169
|
-
// seven characters in every browser and in node. The one spelling: a
|
|
164
|
+
// seven characters in every browser and in node. The one spelling: what a failing main owes is named by it too (a
|
|
165
|
+
// failed job's id, ci/main-fixer.ts).
|
|
170
166
|
const FNV_OFFSET = 0x811c_9dc5;
|
|
171
167
|
const FNV_PRIME = 0x0100_0193;
|
|
172
168
|
export const fnvDigest = (text: string): string => {
|
|
@@ -177,14 +173,12 @@ export const fnvDigest = (text: string): string => {
|
|
|
177
173
|
return (hash >>> 0).toString(36).padStart(7, "0");
|
|
178
174
|
};
|
|
179
175
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
// A red main-line check that no conversation still holding the work could take: keyed by the project and the moment its
|
|
183
|
-
// red streak began, so every attempt at one streak shares a base and a later streak in the same project starts over.
|
|
176
|
+
// A failing main-line check that no conversation still holding the work could take: keyed by the project and the moment
|
|
177
|
+
// it began failing, so every attempt at one streak shares a base and a later streak in the same project starts over.
|
|
184
178
|
// Seconds in base36 keep it short; the workspace root is spelled `workspace`, since its project folder is empty.
|
|
185
179
|
export const LAND_FIX_PREFIX = "land-fix-";
|
|
186
|
-
export const landFixConversationId = (project: string,
|
|
187
|
-
`${LAND_FIX_PREFIX}${repoSlug(project === "" ? "workspace" : project)}-${Math.floor(
|
|
180
|
+
export const landFixConversationId = (project: string, failingSince: number): string =>
|
|
181
|
+
`${LAND_FIX_PREFIX}${repoSlug(project === "" ? "workspace" : project)}-${Math.floor(failingSince / 1000).toString(36)}`;
|
|
188
182
|
|
|
189
183
|
// ONE FAILURE, MANY ATTEMPTS, ONE LIVE ANSWER. The derived id above names the FAILURE; an attempt at it is a
|
|
190
184
|
// conversation of its own, so starting over (another model, a clean worktree) never rewrites a record some other window
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"names": [
|
|
3
|
+
{ "name": "my-app", "valid": true },
|
|
4
|
+
{ "name": "My_App.v2", "valid": true },
|
|
5
|
+
{ "name": "a", "valid": true },
|
|
6
|
+
{ "name": "0", "valid": true },
|
|
7
|
+
{ "name": "Public", "valid": true },
|
|
8
|
+
{ "name": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "valid": true },
|
|
9
|
+
{ "name": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "valid": false },
|
|
10
|
+
{ "name": "public", "valid": false },
|
|
11
|
+
{ "name": "refs", "valid": false },
|
|
12
|
+
{ "name": "site", "valid": false },
|
|
13
|
+
{ "name": "root", "valid": false },
|
|
14
|
+
{ "name": "intent", "valid": false },
|
|
15
|
+
{ "name": "desired-state", "valid": false },
|
|
16
|
+
{ "name": "app", "valid": false },
|
|
17
|
+
{ "name": "AGENTS.md", "valid": false },
|
|
18
|
+
{ "name": "node_modules", "valid": false },
|
|
19
|
+
{ "name": "dist", "valid": false },
|
|
20
|
+
{ "name": "venv", "valid": false },
|
|
21
|
+
{ "name": "claude.json", "valid": false },
|
|
22
|
+
{ "name": "__pycache__", "valid": false },
|
|
23
|
+
{ "name": ".intentic", "valid": false },
|
|
24
|
+
{ "name": ".hidden", "valid": false },
|
|
25
|
+
{ "name": "-flag", "valid": false },
|
|
26
|
+
{ "name": "_under", "valid": false },
|
|
27
|
+
{ "name": "", "valid": false },
|
|
28
|
+
{ "name": "a/b", "valid": false },
|
|
29
|
+
{ "name": "..", "valid": false },
|
|
30
|
+
{ "name": "has space", "valid": false },
|
|
31
|
+
{ "name": "café", "valid": false },
|
|
32
|
+
{ "name": "my-app/", "valid": false }
|
|
33
|
+
],
|
|
34
|
+
"folders": [
|
|
35
|
+
{ "folder": "my-app", "dirName": "my-app" },
|
|
36
|
+
{ "folder": "My App", "dirName": "My-App" },
|
|
37
|
+
{ "folder": "My App!!", "dirName": "My-App" },
|
|
38
|
+
{ "folder": "Café Menu", "dirName": "Cafe-Menu" },
|
|
39
|
+
{ "folder": "日本語", "dirName": "project" },
|
|
40
|
+
{ "folder": "", "dirName": "project" },
|
|
41
|
+
{ "folder": "...", "dirName": "project" },
|
|
42
|
+
{ "folder": ".config", "dirName": "config" },
|
|
43
|
+
{ "folder": "--flag--", "dirName": "flag" },
|
|
44
|
+
{ "folder": "_private", "dirName": "private" },
|
|
45
|
+
{ "folder": "public", "dirName": "public-project" },
|
|
46
|
+
{ "folder": "site", "dirName": "site-project" },
|
|
47
|
+
{ "folder": "app", "dirName": "app-project" },
|
|
48
|
+
{ "folder": "AGENTS.md", "dirName": "AGENTS.md-project" },
|
|
49
|
+
{ "folder": "dist", "dirName": "dist-project" },
|
|
50
|
+
{ "folder": "node_modules", "dirName": "node_modules-project" },
|
|
51
|
+
{ "folder": "__pycache__", "dirName": "pycache__" },
|
|
52
|
+
{ "folder": "a--b", "dirName": "a-b" },
|
|
53
|
+
{ "folder": "report (final) v2", "dirName": "report-final-v2" },
|
|
54
|
+
{
|
|
55
|
+
"folder": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
56
|
+
"dirName": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"folder": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa b",
|
|
60
|
+
"dirName": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
61
|
+
},
|
|
62
|
+
{ "folder": "C++ Project", "dirName": "C-Project" },
|
|
63
|
+
{ "folder": "release.2024.", "dirName": "release.2024" }
|
|
64
|
+
]
|
|
65
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { MEMORY_FILE, PUBLIC_DIR, REFERENCE_DIR, WORKSPACE_ROOT } from "@intentic/constants";
|
|
3
|
+
import { STARTER_REPO } from "../state/starter.js";
|
|
4
|
+
import { isProjectDirName, projectDirNameFor, projectDirNameOf, projectRemoteDir, RESERVED_PROJECT_DIR_NAMES } from "./project-dir.js";
|
|
5
|
+
|
|
6
|
+
// The cases `ic` is held to as well (_sandbox/ic/src/sandbox/project_dir.rs), read from the one file both run.
|
|
7
|
+
interface ProjectDirCases {
|
|
8
|
+
readonly names: readonly { readonly name: string; readonly valid: boolean }[];
|
|
9
|
+
readonly folders: readonly { readonly folder: string; readonly dirName: string }[];
|
|
10
|
+
}
|
|
11
|
+
// SAFETY: the fixture is this repository's own file, written to this shape; one that drifted fails every case below.
|
|
12
|
+
const CASES = JSON.parse(readFileSync(new URL("./project-dir.fixture.json", import.meta.url), "utf8")) as ProjectDirCases;
|
|
13
|
+
|
|
14
|
+
test.each([...CASES.names])("the shared name case $name is valid: $valid", ({ name, valid }) => {
|
|
15
|
+
expect(isProjectDirName(name)).toBe(valid);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test.each([...CASES.folders])("the folder $folder syncs into $dirName", ({ folder, dirName }) => {
|
|
19
|
+
expect(projectDirNameFor(folder)).toBe(dirName);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("every folder name the slug produces is one the validation accepts, and slugging it again changes nothing", () => {
|
|
23
|
+
for (const { folder } of CASES.folders) {
|
|
24
|
+
const name = projectDirNameFor(folder);
|
|
25
|
+
expect(isProjectDirName(name)).toBe(true);
|
|
26
|
+
expect(projectDirNameFor(name)).toBe(name);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("the reserved names are the ones the daemon keeps at the workspace root", () => {
|
|
31
|
+
expect([...RESERVED_PROJECT_DIR_NAMES].toSorted()).toEqual(
|
|
32
|
+
[
|
|
33
|
+
PUBLIC_DIR,
|
|
34
|
+
REFERENCE_DIR,
|
|
35
|
+
STARTER_REPO,
|
|
36
|
+
MEMORY_FILE,
|
|
37
|
+
"root",
|
|
38
|
+
"intent",
|
|
39
|
+
"desired-state",
|
|
40
|
+
"app",
|
|
41
|
+
"node_modules",
|
|
42
|
+
"dist",
|
|
43
|
+
"venv",
|
|
44
|
+
"claude.json",
|
|
45
|
+
].toSorted(),
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("a project dir is exactly one valid name under the workspace root", () => {
|
|
50
|
+
expect(projectRemoteDir("my-app")).toBe(`${WORKSPACE_ROOT}/my-app`);
|
|
51
|
+
expect(projectDirNameOf(`${WORKSPACE_ROOT}/my-app`)).toBe("my-app");
|
|
52
|
+
expect(projectDirNameOf(WORKSPACE_ROOT)).toBeUndefined();
|
|
53
|
+
expect(projectDirNameOf(`${WORKSPACE_ROOT}/`)).toBeUndefined();
|
|
54
|
+
expect(projectDirNameOf(`${WORKSPACE_ROOT}/my-app/`)).toBeUndefined();
|
|
55
|
+
expect(projectDirNameOf(`${WORKSPACE_ROOT}/my-app/src`)).toBeUndefined();
|
|
56
|
+
expect(projectDirNameOf(`${WORKSPACE_ROOT}/public`)).toBeUndefined();
|
|
57
|
+
expect(projectDirNameOf(`${WORKSPACE_ROOT}/../etc`)).toBeUndefined();
|
|
58
|
+
expect(projectDirNameOf("/workspace/my-app")).toBeUndefined();
|
|
59
|
+
expect(projectDirNameOf("/srv/work/my-app", "/srv/work")).toBe("my-app");
|
|
60
|
+
});
|