@intentic/sandbox-contract 1.317.0 → 1.318.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 +12 -4
- 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 +55 -1
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.d.ts +796 -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 -2
- 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 +96 -435
- 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 -80
- package/dist/contracts/workspace.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.js +0 -27
- 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 +46 -0
- package/dist/events/system-events.d.ts.map +1 -1
- package/dist/events/system-events.js +2 -0
- 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/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 +11570 -11030
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -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 +7 -0
- package/dist/protocol/raw-routes.d.ts.map +1 -1
- package/dist/protocol/raw-routes.js +4 -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/schemas/activity.js +1 -1
- package/dist/schemas/activity.js.map +1 -1
- package/dist/schemas/agent.d.ts +19 -2
- package/dist/schemas/agent.d.ts.map +1 -1
- package/dist/schemas/agent.js +2 -1
- package/dist/schemas/agent.js.map +1 -1
- package/dist/schemas/agents.d.ts +61 -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 +18 -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 +1 -1
- package/dist/schemas/settings-history.d.ts.map +1 -1
- package/dist/schemas/settings-history.js +1 -0
- package/dist/schemas/settings-history.js.map +1 -1
- package/dist/schemas/settings.d.ts +42 -2
- package/dist/schemas/settings.d.ts.map +1 -1
- package/dist/schemas/settings.js +26 -4
- 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 -0
- package/dist/schemas/workspace/workspace-tree.d.ts.map +1 -1
- package/dist/schemas/workspace/workspace-tree.js +4 -0
- package/dist/schemas/workspace/workspace-tree.js.map +1 -1
- package/dist/state/definition.d.ts +12 -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 +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 -37
- 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 +7 -0
- package/src/events/transcript.ts +7 -1
- package/src/events/verify-nudge.ts +3 -3
- 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 +12 -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 +5 -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/schemas/activity.ts +1 -1
- package/src/schemas/agent.ts +4 -1
- 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 +2 -0
- package/src/schemas/settings.test.ts +19 -1
- package/src/schemas/settings.ts +58 -9
- 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 +7 -0
- 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,89 @@
|
|
|
1
|
+
import { hostAllowed, hostMatches, normalizeHost, normalizeHostPattern, tightensHostGuard } from "./secret-hosts.js";
|
|
2
|
+
|
|
3
|
+
// The pattern a person writes against the host a use would reach. Every boundary a limit turns on is pinned by value:
|
|
4
|
+
// the apex a wildcard leaves out, the look-alike that only shares a suffix, and which edits take hosts away.
|
|
5
|
+
|
|
6
|
+
describe("normalizeHostPattern", () => {
|
|
7
|
+
it("reads what a person types or pastes as the host it means", () => {
|
|
8
|
+
expect(normalizeHostPattern(" API.GitHub.com. ")).toBe("api.github.com");
|
|
9
|
+
expect(normalizeHostPattern("https://api.github.com/user?per_page=5")).toBe("api.github.com");
|
|
10
|
+
expect(normalizeHostPattern("api.github.com:443")).toBe("api.github.com");
|
|
11
|
+
expect(normalizeHostPattern("*.githubusercontent.com")).toBe("*.githubusercontent.com");
|
|
12
|
+
expect(normalizeHostPattern("localhost")).toBe("localhost");
|
|
13
|
+
expect(normalizeHostPattern("10.0.0.2")).toBe("10.0.0.2");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("refuses what is not one host or one domain's hosts", () => {
|
|
17
|
+
expect(normalizeHostPattern("*")).toBeUndefined();
|
|
18
|
+
expect(normalizeHostPattern("*.com")).toBeUndefined();
|
|
19
|
+
expect(normalizeHostPattern("api.*.com")).toBeUndefined();
|
|
20
|
+
expect(normalizeHostPattern("api.github.com/user")).toBeUndefined();
|
|
21
|
+
expect(normalizeHostPattern("user@api.github.com")).toBeUndefined();
|
|
22
|
+
expect(normalizeHostPattern("-bad.example.com")).toBeUndefined();
|
|
23
|
+
expect(normalizeHostPattern("")).toBeUndefined();
|
|
24
|
+
expect(normalizeHostPattern(`${"a".repeat(64)}.com`)).toBeUndefined();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("normalizeHost", () => {
|
|
29
|
+
it("takes a plain host or address and nothing shaped like a pattern or a stray token", () => {
|
|
30
|
+
expect(normalizeHost("API.github.com.")).toBe("api.github.com");
|
|
31
|
+
expect(normalizeHost("*.github.com")).toBeUndefined();
|
|
32
|
+
expect(normalizeHost("[::1]")).toBeUndefined();
|
|
33
|
+
expect(normalizeHost("api.github.com\u0001")).toBeUndefined();
|
|
34
|
+
expect(normalizeHost("$host")).toBeUndefined();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("hostMatches", () => {
|
|
39
|
+
it("matches an exact host only to itself", () => {
|
|
40
|
+
expect(hostMatches("api.github.com", "api.github.com")).toBe(true);
|
|
41
|
+
expect(hostMatches("api.github.com", "github.com")).toBe(false);
|
|
42
|
+
expect(hostMatches("github.com", "api.github.com")).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("matches a wildcard to every host under the domain, at any depth, and not to the domain itself", () => {
|
|
46
|
+
expect(hostMatches("*.github.com", "api.github.com")).toBe(true);
|
|
47
|
+
expect(hostMatches("*.github.com", "a.b.github.com")).toBe(true);
|
|
48
|
+
expect(hostMatches("*.github.com", "github.com")).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("does not match a host that merely ends in the same letters", () => {
|
|
52
|
+
expect(hostMatches("*.github.com", "evilgithub.com")).toBe(false);
|
|
53
|
+
expect(hostMatches("*.github.com", "github.com.evil.example")).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("allows a host any one pattern in the list matches", () => {
|
|
57
|
+
expect(hostAllowed(["api.github.com", "*.githubusercontent.com"], "raw.githubusercontent.com")).toBe(true);
|
|
58
|
+
expect(hostAllowed(["api.github.com", "*.githubusercontent.com"], "evil.example")).toBe(false);
|
|
59
|
+
expect(hostAllowed([], "api.github.com")).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("tightensHostGuard", () => {
|
|
64
|
+
const on = (...hosts: string[]) => ({ guard: true, hosts });
|
|
65
|
+
const off = (...hosts: string[]) => ({ guard: false, hosts });
|
|
66
|
+
|
|
67
|
+
it("reads turning the guard on as tightening, whatever the list, and turning it off as loosening", () => {
|
|
68
|
+
expect(tightensHostGuard(undefined, on("api.github.com"))).toBe(true);
|
|
69
|
+
expect(tightensHostGuard(off("api.github.com"), on())).toBe(true);
|
|
70
|
+
expect(tightensHostGuard(on("api.github.com"), off("api.github.com"))).toBe(false);
|
|
71
|
+
expect(tightensHostGuard(undefined, off())).toBe(true);
|
|
72
|
+
expect(tightensHostGuard(off(), off("evil.example"))).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("reads taking a host off a guarded list as tightening, down to none, and adding one as loosening", () => {
|
|
76
|
+
expect(tightensHostGuard(on("api.github.com", "github.com"), on("github.com"))).toBe(true);
|
|
77
|
+
expect(tightensHostGuard(on("api.github.com"), on())).toBe(true);
|
|
78
|
+
expect(tightensHostGuard(on("api.github.com"), on("api.github.com", "evil.example"))).toBe(false);
|
|
79
|
+
expect(tightensHostGuard(on(), on("api.github.com"))).toBe(false);
|
|
80
|
+
expect(tightensHostGuard(on("api.github.com"), on("api.github.com"))).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("counts a host or a deeper wildcard under a held wildcard as covered, and the domain itself as not", () => {
|
|
84
|
+
expect(tightensHostGuard(on("*.github.com"), on("api.github.com"))).toBe(true);
|
|
85
|
+
expect(tightensHostGuard(on("*.github.com"), on("*.api.github.com"))).toBe(true);
|
|
86
|
+
expect(tightensHostGuard(on("*.github.com"), on("github.com"))).toBe(false);
|
|
87
|
+
expect(tightensHostGuard(on("api.github.com"), on("*.api.github.com"))).toBe(false);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Which hosts a stored secret may be sent to: an owner's list per secret, matched against the hosts one use would reach.
|
|
2
|
+
// Shared by the daemon's check, its routes and the Secrets view, so the three cannot disagree on what a pattern means.
|
|
3
|
+
// A list names hosts, never ports or paths: the question it answers is who receives the value, not which door.
|
|
4
|
+
|
|
5
|
+
// One DNS label: letters, digits and inner hyphens, at most 63 characters.
|
|
6
|
+
const LABEL = String.raw`[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?`;
|
|
7
|
+
|
|
8
|
+
// An exact host (`api.github.com`, `localhost`, `10.0.0.2`) or every host under a domain (`*.github.com`). A wildcard
|
|
9
|
+
// names at least two labels under it, so `*.com` cannot stand in for most of the internet.
|
|
10
|
+
export const SECRET_HOST_PATTERN_RE = new RegExp(String.raw`^(?:\*\.${LABEL}(?:\.${LABEL})+|${LABEL}(?:\.${LABEL})*)$`);
|
|
11
|
+
export const SECRET_HOST_MAX = 253;
|
|
12
|
+
// Enough for a service's API, uploads and content hosts several times over; a list longer than this is no limit.
|
|
13
|
+
export const SECRET_HOSTS_MAX = 64;
|
|
14
|
+
|
|
15
|
+
const WILDCARD = "*.";
|
|
16
|
+
|
|
17
|
+
// What a person types or pastes, as the pattern it means: lowercased, trimmed, a trailing dot and a port dropped, and a
|
|
18
|
+
// whole URL reduced to its host. Undefined for anything that is not a host pattern, so a caller refuses it by name.
|
|
19
|
+
export const normalizeHostPattern = (raw: string): string | undefined => {
|
|
20
|
+
let text = raw.trim().toLowerCase();
|
|
21
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//.test(text)) {
|
|
22
|
+
try {
|
|
23
|
+
text = new URL(text).hostname;
|
|
24
|
+
} catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
text = text.replace(/:\d+$/, "").replace(/\.$/, "");
|
|
29
|
+
return text.length <= SECRET_HOST_MAX && SECRET_HOST_PATTERN_RE.test(text) ? text : undefined;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// A host a use would reach, in the form patterns are written in: lowercased, one trailing dot dropped. Undefined when it
|
|
33
|
+
// is not a plain host name or IPv4 address, which callers read as "cannot tell where this goes".
|
|
34
|
+
export const normalizeHost = (raw: string): string | undefined => {
|
|
35
|
+
const host = raw.toLowerCase().replace(/\.$/, "");
|
|
36
|
+
return host.length <= SECRET_HOST_MAX && !host.startsWith(WILDCARD) && SECRET_HOST_PATTERN_RE.test(host) ? host : undefined;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// `*.github.com` matches every host under github.com at any depth, and not github.com itself; anything else matches only
|
|
40
|
+
// itself. Both sides are expected normalized.
|
|
41
|
+
export const hostMatches = (pattern: string, host: string): boolean =>
|
|
42
|
+
pattern.startsWith(WILDCARD) ? host.endsWith(pattern.slice(1)) : host === pattern;
|
|
43
|
+
|
|
44
|
+
export const hostAllowed = (patterns: readonly string[], host: string): boolean => patterns.some((pattern) => hostMatches(pattern, host));
|
|
45
|
+
|
|
46
|
+
// Whether every host `pattern` matches is already matched by `patterns`: an exact host by any pattern that matches it, a
|
|
47
|
+
// wildcard only by itself or a wildcard over a domain above it.
|
|
48
|
+
const covered = (patterns: readonly string[], pattern: string): boolean => {
|
|
49
|
+
if (!pattern.startsWith(WILDCARD)) {
|
|
50
|
+
return hostAllowed(patterns, pattern);
|
|
51
|
+
}
|
|
52
|
+
const suffix = pattern.slice(1);
|
|
53
|
+
return patterns.some((held) => held.startsWith(WILDCARD) && suffix.endsWith(held.slice(1)));
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// One secret's host guard as a change reads it: whether it asks off the list, and the list. Absent reads as off.
|
|
57
|
+
export interface HostGuardSetting {
|
|
58
|
+
readonly guard: boolean;
|
|
59
|
+
readonly hosts: readonly string[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Whether going from `current` to `next` only ever makes the agent ask more: turning the guard on, or taking hosts off a
|
|
63
|
+
// guarded list (down to none, where every use asks). The one test for who may make a change: anybody who may use
|
|
64
|
+
// secrets may tighten a guard, only the owner may loosen one, by adding a host or turning it off.
|
|
65
|
+
export const tightensHostGuard = (current: HostGuardSetting | undefined, next: HostGuardSetting): boolean => {
|
|
66
|
+
if (!next.guard) {
|
|
67
|
+
return current === undefined || !current.guard;
|
|
68
|
+
}
|
|
69
|
+
if (current === undefined || !current.guard) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
return next.hosts.every((pattern) => covered(current.hosts, pattern));
|
|
73
|
+
};
|
|
@@ -61,6 +61,9 @@ export const RAW_ROUTES = {
|
|
|
61
61
|
"POST /environment/approve": {},
|
|
62
62
|
"POST /environment/reject": {},
|
|
63
63
|
"POST /environment/runtime-install": {},
|
|
64
|
+
"POST /environment/remove": {},
|
|
65
|
+
"POST /environment/rebuild-when-idle": {},
|
|
66
|
+
"DELETE /environment/rebuild-when-idle": {},
|
|
64
67
|
"GET /engines": {},
|
|
65
68
|
"POST /engines/channel": {},
|
|
66
69
|
"POST /engines/update": {},
|
|
@@ -92,13 +95,14 @@ export const RAW_ROUTES = {
|
|
|
92
95
|
"GET /wallet/status": { agent: true, control: "never" },
|
|
93
96
|
"POST /wallet/fetch": { agent: true, control: "never" },
|
|
94
97
|
"GET /wallet/history": { agent: true, control: "never" },
|
|
95
|
-
// The `agents` CLI's children: start, follow up, answer (never a consent card), cancel, list.
|
|
98
|
+
// The `agents` CLI's children: start, follow up, answer (never a consent card), cancel, merge held work, list.
|
|
96
99
|
"POST /children/spawn": { agent: true, control: "never" },
|
|
97
100
|
"GET /children/providers": { agent: true, control: "never" },
|
|
98
101
|
"POST /children/wait": { agent: true, control: "never" },
|
|
99
102
|
"POST /children/send": { agent: true, control: "never" },
|
|
100
103
|
"POST /children/answer": { agent: true, control: "never" },
|
|
101
104
|
"POST /children/cancel": { agent: true, control: "never" },
|
|
105
|
+
"POST /children/merge": { agent: true, control: "never" },
|
|
102
106
|
"GET /children": { agent: true, control: "never" },
|
|
103
107
|
// The fleet reads, and the one write: words in front of another conversation, which a person can do by typing.
|
|
104
108
|
"GET /fleet": { agent: true },
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { eventIterator, oc } from "@orpc/contract";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import { SANDBOX_ROUTE_NAMES,
|
|
3
|
+
import { SANDBOX_ROUTE_NAMES, SANDBOX_ROUTES, sandboxRouteName, sandboxRouteFingerprints } from "../index.js";
|
|
4
4
|
import { procedure } from "./route-meta.js";
|
|
5
5
|
import { contractRoutes, routeNameForRequest, routeShapes, servedRoute, streamOf } from "./routes.js";
|
|
6
6
|
|
|
@@ -231,7 +231,7 @@ describe(`the real sandbox contract`, () => {
|
|
|
231
231
|
it(`fingerprints every route, streams included`, () => {
|
|
232
232
|
// Nothing may opt out: a route with no fingerprint is a route the browser can never tell has drifted, and the
|
|
233
233
|
// streaming ones (system.events, agent.attach, the exit dials) carry the frames a stale daemon breaks first.
|
|
234
|
-
expect(SANDBOX_ROUTE_NAMES.filter((name) => !(name in
|
|
234
|
+
expect(SANDBOX_ROUTE_NAMES.filter((name) => !(name in sandboxRouteFingerprints()))).toEqual([]);
|
|
235
235
|
});
|
|
236
236
|
|
|
237
237
|
it(`fingerprints the streaming routes eventIterator used to hide`, () => {
|
|
@@ -250,7 +250,7 @@ describe(`the real sandbox contract`, () => {
|
|
|
250
250
|
`system.runDeviceAgentFlow`,
|
|
251
251
|
`vpn.connect`,
|
|
252
252
|
]
|
|
253
|
-
.filter((name) => name in
|
|
253
|
+
.filter((name) => name in sandboxRouteFingerprints())
|
|
254
254
|
.toSorted(),
|
|
255
255
|
).toEqual([
|
|
256
256
|
`agent.attach`,
|
|
@@ -269,8 +269,8 @@ describe(`the real sandbox contract`, () => {
|
|
|
269
269
|
});
|
|
270
270
|
|
|
271
271
|
it(`fingerprints every route exactly once`, () => {
|
|
272
|
-
expect(Object.keys(
|
|
273
|
-
expect(Object.keys(
|
|
272
|
+
expect(Object.keys(sandboxRouteFingerprints()).every((name) => SANDBOX_ROUTE_NAMES.includes(name))).toBe(true);
|
|
273
|
+
expect(Object.keys(sandboxRouteFingerprints()).length).toBe(SANDBOX_ROUTE_NAMES.length);
|
|
274
274
|
});
|
|
275
275
|
|
|
276
276
|
it(`derives a route table with no duplicate names`, () => {
|
|
@@ -287,7 +287,7 @@ describe(`the real sandbox contract`, () => {
|
|
|
287
287
|
// differently has to read as drift rather than as a machine that runs nothing.
|
|
288
288
|
it(`fingerprints the fleet view the device buttons are gated on`, () => {
|
|
289
289
|
expect(sandboxRouteName(`GET`, `/system/devices`)).toBe(`system.devices`);
|
|
290
|
-
expect(
|
|
290
|
+
expect(sandboxRouteFingerprints()[`system.devices`]).toEqual(expect.any(String));
|
|
291
291
|
});
|
|
292
292
|
|
|
293
293
|
it(`resolves a known concrete request back to its contract name`, () => {
|
package/src/protocol/routes.ts
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
|
-
import { eventIterator } from "@orpc/contract";
|
|
2
1
|
import { z } from "zod";
|
|
3
2
|
import type { RouteMeta } from "./route-meta.js";
|
|
3
|
+
import { FRAME, streamOf } from "./stream-of.js";
|
|
4
4
|
|
|
5
5
|
// Named route surface of the daemon's contract (`<group>.<route>`), derived automatically so nothing here is
|
|
6
6
|
// hand-maintained. The daemon advertises which routes it implements (the /events hello frame); the browser diffs that
|
|
7
7
|
// against its own contract so an old daemon's gap is a named feature check, not a silent 404.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
// standard-schema validator holding no reachable inner schema, so a stream's payload would be unfingerprintable and its
|
|
11
|
-
// drift invisible. The frame schema rides along under a symbol, which oRPC never reads and JSON never serializes.
|
|
12
|
-
// Typed as a plain `symbol`, not the inferred unique one: a unique symbol would ride into every contract's inferred
|
|
13
|
-
// type and break declaration emit on a local name nothing outside can refer to.
|
|
14
|
-
const FRAME: symbol = Symbol.for("intentic.contract.frame");
|
|
15
|
-
|
|
16
|
-
export const streamOf = <T extends z.ZodType>(frame: T) => Object.assign(eventIterator(frame), { [FRAME]: frame });
|
|
9
|
+
export { streamOf };
|
|
17
10
|
|
|
18
11
|
// The frame schema behind a streamed route's validator, or undefined for an ordinary request/response schema.
|
|
19
12
|
const frameOf = (schema: unknown): z.ZodType | undefined =>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { eventIterator } from "@orpc/contract";
|
|
2
|
+
import type * as z from "zod";
|
|
3
|
+
|
|
4
|
+
// Every streamed route declares its frames through this, never oRPC's `eventIterator` directly: that returns an opaque
|
|
5
|
+
// standard-schema validator holding no reachable inner schema, so a stream's payload would be unfingerprintable and its
|
|
6
|
+
// drift invisible. The frame schema rides along under a symbol, which oRPC never reads and JSON never serializes.
|
|
7
|
+
// Typed as a plain `symbol`, not the inferred unique one: a unique symbol would ride into every contract's inferred
|
|
8
|
+
// type and break declaration emit on a local name nothing outside can refer to.
|
|
9
|
+
// Its own module, apart from routes.ts, because the browser extension's contract streams too and routes.ts reaches
|
|
10
|
+
// zod's namespace at runtime, which the extension's bundle budget cannot carry.
|
|
11
|
+
export const FRAME: symbol = Symbol.for("intentic.contract.frame");
|
|
12
|
+
|
|
13
|
+
export const streamOf = <T extends z.ZodType>(frame: T) => Object.assign(eventIterator(frame), { [FRAME]: frame });
|
package/src/schemas/activity.ts
CHANGED
|
@@ -26,7 +26,7 @@ export const ActivityEventSchema = z.object({
|
|
|
26
26
|
type: z
|
|
27
27
|
.string()
|
|
28
28
|
.describe(
|
|
29
|
-
"Exactly what happened: a message received or sent, a reaction, a turn starting or ending, a rule doing something. A rule that ran and passed says nothing here, because a feed of
|
|
29
|
+
"Exactly what happened: a message received or sent, a reaction, a turn starting or ending, a rule doing something. A rule that ran and passed says nothing here, because a feed of passes is one the eye learns to skip.",
|
|
30
30
|
),
|
|
31
31
|
channelId: z.string().optional().describe("Which channel or thread it happened in."),
|
|
32
32
|
// Inbound author display name.
|
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
|
|
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 failed 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
|
|
@@ -488,6 +488,9 @@ export type ConversationQueue = z.infer<typeof ConversationQueueSchema>;
|
|
|
488
488
|
// What resuming a queue did: started a turn with what waited, when nothing else ran.
|
|
489
489
|
export const QueueResumedSchema = z.object({
|
|
490
490
|
run: z.string().optional().describe("The turn the waiting messages started. Absent when a turn was already running, and they go after it."),
|
|
491
|
+
queue: ConversationQueueSchema.optional().describe(
|
|
492
|
+
"The queue the release left: what still waits once the turn took what it could. Absent from sandboxes older than the field.",
|
|
493
|
+
),
|
|
491
494
|
});
|
|
492
495
|
export type QueueResumed = z.infer<typeof QueueResumedSchema>;
|
|
493
496
|
// Attaches to a conversation's newest run (live, or finished within retention); no cursor to resume from, the head
|
package/src/schemas/agents.ts
CHANGED
|
@@ -89,7 +89,7 @@ export const UnfinishedWorkSchema = z.object({
|
|
|
89
89
|
})
|
|
90
90
|
.optional()
|
|
91
91
|
.describe("The agent's own checklist where that turn left it. Absent for a conversation that kept no list."),
|
|
92
|
-
// Name of the `turn.ending` check still
|
|
92
|
+
// Name of the `turn.ending` check still failing when the turn ended. Retired with that check: nothing writes it now, and
|
|
93
93
|
// it stays so entries written before still read.
|
|
94
94
|
check: z
|
|
95
95
|
.string()
|
|
@@ -98,7 +98,7 @@ export const UnfinishedWorkSchema = z.object({
|
|
|
98
98
|
});
|
|
99
99
|
export type UnfinishedWork = z.infer<typeof UnfinishedWorkSchema>;
|
|
100
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
|
|
101
|
-
// badges instead of sending the turn back to prove anything. CI checks what the owner pushes (
|
|
101
|
+
// badges instead of sending the turn back to prove anything. CI checks what the owner pushes (schemas/ci.ts).
|
|
102
102
|
export const TurnProofSchema = z.object({
|
|
103
103
|
at: z.number().describe("When the turn that left this ended, in milliseconds."),
|
|
104
104
|
verification: z
|
|
@@ -432,6 +432,32 @@ export const AgentSummarySchema = z.object({
|
|
|
432
432
|
"Since when somebody's composer has held a message for it that they have not sent yet, in milliseconds. While set, the sandbox never archives it on its own for being idle.",
|
|
433
433
|
),
|
|
434
434
|
attention: AgentAttentionSchema.describe("Which kinds of waiting-for-you it is doing."),
|
|
435
|
+
// Beside `attention.permission`, which says only that one waits: the id an answer names and what it asks, so a card
|
|
436
|
+
// can offer the answer itself instead of a trip to the chat. The oldest one, while its turn runs.
|
|
437
|
+
permissionAsk: z
|
|
438
|
+
.object({
|
|
439
|
+
requestId: z.string().describe("Which request this is: the id an answer to it names."),
|
|
440
|
+
ask: z.string().describe("What it asks to do, on one line: the runtime's own sentence, else the tool's short name."),
|
|
441
|
+
})
|
|
442
|
+
.optional()
|
|
443
|
+
.describe(
|
|
444
|
+
"The oldest permission its running turn is waiting on, so it can be answered where the conversation is listed. Absent when none waits, and from a sandbox older than it.",
|
|
445
|
+
),
|
|
446
|
+
// Kept on the record until a land goes through, unlike `failure`, which the next turn clears: a land that broke
|
|
447
|
+
// leaves the work stuck on its branch however many turns run after it.
|
|
448
|
+
landFailure: z
|
|
449
|
+
.object({
|
|
450
|
+
reason: z.string().describe("Why it failed, in the words it failed with."),
|
|
451
|
+
code: z
|
|
452
|
+
.string()
|
|
453
|
+
.optional()
|
|
454
|
+
.describe("Which kind of failure it was, when the sandbox could tell: unlinked when its copy lost its link to the workspace. Absent otherwise."),
|
|
455
|
+
at: z.number().describe("When it failed, in milliseconds."),
|
|
456
|
+
})
|
|
457
|
+
.optional()
|
|
458
|
+
.describe(
|
|
459
|
+
"The last attempt to bring its work into the workspace failed, and why. Cleared by the next land that goes through. Absent when nothing failed, and from a sandbox older than it.",
|
|
460
|
+
),
|
|
435
461
|
// Only the causes that still hold, re-read live (conversations/land/standing.ts), never the stored report's own list: a
|
|
436
462
|
// blocker the user has since cleared is not something to offer them an action about.
|
|
437
463
|
conflictCauses: z
|
|
@@ -452,8 +478,8 @@ export const AgentSummarySchema = z.object({
|
|
|
452
478
|
// Completed turns and lifetime tool calls, the card's msgs/tools counters.
|
|
453
479
|
turns: z.number().optional().describe("Turns it has finished."),
|
|
454
480
|
toolUses: z.number().optional().describe("Tools it has used, over its whole life."),
|
|
455
|
-
// `running` reads the live subagent
|
|
456
|
-
//
|
|
481
|
+
// `running` reads the live subagent roster, `total` counts on the agent's own entry; a child's spend is its own,
|
|
482
|
+
// never folded into the parent's cost.
|
|
457
483
|
subagents: z
|
|
458
484
|
.object({
|
|
459
485
|
running: z.number().describe("Subagents working right now."),
|
|
@@ -479,6 +505,25 @@ export const AgentSummarySchema = z.object({
|
|
|
479
505
|
.object({
|
|
480
506
|
landed: z.number().describe("Paths this conversation merged in."),
|
|
481
507
|
present: z.number().describe("How many of them are still there, either pending or committed."),
|
|
508
|
+
// Read off who acted on the tree between the land and the moment it was found missing; only a sole candidate
|
|
509
|
+
// is named, so an unattributed removal says nothing rather than guessing.
|
|
510
|
+
removedBy: z
|
|
511
|
+
.discriminatedUnion("kind", [
|
|
512
|
+
z.object({
|
|
513
|
+
kind: z.literal("agent"),
|
|
514
|
+
id: z.string().describe("The conversation that took them out."),
|
|
515
|
+
title: z.string().optional().describe("What that conversation is called."),
|
|
516
|
+
}),
|
|
517
|
+
z.object({
|
|
518
|
+
kind: z.literal("person"),
|
|
519
|
+
email: z.string().optional().describe("Who it was, as the sandbox verified them. Absent when the request carried no identity."),
|
|
520
|
+
name: z.string().optional().describe("What to call them, when their sign-in carried a name."),
|
|
521
|
+
}),
|
|
522
|
+
])
|
|
523
|
+
.optional()
|
|
524
|
+
.describe(
|
|
525
|
+
"Who took them out, when the sandbox could tell: an agent working in the workspace, or a person throwing the changes away. Absent when it could not tell, and from a sandbox older than it.",
|
|
526
|
+
),
|
|
482
527
|
})
|
|
483
528
|
.optional()
|
|
484
529
|
.describe(
|
|
@@ -593,6 +638,12 @@ export const awaitsWake = (agent: { readonly awaitingWake?: boolean | undefined
|
|
|
593
638
|
export type AgentWatch = NonNullable<AgentSummary["watches"]>[number];
|
|
594
639
|
// One background job as a card carries it.
|
|
595
640
|
export type AgentJob = NonNullable<AgentSummary["jobs"]>[number];
|
|
641
|
+
// Who took landed work out of the workspace, as the card names them.
|
|
642
|
+
export type LandedRemover = NonNullable<NonNullable<AgentSummary["landedPresence"]>["removedBy"]>;
|
|
643
|
+
// A land that broke, kept on the card until one goes through.
|
|
644
|
+
export type LandFailure = NonNullable<AgentSummary["landFailure"]>;
|
|
645
|
+
// The permission a running turn waits on, as a card can answer it.
|
|
646
|
+
export type WaitingPermission = NonNullable<AgentSummary["permissionAsk"]>;
|
|
596
647
|
// AgentsListSchema is declared later, after AutomationApprovalSchema, since the fleet list carries held wakes and zod
|
|
597
648
|
// needs that type declared first.
|
|
598
649
|
export const AgentIdSchema = z.object({ id: z.string().min(1).describe("Which conversation.") });
|
|
@@ -623,6 +674,9 @@ export const AgentTranscriptQuerySchema = AgentIdSchema.extend({
|
|
|
623
674
|
export const AgentToolChildrenQuerySchema = AgentIdSchema.extend({
|
|
624
675
|
toolId: z.string().min(1).describe("Which tool call, by the id its card carries."),
|
|
625
676
|
});
|
|
677
|
+
export const AgentSubagentQuerySchema = AgentIdSchema.extend({
|
|
678
|
+
subagentId: z.string().min(1).describe("Which subagent this conversation's runtime ran in-process, by the id of the call that started it."),
|
|
679
|
+
});
|
|
626
680
|
// Absent `ids` archives every archivable finished agent (the lane's "Clear"); unarchive always names its own ids.
|
|
627
681
|
export const AgentIdsSchema = z.object({ ids: z.array(z.string().min(1)).min(1).max(500).describe("Which conversations.") });
|
|
628
682
|
// What actually moved, not the roster afterward: two archives finishing at once would otherwise let a slower snapshot
|
|
@@ -29,7 +29,7 @@ import { ZoneSchema } from "../time/zone.js";
|
|
|
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
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
|
|
32
|
+
// they stay so an automation written for them still reads, and it never fires. Main's failing 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.
|
|
@@ -45,7 +45,7 @@ export const WorkspaceEventSchema = z.object({
|
|
|
45
45
|
outcome: z.enum(["landed", "conflict", "ready", "idle", "error"]),
|
|
46
46
|
repos: z.array(z.object({ repo: z.string(), from: z.string(), dir: z.string() })),
|
|
47
47
|
// Present only for `deps.*` events: which project broke, the command and exit code, a bounded log tail, and
|
|
48
|
-
// `attempt` counting consecutive
|
|
48
|
+
// `attempt` counting consecutive failures since the last pass.
|
|
49
49
|
deps: z
|
|
50
50
|
.object({
|
|
51
51
|
project: z.string(),
|
|
@@ -244,7 +244,8 @@ export const LOCAL_MODEL_WINDOW_MAX = 1_048_576;
|
|
|
244
244
|
// against.
|
|
245
245
|
export const LOCAL_MODEL_KV_BYTES_PER_TOKEN = 65_536;
|
|
246
246
|
// What a rung honestly serves. `instant` is the one the connect view prefetches — it downloads in under a minute and
|
|
247
|
-
// cannot drive a full agent turn, so
|
|
247
|
+
// cannot drive a full agent turn, so its catalog row is helper-only (Model.helperOnly): the chat picker does not offer
|
|
248
|
+
// it and the daemon refuses a turn on it. `work` is what that view recommends for real use.
|
|
248
249
|
export type LocalModelTier = "instant" | "work";
|
|
249
250
|
export interface LocalModelChoice {
|
|
250
251
|
// Hugging Face owner/repo/file.gguf, exactly as LocalModelConfig.model carries it.
|
|
@@ -254,21 +255,57 @@ export interface LocalModelChoice {
|
|
|
254
255
|
// estimate all read this, so a model cannot be priced two ways.
|
|
255
256
|
readonly weightsBytes: number;
|
|
256
257
|
readonly tier: LocalModelTier;
|
|
258
|
+
// The repository commit the file is fetched at, a sha and never a branch: `main` moves, and a re-upload under the
|
|
259
|
+
// same name would change what a card promised without the card changing.
|
|
260
|
+
readonly revision: string;
|
|
261
|
+
// The file's own sha256 at that commit (its LFS oid). A download that does not hash to it is deleted, not served.
|
|
262
|
+
readonly sha256: string;
|
|
257
263
|
}
|
|
258
|
-
// The curated list, smallest first. ponytail:
|
|
264
|
+
// The curated list, smallest first. ponytail: sizes, revisions and digests are the Hugging Face API's answer for each
|
|
265
|
+
// file (`/api/models/<repo>/revision/main` for the sha, `/tree/<sha>` for the size and LFS oid), read 2026-09-29 — the
|
|
259
266
|
// hand-written labels that predated them overstated gemma-4 12B by 2x and Qwen3.8 27B by a third, which is the class of
|
|
260
|
-
// drift a machine-readable size exists to end.
|
|
267
|
+
// drift a machine-readable size exists to end. Bump a row's three together: each belongs to its revision.
|
|
261
268
|
export const LOCAL_MODELS: readonly LocalModelChoice[] = [
|
|
262
|
-
{
|
|
269
|
+
{
|
|
270
|
+
id: "unsloth/Qwen3.5-2B-GGUF/Qwen3.5-2B-Q4_K_M.gguf",
|
|
271
|
+
label: "Qwen3.5 2B",
|
|
272
|
+
weightsBytes: 1_280_835_840,
|
|
273
|
+
tier: "instant",
|
|
274
|
+
revision: "f6d5376be1edb4d416d56da11e5397a961aca8ae",
|
|
275
|
+
sha256: "aaf42c8b7c3cab2bf3d69c355048d4a0ee9973d48f16c731c0520ee914699223",
|
|
276
|
+
},
|
|
263
277
|
{
|
|
264
278
|
id: "unsloth/Phi-4-mini-instruct-GGUF/Phi-4-mini-instruct-Q4_K_M.gguf",
|
|
265
279
|
label: "Phi-4-mini 3.8B",
|
|
266
280
|
weightsBytes: 2_491_874_272,
|
|
267
281
|
tier: "work",
|
|
282
|
+
revision: "78eb92a46fc37e6b524df991ed9aca9bc6aa7b80",
|
|
283
|
+
sha256: "88c00229914083cd112853aab84ed51b87bdf6b9ce42f532d8c85c7c63b1730a",
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
id: "unsloth/Qwen3.5-9B-GGUF/Qwen3.5-9B-Q4_K_M.gguf",
|
|
287
|
+
label: "Qwen3.5 9B",
|
|
288
|
+
weightsBytes: 5_680_522_464,
|
|
289
|
+
tier: "work",
|
|
290
|
+
revision: "3885219b6810b007914f3a7950a8d1b469d598a5",
|
|
291
|
+
sha256: "03b74727a860a56338e042c4420bb3f04b2fec5734175f4cb9fa853daf52b7e8",
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
id: "unsloth/gemma-4-12b-it-GGUF/gemma-4-12b-it-Q4_K_M.gguf",
|
|
295
|
+
label: "Gemma 4 12B",
|
|
296
|
+
weightsBytes: 7_121_861_440,
|
|
297
|
+
tier: "work",
|
|
298
|
+
revision: "fc034cfff751157913579611efad8462ac1be606",
|
|
299
|
+
sha256: "0a270ec9fe6b34f4a0d33992b6135117b484ebc4766ab76b51d4ae8c457e4c42",
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
id: "unsloth/Qwen3.8-27B-GGUF/Qwen3.8-27B-UD-Q4_K_M.gguf",
|
|
303
|
+
label: "Qwen3.8 27B",
|
|
304
|
+
weightsBytes: 16_464_440_224,
|
|
305
|
+
tier: "work",
|
|
306
|
+
revision: "4ca720788d1e01f1bff70c033e0d0028fd02e502",
|
|
307
|
+
sha256: "322e194ff79741c7baa497c240f677f54b201b0efab44ca8e50f122b39123482",
|
|
268
308
|
},
|
|
269
|
-
{ id: "unsloth/Qwen3.5-9B-GGUF/Qwen3.5-9B-Q4_K_M.gguf", label: "Qwen3.5 9B", weightsBytes: 5_680_522_464, tier: "work" },
|
|
270
|
-
{ id: "unsloth/gemma-4-12b-it-GGUF/gemma-4-12b-it-Q4_K_M.gguf", label: "Gemma 4 12B", weightsBytes: 7_121_861_440, tier: "work" },
|
|
271
|
-
{ id: "unsloth/Qwen3.8-27B-GGUF/Qwen3.8-27B-UD-Q4_K_M.gguf", label: "Qwen3.8 27B", weightsBytes: 16_464_440_224, tier: "work" },
|
|
272
309
|
];
|
|
273
310
|
export const localModelChoice = (id: string): LocalModelChoice | undefined => LOCAL_MODELS.find((choice) => choice.id === id);
|
|
274
311
|
// Exactly one row carries the instant tier; the fit module and the prefetch route take it from here rather than
|
package/src/schemas/ci.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
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";
|
|
5
4
|
// The daemon maps each workspace repo to its CI project via a connected github/gitlab capability, registers a webhook
|
|
6
5
|
// to fire `ci` listener automations instantly, and serves the Pipelines rail from a webhook-freshened, poll-backfilled
|
|
7
6
|
// cache. `host` names which provider API serves a repo; the listener provider is always `ci`, since a trigger narrows
|
|
@@ -112,31 +111,72 @@ export type CiRepo = z.infer<typeof CiRepoSchema>;
|
|
|
112
111
|
// Fallback poll interval when a repo's webhook couldn't register; lives here since both the poller and the automation
|
|
113
112
|
// editor need it to tell the owner what a `hookWarning` actually costs (minutes' delay, not the feature).
|
|
114
113
|
export const CI_POLL_INTERVAL_MS = 2 * 60_000;
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
114
|
+
// What was decided about a main-line branch while its CI fails, in the order it was decided (ci/main-fixer.ts).
|
|
115
|
+
export const MainFailureDecisionKindSchema = z.enum([
|
|
116
|
+
// Its one fix agent was put on it: by the sandbox at the first job that failed, or by a person's Fix press, which
|
|
117
|
+
// gives the agent its turns back.
|
|
118
|
+
"fix-up",
|
|
119
|
+
// Nobody was sent: repairs are switched off, or nothing could take it.
|
|
120
|
+
"reported",
|
|
121
|
+
// The fix agent had its turns, or stopped without a fix: it waits for a person.
|
|
122
|
+
"spent",
|
|
123
|
+
]);
|
|
124
|
+
export type MainFailureDecisionKind = z.infer<typeof MainFailureDecisionKindSchema>;
|
|
125
|
+
|
|
126
|
+
// Why the fix agent handed the failure back (a `spent` decision), as a closed set the editor words for itself, so what a
|
|
127
|
+
// reader sees stays a few words in every language rather than whatever the turn's error happened to say.
|
|
128
|
+
export const MainFailureHandBackSchema = z.enum([
|
|
129
|
+
// It had every turn the sandbox gives one failure, and main still fails.
|
|
130
|
+
"turns",
|
|
131
|
+
// Its turn ended without changing anything, so the failure is likely not in the code.
|
|
132
|
+
"no-change",
|
|
133
|
+
// A person stopped it.
|
|
134
|
+
"stopped",
|
|
135
|
+
// Its turn was cut off.
|
|
136
|
+
"interrupted",
|
|
137
|
+
// Its turn failed.
|
|
138
|
+
"turn-failed",
|
|
139
|
+
// Its conversation is gone or archived.
|
|
140
|
+
"gone",
|
|
141
|
+
// It could not be told about the newest failure.
|
|
142
|
+
"refused",
|
|
143
|
+
]);
|
|
144
|
+
export type MainFailureHandBack = z.infer<typeof MainFailureHandBackSchema>;
|
|
145
|
+
|
|
146
|
+
export const MainFailureDecisionSchema = z.object({
|
|
147
|
+
kind: MainFailureDecisionKindSchema.describe("What was decided."),
|
|
148
|
+
reason: MainFailureHandBackSchema.optional().describe("Why the fix agent handed it back, on a `spent` decision."),
|
|
149
|
+
conversationId: z.string().optional().describe("The conversation working on it, when one is."),
|
|
150
|
+
at: z.number().describe("When that was decided, in milliseconds."),
|
|
151
|
+
detail: z.string().optional().describe("One short sentence on why, in the sandbox's words."),
|
|
152
|
+
});
|
|
153
|
+
export type MainFailureDecision = z.infer<typeof MainFailureDecisionSchema>;
|
|
154
|
+
|
|
155
|
+
// A main-line branch while its CI fails, as the daemon's one fix agent on it sees it (ci/main-fixer.ts): from the first
|
|
156
|
+
// job that failed until a later run of every workflow that failed on it passes, whatever else failed along the way.
|
|
157
|
+
export const CiMainFailureSchema = z.object({
|
|
118
158
|
repo: z.string().describe("Which workspace repository."),
|
|
119
159
|
branch: z.string().describe("Which main-line branch."),
|
|
120
160
|
since: z.number().describe("When its first job failed, in milliseconds."),
|
|
121
161
|
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
|
|
162
|
+
jobs: z.array(z.string()).describe("The jobs failing on it now, by name: the newest failed run's failures, and any that failed since."),
|
|
123
163
|
fixer: z
|
|
124
164
|
.string()
|
|
125
165
|
.optional()
|
|
126
|
-
.describe("The one conversation working on it, which every failure goes to until the branch
|
|
127
|
-
decision:
|
|
166
|
+
.describe("The one conversation working on it, which every failure goes to until the branch passes. Absent while nobody is on it."),
|
|
167
|
+
decision: MainFailureDecisionSchema.optional().describe(
|
|
128
168
|
"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
169
|
),
|
|
130
170
|
});
|
|
131
|
-
export type
|
|
171
|
+
export type CiMainFailure = z.infer<typeof CiMainFailureSchema>;
|
|
132
172
|
export const CiRunsResponseSchema = z.object({
|
|
133
173
|
repos: z.array(CiRepoSchema).describe("Which workspace repositories are wired to a forge, and how each one's notifications are set up."),
|
|
134
174
|
// Newest first, across all mapped repos.
|
|
135
175
|
runs: z.array(PipelineRunSchema).describe("Runs across all of them, newest first."),
|
|
136
|
-
|
|
137
|
-
.array(
|
|
176
|
+
failures: z
|
|
177
|
+
.array(CiMainFailureSchema)
|
|
138
178
|
.optional()
|
|
139
|
-
.describe("Every main-line branch
|
|
179
|
+
.describe("Every main-line branch failing right now, with the fix agent on it. Absent from a daemon that keeps none."),
|
|
140
180
|
});
|
|
141
181
|
export type CiRunsResponse = z.infer<typeof CiRunsResponseSchema>;
|
|
142
182
|
// Re-resolves repo → project + token on every call, so a stale card can't act on a mapping the workspace no longer has.
|
|
@@ -155,6 +195,11 @@ export const CiFixParamSchema = CiRunParamSchema.extend({
|
|
|
155
195
|
pick: AgentRunPickSchema.describe(
|
|
156
196
|
"Which model to open the conversation on, when somebody chose one. Leave it out for the sandbox's own choice, which is the ordinary path.",
|
|
157
197
|
),
|
|
198
|
+
// The browser's own chat pick, which the daemon never holds: without it an unpinned fix opened on a provider the
|
|
199
|
+
// owner never chose. Absent (an older app, or nobody pressed) keeps the sandbox's own fallback.
|
|
200
|
+
fallback: AgentRunPickSchema.describe(
|
|
201
|
+
"The model a new chat opens on for whoever pressed, for when nobody chose one and no model set for fixing pipelines can run. Used while this sandbox can serve its provider; left out, or not servable, the sandbox takes its default provider when connected, else the first one connected.",
|
|
202
|
+
),
|
|
158
203
|
// The verb the picker's bar was ended with, when an attempt already exists (state/fix-attempt-plan.ts). Left out,
|
|
159
204
|
// an ended attempt is continued and a fresh press opens attempt 1; one still in play answers CONFLICT.
|
|
160
205
|
mode: z
|