@capybara-hq/kernel 0.1.0-rc.20 → 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/bootstrap.json +10 -1
- 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/bootstrap.d.ts +49 -10
- package/dist/extensions/bootstrap.js +104 -27
- 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/lifecycle/kernel-lifecycle.d.ts +5 -1
- package/dist/lifecycle/kernel-lifecycle.js +16 -4
- 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
package/dist/tools/pipeline.js
CHANGED
|
@@ -22,6 +22,8 @@ import { KernelDb } from "../infra/database/kernel-db.js";
|
|
|
22
22
|
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
23
23
|
import { Transactional } from "../infra/database/transactional.decorator.js";
|
|
24
24
|
import { KERNEL_LOGGER } from "../infra/tokens.js";
|
|
25
|
+
import { DevicePolicyService } from "../local-devices/device-policy.service.js";
|
|
26
|
+
import { NetworkGrants } from "../local-devices/network-grants.js";
|
|
25
27
|
import { UNKNOWN_RESULT_TEXT } from "../runtime/context/pairing.js";
|
|
26
28
|
import { fireObserve, hookDeniedAudit, hookDeniedEvent, hookScope, } from "../runtime/hook-calls.js";
|
|
27
29
|
import { subagentWaitingIdemKey } from "../runtime/loop.js";
|
|
@@ -30,8 +32,9 @@ import { rootSurfaceOf } from "../runtime/subagents/child.js";
|
|
|
30
32
|
import { SubagentMessages } from "../runtime/subagents/messages.js";
|
|
31
33
|
import { pairStoppedForks } from "../runtime/subagents/skill-fork.js";
|
|
32
34
|
import { SubagentsService } from "../runtime/subagents/subagents.service.js";
|
|
35
|
+
import { appendMissingGrant, connectionGrantKey, UNATTENDED_CONNECTION_ERROR, UNATTENDED_ELICITATION_NOTE, UNATTENDED_QUESTION_ERROR, unattendedFails, unattendedRunOf, } from "../schedules/unattended.js";
|
|
33
36
|
import { skillLoadOfferedIn } from "../skills/catalog.js";
|
|
34
|
-
import { ApprovalsService } from "./approvals.service.js";
|
|
37
|
+
import { ApprovalsService, } from "./approvals.service.js";
|
|
35
38
|
import { Breaker, breakerOpenMessage } from "./breaker.js";
|
|
36
39
|
import { SEND_MESSAGE, SUBAGENT_HANDBACK, TOOL_SEARCH } from "./builtin-names.js";
|
|
37
40
|
import { AGENT_TOOL, AgentToolInput } from "./builtins/agent.js";
|
|
@@ -43,6 +46,7 @@ import { CredentialMissing, Credentials } from "./credentials.js";
|
|
|
43
46
|
import { ToolDrift } from "./drift.js";
|
|
44
47
|
import { ElicitationSession } from "./elicitation.js";
|
|
45
48
|
import { legacyHint, legacyToolInput, legacyToolKey } from "./legacy-keys.js";
|
|
49
|
+
import { blockedEventHosts, clipKept, NETWORK_ASK_KEPT_MAX_BYTES, networkBlockedIdemKey, planNetworkAsk, settledNetworkNotes, withNetworkNotes, } from "./network-asks.js";
|
|
46
50
|
import { OrgToolSwitches, toolTurnedOffReason } from "./org-tool-switches.js";
|
|
47
51
|
import { renderResult, resolveOutputRefs, spillIfLarge, ToolOutputs } from "./outputs.js";
|
|
48
52
|
import { createDecidePolicy } from "./policy.js";
|
|
@@ -182,6 +186,26 @@ function withNotes(result, notes) {
|
|
|
182
186
|
content: [...content, ...notes.map((text) => ({ type: "text", text }))],
|
|
183
187
|
};
|
|
184
188
|
}
|
|
189
|
+
/** A network ask's kept display fields, written with its result when the ask settles. */
|
|
190
|
+
const keptExtras = (kept) => ({
|
|
191
|
+
...(kept?.outputId !== undefined && { outputId: kept.outputId }),
|
|
192
|
+
...(kept?.contentTypes !== undefined && { contentTypes: kept.contentTypes }),
|
|
193
|
+
...(kept?.display !== undefined && { display: kept.display }),
|
|
194
|
+
...(kept?.view !== undefined && { view: kept.view }),
|
|
195
|
+
});
|
|
196
|
+
/** A builtin result's kernel-internal network report (see `BuiltinResult.networkReport`). */
|
|
197
|
+
function networkReportOf(result) {
|
|
198
|
+
const r = result?.networkReport;
|
|
199
|
+
if (r === undefined || typeof r.deviceId !== "string" || !Array.isArray(r.blocked)) {
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
const blocked = r.blocked.filter((b) => typeof b === "object" &&
|
|
203
|
+
b !== null &&
|
|
204
|
+
typeof b.host === "string" &&
|
|
205
|
+
typeof b.port === "number" &&
|
|
206
|
+
["device", "org", "address", "unknown"].includes(b.layer));
|
|
207
|
+
return blocked.length === 0 ? undefined : { deviceId: r.deviceId, blocked };
|
|
208
|
+
}
|
|
185
209
|
/** 审批的等待时长(小时,四舍五入,至少 1)。 */
|
|
186
210
|
const approvalHours = (a) => Math.max(1, Math.round((new Date(a.expiresAt).getTime() - new Date(a.createdAt).getTime()) / 3_600_000));
|
|
187
211
|
const approvalWait = (a) => ({
|
|
@@ -212,7 +236,7 @@ let ToolPipeline = class ToolPipeline {
|
|
|
212
236
|
/** The message tools between agents; absent (tests): their calls are refused. */
|
|
213
237
|
messages, registry, credentials, breaker, outputs, approvals, questions, config, log,
|
|
214
238
|
/** Plugin hooks and inline ui contributions; absent (tests): no hooks, cards dropped. */
|
|
215
|
-
host, drift, effective, switches, txHost, hooks) {
|
|
239
|
+
host, drift, effective, switches, txHost, hooks, networkGrants, devicePolicy) {
|
|
216
240
|
this.config = config;
|
|
217
241
|
this.txHost = txHost;
|
|
218
242
|
this.deps = {
|
|
@@ -244,6 +268,9 @@ let ToolPipeline = class ToolPipeline {
|
|
|
244
268
|
return subagents ? { dispatch: (a) => subagents.dispatch(a) } : undefined;
|
|
245
269
|
},
|
|
246
270
|
messages,
|
|
271
|
+
network: networkGrants && devicePolicy
|
|
272
|
+
? { grants: networkGrants, devices: devicePolicy }
|
|
273
|
+
: undefined,
|
|
247
274
|
};
|
|
248
275
|
const decide = hooks?.decidePolicy;
|
|
249
276
|
// the default policy reads live, on the context's handle
|
|
@@ -256,12 +283,21 @@ let ToolPipeline = class ToolPipeline {
|
|
|
256
283
|
* 某个调用需要等人处理(审批 / 用户输入)→ 返回 `suspended`,之后的调用留到续跑时处理;
|
|
257
284
|
* 续跑时重新进入本函数,按 PG 里的审批 / 输入请求结果继续。
|
|
258
285
|
*/
|
|
259
|
-
async runStep(
|
|
286
|
+
async runStep(given) {
|
|
287
|
+
let ctx = given;
|
|
260
288
|
const { eventLog } = this.deps;
|
|
261
289
|
const msg = await eventLog.findByIdem(ctx.conversationId, assistantIdemKey(ctx.runId, ctx.step));
|
|
262
290
|
if (msg?.type !== "assistant_message")
|
|
263
291
|
return { kind: "done" };
|
|
264
292
|
const calls = msg.payload.content.flatMap((b) => b.type === "tool_call" ? [{ id: b.id, name: b.name, input: b.input }] : []);
|
|
293
|
+
// scheduled tasks spec §7: read live for each step (a person's message absorbed since makes the
|
|
294
|
+
// rest of the run attended)
|
|
295
|
+
if (calls.length > 0 && ctx.unattended === undefined) {
|
|
296
|
+
ctx = {
|
|
297
|
+
...ctx,
|
|
298
|
+
unattended: await unattendedRunOf(this.deps.db, this.config, ctx.orgId, ctx.runId),
|
|
299
|
+
};
|
|
300
|
+
}
|
|
265
301
|
let resolved;
|
|
266
302
|
const resolve = async () => {
|
|
267
303
|
// 每步实时解析(连接器 / 插件状态可能在模型调用之后变化)
|
|
@@ -324,6 +360,22 @@ let ToolPipeline = class ToolPipeline {
|
|
|
324
360
|
const attempt = rounds.length + 1;
|
|
325
361
|
const started = await this.deps.eventLog.findByIdem(ctx.conversationId, idem.started(ctx.runId, call.id, attempt));
|
|
326
362
|
if (started?.type === "tool_call_started") {
|
|
363
|
+
// a command that ran and was refused a network host waits for the device owner (network
|
|
364
|
+
// ask-to-allow spec §6.2, P-D2): pending → wait on; settled → its kept result and the
|
|
365
|
+
// outcome's note (the command is never run again; its tool.called was audited when it ran)
|
|
366
|
+
const ask = await this.deps.approvals.networkForCall(ctx.runId, call.id);
|
|
367
|
+
if (ask !== null) {
|
|
368
|
+
const notes = settledNetworkNotes(ask, approvalHours(ask));
|
|
369
|
+
if (notes === null)
|
|
370
|
+
return approvalWait(ask);
|
|
371
|
+
const kept = ask.detail?.result;
|
|
372
|
+
await this.writeResult(ctx, call.id, call.name, {
|
|
373
|
+
...keptExtras(kept),
|
|
374
|
+
content: withNetworkNotes(kept?.content ?? UNKNOWN_RESULT_TEXT, notes),
|
|
375
|
+
isError: kept?.isError ?? true,
|
|
376
|
+
});
|
|
377
|
+
return undefined;
|
|
378
|
+
}
|
|
327
379
|
// 确实开始执行过:照样审计 tool.called(耗时未知);放行依据只认批准了所执行定义的审批记录
|
|
328
380
|
const key = started.payload.toolKey;
|
|
329
381
|
const approval = await this.deps.approvals.forCall(ctx.runId, call.id);
|
|
@@ -439,9 +491,16 @@ let ToolPipeline = class ToolPipeline {
|
|
|
439
491
|
...(ctx.parent.otherAgent !== undefined && { parentAgentDiffers: ctx.parent.otherAgent }),
|
|
440
492
|
}),
|
|
441
493
|
callPolicy,
|
|
494
|
+
...(ctx.unattended != null && { unattended: ctx.unattended }),
|
|
442
495
|
});
|
|
443
496
|
if (decision === "deny")
|
|
444
497
|
return fail(deniedText(tool.key, callPolicy?.key), outKey);
|
|
498
|
+
if (typeof decision === "object" && "refused" in decision) {
|
|
499
|
+
if (decision.missingGrant !== undefined && ctx.unattended != null) {
|
|
500
|
+
await appendMissingGrant(this.deps.db, ctx.unattended.taskRunId, decision.missingGrant);
|
|
501
|
+
}
|
|
502
|
+
return fail(decision.refused, outKey);
|
|
503
|
+
}
|
|
445
504
|
if (typeof decision === "object" && "unavailable" in decision) {
|
|
446
505
|
return fail(unavailableText(tool.key, decision.unavailable), outKey);
|
|
447
506
|
}
|
|
@@ -620,6 +679,16 @@ let ToolPipeline = class ToolPipeline {
|
|
|
620
679
|
let outcome = await this.execute(tool, input, ctx, call.id, resume, resolved.tools);
|
|
621
680
|
const durationMs = Date.now() - t0;
|
|
622
681
|
await this.deps.hooks?.afterExecute?.(ctx.runId, call.id);
|
|
682
|
+
// scheduled tasks spec §7, `fail` mode: no one answers an input request or connects an app
|
|
683
|
+
if (unattendedFails(ctx.unattended) && ctx.unattended != null) {
|
|
684
|
+
if (outcome.kind === "input_required") {
|
|
685
|
+
outcome = { kind: "error", message: UNATTENDED_QUESTION_ERROR };
|
|
686
|
+
}
|
|
687
|
+
else if (outcome.kind === "connection_required") {
|
|
688
|
+
await appendMissingGrant(this.deps.db, ctx.unattended.taskRunId, connectionGrantKey(outcome.connectorSlug));
|
|
689
|
+
outcome = { kind: "error", message: UNATTENDED_CONNECTION_ERROR(outcome.connectorName) };
|
|
690
|
+
}
|
|
691
|
+
}
|
|
623
692
|
if (outcome.kind === "input_required") {
|
|
624
693
|
const asked = await this.askInput(ctx, call.id, tool.key, attempt, outcome);
|
|
625
694
|
if (typeof asked !== "string")
|
|
@@ -673,8 +742,40 @@ let ToolPipeline = class ToolPipeline {
|
|
|
673
742
|
},
|
|
674
743
|
]
|
|
675
744
|
: [];
|
|
745
|
+
const callAudit = {
|
|
746
|
+
durationMs,
|
|
747
|
+
connectorId: tool.source.kind === "connector" ? tool.source.connectorId : null,
|
|
748
|
+
authorization,
|
|
749
|
+
};
|
|
750
|
+
// a foreground command refused network hosts (network ask-to-allow spec §6.2): display-only
|
|
751
|
+
// `network_blocked` before the result, then an ask to the device owner (the run waits; the
|
|
752
|
+
// result is written when the ask settles) or notes after the output
|
|
753
|
+
const report = outcome.kind === "result" && tool.source.kind === "builtin"
|
|
754
|
+
? networkReportOf(outcome.result)
|
|
755
|
+
: undefined;
|
|
756
|
+
let content = spilled.content;
|
|
757
|
+
if (report !== undefined) {
|
|
758
|
+
const handled = await this.networkBlocked(ctx, call.id, outKey, input, report, callAudit, {
|
|
759
|
+
text: rendered.text,
|
|
760
|
+
content: spilled.content,
|
|
761
|
+
isError: rendered.isError,
|
|
762
|
+
...(spilled.outputId !== undefined && { outputId: spilled.outputId }),
|
|
763
|
+
...(rendered.contentTypes.length > 0 && { contentTypes: rendered.contentTypes }),
|
|
764
|
+
...(display !== undefined && { display }),
|
|
765
|
+
...(view !== undefined && { view }),
|
|
766
|
+
});
|
|
767
|
+
if (handled.kind === "wait") {
|
|
768
|
+
if (hooks && hookBase) {
|
|
769
|
+
fireObserve(hooks, "tool.postExecute", { ...hookBase, isError: rendered.isError, content: handled.content, durationMs }, ctx.signal, this.deps.log);
|
|
770
|
+
}
|
|
771
|
+
return handled.wait;
|
|
772
|
+
}
|
|
773
|
+
if (handled.event !== null)
|
|
774
|
+
pre.push(handled.event);
|
|
775
|
+
content = withNetworkNotes(content, handled.notes);
|
|
776
|
+
}
|
|
676
777
|
await this.writeResult(ctx, call.id, outKey, {
|
|
677
|
-
content
|
|
778
|
+
content,
|
|
678
779
|
isError: rendered.isError,
|
|
679
780
|
...(spilled.outputId !== undefined && { outputId: spilled.outputId }),
|
|
680
781
|
...(rendered.contentTypes.length > 0 && { contentTypes: rendered.contentTypes }),
|
|
@@ -682,11 +783,7 @@ let ToolPipeline = class ToolPipeline {
|
|
|
682
783
|
...(display !== undefined && { display }),
|
|
683
784
|
...(view !== undefined && { view }),
|
|
684
785
|
...(card !== undefined && { card }),
|
|
685
|
-
}, pre,
|
|
686
|
-
durationMs,
|
|
687
|
-
connectorId: tool.source.kind === "connector" ? tool.source.connectorId : null,
|
|
688
|
-
authorization,
|
|
689
|
-
});
|
|
786
|
+
}, pre, callAudit);
|
|
690
787
|
// a sub-agent that needs a personal connection: mirrored in the root conversation of its tree
|
|
691
788
|
// for display (sub-agents spec §9, agent map spec §7; not a wait, the child got the connect
|
|
692
789
|
// text as its result)
|
|
@@ -697,10 +794,94 @@ let ToolPipeline = class ToolPipeline {
|
|
|
697
794
|
}));
|
|
698
795
|
}
|
|
699
796
|
if (hooks && hookBase) {
|
|
700
|
-
fireObserve(hooks, "tool.postExecute", { ...hookBase, isError: rendered.isError, content
|
|
797
|
+
fireObserve(hooks, "tool.postExecute", { ...hookBase, isError: rendered.isError, content, durationMs }, ctx.signal, this.deps.log);
|
|
701
798
|
}
|
|
702
799
|
return undefined;
|
|
703
800
|
}
|
|
801
|
+
/**
|
|
802
|
+
* A foreground command's refused network hosts (network ask-to-allow spec §6.2, §6.8, §8.3):
|
|
803
|
+
* the `network_blocked` event, the missing grants of an unattended run, and either notes for the
|
|
804
|
+
* result written now, or (device-layer hosts worth asking) `network_blocked` and the call's
|
|
805
|
+
* `tool.called` audit written now, the result kept in a network ask to the device's owner and
|
|
806
|
+
* the run's wait. The kept result is the formatted one (≤ 16 KB: larger output is archived)
|
|
807
|
+
* followed by the notes written now; it is written with the decision's note when the ask
|
|
808
|
+
* settles (P-D2).
|
|
809
|
+
*/
|
|
810
|
+
async networkBlocked(ctx, callId, toolKey, input, report, callAudit, result) {
|
|
811
|
+
const hosts = blockedEventHosts(report.blocked);
|
|
812
|
+
if (hosts.length === 0)
|
|
813
|
+
return { kind: "write", event: null, notes: [] };
|
|
814
|
+
const event = {
|
|
815
|
+
type: "network_blocked",
|
|
816
|
+
runId: ctx.runId,
|
|
817
|
+
idemKey: networkBlockedIdemKey(ctx.runId, callId),
|
|
818
|
+
payload: { step: ctx.step, callId, hosts },
|
|
819
|
+
};
|
|
820
|
+
const network = this.deps.network;
|
|
821
|
+
if (network === undefined)
|
|
822
|
+
return { kind: "write", event, notes: [] };
|
|
823
|
+
const device = await network.devices.summary(report.deviceId);
|
|
824
|
+
const run = hosts.some((h) => h.layer === "device")
|
|
825
|
+
? await network.grants.runState(ctx.runId, report.deviceId)
|
|
826
|
+
: null;
|
|
827
|
+
const plan = planNetworkAsk(hosts, run, { device, unattended: ctx.unattended });
|
|
828
|
+
if (ctx.unattended != null) {
|
|
829
|
+
for (const key of plan.missingGrants) {
|
|
830
|
+
await appendMissingGrant(this.deps.db, ctx.unattended.taskRunId, key);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
if (plan.ask.length === 0)
|
|
834
|
+
return { kind: "write", event, notes: plan.notes };
|
|
835
|
+
// the device's owner is the only person who may decide (spec §5.2)
|
|
836
|
+
const owner = await this.deps.db
|
|
837
|
+
.selectFrom("devices")
|
|
838
|
+
.select("owner_account_id")
|
|
839
|
+
.where("id", "=", report.deviceId)
|
|
840
|
+
.where("org_id", "=", ctx.orgId)
|
|
841
|
+
.where("deleted_at", "is", null)
|
|
842
|
+
.executeTakeFirst();
|
|
843
|
+
if (owner === undefined)
|
|
844
|
+
return { kind: "write", event, notes: plan.notes };
|
|
845
|
+
let { content, outputId } = result;
|
|
846
|
+
if (Buffer.byteLength(content, "utf8") > NETWORK_ASK_KEPT_MAX_BYTES && outputId === undefined) {
|
|
847
|
+
// output under the spill threshold but over what an ask keeps: archive it after all
|
|
848
|
+
const spilled = await spillIfLarge(result.text, { spillChars: 0, previewChars: this.config.get().tools.outputPreviewChars }, (c) => this.deps.outputs.save({
|
|
849
|
+
orgId: ctx.orgId,
|
|
850
|
+
conversationId: ctx.conversationId,
|
|
851
|
+
callId,
|
|
852
|
+
content: c,
|
|
853
|
+
}));
|
|
854
|
+
content = spilled.content;
|
|
855
|
+
outputId = spilled.outputId;
|
|
856
|
+
}
|
|
857
|
+
const { text: _text, ...rest } = result;
|
|
858
|
+
const kept = {
|
|
859
|
+
...rest,
|
|
860
|
+
content: withNetworkNotes(clipKept(content), plan.notes),
|
|
861
|
+
...(outputId !== undefined && { outputId }),
|
|
862
|
+
};
|
|
863
|
+
// the command ran: network_blocked and its tool.called now, the result once the ask settles
|
|
864
|
+
const written = await this.writeResultIn(ctx, callId, toolKey, { isError: result.isError }, [event], callAudit, []);
|
|
865
|
+
await this.deps.eventLog.notify(ctx.conversationId, Math.max(...written.map((e) => e.seq)));
|
|
866
|
+
const given = (input ?? {});
|
|
867
|
+
const ask = await this.deps.approvals.requestNetwork({
|
|
868
|
+
orgId: ctx.orgId,
|
|
869
|
+
conversationId: ctx.conversationId,
|
|
870
|
+
runId: ctx.runId,
|
|
871
|
+
step: ctx.step,
|
|
872
|
+
callId,
|
|
873
|
+
requestedFor: owner.owner_account_id,
|
|
874
|
+
detail: {
|
|
875
|
+
deviceId: report.deviceId,
|
|
876
|
+
hosts: plan.ask,
|
|
877
|
+
command: typeof given.command === "string" ? clipText(given.command, 2000) : "",
|
|
878
|
+
description: typeof given.description === "string" ? given.description : null,
|
|
879
|
+
result: kept,
|
|
880
|
+
},
|
|
881
|
+
scopes: plan.scopes,
|
|
882
|
+
});
|
|
883
|
+
return { kind: "wait", wait: approvalWait(ask), content: kept.content };
|
|
884
|
+
}
|
|
704
885
|
/** Appends a sub-agent's connect card to its root conversation ({@link rootSurfaceOf}). */
|
|
705
886
|
async surfaceConnectInRoot(ctx, parent, callId, toolKey) {
|
|
706
887
|
const target = await rootSurfaceOf(this.deps.db, ctx.conversationId, ctx.runId, parent);
|
|
@@ -886,7 +1067,7 @@ let ToolPipeline = class ToolPipeline {
|
|
|
886
1067
|
const connectorId = source.connectorId;
|
|
887
1068
|
const app = await db
|
|
888
1069
|
.selectFrom("connectors")
|
|
889
|
-
.select(["name", "connector", "endpoint", "auth", "status"])
|
|
1070
|
+
.select(["name", "slug", "connector", "endpoint", "auth", "status"])
|
|
890
1071
|
.where("id", "=", connectorId)
|
|
891
1072
|
.where("org_id", "=", orgId)
|
|
892
1073
|
.where("deleted_at", "is", null)
|
|
@@ -922,10 +1103,13 @@ let ToolPipeline = class ToolPipeline {
|
|
|
922
1103
|
// 远端在调用途中向用户要输入(MCP elicitation,见 elicitation.ts):需要问用户时中止本次调用、按 input_required
|
|
923
1104
|
// 处理;续跑时用 ctx.resume 的答案答复远端再次发起的同一请求。resources 工具不续跑,一律拒绝。
|
|
924
1105
|
const suspend = new AbortController();
|
|
1106
|
+
// an unattended run in `fail` mode declines every elicitation: no one is there to answer
|
|
1107
|
+
const unattended = unattendedFails(ctx.unattended);
|
|
925
1108
|
const elicit = new ElicitationSession({
|
|
926
1109
|
resume,
|
|
927
1110
|
onSuspend: () => suspend.abort(),
|
|
928
|
-
declineAll: source.resources === true,
|
|
1111
|
+
declineAll: source.resources === true || unattended,
|
|
1112
|
+
...(unattended && { declineNote: UNATTENDED_ELICITATION_NOTE }),
|
|
929
1113
|
});
|
|
930
1114
|
// 远端可能把收到的凭据回显在结果 / 错误 / elicitation 表单里:写入事件之前按本次注入的值打码(见 redact.ts)
|
|
931
1115
|
const clean = (o) => {
|
|
@@ -961,6 +1145,7 @@ let ToolPipeline = class ToolPipeline {
|
|
|
961
1145
|
kind: "connection_required",
|
|
962
1146
|
connectorId,
|
|
963
1147
|
connectorName: app.name,
|
|
1148
|
+
connectorSlug: app.slug,
|
|
964
1149
|
message: personalMissingText(app.name),
|
|
965
1150
|
}
|
|
966
1151
|
: { kind: "error", message: orgMissingText(app.name) };
|
|
@@ -1251,6 +1436,10 @@ ToolPipeline = __decorate([
|
|
|
1251
1436
|
__param(15, Inject(OrgToolSwitches)),
|
|
1252
1437
|
__param(16, Inject(TransactionHost)),
|
|
1253
1438
|
__param(17, Optional()),
|
|
1254
|
-
__param(17, Inject(TOOL_PIPELINE_HOOKS))
|
|
1439
|
+
__param(17, Inject(TOOL_PIPELINE_HOOKS)),
|
|
1440
|
+
__param(18, Optional()),
|
|
1441
|
+
__param(18, Inject(NetworkGrants)),
|
|
1442
|
+
__param(19, Optional()),
|
|
1443
|
+
__param(19, Inject(DevicePolicyService))
|
|
1255
1444
|
], ToolPipeline);
|
|
1256
1445
|
export { ToolPipeline };
|
package/dist/tools/policy.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { KernelSchemaDb } from "../db/kernel-schema.js";
|
|
2
2
|
import type { DecidePolicy } from "./pipeline.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { ToolPolicy } from "./registry.js";
|
|
4
|
+
export { R3_FIXED_ASK_TOOL_KEYS } from "./builtin-names.js";
|
|
5
|
+
export { isFixedAsk } from "./fixed-ask.js";
|
|
6
|
+
/**
|
|
7
|
+
* What allowed an ask without an approval: this conversation's grant, an "always" grant, or a
|
|
8
|
+
* scheduled task's grant (its unattended runs, scheduled tasks spec §7).
|
|
9
|
+
*/
|
|
10
|
+
export type GrantKind = "session" | "always" | "task";
|
|
4
11
|
/** 取更严的策略(`allow < ask < deny`)。 */
|
|
5
12
|
export declare function stricter(a: ToolPolicy, b: ToolPolicy | undefined): ToolPolicy;
|
|
6
13
|
export interface PolicyInput {
|
|
@@ -10,19 +17,16 @@ export interface PolicyInput {
|
|
|
10
17
|
agentOverride?: "ask" | "deny";
|
|
11
18
|
/** `requiresUserInteraction`。 */
|
|
12
19
|
fixedAsk: boolean;
|
|
13
|
-
/**
|
|
14
|
-
|
|
20
|
+
/**
|
|
21
|
+
* 已记住且 specHash 与当前一致的授权; `task`: a scheduled task's grant in its unattended run.
|
|
22
|
+
*/
|
|
23
|
+
grant: GrantKind | null;
|
|
15
24
|
}
|
|
16
25
|
/**
|
|
17
26
|
* 策略判定(纯函数):取组织默认、agent 收紧、固定询问三者中最严者;
|
|
18
27
|
* 记住的授权只能把 `ask` 放行为 `allow`,不能越过 `deny`,对 `requiresUserInteraction` 的工具无效。
|
|
19
28
|
*/
|
|
20
29
|
export declare function decide(input: PolicyInput): ToolPolicy;
|
|
21
|
-
/**
|
|
22
|
-
* Whether the tool is a fixed ask: its spec declares `requiresUserInteraction`, so its policy is
|
|
23
|
-
* at least `ask` and remembered grants never let it through.
|
|
24
|
-
*/
|
|
25
|
-
export declare function isFixedAsk(tool: ResolvedTool): boolean;
|
|
26
30
|
/** Which level decided a connector tool's policy ({@link connectorPolicy}). */
|
|
27
31
|
export type ConnectorPolicySource = "org" | "member" | "agent";
|
|
28
32
|
/**
|
|
@@ -63,5 +67,14 @@ export declare function connectorPolicy(input: {
|
|
|
63
67
|
* (`approverRole` = 所需角色、`requestedFor` = null),此时记住的授权不放行。
|
|
64
68
|
*
|
|
65
69
|
* 结果为 `ask` 时返回审批请求,默认指向发起人本人;记住的授权把 `ask` 放行时返回 `{ grant }`(按 allow 执行)。
|
|
70
|
+
*
|
|
71
|
+
* An unattended run (`ctx.unattended`, not a trial run; scheduled tasks spec §7):
|
|
72
|
+
* - a tool in the org's `forbiddenTools` (by tool key or per-call key) is denied;
|
|
73
|
+
* - a Platform call that changes the agent's skills (`agents.register-skill`, ...) is an ask no
|
|
74
|
+
* grant covers, even on a private agent where it would run without asking;
|
|
75
|
+
* - an ask is also allowed by a task grant of the current definition (`{ grant: "task" }`), never
|
|
76
|
+
* for a fixed ask, a role escalation or a connector tool whose definition changed;
|
|
77
|
+
* - in `fail` mode an ask still left is refused (`{ refused, missingGrant }`, no approval), and
|
|
78
|
+
* `AskUserQuestion` is refused; in `wait` mode both ask the creator as in an attended run.
|
|
66
79
|
*/
|
|
67
80
|
export declare function createDecidePolicy(db: KernelSchemaDb): DecidePolicy;
|
package/dist/tools/policy.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { sql } from "kysely";
|
|
2
2
|
import { roleAtLeast } from "../orgs/roles.js";
|
|
3
3
|
import { readOrgSettings } from "../orgs/settings.service.js";
|
|
4
|
-
import {
|
|
4
|
+
import { grantRefusalOf } from "../schedules/grant-rules.js";
|
|
5
|
+
import { findTaskGrant } from "../schedules/store.js";
|
|
6
|
+
import { changesAgentSkills, UNATTENDED_ASK_ERROR, UNATTENDED_FIXED_ASK_ERROR, UNATTENDED_QUESTION_ERROR, unattendedRules, } from "../schedules/unattended.js";
|
|
7
|
+
import { BUILTIN, SANDBOX_ASK_TOOL_NAMES } from "./builtin-names.js";
|
|
5
8
|
import { personalAuth } from "./connector-account-policies.js";
|
|
9
|
+
import { isFixedAsk } from "./fixed-ask.js";
|
|
6
10
|
import { normalizeStoredDefinitionIn } from "./legacy-data.js";
|
|
11
|
+
export { R3_FIXED_ASK_TOOL_KEYS } from "./builtin-names.js";
|
|
12
|
+
export { isFixedAsk } from "./fixed-ask.js";
|
|
7
13
|
const RANK = { allow: 0, ask: 1, deny: 2 };
|
|
8
14
|
/** 取更严的策略(`allow < ask < deny`)。 */
|
|
9
15
|
export function stricter(a, b) {
|
|
@@ -21,13 +27,6 @@ export function decide(input) {
|
|
|
21
27
|
return "allow";
|
|
22
28
|
return p;
|
|
23
29
|
}
|
|
24
|
-
/**
|
|
25
|
-
* Whether the tool is a fixed ask: its spec declares `requiresUserInteraction`, so its policy is
|
|
26
|
-
* at least `ask` and remembered grants never let it through.
|
|
27
|
-
*/
|
|
28
|
-
export function isFixedAsk(tool) {
|
|
29
|
-
return tool.spec.annotations?.requiresUserInteraction === true;
|
|
30
|
-
}
|
|
31
30
|
const asSetting = (v) => v === "allow" || v === "ask" || v === "deny" ? v : undefined;
|
|
32
31
|
/** The stricter of the settings that are set (none set: undefined). */
|
|
33
32
|
function combine(...xs) {
|
|
@@ -92,6 +91,15 @@ export function connectorPolicy(input) {
|
|
|
92
91
|
* (`approverRole` = 所需角色、`requestedFor` = null),此时记住的授权不放行。
|
|
93
92
|
*
|
|
94
93
|
* 结果为 `ask` 时返回审批请求,默认指向发起人本人;记住的授权把 `ask` 放行时返回 `{ grant }`(按 allow 执行)。
|
|
94
|
+
*
|
|
95
|
+
* An unattended run (`ctx.unattended`, not a trial run; scheduled tasks spec §7):
|
|
96
|
+
* - a tool in the org's `forbiddenTools` (by tool key or per-call key) is denied;
|
|
97
|
+
* - a Platform call that changes the agent's skills (`agents.register-skill`, ...) is an ask no
|
|
98
|
+
* grant covers, even on a private agent where it would run without asking;
|
|
99
|
+
* - an ask is also allowed by a task grant of the current definition (`{ grant: "task" }`), never
|
|
100
|
+
* for a fixed ask, a role escalation or a connector tool whose definition changed;
|
|
101
|
+
* - in `fail` mode an ask still left is refused (`{ refused, missingGrant }`, no approval), and
|
|
102
|
+
* `AskUserQuestion` is refused; in `wait` mode both ask the creator as in an attended run.
|
|
95
103
|
*/
|
|
96
104
|
export function createDecidePolicy(db) {
|
|
97
105
|
return async (tool, agentDef, ctx) => {
|
|
@@ -150,6 +158,18 @@ export function createDecidePolicy(db) {
|
|
|
150
158
|
memberSetting = mine?.policy;
|
|
151
159
|
}
|
|
152
160
|
}
|
|
161
|
+
// scheduled tasks spec §7: what an unattended run may not do at all
|
|
162
|
+
const unattended = unattendedRules(ctx.unattended) ? ctx.unattended : undefined;
|
|
163
|
+
if (unattended !== undefined) {
|
|
164
|
+
const forbidden = unattended.forbiddenTools;
|
|
165
|
+
if (forbidden.includes(tool.key) || forbidden.includes(policyKey))
|
|
166
|
+
return "deny";
|
|
167
|
+
if (unattended.unattendedAsk === "fail" &&
|
|
168
|
+
tool.source.kind === "builtin" &&
|
|
169
|
+
tool.key === BUILTIN.AskUserQuestion) {
|
|
170
|
+
return { refused: UNATTENDED_QUESTION_ERROR };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
153
173
|
// 沙箱工具的组织默认(M5 T8):组织设置 sandboxToolPolicy 为 ask 时,有副作用的沙箱工具至少 ask
|
|
154
174
|
// `orgAsk`: the org setting, not the tool's own default, is what makes it ask
|
|
155
175
|
let orgAsk = false;
|
|
@@ -202,6 +222,8 @@ export function createDecidePolicy(db) {
|
|
|
202
222
|
agentOverride = undefined;
|
|
203
223
|
}
|
|
204
224
|
let escalateTo = null;
|
|
225
|
+
/** The acting account's role, read only for a tool that declares `requiredRole`. */
|
|
226
|
+
let memberRole = null;
|
|
205
227
|
if (tool.requiredRole !== undefined) {
|
|
206
228
|
const m = await db
|
|
207
229
|
.selectFrom("memberships")
|
|
@@ -211,12 +233,20 @@ export function createDecidePolicy(db) {
|
|
|
211
233
|
.executeTakeFirst();
|
|
212
234
|
if (!m)
|
|
213
235
|
return "deny";
|
|
236
|
+
memberRole = m.role;
|
|
214
237
|
if (!roleAtLeast(m.role, tool.requiredRole))
|
|
215
238
|
escalateTo = tool.requiredRole;
|
|
216
239
|
}
|
|
217
240
|
// `remember: false` on a call that is not a plain allow (an org / platform operation, or a read
|
|
218
241
|
// an agent tightened to ask): every call asks, grants never apply. A read (`allow`) never asks.
|
|
219
|
-
|
|
242
|
+
// an unattended run never changes the agent's skills on its own (spec §7): a Platform call
|
|
243
|
+
// that registers, deletes or shares one asks, outside every grant
|
|
244
|
+
const unattendedSkillChange = unattended !== undefined &&
|
|
245
|
+
tool.source.kind === "builtin" &&
|
|
246
|
+
tool.key === BUILTIN.Platform &&
|
|
247
|
+
changesAgentSkills(policyKey);
|
|
248
|
+
const fixedAsk = unattendedSkillChange ||
|
|
249
|
+
isFixedAsk(tool) ||
|
|
220
250
|
(call !== null && !call.remember && (call.policy !== "allow" || agentOverride !== undefined));
|
|
221
251
|
const input = {
|
|
222
252
|
connectorDefault,
|
|
@@ -229,12 +259,36 @@ export function createDecidePolicy(db) {
|
|
|
229
259
|
return "deny";
|
|
230
260
|
if (pre === "ask" && !fixedAsk && escalateTo === null && hashCurrent) {
|
|
231
261
|
input.grant = await findGrant(db, tool, policyKey, connectorId, ctx);
|
|
262
|
+
// an unattended run: the task's grants, like a tool_grants entry (spec §7)
|
|
263
|
+
if (input.grant === null &&
|
|
264
|
+
unattended !== undefined &&
|
|
265
|
+
(await findTaskGrant(db, unattended.taskId, policyKey, tool.specHash, connectorId))) {
|
|
266
|
+
input.grant = "task";
|
|
267
|
+
}
|
|
232
268
|
}
|
|
233
269
|
const result = decide(input);
|
|
234
270
|
if (result === "allow" && escalateTo === null) {
|
|
235
271
|
// 记住的授权把 ask 放行:返回授权种类(审计链记录凭什么放行)
|
|
236
272
|
return pre === "ask" && input.grant !== null ? { grant: input.grant } : "allow";
|
|
237
273
|
}
|
|
274
|
+
// an ask outside the grants in an unattended run, `fail` mode: no approval (spec §7)
|
|
275
|
+
if (unattended?.unattendedAsk === "fail") {
|
|
276
|
+
// recorded as a missing grant only when the creator could grant it to the task (the task
|
|
277
|
+
// grants' own rule) or could once promoted (`needs_role`, which the inbox says); a fixed
|
|
278
|
+
// ask, a per-call key (a skill change among them) or a forbidden tool never can: refused as a fixed ask,
|
|
279
|
+
// not recorded, so the inbox never offers a grant that cannot be added
|
|
280
|
+
const why = fixedAsk
|
|
281
|
+
? { code: "fixed_ask" }
|
|
282
|
+
: grantRefusalOf(policyKey, tool, {
|
|
283
|
+
// only `requiredRole` reads the role, and then it was read above
|
|
284
|
+
role: memberRole ?? "guest",
|
|
285
|
+
forbiddenTools: unattended.forbiddenTools,
|
|
286
|
+
});
|
|
287
|
+
if (why !== null && why.code !== "needs_role") {
|
|
288
|
+
return { refused: UNATTENDED_FIXED_ASK_ERROR(policyKey) };
|
|
289
|
+
}
|
|
290
|
+
return { refused: UNATTENDED_ASK_ERROR(policyKey), missingGrant: policyKey };
|
|
291
|
+
}
|
|
238
292
|
const ask = {
|
|
239
293
|
toolKey: policyKey,
|
|
240
294
|
specHash: tool.specHash,
|
package/dist/tools/registry.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Connector, HookParentScope, InputRequired, ToolAnnotations, ToolCallContext, ToolResult, ToolSpec } from "@capybara-hq/plugin-sdk";
|
|
1
|
+
import type { Connector, HookParentScope, InputRequired, NetworkBlockedHost, ToolAnnotations, ToolCallContext, ToolResult, ToolSpec } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import { type AgentDefinition, type DisabledToolRef, type SubagentTools } from "../agents/definition.js";
|
|
3
3
|
import type { ToolView } from "../conversations/events.js";
|
|
4
4
|
import { EffectiveConfigs } from "../extensions/effective-config.js";
|
|
@@ -77,6 +77,15 @@ export type BuiltinResult = (ToolResult & {
|
|
|
77
77
|
* checking it against `ToolViewSchema`). Builtins only: extension tools cannot set it.
|
|
78
78
|
*/
|
|
79
79
|
view?: ToolView;
|
|
80
|
+
/**
|
|
81
|
+
* Kernel-internal (never written to the log or sent to the model): the network hosts a
|
|
82
|
+
* foreground command on a device was refused (`Bash`; network ask-to-allow spec §6.2). The
|
|
83
|
+
* pipeline turns it into `network_blocked`, an ask to the device owner or notes.
|
|
84
|
+
*/
|
|
85
|
+
networkReport?: {
|
|
86
|
+
deviceId: string;
|
|
87
|
+
blocked: NetworkBlockedHost[];
|
|
88
|
+
};
|
|
80
89
|
}) | InputRequired;
|
|
81
90
|
/** 内置工具的处理函数(插件的 `ToolHandler` 可直接赋给它)。 */
|
|
82
91
|
export type BuiltinHandler = (input: unknown, ctx: BuiltinCallContext) => Promise<BuiltinResult>;
|
|
@@ -12,6 +12,7 @@ import { EventLogModule } from "../conversations/event-log.js";
|
|
|
12
12
|
import { DevicesModule } from "../devices/devices.module.js";
|
|
13
13
|
import { ExtensionsModule } from "../extensions/extensions.module.js";
|
|
14
14
|
import { QueueModule } from "../infra/queue/queue.module.js";
|
|
15
|
+
import { LocalDevicesModule } from "../local-devices/local-devices.module.js";
|
|
15
16
|
import { ModelsModule } from "../models/models.module.js";
|
|
16
17
|
import { OrgLockModule } from "../orgs/org-lock.js";
|
|
17
18
|
import { PlatformModule } from "../platform/platform.module.js";
|
|
@@ -20,12 +21,14 @@ import { StepLogModule } from "../runtime/step-log.js";
|
|
|
20
21
|
import { SubagentsModule } from "../runtime/subagents/subagents.service.js";
|
|
21
22
|
import { UsageReportModule } from "../runtime/usage-report.js";
|
|
22
23
|
import { SandboxModule } from "../sandbox/sandbox.module.js";
|
|
24
|
+
import { ScheduledTaskStoreModule } from "../schedules/store.module.js";
|
|
23
25
|
import { SkillsModule } from "../skills/skills.module.js";
|
|
24
26
|
import { StorageModule } from "../storage/storage.module.js";
|
|
25
27
|
import { AdminConnectors } from "./admin-connectors.js";
|
|
26
28
|
import { ApprovalsService } from "./approvals.service.js";
|
|
27
29
|
import { Breaker } from "./breaker.js";
|
|
28
30
|
import { BuiltinTools } from "./builtins/builtin-tools.js";
|
|
31
|
+
import { ScheduleToolsPort, ScheduleUpdates } from "./builtins/schedules.js";
|
|
29
32
|
import { WebPages } from "./builtins/web/page-cache.js";
|
|
30
33
|
import { ConnectorAccountPolicies } from "./connector-account-policies.js";
|
|
31
34
|
import { ConnectorContent } from "./connector-content.js";
|
|
@@ -63,6 +66,8 @@ ToolsModule = __decorate([
|
|
|
63
66
|
// the builtin sandbox and skill tools run in the conversation's device
|
|
64
67
|
SandboxModule,
|
|
65
68
|
DevicesModule,
|
|
69
|
+
// 「这台设备一直允许」 changes the device's local policy (network asks)
|
|
70
|
+
LocalDevicesModule,
|
|
66
71
|
// `Bash` with run_in_background records its task; TaskOutput / TaskStop finish and stop it
|
|
67
72
|
BackgroundModule,
|
|
68
73
|
// the conversation page's tool metadata reads only what the viewer may see
|
|
@@ -74,11 +79,17 @@ ToolsModule = __decorate([
|
|
|
74
79
|
// the `Platform` tool's operations, actor facts and agent docs (imports no domain module)
|
|
75
80
|
PlatformModule,
|
|
76
81
|
QueueModule.registerQueue(MAINTENANCE_QUEUE_NAME),
|
|
82
|
+
// a trial run's approvals become its scheduled task's grants (scheduled tasks spec §7)
|
|
83
|
+
ScheduledTaskStoreModule,
|
|
77
84
|
],
|
|
78
85
|
providers: [
|
|
79
86
|
Credentials,
|
|
80
87
|
ToolOutputs,
|
|
81
88
|
BuiltinTools,
|
|
89
|
+
// `ScheduleUpdate`'s write in one transaction (scheduled tasks spec §11)
|
|
90
|
+
ScheduleUpdates,
|
|
91
|
+
// where `SchedulesModule` binds the task service for the schedule tools (no provider cycle)
|
|
92
|
+
ScheduleToolsPort,
|
|
82
93
|
WebPages,
|
|
83
94
|
ToolRegistry,
|
|
84
95
|
ApprovalsService,
|
|
@@ -96,6 +107,7 @@ ToolsModule = __decorate([
|
|
|
96
107
|
OrgToolSwitches,
|
|
97
108
|
],
|
|
98
109
|
exports: [
|
|
110
|
+
ScheduleToolsPort,
|
|
99
111
|
Credentials,
|
|
100
112
|
ToolOutputs,
|
|
101
113
|
BuiltinTools,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capybara-hq/kernel",
|
|
3
|
-
"version": "0.1.0-rc.
|
|
3
|
+
"version": "0.1.0-rc.22",
|
|
4
4
|
"description": "Capybara kernel: config, database, plugin host and HTTP API of the Capybara agent platform",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"yaml": "^2.9.1",
|
|
66
66
|
"yauzl": "3.4.0",
|
|
67
67
|
"zod": "^4.6.7",
|
|
68
|
-
"@capybara-hq/plugin-sdk": "^0.1.0-rc.
|
|
68
|
+
"@capybara-hq/plugin-sdk": "^0.1.0-rc.16"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@langchain/langgraph-checkpoint-validation": "^1.2.0",
|