@capybara-hq/kernel 0.1.0-rc.21 → 0.1.0-rc.22
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/dist/agents/admin-agents.d.ts +16 -3
- package/dist/agents/admin-agents.js +65 -7
- package/dist/agents/agents.schemas.d.ts +24 -6
- package/dist/agents/agents.service.d.ts +4 -3
- package/dist/agents/agents.service.js +39 -26
- package/dist/agents/definition.d.ts +44 -6
- package/dist/agents/definition.js +86 -18
- package/dist/agents/from-template.d.ts +6 -3
- package/dist/agents/from-template.js +57 -44
- package/dist/agents/org-templates.js +97 -41
- package/dist/agents/templates.d.ts +6 -0
- package/dist/agents/templates.js +15 -1
- package/dist/catalog/admin-catalog.controller.d.ts +2 -4
- package/dist/catalog/admin-catalog.controller.js +2 -6
- package/dist/catalog/catalog.module.js +3 -0
- package/dist/catalog/claude/report.d.ts +9 -2
- package/dist/catalog/claude/report.js +1 -0
- package/dist/catalog/claude/translate.js +1 -1
- package/dist/catalog/component-promotion.js +1 -2
- package/dist/catalog/component-state.d.ts +13 -1
- package/dist/catalog/component-state.js +14 -5
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/catalog/marketplaces.service.js +2 -1
- package/dist/catalog/org-catalog.controller.d.ts +13 -3
- package/dist/catalog/org-catalog.controller.js +26 -4
- package/dist/catalog/org-catalog.schemas.d.ts +13 -0
- package/dist/catalog/org-catalog.schemas.js +34 -0
- package/dist/catalog/org-plugin-uploads.controller.js +0 -1
- package/dist/catalog/org-plugin-uploads.d.ts +0 -1
- package/dist/catalog/org-plugin-uploads.js +0 -3
- package/dist/catalog/org-plugin-uploads.schemas.d.ts +0 -1
- package/dist/catalog/org-plugin-uploads.schemas.js +0 -2
- package/dist/catalog/platform-components.d.ts +0 -1
- package/dist/catalog/platform-components.js +3 -8
- package/dist/catalog/plugin-import.d.ts +0 -8
- package/dist/catalog/plugin-import.js +4 -35
- package/dist/catalog/plugin-install.d.ts +2 -2
- package/dist/catalog/plugin-install.js +2 -2
- package/dist/catalog/plugin-package.d.ts +7 -0
- package/dist/catalog/plugin-package.js +27 -1
- package/dist/catalog/plugin-upgrade.d.ts +5 -6
- package/dist/catalog/plugin-upgrade.js +5 -23
- package/dist/catalog/requests.service.d.ts +66 -10
- package/dist/catalog/requests.service.js +196 -15
- package/dist/channels/actions.d.ts +2 -0
- package/dist/channels/actions.js +19 -4
- package/dist/channels/cards.d.ts +16 -2
- package/dist/channels/cards.js +40 -7
- package/dist/channels/channels.module.js +3 -0
- package/dist/channels/outbound.d.ts +22 -1
- package/dist/channels/outbound.js +116 -7
- package/dist/channels/phrases.d.ts +25 -0
- package/dist/channels/phrases.js +19 -0
- package/dist/channels/threads.d.ts +12 -0
- package/dist/channels/threads.js +34 -0
- package/dist/config/schema.d.ts +23 -1
- package/dist/config/schema.js +105 -5
- package/dist/conversations/conversations.service.d.ts +42 -9
- package/dist/conversations/conversations.service.js +57 -25
- package/dist/conversations/events.d.ts +106 -11
- package/dist/conversations/events.js +121 -18
- package/dist/conversations/message-context.d.ts +6 -3
- package/dist/conversations/message-context.js +14 -10
- package/dist/conversations/queue.d.ts +15 -2
- package/dist/conversations/queue.js +22 -3
- package/dist/conversations/queue.service.d.ts +12 -6
- package/dist/conversations/queue.service.js +36 -13
- package/dist/db/kernel-schema.d.ts +105 -3
- package/dist/devices/conversation-devices.d.ts +8 -1
- package/dist/devices/conversation-devices.js +8 -0
- package/dist/devices/devices.controller.d.ts +21 -4
- package/dist/devices/devices.controller.js +46 -8
- package/dist/devices/devices.schemas.d.ts +9 -1
- package/dist/devices/devices.schemas.js +11 -3
- package/dist/devices/devices.service.d.ts +176 -4
- package/dist/devices/devices.service.js +341 -6
- package/dist/devices/dto.d.ts +56 -3
- package/dist/devices/dto.js +51 -1
- package/dist/devices/public-suffix.d.ts +6 -0
- package/dist/devices/public-suffix.js +79 -0
- package/dist/errors.d.ts +46 -5
- package/dist/errors.js +46 -5
- package/dist/evals/reconstruct.d.ts +18 -13
- package/dist/evals/reconstruct.js +50 -36
- package/dist/evals/recorded-tools.d.ts +7 -7
- package/dist/evals/recorded-tools.js +4 -3
- package/dist/evals/trajectory.d.ts +6 -1
- package/dist/evals/trajectory.js +7 -0
- package/dist/extensions/package.js +2 -2
- package/dist/http/api.module.js +5 -0
- package/dist/inbox/inbox.module.js +2 -1
- package/dist/inbox/inbox.service.d.ts +19 -4
- package/dist/inbox/inbox.service.js +14 -3
- package/dist/infra/config/kernel-config.service.js +11 -0
- package/dist/infra/events/kernel-events.d.ts +2 -1
- package/dist/local-devices/device-policy.service.d.ts +65 -0
- package/dist/local-devices/device-policy.service.js +217 -0
- package/dist/local-devices/local-device-gateway.js +4 -1
- package/dist/local-devices/local-device-provider.d.ts +40 -3
- package/dist/local-devices/local-device-provider.js +109 -7
- package/dist/local-devices/local-devices.module.d.ts +2 -1
- package/dist/local-devices/local-devices.module.js +19 -3
- package/dist/local-devices/machines.d.ts +14 -3
- package/dist/local-devices/machines.js +41 -3
- package/dist/local-devices/network-grants.d.ts +104 -0
- package/dist/local-devices/network-grants.js +288 -0
- package/dist/local-devices/policy-summary.d.ts +20 -0
- package/dist/local-devices/policy-summary.js +63 -0
- package/dist/orgs/orgs.module.js +4 -10
- package/dist/orgs/roles.d.ts +1 -1
- package/dist/orgs/roles.js +4 -0
- package/dist/orgs/settings.controller.js +2 -1
- package/dist/orgs/settings.service.d.ts +164 -12
- package/dist/orgs/settings.service.js +212 -24
- package/dist/platform/exclusions.js +11 -1
- package/dist/platform/operation.js +4 -0
- package/dist/platform/ops/admin-items.ops.js +1 -1
- package/dist/platform/ops/agent-skills.ops.js +2 -2
- package/dist/platform/ops/agent-templates.ops.js +2 -2
- package/dist/platform/ops/agents.ops.js +11 -15
- package/dist/platform/ops/devices.ops.js +3 -1
- package/dist/platform/ops/inbox.ops.js +19 -0
- package/dist/platform/ops/org-settings.ops.js +14 -5
- package/dist/platform/ops/platform.ops.d.ts +9 -1
- package/dist/platform/ops/platform.ops.js +34 -12
- package/dist/platform/ops/requests.ops.js +3 -2
- package/dist/platform/ops/schedules.ops.d.ts +21 -0
- package/dist/platform/ops/schedules.ops.js +470 -0
- package/dist/platform/ops/skills.ops.js +3 -3
- package/dist/platform/platform-ops.module.d.ts +2 -1
- package/dist/platform/platform-ops.module.js +5 -0
- package/dist/platform/route-schemas.js +3 -0
- package/dist/roles/worker.module.js +3 -0
- package/dist/runtime/context/build.js +13 -4
- package/dist/runtime/context/compaction.js +6 -3
- package/dist/runtime/context/device-paths.d.ts +43 -21
- package/dist/runtime/context/device-paths.js +104 -21
- package/dist/runtime/context/run-context.d.ts +9 -0
- package/dist/runtime/context/run-context.js +8 -0
- package/dist/runtime/context/skills.d.ts +14 -1
- package/dist/runtime/context/skills.js +36 -5
- package/dist/runtime/maintenance.d.ts +4 -2
- package/dist/runtime/maintenance.js +16 -5
- package/dist/runtime/run-finish.d.ts +6 -1
- package/dist/runtime/run-finish.js +16 -2
- package/dist/runtime/run-queue.js +21 -2
- package/dist/runtime/run-worker.js +14 -33
- package/dist/runtime/runtime.module.js +3 -0
- package/dist/runtime/subagents/definition.d.ts +32 -14
- package/dist/runtime/subagents/definition.js +106 -109
- package/dist/runtime/subagents/dispatch.js +4 -3
- package/dist/runtime/subagents/preamble.d.ts +2 -1
- package/dist/runtime/subagents/preamble.js +2 -1
- package/dist/runtime/waits.js +14 -4
- package/dist/sandbox/sandbox.service.d.ts +120 -10
- package/dist/sandbox/sandbox.service.js +335 -28
- package/dist/sandbox/tiers.d.ts +86 -0
- package/dist/sandbox/tiers.js +96 -0
- package/dist/schedules/effective-settings.d.ts +16 -0
- package/dist/schedules/effective-settings.js +17 -0
- package/dist/schedules/errors.d.ts +8 -0
- package/dist/schedules/errors.js +8 -0
- package/dist/schedules/fire.d.ts +118 -0
- package/dist/schedules/fire.js +625 -0
- package/dist/schedules/grant-rules.d.ts +94 -0
- package/dist/schedules/grant-rules.js +126 -0
- package/dist/schedules/job-ids.d.ts +32 -0
- package/dist/schedules/job-ids.js +27 -0
- package/dist/schedules/pause.d.ts +9 -0
- package/dist/schedules/pause.js +33 -0
- package/dist/schedules/run-tracker.d.ts +81 -0
- package/dist/schedules/run-tracker.js +333 -0
- package/dist/schedules/scan.job.d.ts +64 -0
- package/dist/schedules/scan.job.js +303 -0
- package/dist/schedules/schedule-math.d.ts +31 -0
- package/dist/schedules/schedule-math.js +223 -0
- package/dist/schedules/scheduled-tasks.service.d.ts +351 -0
- package/dist/schedules/scheduled-tasks.service.js +1193 -0
- package/dist/schedules/schedules.controller.d.ts +68 -0
- package/dist/schedules/schedules.controller.js +242 -0
- package/dist/schedules/schedules.module.d.ts +16 -0
- package/dist/schedules/schedules.module.js +65 -0
- package/dist/schedules/schedules.schemas.d.ts +316 -0
- package/dist/schedules/schedules.schemas.js +186 -0
- package/dist/schedules/store.d.ts +81 -0
- package/dist/schedules/store.js +390 -0
- package/dist/schedules/store.module.d.ts +6 -0
- package/dist/schedules/store.module.js +21 -0
- package/dist/schedules/types.d.ts +108 -0
- package/dist/schedules/types.js +1 -0
- package/dist/schedules/unattended.d.ts +117 -0
- package/dist/schedules/unattended.js +228 -0
- package/dist/setup/oauth-token-probe.js +8 -4
- package/dist/skills/agent-skills.service.d.ts +4 -8
- package/dist/skills/agent-skills.service.js +18 -53
- package/dist/skills/catalog.d.ts +65 -62
- package/dist/skills/catalog.js +109 -206
- package/dist/skills/conversation-catalog.d.ts +3 -3
- package/dist/skills/conversation-catalog.js +5 -12
- package/dist/skills/invoke.d.ts +2 -2
- package/dist/skills/skills.controller.d.ts +3 -1
- package/dist/skills/skills.service.d.ts +38 -37
- package/dist/skills/skills.service.js +168 -107
- package/dist/skills/usable.d.ts +57 -8
- package/dist/skills/usable.js +172 -38
- package/dist/tools/approvals.controller.d.ts +4 -2
- package/dist/tools/approvals.controller.js +17 -2
- package/dist/tools/approvals.schemas.d.ts +1 -0
- package/dist/tools/approvals.schemas.js +3 -1
- package/dist/tools/approvals.service.d.ts +182 -7
- package/dist/tools/approvals.service.js +597 -13
- package/dist/tools/builtin-names.d.ts +25 -4
- package/dist/tools/builtin-names.js +45 -4
- package/dist/tools/builtins/builtin-tools.d.ts +8 -0
- package/dist/tools/builtins/builtin-tools.js +23 -2
- package/dist/tools/builtins/sandbox/bash.js +16 -7
- package/dist/tools/builtins/sandbox/common.d.ts +1 -1
- package/dist/tools/builtins/sandbox/common.js +33 -10
- package/dist/tools/builtins/sandbox/edit.js +7 -3
- package/dist/tools/builtins/sandbox/file-state.d.ts +31 -1
- package/dist/tools/builtins/sandbox/file-state.js +33 -3
- package/dist/tools/builtins/sandbox/write.js +8 -4
- package/dist/tools/builtins/schedules.d.ts +107 -0
- package/dist/tools/builtins/schedules.js +789 -0
- package/dist/tools/elicitation.d.ts +2 -0
- package/dist/tools/elicitation.js +1 -1
- package/dist/tools/fixed-ask.d.ts +7 -0
- package/dist/tools/fixed-ask.js +9 -0
- package/dist/tools/network-asks.d.ts +109 -0
- package/dist/tools/network-asks.js +221 -0
- package/dist/tools/pipeline.d.ts +53 -6
- package/dist/tools/pipeline.js +202 -13
- package/dist/tools/policy.d.ts +21 -8
- package/dist/tools/policy.js +63 -9
- package/dist/tools/registry.d.ts +10 -1
- package/dist/tools/tools.module.js +12 -0
- package/package.json +2 -2
- package/src/migrations/0113_scheduled_tasks.sql +108 -0
- package/src/migrations/0114_network_asks.sql +23 -0
- package/src/migrations/0115_network_requests.sql +28 -0
- package/src/migrations/0116_skills_default_on.sql +37 -0
- package/src/migrations/0117_sandbox_tiers.sql +11 -0
|
@@ -27,6 +27,10 @@ export declare const BUILTIN: {
|
|
|
27
27
|
readonly WebFetch: "WebFetch";
|
|
28
28
|
readonly Platform: "Platform";
|
|
29
29
|
readonly CurrentTime: "CurrentTime";
|
|
30
|
+
readonly ScheduleCreate: "ScheduleCreate";
|
|
31
|
+
readonly ScheduleUpdate: "ScheduleUpdate";
|
|
32
|
+
readonly ScheduleList: "ScheduleList";
|
|
33
|
+
readonly ScheduleDelete: "ScheduleDelete";
|
|
30
34
|
};
|
|
31
35
|
export type BuiltinName = (typeof BUILTIN)[keyof typeof BUILTIN];
|
|
32
36
|
/**
|
|
@@ -47,6 +51,11 @@ export declare const BASH_COMPANION_TOOLS: readonly BuiltinName[];
|
|
|
47
51
|
* already went through `Bash`'s policy (background Bash spec §4.5).
|
|
48
52
|
*/
|
|
49
53
|
export declare const SANDBOX_ASK_TOOL_NAMES: ReadonlySet<string>;
|
|
54
|
+
/**
|
|
55
|
+
* The builtins that manage the person's scheduled tasks (scheduled tasks spec §11): create and
|
|
56
|
+
* update are fixed asks ({@link R3_FIXED_ASK_TOOL_KEYS}), list and delete only read or narrow.
|
|
57
|
+
*/
|
|
58
|
+
export declare const SCHEDULE_TOOL_NAMES: readonly BuiltinName[];
|
|
50
59
|
/**
|
|
51
60
|
* Every builtin name. Saving an agent checks turned-off builtins against this list, not against
|
|
52
61
|
* what is registered now, so the order the implementations land in never affects a saved
|
|
@@ -54,7 +63,8 @@ export declare const SANDBOX_ASK_TOOL_NAMES: ReadonlySet<string>;
|
|
|
54
63
|
* by the registry; `Agent` dispatches sub-agents and its description is built per agent;
|
|
55
64
|
* `SendMessage` and `SubagentHandback` are added by the registry ({@link SUBAGENT_MESSAGE_TOOLS}).
|
|
56
65
|
* `Platform` calls the platform's own operations as the run's person (Platform tool spec).
|
|
57
|
-
* `CurrentTime` reads the clock (the run context carries only the date).
|
|
66
|
+
* `CurrentTime` reads the clock (the run context carries only the date). The schedule tools
|
|
67
|
+
* manage the person's scheduled tasks ({@link SCHEDULE_TOOL_NAMES}).
|
|
58
68
|
*/
|
|
59
69
|
export declare const BUILTIN_TOOL_NAMES: readonly BuiltinName[];
|
|
60
70
|
/**
|
|
@@ -80,12 +90,23 @@ export declare const SUBAGENT_HANDBACK: "SubagentHandback";
|
|
|
80
90
|
* `SubagentHandback` to a sub-agent. A definition's ref to either is ignored.
|
|
81
91
|
*/
|
|
82
92
|
export declare const SUBAGENT_MESSAGE_TOOLS: ReadonlySet<string>;
|
|
93
|
+
/**
|
|
94
|
+
/**
|
|
95
|
+
* Tool keys on the spec 3.6 R3 fixed list ("allowed is not confirmed"): their policy is at least
|
|
96
|
+
* `ask` and remembered grants never let them through. `ScheduleCreate` and `ScheduleUpdate` set
|
|
97
|
+
* what a task runs unattended, so a person approves every task an agent creates or widens
|
|
98
|
+
* (scheduled tasks spec §11), never covered by a task grant and refused in an unattended run in
|
|
99
|
+
* `fail` mode. Connector and extension tools express the same rule with
|
|
100
|
+
* `annotations.requiresUserInteraction`.
|
|
101
|
+
*/
|
|
102
|
+
export declare const R3_FIXED_ASK_TOOL_KEYS: ReadonlySet<string>;
|
|
83
103
|
/**
|
|
84
104
|
* Tools a sub-agent never gets (sub-agents spec §5.3, §5.5): `AskUserQuestion` (the person does
|
|
85
|
-
* not see the child)
|
|
86
|
-
*
|
|
105
|
+
* not see the child), `Deliver` (delivering files is the parent's job) and the schedule tools (a
|
|
106
|
+
* person manages tasks from the conversation they see, never from a child). `Agent` is removed
|
|
107
|
+
* only from a child at `subagents.maxDepth` (agent map spec §4.1, `effectiveDefinition`).
|
|
87
108
|
*/
|
|
88
|
-
export declare const CHILD_REMOVED_TOOLS: readonly ["AskUserQuestion", "Deliver"];
|
|
109
|
+
export declare const CHILD_REMOVED_TOOLS: readonly ["AskUserQuestion", "Deliver", "ScheduleCreate", "ScheduleUpdate", "ScheduleList", "ScheduleDelete"];
|
|
89
110
|
/**
|
|
90
111
|
* The builtins every agent has unless it turns them off (tools default-on spec §4), in this
|
|
91
112
|
* order: {@link BUILTIN_TOOL_NAMES} without the ones the registry manages by rule (`ToolSearch`,
|
|
@@ -27,6 +27,10 @@ export const BUILTIN = {
|
|
|
27
27
|
WebFetch: "WebFetch",
|
|
28
28
|
Platform: "Platform",
|
|
29
29
|
CurrentTime: "CurrentTime",
|
|
30
|
+
ScheduleCreate: "ScheduleCreate",
|
|
31
|
+
ScheduleUpdate: "ScheduleUpdate",
|
|
32
|
+
ScheduleList: "ScheduleList",
|
|
33
|
+
ScheduleDelete: "ScheduleDelete",
|
|
30
34
|
};
|
|
31
35
|
/**
|
|
32
36
|
* The builtin tools that run in the conversation's sandbox. `TaskOutput` and `TaskStop` read and
|
|
@@ -61,6 +65,16 @@ export const SANDBOX_ASK_TOOL_NAMES = new Set([
|
|
|
61
65
|
BUILTIN.Edit,
|
|
62
66
|
BUILTIN.Deliver,
|
|
63
67
|
]);
|
|
68
|
+
/**
|
|
69
|
+
* The builtins that manage the person's scheduled tasks (scheduled tasks spec §11): create and
|
|
70
|
+
* update are fixed asks ({@link R3_FIXED_ASK_TOOL_KEYS}), list and delete only read or narrow.
|
|
71
|
+
*/
|
|
72
|
+
export const SCHEDULE_TOOL_NAMES = [
|
|
73
|
+
BUILTIN.ScheduleCreate,
|
|
74
|
+
BUILTIN.ScheduleUpdate,
|
|
75
|
+
BUILTIN.ScheduleList,
|
|
76
|
+
BUILTIN.ScheduleDelete,
|
|
77
|
+
];
|
|
64
78
|
/**
|
|
65
79
|
* Every builtin name. Saving an agent checks turned-off builtins against this list, not against
|
|
66
80
|
* what is registered now, so the order the implementations land in never affects a saved
|
|
@@ -68,7 +82,8 @@ export const SANDBOX_ASK_TOOL_NAMES = new Set([
|
|
|
68
82
|
* by the registry; `Agent` dispatches sub-agents and its description is built per agent;
|
|
69
83
|
* `SendMessage` and `SubagentHandback` are added by the registry ({@link SUBAGENT_MESSAGE_TOOLS}).
|
|
70
84
|
* `Platform` calls the platform's own operations as the run's person (Platform tool spec).
|
|
71
|
-
* `CurrentTime` reads the clock (the run context carries only the date).
|
|
85
|
+
* `CurrentTime` reads the clock (the run context carries only the date). The schedule tools
|
|
86
|
+
* manage the person's scheduled tasks ({@link SCHEDULE_TOOL_NAMES}).
|
|
72
87
|
*/
|
|
73
88
|
export const BUILTIN_TOOL_NAMES = [
|
|
74
89
|
BUILTIN.AskUserQuestion,
|
|
@@ -82,6 +97,7 @@ export const BUILTIN_TOOL_NAMES = [
|
|
|
82
97
|
...WEB_TOOL_NAMES,
|
|
83
98
|
BUILTIN.Platform,
|
|
84
99
|
BUILTIN.CurrentTime,
|
|
100
|
+
...SCHEDULE_TOOL_NAMES,
|
|
85
101
|
];
|
|
86
102
|
const BUILTIN_NAME_SET = new Set(Object.values(BUILTIN));
|
|
87
103
|
/**
|
|
@@ -112,12 +128,33 @@ export const SUBAGENT_MESSAGE_TOOLS = new Set([
|
|
|
112
128
|
SEND_MESSAGE,
|
|
113
129
|
SUBAGENT_HANDBACK,
|
|
114
130
|
]);
|
|
131
|
+
/**
|
|
132
|
+
/**
|
|
133
|
+
* Tool keys on the spec 3.6 R3 fixed list ("allowed is not confirmed"): their policy is at least
|
|
134
|
+
* `ask` and remembered grants never let them through. `ScheduleCreate` and `ScheduleUpdate` set
|
|
135
|
+
* what a task runs unattended, so a person approves every task an agent creates or widens
|
|
136
|
+
* (scheduled tasks spec §11), never covered by a task grant and refused in an unattended run in
|
|
137
|
+
* `fail` mode. Connector and extension tools express the same rule with
|
|
138
|
+
* `annotations.requiresUserInteraction`.
|
|
139
|
+
*/
|
|
140
|
+
export const R3_FIXED_ASK_TOOL_KEYS = new Set([
|
|
141
|
+
BUILTIN.ScheduleCreate,
|
|
142
|
+
BUILTIN.ScheduleUpdate,
|
|
143
|
+
]);
|
|
115
144
|
/**
|
|
116
145
|
* Tools a sub-agent never gets (sub-agents spec §5.3, §5.5): `AskUserQuestion` (the person does
|
|
117
|
-
* not see the child)
|
|
118
|
-
*
|
|
146
|
+
* not see the child), `Deliver` (delivering files is the parent's job) and the schedule tools (a
|
|
147
|
+
* person manages tasks from the conversation they see, never from a child). `Agent` is removed
|
|
148
|
+
* only from a child at `subagents.maxDepth` (agent map spec §4.1, `effectiveDefinition`).
|
|
119
149
|
*/
|
|
120
|
-
export const CHILD_REMOVED_TOOLS = [
|
|
150
|
+
export const CHILD_REMOVED_TOOLS = [
|
|
151
|
+
BUILTIN.AskUserQuestion,
|
|
152
|
+
BUILTIN.Deliver,
|
|
153
|
+
BUILTIN.ScheduleCreate,
|
|
154
|
+
BUILTIN.ScheduleUpdate,
|
|
155
|
+
BUILTIN.ScheduleList,
|
|
156
|
+
BUILTIN.ScheduleDelete,
|
|
157
|
+
];
|
|
121
158
|
/**
|
|
122
159
|
* The builtins every agent has unless it turns them off (tools default-on spec §4), in this
|
|
123
160
|
* order: {@link BUILTIN_TOOL_NAMES} without the ones the registry manages by rule (`ToolSearch`,
|
|
@@ -150,6 +187,10 @@ export const BUILTIN_TOOL_KINDS = {
|
|
|
150
187
|
[BUILTIN.WebFetch]: "read",
|
|
151
188
|
[BUILTIN.Platform]: "tool",
|
|
152
189
|
[BUILTIN.CurrentTime]: "tool",
|
|
190
|
+
[BUILTIN.ScheduleCreate]: "tool",
|
|
191
|
+
[BUILTIN.ScheduleUpdate]: "tool",
|
|
192
|
+
[BUILTIN.ScheduleList]: "tool",
|
|
193
|
+
[BUILTIN.ScheduleDelete]: "tool",
|
|
153
194
|
};
|
|
154
195
|
/**
|
|
155
196
|
* Input fields left out when matching a replayed call with a recorded one (full-run replay), per
|
|
@@ -16,9 +16,11 @@ import type { HookRunner } from "../../runtime/hook-calls.js";
|
|
|
16
16
|
import { StepLog } from "../../runtime/step-log.js";
|
|
17
17
|
import { UsageReporter } from "../../runtime/usage-report.js";
|
|
18
18
|
import { SandboxService } from "../../sandbox/sandbox.service.js";
|
|
19
|
+
import { ScheduledTaskStore } from "../../schedules/store.js";
|
|
19
20
|
import { StorageService } from "../../storage/storage.service.js";
|
|
20
21
|
import { ToolOutputs } from "../outputs.js";
|
|
21
22
|
import type { BuiltinTool } from "../registry.js";
|
|
23
|
+
import { ScheduleToolsPort, ScheduleUpdates } from "./schedules.js";
|
|
22
24
|
import { WebPages } from "./web/index.js";
|
|
23
25
|
/**
|
|
24
26
|
* The kernel's builtin tools, built once from the injected services (their specs: names,
|
|
@@ -33,6 +35,12 @@ export declare class BuiltinTools {
|
|
|
33
35
|
purposes: ModelPurposesService, host: {
|
|
34
36
|
readonly hooks?: HookRunner;
|
|
35
37
|
} | undefined, usageReport: UsageReporter, stepLog: StepLog, log: Logger, background: BackgroundTaskStore, finisher: BackgroundTaskFinisher, stopper: BackgroundTaskStopper, platformOps: PlatformOperations, platformActor: PlatformActor, agentDocs: AgentDocs,
|
|
38
|
+
/** The schedule tools read tasks and grants; their writes go through the task service. */
|
|
39
|
+
scheduleStore: ScheduledTaskStore,
|
|
40
|
+
/** `ScheduleUpdate`'s write: its field change and grant changes in one transaction. */
|
|
41
|
+
scheduleUpdates: ScheduleUpdates,
|
|
42
|
+
/** The task service, bound by `SchedulesModule` at start (a registration port: no cycle). */
|
|
43
|
+
schedulePort: ScheduleToolsPort,
|
|
36
44
|
/** The clock `CurrentTime` reads (the run context's clock seam; `Date.now` by default). */
|
|
37
45
|
now?: () => number);
|
|
38
46
|
/** The builtin tools, in a fixed order. */
|
|
@@ -28,6 +28,7 @@ import { conversationTimeZone, RUN_CONTEXT_CLOCK } from "../../runtime/run-conte
|
|
|
28
28
|
import { StepLog } from "../../runtime/step-log.js";
|
|
29
29
|
import { UsageReporter } from "../../runtime/usage-report.js";
|
|
30
30
|
import { SandboxService } from "../../sandbox/sandbox.service.js";
|
|
31
|
+
import { ScheduledTaskStore } from "../../schedules/store.js";
|
|
31
32
|
import { StorageService } from "../../storage/storage.service.js";
|
|
32
33
|
import { ToolOutputs } from "../outputs.js";
|
|
33
34
|
import { agentTool } from "./agent.js";
|
|
@@ -37,6 +38,7 @@ import { handbackTool } from "./handback.js";
|
|
|
37
38
|
import { platformTool } from "./platform.js";
|
|
38
39
|
import { readToolOutputTool } from "./read-tool-output.js";
|
|
39
40
|
import { sandboxTools } from "./sandbox/index.js";
|
|
41
|
+
import { ScheduleToolsPort, ScheduleUpdates, scheduleTools } from "./schedules.js";
|
|
40
42
|
import { sendMessageTool } from "./send-message.js";
|
|
41
43
|
import { skillLoadTool } from "./skill-load.js";
|
|
42
44
|
import { toolSearchTool } from "./tool-search.js";
|
|
@@ -52,6 +54,12 @@ let BuiltinTools = class BuiltinTools {
|
|
|
52
54
|
constructor(db, config, outputs, storage, attachments, eventLog, sandbox, devices, points, webPages,
|
|
53
55
|
/** The WebFetch extraction call: the `web_fetch` purpose, its hooks, usage and denials. */
|
|
54
56
|
purposes, host, usageReport, stepLog, log, background, finisher, stopper, platformOps, platformActor, agentDocs,
|
|
57
|
+
/** The schedule tools read tasks and grants; their writes go through the task service. */
|
|
58
|
+
scheduleStore,
|
|
59
|
+
/** `ScheduleUpdate`'s write: its field change and grant changes in one transaction. */
|
|
60
|
+
scheduleUpdates,
|
|
61
|
+
/** The task service, bound by `SchedulesModule` at start (a registration port: no cycle). */
|
|
62
|
+
schedulePort,
|
|
55
63
|
/** The clock `CurrentTime` reads (the run context's clock seam; `Date.now` by default). */
|
|
56
64
|
now) {
|
|
57
65
|
const sandboxDeps = {
|
|
@@ -110,6 +118,16 @@ let BuiltinTools = class BuiltinTools {
|
|
|
110
118
|
now: now ?? Date.now,
|
|
111
119
|
timeZoneOf: ({ orgId, conversationId }) => conversationTimeZone(db.db, orgId, conversationId),
|
|
112
120
|
}),
|
|
121
|
+
// the person's scheduled tasks (scheduled tasks spec §11)
|
|
122
|
+
...scheduleTools({
|
|
123
|
+
db: () => db.db,
|
|
124
|
+
config,
|
|
125
|
+
store: scheduleStore,
|
|
126
|
+
service: () => schedulePort.get(),
|
|
127
|
+
updates: scheduleUpdates,
|
|
128
|
+
timeZoneOf: (orgId, conversationId) => conversationTimeZone(db.db, orgId, conversationId),
|
|
129
|
+
now: now ?? Date.now,
|
|
130
|
+
}),
|
|
113
131
|
];
|
|
114
132
|
}
|
|
115
133
|
/** The builtin tools, in a fixed order. */
|
|
@@ -140,7 +158,10 @@ BuiltinTools = __decorate([
|
|
|
140
158
|
__param(18, Inject(PlatformOperations)),
|
|
141
159
|
__param(19, Inject(PlatformActor)),
|
|
142
160
|
__param(20, Inject(AgentDocs)),
|
|
143
|
-
__param(21,
|
|
144
|
-
__param(
|
|
161
|
+
__param(21, Inject(ScheduledTaskStore)),
|
|
162
|
+
__param(22, Inject(ScheduleUpdates)),
|
|
163
|
+
__param(23, Inject(ScheduleToolsPort)),
|
|
164
|
+
__param(24, Optional()),
|
|
165
|
+
__param(24, Inject(RUN_CONTEXT_CLOCK))
|
|
145
166
|
], BuiltinTools);
|
|
146
167
|
export { BuiltinTools };
|
|
@@ -38,7 +38,10 @@ const SPEC = {
|
|
|
38
38
|
"deployment's background maximum (7200000 by default). A background task ends when its " +
|
|
39
39
|
"command exits, and processes it left running are stopped. A background task is lost if the " +
|
|
40
40
|
"sandbox is suspended (it is suspended after it has been running for a while, " +
|
|
41
|
-
"sandbox.limits.maxRunningMinutes), so keep its results in files under the workspace directory
|
|
41
|
+
"sandbox.limits.maxRunningMinutes), so keep its results in files under the workspace directory.\n\n" +
|
|
42
|
+
"Network access goes through a proxy set in HTTP_PROXY / HTTPS_PROXY / ALL_PROXY; a tool " +
|
|
43
|
+
"that ignores them fails with 'Operation not permitted'. A blocked domain is reported after " +
|
|
44
|
+
"the output.",
|
|
42
45
|
inputSchema: {
|
|
43
46
|
type: "object",
|
|
44
47
|
properties: {
|
|
@@ -349,18 +352,22 @@ export function sandboxBashTool(deps) {
|
|
|
349
352
|
// sandbox is obtained, so the time spent waiting for the lock and resuming is deducted
|
|
350
353
|
let ms = askedMs;
|
|
351
354
|
let workspace = DEFAULT_SANDBOX_ROOTS.workspace;
|
|
352
|
-
|
|
355
|
+
// the device the command ran on (a refused network host is asked about there)
|
|
356
|
+
let deviceId = "";
|
|
357
|
+
const { value: r, notices } = await inSandbox(deps, ctx, (s) => {
|
|
353
358
|
const left = Math.floor(runTimeLeftMs(ctx.runDeadline) / 1000) * 1000;
|
|
354
359
|
ms = Math.max(1000, Math.min(askedMs, left));
|
|
355
|
-
workspace = roots.workspace;
|
|
356
|
-
|
|
360
|
+
workspace = s.roots.workspace;
|
|
361
|
+
deviceId = s.deviceId;
|
|
362
|
+
return s.p.exec(s.h, {
|
|
357
363
|
command,
|
|
358
|
-
cwd: roots.workspace,
|
|
364
|
+
cwd: s.roots.workspace,
|
|
359
365
|
timeoutMs: ms,
|
|
360
366
|
maxOutputBytes: MAX_OUTPUT_BYTES,
|
|
361
|
-
signal,
|
|
367
|
+
signal: s.signal,
|
|
362
368
|
});
|
|
363
369
|
});
|
|
370
|
+
const blocked = r.network?.blocked ?? [];
|
|
364
371
|
const seconds = ms / 1000;
|
|
365
372
|
const clamped = ms < askedMs;
|
|
366
373
|
const shown = clip(command, 120);
|
|
@@ -371,7 +378,7 @@ export function sandboxBashTool(deps) {
|
|
|
371
378
|
return {
|
|
372
379
|
content: [{ type: "text", text: withNotices(notices, text) }],
|
|
373
380
|
isError: r.timedOut || r.exitCode === null,
|
|
374
|
-
// display only: a non-zero exit
|
|
381
|
+
// display only: a non-zero exit is information on the row (not a failure), like `isError`
|
|
375
382
|
view: {
|
|
376
383
|
kind: "command",
|
|
377
384
|
...(description !== undefined && { description }),
|
|
@@ -383,6 +390,8 @@ export function sandboxBashTool(deps) {
|
|
|
383
390
|
: r.exitCode === null
|
|
384
391
|
? display(`命令被中止:${shown}`, `Command stopped: ${shown}`)
|
|
385
392
|
: display(`运行命令(退出码 ${r.exitCode}):${shown}`, `Ran command (exit ${r.exitCode}): ${shown}`),
|
|
393
|
+
// kernel-internal: the pipeline asks the device owner or writes notes (spec §6.2)
|
|
394
|
+
...(blocked.length > 0 && deviceId !== "" && { networkReport: { deviceId, blocked } }),
|
|
386
395
|
};
|
|
387
396
|
},
|
|
388
397
|
};
|
|
@@ -23,7 +23,7 @@ export interface SandboxToolDeps {
|
|
|
23
23
|
* `usableNow` (absent in test stubs: only the cloud provider counts) tells a listing whether a
|
|
24
24
|
* conversation's device can run without a cloud provider (ruling R8).
|
|
25
25
|
*/
|
|
26
|
-
devices: Pick<ConversationDevices, "forTool"> & Partial<Pick<ConversationDevices, "usableNow">>;
|
|
26
|
+
devices: Pick<ConversationDevices, "forTool"> & Partial<Pick<ConversationDevices, "usableNow" | "sandboxLevel">>;
|
|
27
27
|
/** Background `Bash` tasks; absent in test stubs, where `run_in_background` is refused. */
|
|
28
28
|
background?: Pick<BackgroundTaskStore, "reserve" | "markStarted" | "failLaunch" | "finishStoppedLaunch" | "releaseStop" | "get" | "advanceOffset">;
|
|
29
29
|
/**
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { DEFAULT_SANDBOX_ROOTS, DeviceProtocolError, isFileSystemGoneError, isSandboxGoneError, ToolError, validateSandboxPath, validateSandboxPathShape, } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import { ANONYMOUS_NO_DEVICE, } from "../../../devices/conversation-devices.js";
|
|
3
3
|
import { ApiError } from "../../../errors.js";
|
|
4
|
+
import { UNSANDBOXED_NOT_ALLOWED_ERROR } from "../../../local-devices/local-device-provider.js";
|
|
4
5
|
import { devicePathsChange } from "../../../runtime/context/device-paths.js";
|
|
5
6
|
import { SandboxPathBusyError } from "../../../sandbox/leases.js";
|
|
6
7
|
import { SandboxLockLostError, } from "../../../sandbox/sandbox.service.js";
|
|
7
8
|
import { SandboxProviderUnreachableError } from "../../../sandbox/unreachable.js";
|
|
9
|
+
import { UNATTENDED_OPEN_TIER_ERROR, UNATTENDED_UNSANDBOXED_ERROR, } from "../../../schedules/unattended.js";
|
|
8
10
|
// ---- 模型可见文案(英文) ----
|
|
9
11
|
/** This conversation's first use of its device (in the instance's workspace). */
|
|
10
12
|
export const sandboxStartedNotice = (roots) => `Device started (first use in this conversation); the working directory is ${roots.workspace}, shared with the device's other conversations.`;
|
|
@@ -300,6 +302,11 @@ async function copyAttachments(deps, ctx, s, conv, list) {
|
|
|
300
302
|
}
|
|
301
303
|
return { copied, failures };
|
|
302
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* A device that runs commands without a sandbox the org does not allow (sandbox dev tiers spec
|
|
307
|
+
* §4.3): the kernel's `device.unsandboxed_not_allowed` text plus what the model can do.
|
|
308
|
+
*/
|
|
309
|
+
const UNSANDBOXED_TEXT = `${UNSANDBOXED_NOT_ALLOWED_ERROR} Ask the user to do that, or to start a new conversation on another device.`;
|
|
303
310
|
/**
|
|
304
311
|
* Sandbox service errors → actionable text for the model; anything else is returned unchanged
|
|
305
312
|
* (the pipeline reports a generic tool failure). A provider that cannot be reached becomes a
|
|
@@ -343,6 +350,13 @@ export function mapSandboxError(e, log) {
|
|
|
343
350
|
return new ToolError(DEVICE_OFFLINE_TEXT);
|
|
344
351
|
if (e.code === "device.program_outdated")
|
|
345
352
|
return new ToolError(DEVICE_OUTDATED_TEXT);
|
|
353
|
+
if (e.code === "device.unsandboxed_not_allowed" && detail?.reason === "unattended") {
|
|
354
|
+
return new ToolError(UNATTENDED_UNSANDBOXED_ERROR);
|
|
355
|
+
}
|
|
356
|
+
if (e.code === "device.unsandboxed_not_allowed")
|
|
357
|
+
return new ToolError(UNSANDBOXED_TEXT);
|
|
358
|
+
if (e.code === "device.tier_not_granted")
|
|
359
|
+
return new ToolError(UNATTENDED_OPEN_TIER_ERROR);
|
|
346
360
|
}
|
|
347
361
|
// an operation outside withSandbox's own mapping (it records lost files itself)
|
|
348
362
|
if (isFileSystemGoneError(e))
|
|
@@ -373,10 +387,15 @@ export async function inSandbox(deps, ctx, fn) {
|
|
|
373
387
|
deviceId,
|
|
374
388
|
// a sub-agent is told when another holder rebuilt the shared instance (final review m5)
|
|
375
389
|
holderConversationId: ctx.conversationId,
|
|
376
|
-
|
|
390
|
+
// the run routes its network grants to a connected device (network ask-to-allow P-D3)
|
|
391
|
+
actor: {
|
|
392
|
+
accountId: ctx.accountId,
|
|
393
|
+
conversationId: ctx.conversationId,
|
|
394
|
+
...(ctx.runId !== undefined && { runId: ctx.runId }),
|
|
395
|
+
},
|
|
377
396
|
}, async (h, p, info, signal) => {
|
|
378
397
|
const s = { deviceId, h, roots: info.roots, p, signal };
|
|
379
|
-
await recordDevicePaths(deps, ctx, info.roots);
|
|
398
|
+
await recordDevicePaths(deps, ctx, deviceId, info.roots);
|
|
380
399
|
const notices = [];
|
|
381
400
|
if (info.filesReset)
|
|
382
401
|
notices.push(sandboxFilesResetNotice(info.roots));
|
|
@@ -397,17 +416,21 @@ export async function inSandbox(deps, ctx, fn) {
|
|
|
397
416
|
}
|
|
398
417
|
}
|
|
399
418
|
/**
|
|
400
|
-
* Environments spec §13.1, ruling R22: the calling conversation's
|
|
401
|
-
* sub-agent's own too) gets
|
|
402
|
-
* roots included, and again whenever the held instance's roots
|
|
403
|
-
*
|
|
404
|
-
*
|
|
419
|
+
* Environments spec §13.1, ruling R22; sandbox dev tiers spec §11.5: the calling conversation's
|
|
420
|
+
* log (the log its model reads; a sub-agent's own too) gets a `device_paths` event on its first
|
|
421
|
+
* sandbox call, the default roots included, and again whenever the held instance's roots or the
|
|
422
|
+
* device's sandbox level (tier, isolation) differ from the latest recorded ones; always before the
|
|
423
|
+
* call's result. Two indexed reads per call (migration 0094, the device's row); parallel calls
|
|
424
|
+
* that saw the same latest event write one event (its idempotency key).
|
|
405
425
|
*/
|
|
406
|
-
async function recordDevicePaths(deps, ctx, roots) {
|
|
426
|
+
async function recordDevicePaths(deps, ctx, deviceId, roots) {
|
|
407
427
|
if (!deps.eventLog.latestOfType)
|
|
408
428
|
return;
|
|
409
|
-
const latest = await
|
|
410
|
-
|
|
429
|
+
const [latest, level] = await Promise.all([
|
|
430
|
+
deps.eventLog.latestOfType(ctx.conversationId, "device_paths"),
|
|
431
|
+
deps.devices.sandboxLevel?.(ctx.orgId, deviceId, ctx.runId) ?? null,
|
|
432
|
+
]);
|
|
433
|
+
const change = devicePathsChange(latest, roots, ctx.runId, level ?? undefined);
|
|
411
434
|
if (change)
|
|
412
435
|
await deps.eventLog.append(ctx.conversationId, [change]);
|
|
413
436
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ToolError } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import { lineDiff, viewPath } from "../../view.js";
|
|
3
3
|
import { checkPathShape, checkWritable, clip, display, inSandbox, looksBinary, sandboxBaseTimeoutMs, unavailableWhenNoSandbox, withNotices, withPathLease, } from "./common.js";
|
|
4
|
-
import { assertReadAndUnchanged, fileStateOf, normalizeSandboxPath, recordedFile, } from "./file-state.js";
|
|
4
|
+
import { assertReadAndUnchanged, FileGuardError, fileStateOf, guardResult, keepGuardRefusal, normalizeSandboxPath, recordedFile, } from "./file-state.js";
|
|
5
5
|
import { READ_MAX_BYTES } from "./read.js";
|
|
6
6
|
/** 可编辑的最大文件(字节):更大的用 Bash 处理。 */
|
|
7
7
|
const EDIT_MAX_BYTES = 10 * 1024 * 1024;
|
|
@@ -57,7 +57,8 @@ export function sandboxEditTool(deps) {
|
|
|
57
57
|
throw new ToolError(SAME_TEXT);
|
|
58
58
|
checkPathShape(input.file_path, "file_path");
|
|
59
59
|
let path = "";
|
|
60
|
-
|
|
60
|
+
// a read-before-write refusal is the guard working: a result with the guard view
|
|
61
|
+
const ran = await inSandbox(deps, ctx, ({ deviceId, h, roots, p, signal }) => {
|
|
61
62
|
path = checkWritable(input.file_path, roots);
|
|
62
63
|
// read, replace, write: one holder at a time per file
|
|
63
64
|
return withPathLease(deps, { deviceId }, path, signal, async (signal) => {
|
|
@@ -92,7 +93,10 @@ export function sandboxEditTool(deps) {
|
|
|
92
93
|
file: recordedFile(normalizeSandboxPath(path), nextBytes.subarray(0, READ_MAX_BYTES), nextBytes.byteLength),
|
|
93
94
|
};
|
|
94
95
|
});
|
|
95
|
-
});
|
|
96
|
+
}).catch(keepGuardRefusal);
|
|
97
|
+
if (ran instanceof FileGuardError)
|
|
98
|
+
return guardResult(ran);
|
|
99
|
+
const { value: edit, notices } = ran;
|
|
96
100
|
const { n } = edit;
|
|
97
101
|
const text = `Edited ${path}: replaced ${n} occurrence${n === 1 ? "" : "s"}.`;
|
|
98
102
|
return {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ToolError } from "@capybara-hq/plugin-sdk";
|
|
1
2
|
import type { EventLog } from "../../../conversations/event-log.js";
|
|
2
3
|
/**
|
|
3
4
|
* Read before `Write` / `Edit` (tool naming spec §4.5). The state of a file as a `Read`, `Write` or
|
|
@@ -17,6 +18,35 @@ export interface RecordedFile extends FileState {
|
|
|
17
18
|
}
|
|
18
19
|
export declare const NOT_READ_TEXT = "File has not been read yet. Read it first before writing to it.";
|
|
19
20
|
export declare const MODIFIED_TEXT = "File has been modified since it was read, either by the user or by another command. Read it again before writing to it.";
|
|
21
|
+
/** Why the read-before-write check refused a `Write` / `Edit` (`tool_result.view.reason`). */
|
|
22
|
+
export type FileGuardReason = "not_read" | "modified";
|
|
23
|
+
/**
|
|
24
|
+
* The read-before-write check refused the call: the guard working as designed, not a failed tool.
|
|
25
|
+
* The model sees the message as an error result (as any `ToolError`); the tools turn it into a
|
|
26
|
+
* result with the display-only `guard` view ({@link guardResult}) so the row reads as a neutral
|
|
27
|
+
* outcome.
|
|
28
|
+
*/
|
|
29
|
+
export declare class FileGuardError extends ToolError {
|
|
30
|
+
readonly reason: FileGuardReason;
|
|
31
|
+
constructor(reason: FileGuardReason);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The result of a call the read-before-write check refused: the same text and `isError` the model
|
|
35
|
+
* got before the marker existed, plus the display-only `guard` view (never in a request).
|
|
36
|
+
*/
|
|
37
|
+
export declare function guardResult(e: FileGuardError): {
|
|
38
|
+
content: {
|
|
39
|
+
type: "text";
|
|
40
|
+
text: string;
|
|
41
|
+
}[];
|
|
42
|
+
isError: boolean;
|
|
43
|
+
view: {
|
|
44
|
+
kind: "guard";
|
|
45
|
+
reason: FileGuardReason;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
/** A `.catch` that returns a {@link FileGuardError} and rethrows everything else. */
|
|
49
|
+
export declare function keepGuardRefusal(e: unknown): FileGuardError;
|
|
20
50
|
/** The state of `bytes`, the bytes handled of a file whose full size is `fullSize`. */
|
|
21
51
|
export declare function fileStateOf(bytes: Uint8Array, fullSize: number): FileState;
|
|
22
52
|
/** The `view.file` value for a file at `path` (already normalized). */
|
|
@@ -26,7 +56,7 @@ export declare function normalizeSandboxPath(path: string): string;
|
|
|
26
56
|
/** The latest recorded state of `path` in the conversation's log; null when it was never recorded. */
|
|
27
57
|
export declare function lastFileState(log: Pick<EventLog, "latestFileState">, conversationId: string, path: string): Promise<FileState | null>;
|
|
28
58
|
/**
|
|
29
|
-
* Throws the model-visible error unless the conversation read (or wrote) the file and it is
|
|
59
|
+
* Throws the model-visible error ({@link FileGuardError}) unless the conversation read (or wrote) the file and it is
|
|
30
60
|
* unchanged: `current` is the state of the content the caller just read.
|
|
31
61
|
*/
|
|
32
62
|
export declare function assertReadAndUnchanged(deps: {
|
|
@@ -3,6 +3,36 @@ import { posix } from "node:path";
|
|
|
3
3
|
import { ToolError } from "@capybara-hq/plugin-sdk";
|
|
4
4
|
export const NOT_READ_TEXT = "File has not been read yet. Read it first before writing to it.";
|
|
5
5
|
export const MODIFIED_TEXT = "File has been modified since it was read, either by the user or by another command. Read it again before writing to it.";
|
|
6
|
+
/**
|
|
7
|
+
* The read-before-write check refused the call: the guard working as designed, not a failed tool.
|
|
8
|
+
* The model sees the message as an error result (as any `ToolError`); the tools turn it into a
|
|
9
|
+
* result with the display-only `guard` view ({@link guardResult}) so the row reads as a neutral
|
|
10
|
+
* outcome.
|
|
11
|
+
*/
|
|
12
|
+
export class FileGuardError extends ToolError {
|
|
13
|
+
reason;
|
|
14
|
+
constructor(reason) {
|
|
15
|
+
super(reason === "not_read" ? NOT_READ_TEXT : MODIFIED_TEXT);
|
|
16
|
+
this.reason = reason;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The result of a call the read-before-write check refused: the same text and `isError` the model
|
|
21
|
+
* got before the marker existed, plus the display-only `guard` view (never in a request).
|
|
22
|
+
*/
|
|
23
|
+
export function guardResult(e) {
|
|
24
|
+
return {
|
|
25
|
+
content: [{ type: "text", text: e.message }],
|
|
26
|
+
isError: true,
|
|
27
|
+
view: { kind: "guard", reason: e.reason },
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** A `.catch` that returns a {@link FileGuardError} and rethrows everything else. */
|
|
31
|
+
export function keepGuardRefusal(e) {
|
|
32
|
+
if (e instanceof FileGuardError)
|
|
33
|
+
return e;
|
|
34
|
+
throw e;
|
|
35
|
+
}
|
|
6
36
|
/** The state of `bytes`, the bytes handled of a file whose full size is `fullSize`. */
|
|
7
37
|
export function fileStateOf(bytes, fullSize) {
|
|
8
38
|
return { size: fullSize, sha256: createHash("sha256").update(bytes).digest("hex") };
|
|
@@ -21,14 +51,14 @@ export async function lastFileState(log, conversationId, path) {
|
|
|
21
51
|
return log.latestFileState(conversationId, normalizeSandboxPath(path));
|
|
22
52
|
}
|
|
23
53
|
/**
|
|
24
|
-
* Throws the model-visible error unless the conversation read (or wrote) the file and it is
|
|
54
|
+
* Throws the model-visible error ({@link FileGuardError}) unless the conversation read (or wrote) the file and it is
|
|
25
55
|
* unchanged: `current` is the state of the content the caller just read.
|
|
26
56
|
*/
|
|
27
57
|
export async function assertReadAndUnchanged(deps, conversationId, path, current) {
|
|
28
58
|
const seen = await lastFileState(deps.eventLog, conversationId, path);
|
|
29
59
|
if (seen === null)
|
|
30
|
-
throw new
|
|
60
|
+
throw new FileGuardError("not_read");
|
|
31
61
|
if (seen.size !== current.size || seen.sha256 !== current.sha256) {
|
|
32
|
-
throw new
|
|
62
|
+
throw new FileGuardError("modified");
|
|
33
63
|
}
|
|
34
64
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ToolError } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import { lineDiff, TOOL_VIEW_DIFF_MAX_BYTES, viewPath } from "../../view.js";
|
|
3
3
|
import { checkPathShape, checkWritable, clip, display, inSandbox, looksBinary, sandboxBaseTimeoutMs, unavailableWhenNoSandbox, withNotices, withPathLease, } from "./common.js";
|
|
4
|
-
import { assertReadAndUnchanged, fileStateOf, normalizeSandboxPath, recordedFile, } from "./file-state.js";
|
|
4
|
+
import { assertReadAndUnchanged, FileGuardError, fileStateOf, guardResult, keepGuardRefusal, normalizeSandboxPath, recordedFile, } from "./file-state.js";
|
|
5
5
|
import { READ_MAX_BYTES } from "./read.js";
|
|
6
6
|
const SPEC = {
|
|
7
7
|
name: "Write",
|
|
@@ -46,7 +46,8 @@ export function sandboxWriteTool(deps) {
|
|
|
46
46
|
const data = new TextEncoder().encode(input.content);
|
|
47
47
|
checkPathShape(input.file_path, "file_path");
|
|
48
48
|
let path = "";
|
|
49
|
-
|
|
49
|
+
// a read-before-write refusal is the guard working: a result with the guard view
|
|
50
|
+
const ran = await inSandbox(deps, ctx, ({ deviceId, h, roots, p, signal }) => {
|
|
50
51
|
path = checkWritable(input.file_path, roots);
|
|
51
52
|
// stat, read the previous content, write: one holder at a time per file
|
|
52
53
|
return withPathLease(deps, { deviceId }, path, signal, async (signal) => {
|
|
@@ -72,8 +73,11 @@ export function sandboxWriteTool(deps) {
|
|
|
72
73
|
await p.writeFile(h, path, data, signal);
|
|
73
74
|
return { existed: st !== null, previous };
|
|
74
75
|
});
|
|
75
|
-
});
|
|
76
|
-
|
|
76
|
+
}).catch(keepGuardRefusal);
|
|
77
|
+
if (ran instanceof FileGuardError)
|
|
78
|
+
return guardResult(ran);
|
|
79
|
+
const { existed, previous } = ran.value;
|
|
80
|
+
const { notices } = ran;
|
|
77
81
|
const text = `${existed ? "Overwrote" : "Created"} ${path} (${data.byteLength} bytes).`;
|
|
78
82
|
return {
|
|
79
83
|
content: [{ type: "text", text: withNotices(notices, text) }],
|