@capybara-hq/kernel 0.1.0-rc.7 → 0.1.0-rc.9

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.
Files changed (118) hide show
  1. package/dist/background/finish.js +2 -1
  2. package/dist/background/launch.d.ts +13 -7
  3. package/dist/background/launch.js +15 -8
  4. package/dist/background/poll.job.d.ts +1 -1
  5. package/dist/background/poll.job.js +8 -3
  6. package/dist/background/session.d.ts +2 -2
  7. package/dist/background/session.js +3 -2
  8. package/dist/background/status.d.ts +1 -1
  9. package/dist/background/status.js +1 -1
  10. package/dist/background/stop.js +4 -3
  11. package/dist/background/store.d.ts +6 -1
  12. package/dist/background/store.js +5 -1
  13. package/dist/background/tasks.service.d.ts +1 -1
  14. package/dist/background/tasks.service.js +4 -4
  15. package/dist/catalog/marketplaces.service.d.ts +1 -1
  16. package/dist/config/docs.js +2 -0
  17. package/dist/config/schema.d.ts +7 -0
  18. package/dist/config/schema.js +45 -0
  19. package/dist/conversations/event-log.d.ts +4 -3
  20. package/dist/conversations/event-log.js +4 -3
  21. package/dist/conversations/events.d.ts +9 -0
  22. package/dist/conversations/events.js +14 -0
  23. package/dist/db/kernel-schema.d.ts +26 -1
  24. package/dist/devices/device-auth.controller.d.ts +42 -0
  25. package/dist/devices/device-auth.controller.js +116 -0
  26. package/dist/devices/device-auth.d.ts +119 -0
  27. package/dist/devices/device-auth.js +403 -0
  28. package/dist/devices/device-auth.module.d.ts +7 -0
  29. package/dist/devices/device-auth.module.js +25 -0
  30. package/dist/devices/device-gateway.d.ts +128 -0
  31. package/dist/devices/device-gateway.js +530 -0
  32. package/dist/devices/device-provider.d.ts +89 -0
  33. package/dist/devices/device-provider.js +151 -0
  34. package/dist/devices/device-routes.d.ts +36 -0
  35. package/dist/devices/device-routes.js +90 -0
  36. package/dist/devices/devices.module.d.ts +16 -0
  37. package/dist/devices/devices.module.js +44 -0
  38. package/dist/devices/dto.d.ts +66 -0
  39. package/dist/devices/dto.js +44 -0
  40. package/dist/devices/keys.d.ts +25 -0
  41. package/dist/devices/keys.js +25 -0
  42. package/dist/devices/machines.d.ts +106 -0
  43. package/dist/devices/machines.js +301 -0
  44. package/dist/devices/relay.d.ts +100 -0
  45. package/dist/devices/relay.js +373 -0
  46. package/dist/devices/targets.d.ts +28 -0
  47. package/dist/devices/targets.js +73 -0
  48. package/dist/environments/access.d.ts +11 -6
  49. package/dist/environments/access.js +23 -8
  50. package/dist/environments/conversation-environments.d.ts +20 -6
  51. package/dist/environments/conversation-environments.js +70 -9
  52. package/dist/environments/environments.controller.d.ts +4 -1
  53. package/dist/environments/environments.controller.js +15 -1
  54. package/dist/environments/environments.module.js +3 -1
  55. package/dist/environments/environments.service.d.ts +54 -7
  56. package/dist/environments/environments.service.js +104 -20
  57. package/dist/environments/legacy-cleanup.job.d.ts +1 -1
  58. package/dist/environments/legacy-cleanup.job.js +4 -4
  59. package/dist/errors.d.ts +24 -0
  60. package/dist/errors.js +24 -0
  61. package/dist/evals/recorded-tools.d.ts +9 -0
  62. package/dist/evals/recorded-tools.js +27 -0
  63. package/dist/http/api.module.js +4 -0
  64. package/dist/http/gateway-routes.d.ts +7 -3
  65. package/dist/http/gateway-routes.js +6 -3
  66. package/dist/identity/me.controller.d.ts +1 -3
  67. package/dist/identity/me.controller.js +1 -9
  68. package/dist/identity/me.schemas.d.ts +14 -0
  69. package/dist/identity/me.schemas.js +17 -0
  70. package/dist/infra/config/kernel-config.service.js +2 -0
  71. package/dist/infra/throttler/named-throttlers.d.ts +5 -0
  72. package/dist/infra/throttler/named-throttlers.js +38 -0
  73. package/dist/kernel.module.js +3 -0
  74. package/dist/lifecycle/kernel-lifecycle.js +5 -0
  75. package/dist/platform/exclusions.js +33 -10
  76. package/dist/platform/operation.js +1 -1
  77. package/dist/platform/ops/environments.ops.js +21 -3
  78. package/dist/platform/ops/platform.ops.d.ts +7 -2
  79. package/dist/platform/ops/platform.ops.js +50 -3
  80. package/dist/platform/route-schemas.js +3 -0
  81. package/dist/roles/gateway-role.d.ts +14 -3
  82. package/dist/roles/gateway-role.js +25 -5
  83. package/dist/roles/gateway.module.d.ts +2 -1
  84. package/dist/roles/gateway.module.js +6 -3
  85. package/dist/runtime/context/build.d.ts +2 -0
  86. package/dist/runtime/context/build.js +14 -0
  87. package/dist/runtime/context/compaction.js +1 -0
  88. package/dist/runtime/context/environment-paths.d.ts +40 -0
  89. package/dist/runtime/context/environment-paths.js +48 -0
  90. package/dist/runtime/model-call.js +2 -0
  91. package/dist/sandbox/providers.d.ts +29 -7
  92. package/dist/sandbox/providers.js +43 -8
  93. package/dist/sandbox/sandbox.service.d.ts +70 -18
  94. package/dist/sandbox/sandbox.service.js +176 -59
  95. package/dist/tools/builtins/sandbox/bash.d.ts +2 -2
  96. package/dist/tools/builtins/sandbox/bash.js +39 -27
  97. package/dist/tools/builtins/sandbox/common.d.ts +60 -25
  98. package/dist/tools/builtins/sandbox/common.js +121 -30
  99. package/dist/tools/builtins/sandbox/deliver.js +9 -6
  100. package/dist/tools/builtins/sandbox/edit.js +40 -37
  101. package/dist/tools/builtins/sandbox/glob.js +13 -8
  102. package/dist/tools/builtins/sandbox/grep.js +10 -6
  103. package/dist/tools/builtins/sandbox/index.d.ts +6 -4
  104. package/dist/tools/builtins/sandbox/index.js +6 -4
  105. package/dist/tools/builtins/sandbox/read.js +9 -6
  106. package/dist/tools/builtins/sandbox/task-output.d.ts +7 -3
  107. package/dist/tools/builtins/sandbox/task-output.js +14 -10
  108. package/dist/tools/builtins/sandbox/task-stop.js +3 -3
  109. package/dist/tools/builtins/sandbox/write.js +33 -30
  110. package/dist/tools/builtins/skill-load.d.ts +8 -5
  111. package/dist/tools/builtins/skill-load.js +21 -15
  112. package/dist/tools/builtins/skill-write.js +2 -2
  113. package/dist/tools/pipeline.js +2 -0
  114. package/dist/tools/registry.d.ts +21 -4
  115. package/dist/tools/registry.js +10 -2
  116. package/package.json +6 -3
  117. package/src/migrations/0093_connected_environments.sql +37 -0
  118. package/src/migrations/0094_environment_paths_idx.sql +7 -0
@@ -10,6 +10,8 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
10
10
  import { Inject, Injectable } from "@nestjs/common";
11
11
  import { z } from "zod";
12
12
  import { AgentsService } from "../../agents/agents.service.js";
13
+ import { AccountsService } from "../../identity/accounts.js";
14
+ import { PreferencesSchema } from "../../identity/me.schemas.js";
13
15
  import { can } from "../../orgs/roles.js";
14
16
  import { effectiveSandboxEgress, OrgSettingsService } from "../../orgs/settings.service.js";
15
17
  import { SANDBOX_TOOL_NAMES } from "../../tools/builtin-names.js";
@@ -34,19 +36,24 @@ const FAMILIES = [
34
36
  "Plugin and connector tools, named plugin__<plugin>__<tool> and mcp__<connector>__<tool>.",
35
37
  ];
36
38
  const EMPTY = z.object({}).strict();
37
- /** The `platform` domain: orientation, and what this agent is (spec §7). Synthetic, no route. */
39
+ /**
40
+ * The `platform` domain: orientation and what this agent is (spec §7, synthetic), and the
41
+ * person's own preferences (`/me/preferences`).
42
+ */
38
43
  let PlatformOperationsProvider = class PlatformOperationsProvider {
39
44
  registry;
40
45
  actor;
41
46
  agents;
42
47
  settings;
43
48
  tools;
44
- constructor(registry, actor, agents, settings, tools) {
49
+ accounts;
50
+ constructor(registry, actor, agents, settings, tools, accounts) {
45
51
  this.registry = registry;
46
52
  this.actor = actor;
47
53
  this.agents = agents;
48
54
  this.settings = settings;
49
55
  this.tools = tools;
56
+ this.accounts = accounts;
50
57
  }
51
58
  /**
52
59
  * The agent's tools by the keys the model sees, as the registry resolves them for a run (tools
@@ -143,6 +150,45 @@ let PlatformOperationsProvider = class PlatformOperationsProvider {
143
150
  };
144
151
  },
145
152
  }),
153
+ defineOperation({
154
+ id: "platform.preferences",
155
+ description: "Read your own preferences: your time zone (an IANA name such as Asia/Shanghai), or null when it follows the time zone of the browser you send messages from. The run context already tells you the zone in effect for this message.",
156
+ input: EMPTY,
157
+ example: {},
158
+ // any signed-in person may call the route; every run's actor is a member of this org
159
+ authz: { orgAction: "agents.use" },
160
+ mirrors: ["GET /api/v1/me/preferences"],
161
+ confirm: "read",
162
+ run: async (_input, ctx) => this.accounts.preferences(ctx.actorAccountId),
163
+ }),
164
+ defineOperation({
165
+ id: "platform.set-preferences",
166
+ description: "Set your own time zone (an IANA name such as Europe/Berlin), or null to follow the time zone of the browser you send messages from. It applies to the messages you send afterwards, on the web and in IM; the current run keeps its zone.",
167
+ input: PreferencesSchema,
168
+ example: { timeZone: "Asia/Shanghai" },
169
+ authz: { orgAction: "agents.use" },
170
+ mirrors: ["PUT /api/v1/me/preferences"],
171
+ confirm: "personal",
172
+ errors: [
173
+ {
174
+ code: "validation_failed",
175
+ next: "Use an IANA time zone name such as Asia/Shanghai or America/New_York, or null.",
176
+ },
177
+ ],
178
+ summary: async (input, ctx) => {
179
+ const now = (await this.accounts.preferences(ctx.actorAccountId)).timeZone;
180
+ const from = { zh: now ?? "跟随浏览器", en: now ?? "follow the browser" };
181
+ const to = {
182
+ zh: input.timeZone ?? "跟随浏览器",
183
+ en: input.timeZone ?? "follow the browser",
184
+ };
185
+ return {
186
+ zh: `把你的时区从「${from.zh}」改为「${to.zh}」`,
187
+ en: `Change your time zone from "${from.en}" to "${to.en}"`,
188
+ };
189
+ },
190
+ run: async (input, ctx) => this.accounts.setPreferences(ctx.actorAccountId, { timeZone: input.timeZone }),
191
+ }),
146
192
  ]);
147
193
  }
148
194
  };
@@ -152,6 +198,7 @@ PlatformOperationsProvider = __decorate([
152
198
  __param(1, Inject(PlatformActor)),
153
199
  __param(2, Inject(AgentsService)),
154
200
  __param(3, Inject(OrgSettingsService)),
155
- __param(4, Inject(ToolRegistry))
201
+ __param(4, Inject(ToolRegistry)),
202
+ __param(5, Inject(AccountsService))
156
203
  ], PlatformOperationsProvider);
157
204
  export { PlatformOperationsProvider };
@@ -15,6 +15,7 @@ import { ENVIRONMENT_ROUTES } from "../environments/environments.schemas.js";
15
15
  import { EVAL_ROUTES } from "../evals/evals.schemas.js";
16
16
  import { EXTENSION_POINT_ROUTES } from "../extensions/point-defaults.schemas.js";
17
17
  import { FILE_PREVIEWER_ROUTES } from "../files/previewers.schemas.js";
18
+ import { ME_ROUTES } from "../identity/me.schemas.js";
18
19
  import { INBOX_ROUTES } from "../inbox/inbox.schemas.js";
19
20
  import { ORG_MODEL_ROUTES } from "../models/org-models.schemas.js";
20
21
  import { ORG_ROUTES, ORG_SETTINGS_ROUTES } from "../orgs/orgs.schemas.js";
@@ -57,4 +58,6 @@ export const ROUTE_SCHEMAS = [
57
58
  ...FILE_PREVIEWER_ROUTES,
58
59
  // Task 11b: environments
59
60
  ...ENVIRONMENT_ROUTES,
61
+ // personal preferences (run context spec §3.4)
62
+ ...ME_ROUTES,
60
63
  ];
@@ -1,13 +1,15 @@
1
1
  import type { ExtensionContext } from "@capybara-hq/plugin-sdk";
2
2
  import { ChannelSupervisor } from "../channels/supervisor.js";
3
3
  import { ChannelWebhooks } from "../channels/webhook.js";
4
+ import { DeviceGateway } from "../devices/device-gateway.js";
4
5
  import { ExtensionHost } from "../extensions/host.js";
5
6
  import type { GatewayRoutes } from "../http/gateway-routes.js";
6
7
  import { KernelConfigService } from "../infra/config/kernel-config.service.js";
7
8
  /**
8
9
  * The gateway role's parts, driven by `KernelLifecycle` at fixed points of its start order and
9
10
  * stop phases: the channel long-connection supervisor (leader election and share) and the webhook
10
- * inbound (its raw Fastify route, {@link routes}, and its runtime cache's invalidation). Provided
11
+ * inbound (its raw Fastify route, {@link routes}, and its runtime cache's invalidation), and the
12
+ * device connections ({@link DeviceGateway}, `GET /device/connect`). Provided
11
13
  * only by `GatewayModule`, so a process without the gateway role has none of them.
12
14
  */
13
15
  export declare class GatewayRole {
@@ -16,6 +18,7 @@ export declare class GatewayRole {
16
18
  private readonly bus;
17
19
  private readonly supervisor;
18
20
  private readonly webhooks;
21
+ private readonly devices;
19
22
  /** The supervisor's subscription to extension instance changes. */
20
23
  private unwatchPlugins;
21
24
  private unwatchWebhooks;
@@ -23,8 +26,11 @@ export declare class GatewayRole {
23
26
  private unsubscribeWebhooks;
24
27
  /** The supervisor was started: the runs phase stops it. */
25
28
  private supervisorStarted;
26
- constructor(config: KernelConfigService, host: ExtensionHost, bus: Pick<ExtensionContext["bus"], "subscribe">, supervisor: ChannelSupervisor, webhooks: ChannelWebhooks);
27
- /** The gateway role's HTTP routes (the channel webhook), registered when the app is built. */
29
+ constructor(config: KernelConfigService, host: ExtensionHost, bus: Pick<ExtensionContext["bus"], "subscribe">, supervisor: ChannelSupervisor, webhooks: ChannelWebhooks, devices: DeviceGateway);
30
+ /**
31
+ * The gateway role's HTTP routes (the channel webhook, the device connection), registered when
32
+ * the app is built.
33
+ */
28
34
  routes(): GatewayRoutes;
29
35
  /**
30
36
  * Starts the supervisor and the webhook cache invalidation (after the extension load).
@@ -35,6 +41,11 @@ export declare class GatewayRole {
35
41
  unwatch(): void;
36
42
  /** Ends the webhook cache's `channel.changed` subscription. */
37
43
  unsubscribe(): Promise<void>;
44
+ /**
45
+ * Closes the device connections (1001) and hands their leases back, before the HTTP server
46
+ * closes (the machines reconnect to another gateway replica).
47
+ */
48
+ stopDevices(): Promise<void>;
38
49
  /**
39
50
  * The runs phase: the supervisor aborts its connects and hands back its leases (undefined: never
40
51
  * started).
@@ -11,13 +11,15 @@ import { Inject, Injectable } from "@nestjs/common";
11
11
  import { CHANNEL_CHANGED_TOPIC } from "../channels/connections.service.js";
12
12
  import { ChannelSupervisor } from "../channels/supervisor.js";
13
13
  import { ChannelWebhooks } from "../channels/webhook.js";
14
+ import { DeviceGateway } from "../devices/device-gateway.js";
14
15
  import { ExtensionHost } from "../extensions/host.js";
15
16
  import { KernelConfigService } from "../infra/config/kernel-config.service.js";
16
17
  import { KERNEL_BUS } from "../infra/tokens.js";
17
18
  /**
18
19
  * The gateway role's parts, driven by `KernelLifecycle` at fixed points of its start order and
19
20
  * stop phases: the channel long-connection supervisor (leader election and share) and the webhook
20
- * inbound (its raw Fastify route, {@link routes}, and its runtime cache's invalidation). Provided
21
+ * inbound (its raw Fastify route, {@link routes}, and its runtime cache's invalidation), and the
22
+ * device connections ({@link DeviceGateway}, `GET /device/connect`). Provided
21
23
  * only by `GatewayModule`, so a process without the gateway role has none of them.
22
24
  */
23
25
  let GatewayRole = class GatewayRole {
@@ -26,6 +28,7 @@ let GatewayRole = class GatewayRole {
26
28
  bus;
27
29
  supervisor;
28
30
  webhooks;
31
+ devices;
29
32
  /** The supervisor's subscription to extension instance changes. */
30
33
  unwatchPlugins;
31
34
  unwatchWebhooks;
@@ -33,18 +36,27 @@ let GatewayRole = class GatewayRole {
33
36
  unsubscribeWebhooks;
34
37
  /** The supervisor was started: the runs phase stops it. */
35
38
  supervisorStarted = false;
36
- constructor(config, host, bus, supervisor, webhooks) {
39
+ constructor(config, host, bus, supervisor, webhooks, devices) {
37
40
  this.config = config;
38
41
  this.host = host;
39
42
  this.bus = bus;
40
43
  this.supervisor = supervisor;
41
44
  this.webhooks = webhooks;
45
+ this.devices = devices;
42
46
  }
43
- /** The gateway role's HTTP routes (the channel webhook), registered when the app is built. */
47
+ /**
48
+ * The gateway role's HTTP routes (the channel webhook, the device connection), registered when
49
+ * the app is built.
50
+ */
44
51
  routes() {
52
+ const config = this.config.get();
45
53
  return {
46
54
  webhooks: this.webhooks,
47
- webhookMaxBodyBytes: this.config.get().channels.webhookMaxBodyBytes,
55
+ webhookMaxBodyBytes: config.channels.webhookMaxBodyBytes,
56
+ devices: {
57
+ gateway: this.devices,
58
+ maxFrameBytes: config.environments.device.maxFrameBytes,
59
+ },
48
60
  };
49
61
  }
50
62
  /**
@@ -79,6 +91,13 @@ let GatewayRole = class GatewayRole {
79
91
  unsubscribe() {
80
92
  return this.unsubscribeWebhooks?.() ?? Promise.resolve();
81
93
  }
94
+ /**
95
+ * Closes the device connections (1001) and hands their leases back, before the HTTP server
96
+ * closes (the machines reconnect to another gateway replica).
97
+ */
98
+ stopDevices() {
99
+ return this.devices.stop();
100
+ }
82
101
  /**
83
102
  * The runs phase: the supervisor aborts its connects and hands back its leases (undefined: never
84
103
  * started).
@@ -93,6 +112,7 @@ GatewayRole = __decorate([
93
112
  __param(1, Inject(ExtensionHost)),
94
113
  __param(2, Inject(KERNEL_BUS)),
95
114
  __param(3, Inject(ChannelSupervisor)),
96
- __param(4, Inject(ChannelWebhooks))
115
+ __param(4, Inject(ChannelWebhooks)),
116
+ __param(5, Inject(DeviceGateway))
97
117
  ], GatewayRole);
98
118
  export { GatewayRole };
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * The gateway role (nest kernel spec §2), imported by `KernelModule` only when `roles` includes
3
3
  * `gateway`: the channel long-connection supervisor, the webhook inbound (served as a raw Fastify
4
- * route, `http/gateway-routes.ts`) and {@link GatewayRole}, which `KernelLifecycle` drives. It runs
4
+ * route, `http/gateway-routes.ts`), the device connections ({@link DeviceGateway}, `GET
5
+ * /device/connect`) and {@link GatewayRole}, which `KernelLifecycle` drives. It runs
5
6
  * no cron and no queue consumer (those are the worker role's).
6
7
  */
7
8
  export declare class GatewayModule {
@@ -8,20 +8,23 @@ import { Module } from "@nestjs/common";
8
8
  import { ChannelsModule } from "../channels/channels.module.js";
9
9
  import { ChannelSupervisor } from "../channels/supervisor.js";
10
10
  import { ChannelWebhooks } from "../channels/webhook.js";
11
+ import { DeviceGateway } from "../devices/device-gateway.js";
12
+ import { DevicesModule } from "../devices/devices.module.js";
11
13
  import { ExtensionsModule } from "../extensions/extensions.module.js";
12
14
  import { GatewayRole } from "./gateway-role.js";
13
15
  /**
14
16
  * The gateway role (nest kernel spec §2), imported by `KernelModule` only when `roles` includes
15
17
  * `gateway`: the channel long-connection supervisor, the webhook inbound (served as a raw Fastify
16
- * route, `http/gateway-routes.ts`) and {@link GatewayRole}, which `KernelLifecycle` drives. It runs
18
+ * route, `http/gateway-routes.ts`), the device connections ({@link DeviceGateway}, `GET
19
+ * /device/connect`) and {@link GatewayRole}, which `KernelLifecycle` drives. It runs
17
20
  * no cron and no queue consumer (those are the worker role's).
18
21
  */
19
22
  let GatewayModule = class GatewayModule {
20
23
  };
21
24
  GatewayModule = __decorate([
22
25
  Module({
23
- imports: [ChannelsModule, ExtensionsModule],
24
- providers: [ChannelSupervisor, ChannelWebhooks, GatewayRole],
26
+ imports: [ChannelsModule, ExtensionsModule, DevicesModule],
27
+ providers: [ChannelSupervisor, ChannelWebhooks, DeviceGateway, GatewayRole],
25
28
  exports: [GatewayRole],
26
29
  })
27
30
  ], GatewayModule);
@@ -233,6 +233,8 @@ export declare function summaryText(summary: string): string;
233
233
  * `user_message` / `assistant_message`(content 原样,含 opaque;aborted 的也回放;媒体引用按
234
234
  * {@link resolveMedia} 换成 base64 或文字占位)/ `reminder`
235
235
  * (`role: "system"`,放在它所在的位置)/ `tools_changed` (`role: "system"`, the text of {@link toolsChangedText}, at its position) /
236
+ * `environment_paths` (`role: "system"` at its position, `environmentPathsText`; covered by the
237
+ * summary: the latest one follows the summary) /
236
238
  * `run_context` (`role: "system"` at its position, `runContextText`; covered by the summary: the
237
239
  * current context follows the summary whole; a user message's sender line by `senderLines`) /
238
240
  * `background_task_finished`(`role: "system"`, the text of
@@ -4,6 +4,7 @@ import { renderBackgroundFinished } from "../../background/render.js";
4
4
  import { isAbsorbable, } from "../../conversations/events.js";
5
5
  import { catalogText, isAlwaysLoaded } from "../../tools/builtins/tool-search.js";
6
6
  import { renderSubagentDelivery } from "../subagents/render.js";
7
+ import { environmentPathsAt, environmentPathsText } from "./environment-paths.js";
7
8
  import { LEGACY_CATALOG_HEADER, LEGACY_SKILL_TEXTS, usesLegacyToolNames } from "./legacy-text.js";
8
9
  import { currentRunContext, runContextEvents, runContextFullText, runContextText, senderLines, } from "./run-context.js";
9
10
  import { invokedSkillNames, invokedSkillsText, SKILL_TEXTS, skillsCatalogEvents, snapshotChangeText, snapshotCurrentText, snapshotSystemText, systemSkillsCatalog, } from "./skills.js";
@@ -336,6 +337,8 @@ export function summaryText(summary) {
336
337
  * `user_message` / `assistant_message`(content 原样,含 opaque;aborted 的也回放;媒体引用按
337
338
  * {@link resolveMedia} 换成 base64 或文字占位)/ `reminder`
338
339
  * (`role: "system"`,放在它所在的位置)/ `tools_changed` (`role: "system"`, the text of {@link toolsChangedText}, at its position) /
340
+ * `environment_paths` (`role: "system"` at its position, `environmentPathsText`; covered by the
341
+ * summary: the latest one follows the summary) /
339
342
  * `run_context` (`role: "system"` at its position, `runContextText`; covered by the summary: the
340
343
  * current context follows the summary whole; a user message's sender line by `senderLines`) /
341
344
  * `background_task_finished`(`role: "system"`, the text of
@@ -547,6 +550,12 @@ export function buildModelRequest(input) {
547
550
  content: runContextFullText(currentRunContext(events, after)),
548
551
  });
549
552
  }
553
+ // environments spec §13.1: the instance's directories told before the boundary follow the
554
+ // summary again (the model keeps using the real paths)
555
+ const paths = environmentPathsAt(events, after);
556
+ if (paths) {
557
+ messages.push({ role: "system", content: environmentPathsText(paths.payload.roots) });
558
+ }
550
559
  // 被摘要覆盖的变更提醒不再发送:摘要时生效的目录与 system 里的不同 → 紧随摘要补上完整的当前目录
551
560
  const covered = skillSnaps.filter((e) => e.seq <= after).at(-1);
552
561
  // (phase B: plus the current pinned skills and tool-name map when they differ from system's)
@@ -620,6 +629,11 @@ export function buildModelRequest(input) {
620
629
  case "run_context":
621
630
  messages.push({ role: "system", content: runContextText(events, e) });
622
631
  break;
632
+ // the instance's directories (environments spec §13.1): logged during a step's tool call,
633
+ // so rendered after that step's tool results
634
+ case "environment_paths":
635
+ messages.push({ role: "system", content: environmentPathsText(e.payload.roots) });
636
+ break;
623
637
  // hook_denied is not model-visible: the model sees only the denied tool call's tool_result
624
638
  // (and nothing for a denied model request), so request building skips it
625
639
  case "hook_denied":
@@ -134,6 +134,7 @@ function transcriptLine(e) {
134
134
  // phase D: a forked skill's report is user content of the request (build.ts)
135
135
  case "skill_fork_result":
136
136
  return `User: ${e.payload.text}`;
137
+ // environment_paths is left out too: the latest one follows the summary (build.ts)
137
138
  // run_context is left out: its current values follow the summary anyway (build.ts), and
138
139
  // stale names should not be summarised as current
139
140
  case "tool_result":
@@ -0,0 +1,40 @@
1
+ import type { SandboxRoots } from "@capybara-hq/plugin-sdk";
2
+ import type { ConversationEvent } from "../../conversations/events.js";
3
+ /**
4
+ * The `environment_paths` event (environments spec §13.1, ruling R22): the first sandbox tool call
5
+ * of every conversation records the instance's roots (the defaults included), and a later call
6
+ * records them again whenever they differ from the conversation's latest recorded ones (a
7
+ * connected machine's real paths, a mode switch back to the defaults). Model-visible: rendered at
8
+ * its position as a system message; in the log, so it needs no `DriftReason`.
9
+ */
10
+ /** The system line of an `environment_paths` event (model-visible, English). */
11
+ export declare function environmentPathsText(r: SandboxRoots): string;
12
+ /** Whether two sets of roots are the same paths. */
13
+ export declare const sameRoots: (a: SandboxRoots, b: SandboxRoots) => boolean;
14
+ /**
15
+ * The idempotency key of an `environment_paths` event for `r` that follows the conversation's
16
+ * event at `prevSeq` (0: none). Unique per change (A → B → A gets three events), while parallel
17
+ * calls that saw the same latest event write it once.
18
+ */
19
+ export declare const environmentPathsIdemKey: (r: SandboxRoots, prevSeq: number) => string;
20
+ /**
21
+ * The `environment_paths` event to append when a sandbox call holds an instance with `roots` and
22
+ * the conversation's latest such event is `latest`; null when nothing changed.
23
+ */
24
+ export declare function environmentPathsChange(latest: {
25
+ seq: number;
26
+ payload: {
27
+ roots: SandboxRoots;
28
+ };
29
+ } | undefined, roots: SandboxRoots, runId: string | undefined): {
30
+ type: "environment_paths";
31
+ runId?: string;
32
+ idemKey: string;
33
+ payload: {
34
+ roots: SandboxRoots;
35
+ };
36
+ } | null;
37
+ /** The latest `environment_paths` event at or before `seq`; undefined without one. */
38
+ export declare function environmentPathsAt(events: readonly ConversationEvent[], seq: number): Extract<ConversationEvent, {
39
+ type: "environment_paths";
40
+ }> | undefined;
@@ -0,0 +1,48 @@
1
+ import { createHash } from "node:crypto";
2
+ /**
3
+ * The `environment_paths` event (environments spec §13.1, ruling R22): the first sandbox tool call
4
+ * of every conversation records the instance's roots (the defaults included), and a later call
5
+ * records them again whenever they differ from the conversation's latest recorded ones (a
6
+ * connected machine's real paths, a mode switch back to the defaults). Model-visible: rendered at
7
+ * its position as a system message; in the log, so it needs no `DriftReason`.
8
+ */
9
+ /** The system line of an `environment_paths` event (model-visible, English). */
10
+ export function environmentPathsText(r) {
11
+ return `This environment's directories: workspace ${r.workspace}, skills ${r.skills} (read only), temporary files ${r.tmp}. Use these paths in commands and tool calls.`;
12
+ }
13
+ /** Whether two sets of roots are the same paths. */
14
+ export const sameRoots = (a, b) => a.workspace === b.workspace && a.skills === b.skills && a.tmp === b.tmp;
15
+ /**
16
+ * The idempotency key of an `environment_paths` event for `r` that follows the conversation's
17
+ * event at `prevSeq` (0: none). Unique per change (A → B → A gets three events), while parallel
18
+ * calls that saw the same latest event write it once.
19
+ */
20
+ export const environmentPathsIdemKey = (r, prevSeq) => `environment_paths:${createHash("sha256")
21
+ .update(JSON.stringify([r.workspace, r.skills, r.tmp]))
22
+ .digest("hex")
23
+ .slice(0, 16)}:${prevSeq}`;
24
+ /**
25
+ * The `environment_paths` event to append when a sandbox call holds an instance with `roots` and
26
+ * the conversation's latest such event is `latest`; null when nothing changed.
27
+ */
28
+ export function environmentPathsChange(latest, roots, runId) {
29
+ if (latest && sameRoots(latest.payload.roots, roots))
30
+ return null;
31
+ return {
32
+ type: "environment_paths",
33
+ ...(runId !== undefined && { runId }),
34
+ idemKey: environmentPathsIdemKey(roots, latest?.seq ?? 0),
35
+ payload: { roots: { workspace: roots.workspace, skills: roots.skills, tmp: roots.tmp } },
36
+ };
37
+ }
38
+ /** The latest `environment_paths` event at or before `seq`; undefined without one. */
39
+ export function environmentPathsAt(events, seq) {
40
+ let found;
41
+ for (const e of events) {
42
+ if (e.seq > seq)
43
+ break;
44
+ if (e.type === "environment_paths")
45
+ found = e;
46
+ }
47
+ return found;
48
+ }
@@ -163,6 +163,8 @@ wrapUpReason) {
163
163
  subagents: input.agent.subagents,
164
164
  child: input.parent !== undefined || input.parentRunId !== undefined || input.subagent === true,
165
165
  forkChild: input.parent?.forSkill === true,
166
+ sandboxConversationId: input.parent?.rootConversationId ?? input.conversationId,
167
+ replay: input.conversationKind === "eval",
166
168
  skillLoad: skillLoadOffered(events.findLast((e) => e.type === "skills_catalog"), hasInvocation(events)),
167
169
  });
168
170
  const tools = {
@@ -1,19 +1,41 @@
1
1
  import { type SandboxProvider } from "@capybara-hq/plugin-sdk";
2
2
  import { ExtensionRegistry } from "../extensions/registry.js";
3
- /** 当前的 `sandbox@2` 提供方:插件名与实现。 */
3
+ /** The kernel-internal provider of connected machines (`device` environments). */
4
+ export declare const DEVICE_PROVIDER = "@device";
5
+ /**
6
+ * A kernel-internal provider name: it starts with `@`, which no extension name can
7
+ * (`EXTENSION_NAME_RE`), so it never collides with a loaded `sandbox@2` plugin.
8
+ */
9
+ export declare const isInternalProvider: (name: string) => boolean;
10
+ /** A sandbox provider: its name (plugin name, or an internal `@…` name) and implementation. */
4
11
  export interface SandboxProviderEntry {
5
12
  plugin: string;
6
13
  impl: SandboxProvider;
7
14
  }
8
15
  /**
9
- * The `sandbox@2` providers of this process, read from the {@link ExtensionRegistry} at every
10
- * call: providers are ready only after the extension load, and change with installs.
16
+ * The sandbox providers of this process. The cloud provider is the loaded `sandbox@2` extension,
17
+ * read from the {@link ExtensionRegistry} at every call (providers are ready only after the
18
+ * extension load, and change with installs). Kernel-internal providers (`@device`) are registered
19
+ * by their module on init, in every replica: they are code, not shared state.
11
20
  */
12
21
  export declare class SandboxProviders {
13
22
  private readonly registry;
23
+ private readonly internal;
14
24
  constructor(registry: Pick<ExtensionRegistry, "list">);
15
- /** The current provider; undefined without one. */
16
- current(): SandboxProviderEntry | undefined;
17
- /** A still-loaded provider by plugin name (to destroy an earlier provider's instance). */
18
- lookup(plugin: string): SandboxProvider | undefined;
25
+ /** Registers a kernel-internal provider (an `@…` name); a later registration replaces it. */
26
+ register(entry: SandboxProviderEntry): void;
27
+ /** The cloud provider (the loaded `sandbox@2` extension); undefined without one. */
28
+ cloud(): SandboxProviderEntry | undefined;
29
+ /**
30
+ * A provider by name: a registered internal provider, else a still-loaded `sandbox@2` extension
31
+ * of that plugin name (to destroy an earlier provider's resources).
32
+ */
33
+ forName(name: string): SandboxProviderEntry | undefined;
19
34
  }
35
+ /**
36
+ * The provider an environment works with, from its `environments.provider`: an internal one
37
+ * (`@device`) by that name only, never falling back to the cloud provider; any other environment
38
+ * is a cloud environment and uses the current cloud provider (files kept by another provider are
39
+ * refused by the sandbox service, `environment.unavailable`).
40
+ */
41
+ export declare function providerForEnvironment(providers: Pick<SandboxProviders, "cloud" | "forName">, provider: string | null): SandboxProviderEntry | undefined;
@@ -10,25 +10,49 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
10
10
  import { SANDBOX_EXTENSION_POINT } from "@capybara-hq/plugin-sdk";
11
11
  import { Inject, Injectable } from "@nestjs/common";
12
12
  import { ExtensionRegistry } from "../extensions/registry.js";
13
+ /** The kernel-internal provider of connected machines (`device` environments). */
14
+ export const DEVICE_PROVIDER = "@device";
13
15
  /**
14
- * The `sandbox@2` providers of this process, read from the {@link ExtensionRegistry} at every
15
- * call: providers are ready only after the extension load, and change with installs.
16
+ * A kernel-internal provider name: it starts with `@`, which no extension name can
17
+ * (`EXTENSION_NAME_RE`), so it never collides with a loaded `sandbox@2` plugin.
18
+ */
19
+ export const isInternalProvider = (name) => name.startsWith("@");
20
+ /**
21
+ * The sandbox providers of this process. The cloud provider is the loaded `sandbox@2` extension,
22
+ * read from the {@link ExtensionRegistry} at every call (providers are ready only after the
23
+ * extension load, and change with installs). Kernel-internal providers (`@device`) are registered
24
+ * by their module on init, in every replica: they are code, not shared state.
16
25
  */
17
26
  let SandboxProviders = class SandboxProviders {
18
27
  registry;
28
+ internal = new Map();
19
29
  constructor(registry) {
20
30
  this.registry = registry;
21
31
  }
22
- /** The current provider; undefined without one. */
23
- current() {
32
+ /** Registers a kernel-internal provider (an `@…` name); a later registration replaces it. */
33
+ register(entry) {
34
+ if (!isInternalProvider(entry.plugin)) {
35
+ throw new Error(`an internal sandbox provider name starts with @: ${entry.plugin}`);
36
+ }
37
+ this.internal.set(entry.plugin, entry);
38
+ }
39
+ /** The cloud provider (the loaded `sandbox@2` extension); undefined without one. */
40
+ cloud() {
24
41
  const e = this.registry.list("sandbox").find((x) => x.capability === SANDBOX_EXTENSION_POINT);
25
42
  return e ? { plugin: e.plugin, impl: e.impl } : undefined;
26
43
  }
27
- /** A still-loaded provider by plugin name (to destroy an earlier provider's instance). */
28
- lookup(plugin) {
29
- return this.registry
44
+ /**
45
+ * A provider by name: a registered internal provider, else a still-loaded `sandbox@2` extension
46
+ * of that plugin name (to destroy an earlier provider's resources).
47
+ */
48
+ forName(name) {
49
+ const own = this.internal.get(name);
50
+ if (own)
51
+ return own;
52
+ const e = this.registry
30
53
  .list("sandbox")
31
- .find((x) => x.capability === SANDBOX_EXTENSION_POINT && x.plugin === plugin)?.impl;
54
+ .find((x) => x.capability === SANDBOX_EXTENSION_POINT && x.plugin === name);
55
+ return e ? { plugin: e.plugin, impl: e.impl } : undefined;
32
56
  }
33
57
  };
34
58
  SandboxProviders = __decorate([
@@ -36,3 +60,14 @@ SandboxProviders = __decorate([
36
60
  __param(0, Inject(ExtensionRegistry))
37
61
  ], SandboxProviders);
38
62
  export { SandboxProviders };
63
+ /**
64
+ * The provider an environment works with, from its `environments.provider`: an internal one
65
+ * (`@device`) by that name only, never falling back to the cloud provider; any other environment
66
+ * is a cloud environment and uses the current cloud provider (files kept by another provider are
67
+ * refused by the sandbox service, `environment.unavailable`).
68
+ */
69
+ export function providerForEnvironment(providers, provider) {
70
+ if (provider !== null && isInternalProvider(provider))
71
+ return providers.forName(provider);
72
+ return providers.cloud();
73
+ }