@capybara-hq/kernel 0.1.0-rc.19 → 0.1.0-rc.20

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 (141) hide show
  1. package/bootstrap.json +25 -25
  2. package/dist/admin/admin-agents.controller.d.ts +28 -0
  3. package/dist/admin/admin-agents.controller.js +79 -0
  4. package/dist/admin/admin-connectors.controller.d.ts +29 -0
  5. package/dist/admin/admin-connectors.controller.js +79 -0
  6. package/dist/admin/admin-items.schemas.d.ts +42 -0
  7. package/dist/admin/admin-items.schemas.js +48 -0
  8. package/dist/admin/admin.controller.d.ts +11 -20
  9. package/dist/admin/admin.controller.js +28 -34
  10. package/dist/admin/admin.module.d.ts +4 -1
  11. package/dist/admin/admin.module.js +50 -4
  12. package/dist/admin/admin.schemas.d.ts +4 -0
  13. package/dist/admin/admin.schemas.js +4 -1
  14. package/dist/admin/audit.controller.d.ts +14 -0
  15. package/dist/admin/audit.controller.js +42 -0
  16. package/dist/admin/audit.schemas.d.ts +8 -0
  17. package/dist/admin/audit.schemas.js +17 -0
  18. package/dist/admin/health.service.d.ts +104 -0
  19. package/dist/admin/health.service.js +246 -0
  20. package/dist/admin/overview.controller.d.ts +31 -0
  21. package/dist/admin/overview.controller.js +78 -0
  22. package/dist/admin/overview.schemas.d.ts +8 -0
  23. package/dist/admin/overview.schemas.js +13 -0
  24. package/dist/admin/overview.service.d.ts +112 -0
  25. package/dist/admin/overview.service.js +223 -0
  26. package/dist/admin/platform-audit.service.d.ts +41 -0
  27. package/dist/admin/platform-audit.service.js +70 -0
  28. package/dist/admin/usage.service.d.ts +149 -0
  29. package/dist/admin/usage.service.js +321 -0
  30. package/dist/agents/admin-agents.d.ts +92 -0
  31. package/dist/agents/admin-agents.js +170 -0
  32. package/dist/agents/agents.module.d.ts +4 -3
  33. package/dist/agents/agents.module.js +7 -3
  34. package/dist/agents/agents.service.d.ts +41 -3
  35. package/dist/agents/agents.service.js +113 -27
  36. package/dist/agents/definition.d.ts +16 -0
  37. package/dist/agents/definition.js +32 -2
  38. package/dist/agents/org-templates.js +4 -0
  39. package/dist/background/launch.d.ts +1 -1
  40. package/dist/background/launch.js +3 -3
  41. package/dist/background/tasks.service.d.ts +35 -3
  42. package/dist/background/tasks.service.js +91 -36
  43. package/dist/catalog/catalog.module.js +8 -2
  44. package/dist/channels/channels.module.js +11 -2
  45. package/dist/channels/supervisor.d.ts +2 -0
  46. package/dist/channels/supervisor.js +4 -0
  47. package/dist/conversations/events.js +1 -1
  48. package/dist/conversations/titles.js +4 -8
  49. package/dist/db/kernel-schema.d.ts +8 -1
  50. package/dist/devices/devices.module.js +5 -2
  51. package/dist/errors.d.ts +22 -2
  52. package/dist/errors.js +22 -2
  53. package/dist/evals/evals.module.js +11 -2
  54. package/dist/extensions/permission-view.js +3 -3
  55. package/dist/http/api.module.js +10 -1
  56. package/dist/identity/settings.js +3 -2
  57. package/dist/identity/write-actor.d.ts +11 -0
  58. package/dist/identity/write-actor.js +7 -0
  59. package/dist/inbox/inbox.service.d.ts +2 -1
  60. package/dist/inbox/platform-inbox.service.d.ts +7 -1
  61. package/dist/inbox/platform-inbox.service.js +20 -2
  62. package/dist/infra/presence/presence.module.d.ts +6 -0
  63. package/dist/infra/presence/presence.module.js +22 -0
  64. package/dist/infra/presence/replica-presence.d.ts +87 -0
  65. package/dist/infra/presence/replica-presence.js +189 -0
  66. package/dist/kernel.module.js +3 -0
  67. package/dist/lifecycle/kernel-lifecycle.d.ts +3 -1
  68. package/dist/lifecycle/kernel-lifecycle.js +17 -3
  69. package/dist/local-devices/local-device-gateway.d.ts +6 -0
  70. package/dist/local-devices/local-device-gateway.js +12 -0
  71. package/dist/overrides.d.ts +7 -1
  72. package/dist/overrides.js +4 -0
  73. package/dist/platform/exclusions.js +15 -0
  74. package/dist/platform/operation.d.ts +1 -1
  75. package/dist/platform/operation.js +1 -1
  76. package/dist/platform/ops/admin-items.ops.d.ts +37 -0
  77. package/dist/platform/ops/admin-items.ops.js +278 -0
  78. package/dist/platform/ops/admin.ops.d.ts +10 -10
  79. package/dist/platform/ops/admin.ops.js +111 -42
  80. package/dist/platform/ops/agent-skills.ops.d.ts +29 -3
  81. package/dist/platform/ops/agent-skills.ops.js +290 -7
  82. package/dist/platform/ops/inbox.ops.js +7 -4
  83. package/dist/platform/ops/platform.ops.js +1 -1
  84. package/dist/platform/ops/skills.ops.js +1 -57
  85. package/dist/platform/platform-ops.module.d.ts +2 -1
  86. package/dist/platform/platform-ops.module.js +5 -0
  87. package/dist/platform/route-schemas.js +8 -0
  88. package/dist/roles/gateway-role.d.ts +4 -0
  89. package/dist/roles/gateway-role.js +8 -0
  90. package/dist/runtime/run-jobs.js +4 -2
  91. package/dist/sandbox/sandbox-file-reader.d.ts +35 -0
  92. package/dist/sandbox/sandbox-file-reader.js +98 -0
  93. package/dist/skills/agent-skills.controller.d.ts +48 -0
  94. package/dist/skills/agent-skills.controller.js +131 -0
  95. package/dist/skills/agent-skills.service.d.ts +262 -0
  96. package/dist/skills/agent-skills.service.js +996 -0
  97. package/dist/skills/catalog.d.ts +8 -6
  98. package/dist/skills/catalog.js +8 -4
  99. package/dist/skills/changes.controller.d.ts +5 -22
  100. package/dist/skills/changes.controller.js +7 -55
  101. package/dist/skills/changes.service.d.ts +69 -115
  102. package/dist/skills/changes.service.js +249 -599
  103. package/dist/skills/conversation-catalog.d.ts +53 -0
  104. package/dist/skills/conversation-catalog.js +149 -0
  105. package/dist/skills/invocation-keys.d.ts +1 -1
  106. package/dist/skills/invocation-keys.js +1 -1
  107. package/dist/skills/package.d.ts +0 -5
  108. package/dist/skills/package.js +0 -14
  109. package/dist/skills/skills.controller.js +1 -10
  110. package/dist/skills/skills.module.js +15 -2
  111. package/dist/skills/skills.schemas.d.ts +11 -15
  112. package/dist/skills/skills.schemas.js +27 -20
  113. package/dist/skills/skills.service.d.ts +37 -30
  114. package/dist/skills/skills.service.js +107 -59
  115. package/dist/skills/usable.d.ts +4 -3
  116. package/dist/skills/usable.js +13 -4
  117. package/dist/tools/admin-connectors.d.ts +69 -0
  118. package/dist/tools/admin-connectors.js +135 -0
  119. package/dist/tools/builtin-names.d.ts +9 -14
  120. package/dist/tools/builtin-names.js +13 -22
  121. package/dist/tools/builtins/builtin-tools.d.ts +2 -3
  122. package/dist/tools/builtins/builtin-tools.js +21 -26
  123. package/dist/tools/builtins/sandbox/bash.js +2 -2
  124. package/dist/tools/builtins/skill-load.js +1 -0
  125. package/dist/tools/connectors.controller.d.ts +5 -1
  126. package/dist/tools/connectors.controller.js +17 -3
  127. package/dist/tools/connectors.service.d.ts +20 -1
  128. package/dist/tools/connectors.service.js +61 -0
  129. package/dist/tools/legacy-keys.js +0 -1
  130. package/dist/tools/org-tool-switches.d.ts +3 -2
  131. package/dist/tools/org-tool-switches.js +4 -4
  132. package/dist/tools/policy.d.ts +7 -9
  133. package/dist/tools/policy.js +11 -22
  134. package/dist/tools/tools.module.js +3 -0
  135. package/package.json +1 -1
  136. package/src/migrations/0109_audit_platform_idx.sql +5 -0
  137. package/src/migrations/0110_usage_time_idx.sql +8 -0
  138. package/src/migrations/0111_agent_skill_sharing.sql +22 -0
  139. package/src/migrations/0112_skill_share_requests.sql +4 -0
  140. package/dist/tools/builtins/skill-write.d.ts +0 -59
  141. package/dist/tools/builtins/skill-write.js +0 -244
@@ -135,46 +135,91 @@ let BackgroundTasks = class BackgroundTasks {
135
135
  }
136
136
  /**
137
137
  * The end of the task's output, at most `tail` bytes. Read live when the task's device
138
- * instance runs (never resuming a suspended one); a finished task whose sandbox does not run answers
139
- * with the last lines its finished event kept. Anything else is
140
- * `background_task.output_unavailable` (409).
138
+ * instance runs (never resuming a suspended one); a finished task that cannot be read live
139
+ * answers with the last lines its finished event kept and why (`unavailable`). Anything else is
140
+ * `background_task.output_unavailable` (409) with `details: [{ reason, environment }]`.
141
141
  */
142
142
  async output(orgId, conversationId, accountId, taskId, tail) {
143
143
  const task = await this.owned(orgId, conversationId, accountId, taskId);
144
- if (task.sid !== null &&
145
- task.sandboxCreatedAt !== null &&
146
- task.deviceId !== null &&
147
- (await this.sandbox.availableFor(task.deviceId)) &&
148
- (await this.deviceUsable(orgId, task.conversationId))) {
149
- const createdAt = task.sandboxCreatedAt;
150
- // the person reading it acts in the task's conversation
151
- const actor = { accountId, conversationId: task.conversationId };
152
- try {
153
- const r = await this.sandbox.ifRunning(task.deviceId, { createdAt }, async (p, h, signal) => {
154
- const res = await actingAs(p, actor).exec(h, {
155
- command: tailScript(task.id, tail),
156
- cwd: rootsOf(h).workspace,
157
- timeoutMs: OUTPUT_READ_TIMEOUT_MS,
158
- maxOutputBytes: 2 * OUTPUT_TAIL_MAX_BYTES + 4096,
159
- signal,
160
- });
161
- return res.exitCode === 0 ? parseTail(res.stdout, tail) : null;
162
- }, AbortSignal.timeout(OUTPUT_READ_TIMEOUT_MS));
163
- if (r.kind === "ran" && r.value !== null)
164
- return { ...r.value, source: "live" };
165
- }
166
- catch (e) {
167
- if (!isSandboxGoneError(e))
168
- throw e;
169
- }
170
- }
144
+ const read = await this.readLive(orgId, accountId, task, tail);
145
+ if (read.kind === "ok")
146
+ return { ...read.value, source: "live" };
147
+ const unavailable = {
148
+ reason: read.reason,
149
+ environment: await this.environmentOf(task.deviceId),
150
+ };
171
151
  if (task.status !== "running") {
172
152
  const ev = await this.eventLog.findByIdem(task.conversationId, finishedEventKey(task.id));
173
153
  if (ev?.type === "background_task_finished") {
174
- return { text: ev.payload.lastLines, truncated: true, source: "event" };
154
+ return { text: ev.payload.lastLines, truncated: true, source: "event", unavailable };
155
+ }
156
+ }
157
+ throw new ApiError("background_task.output_unavailable", `The task's output cannot be read now (${unavailable.reason})`, [unavailable]);
158
+ }
159
+ /** Whether the task ran on a local device (its machine) or in a cloud sandbox. */
160
+ async environmentOf(deviceId) {
161
+ if (deviceId === null)
162
+ return "cloud";
163
+ const row = await this.db.db
164
+ .selectFrom("devices")
165
+ .select("source")
166
+ .where("id", "=", deviceId)
167
+ .executeTakeFirst();
168
+ return row?.source === "local" ? "local" : "cloud";
169
+ }
170
+ /** Reads the output from the task's running instance, or says why it cannot. */
171
+ async readLive(orgId, accountId, task, tail) {
172
+ if (task.sid === null || task.sandboxCreatedAt === null || task.deviceId === null) {
173
+ // a reservation the launch has not finished, or a launch that failed
174
+ return {
175
+ kind: "unavailable",
176
+ reason: task.status === "running" ? "starting" : "not_started",
177
+ };
178
+ }
179
+ if (!(await this.sandbox.availableFor(task.deviceId))) {
180
+ return { kind: "unavailable", reason: "unavailable" };
181
+ }
182
+ if (!(await this.deviceUsable(orgId, task.conversationId))) {
183
+ return { kind: "unavailable", reason: "no_access" };
184
+ }
185
+ const createdAt = task.sandboxCreatedAt;
186
+ // the person reading it acts in the task's conversation
187
+ const actor = { accountId, conversationId: task.conversationId };
188
+ try {
189
+ const r = await this.sandbox.ifRunning(task.deviceId, { createdAt }, async (p, h, signal) => {
190
+ const res = await actingAs(p, actor).exec(h, {
191
+ command: tailScript(task.id, tail),
192
+ cwd: rootsOf(h).workspace,
193
+ timeoutMs: OUTPUT_READ_TIMEOUT_MS,
194
+ maxOutputBytes: 2 * OUTPUT_TAIL_MAX_BYTES + 4096,
195
+ signal,
196
+ });
197
+ return res.exitCode === 0 ? parseTail(res.stdout, tail) : null;
198
+ }, AbortSignal.timeout(OUTPUT_READ_TIMEOUT_MS));
199
+ if (r.kind === "other_instance")
200
+ return { kind: "unavailable", reason: "rebuilt" };
201
+ if (r.kind === "not_running") {
202
+ // a suspended instance keeps its files; any other state means they went with it
203
+ return { kind: "unavailable", reason: r.status === "suspended" ? "suspended" : "rebuilt" };
204
+ }
205
+ if (r.value === null)
206
+ return { kind: "unavailable", reason: "read_failed" };
207
+ if (r.value === "missing")
208
+ return { kind: "unavailable", reason: "output_missing" };
209
+ return { kind: "ok", value: r.value };
210
+ }
211
+ catch (e) {
212
+ if (isSandboxGoneError(e))
213
+ return { kind: "unavailable", reason: "rebuilt" };
214
+ // a local device's machine: offline, or no longer the owner's (revoked, deleted)
215
+ if (e instanceof ApiError && e.code.startsWith("device.")) {
216
+ return {
217
+ kind: "unavailable",
218
+ reason: e.code === "device.offline" ? "device_offline" : "no_access",
219
+ };
175
220
  }
221
+ throw e;
176
222
  }
177
- throw new ApiError("background_task.output_unavailable", "The task's output cannot be read now: its sandbox is not running");
178
223
  }
179
224
  };
180
225
  BackgroundTasks = __decorate([
@@ -188,18 +233,28 @@ BackgroundTasks = __decorate([
188
233
  __param(6, Inject(ConversationDevices))
189
234
  ], BackgroundTasks);
190
235
  export { BackgroundTasks };
191
- /** Prints `size <bytes>` and `out <base64 of the last `tail` bytes>`. Relative to the workspace root. */
192
- function tailScript(id, tail) {
236
+ /**
237
+ * Prints `size <bytes>` and `out <base64 of the last `tail` bytes>`, or `missing` when the output
238
+ * file is not there. Relative to the workspace root; POSIX tools only (`wc -c`, `tail -c`,
239
+ * `base64`, `tr`), so it reads the same on Linux and macOS.
240
+ */
241
+ export function tailScript(id, tail) {
193
242
  if (!TASK_ID_RE.test(id))
194
243
  throw new Error(`invalid background task id: ${id}`);
195
244
  const f = `${TASKS_DIR}/${id}.out`;
196
245
  return [
246
+ `if [ ! -f ${f} ]; then echo missing; exit 0; fi`,
197
247
  `s=$({ wc -c < ${f}; } 2>/dev/null | tr -d ' '); echo "size \${s:-0}"`,
198
248
  `printf 'out '; { tail -c ${Math.floor(tail)} ${f} 2>/dev/null | base64 | tr -d '\\n'; }; echo`,
199
249
  ].join("\n");
200
250
  }
201
- /** Decodes {@link tailScript}'s output; a cut that splits a UTF-8 character drops its tail bytes. */
202
- function parseTail(stdout, tail) {
251
+ /**
252
+ * Decodes {@link tailScript}'s output (`missing`: no output file; null: unreadable); a cut that
253
+ * splits a UTF-8 character drops its tail bytes.
254
+ */
255
+ export function parseTail(stdout, tail) {
256
+ if (/^missing$/m.test(stdout))
257
+ return "missing";
203
258
  const size = /^size ([0-9]+)$/m.exec(stdout);
204
259
  const out = /^out (\S*)$/m.exec(stdout);
205
260
  if (!size || !out)
@@ -25,6 +25,10 @@ import { PlatformPluginUpdater } from "./plugin-upgrade.js";
25
25
  import { CatalogRequestsService } from "./requests.service.js";
26
26
  import { PLUGIN_SCOPE_QUEUE, PluginScopeSync } from "./scope-sync.js";
27
27
  export { CatalogImportModule };
28
+ /** The plugin scope sync's queue registration (re-exported: the admin health reads its counts). */
29
+ const PLUGIN_SCOPE_QUEUE_MODULE = QueueModule.registerQueue(PLUGIN_SCOPE_QUEUE);
30
+ /** This module's maintenance queue registration (re-exported for the admin health). */
31
+ const MAINTENANCE_QUEUE_MODULE = QueueModule.registerQueue(MAINTENANCE_QUEUE_NAME);
28
32
  /**
29
33
  * The plugin catalog (spec §4, §10): {@link CatalogImportModule}, catalog requests, org installs
30
34
  * with their dependencies, the platform plugin update (`PlatformPluginUpdater`), the admin's scope
@@ -46,10 +50,10 @@ CatalogModule = __decorate([
46
50
  SkillsModule,
47
51
  ToolsModule,
48
52
  // the producer side of the plugin scope sync (`PluginScopeWorkersModule` consumes it)
49
- QueueModule.registerQueue(PLUGIN_SCOPE_QUEUE),
53
+ PLUGIN_SCOPE_QUEUE_MODULE,
50
54
  // a removed org plugin's cleanup job (`OwnedItems`, `CatalogScopeService`; consumed by the
51
55
  // worker role's `MaintenanceProcessor`)
52
- QueueModule.registerQueue(MAINTENANCE_QUEUE_NAME),
56
+ MAINTENANCE_QUEUE_MODULE,
53
57
  ],
54
58
  providers: [
55
59
  CatalogRequestsService,
@@ -64,6 +68,8 @@ CatalogModule = __decorate([
64
68
  OwnedItems,
65
69
  ],
66
70
  exports: [
71
+ PLUGIN_SCOPE_QUEUE_MODULE,
72
+ MAINTENANCE_QUEUE_MODULE,
67
73
  CatalogImportModule,
68
74
  CatalogRequestsService,
69
75
  PluginInstaller,
@@ -27,6 +27,8 @@ import { ChannelInbound } from "./inbound.js";
27
27
  import { CHANNEL_QUEUE_NAME, ChannelJobs } from "./jobs.js";
28
28
  import { ChannelOutbound, deliverBackoff } from "./outbound.js";
29
29
  import { AnonymousGuestLock, AnonymousGuests } from "./threads.js";
30
+ /** The channel queue's registration (its producer here; the admin health reads its counts). */
31
+ const CHANNEL_QUEUE_MODULE = QueueModule.registerQueue(CHANNEL_QUEUE_NAME);
30
32
  /**
31
33
  * Channels on every role: the org's channel connections, inbound (dedupe and enqueue; the inbound
32
34
  * jobs), the outbound delivery, the producer of the channel queue `cap-kernel-channels`, the
@@ -48,7 +50,7 @@ ChannelsModule = __decorate([
48
50
  ToolsModule,
49
51
  ConversationsModule,
50
52
  RuntimeModule,
51
- QueueModule.registerQueue(CHANNEL_QUEUE_NAME),
53
+ CHANNEL_QUEUE_MODULE,
52
54
  ],
53
55
  providers: [
54
56
  ChannelJobs,
@@ -58,7 +60,14 @@ ChannelsModule = __decorate([
58
60
  ChannelInbound,
59
61
  ChannelOutbound,
60
62
  ],
61
- exports: [ChannelJobs, ChannelConnections, AnonymousGuests, ChannelInbound, ChannelOutbound],
63
+ exports: [
64
+ ChannelJobs,
65
+ ChannelConnections,
66
+ AnonymousGuests,
67
+ ChannelInbound,
68
+ ChannelOutbound,
69
+ CHANNEL_QUEUE_MODULE,
70
+ ],
62
71
  })
63
72
  ], ChannelsModule);
64
73
  export { ChannelsModule };
@@ -127,6 +127,8 @@ export declare class ChannelSupervisor {
127
127
  * written yet on the first round).
128
128
  */
129
129
  private targets;
130
+ /** How many long connections this process holds now (replica presence reports it). */
131
+ heldCount(): number;
130
132
  private heldOf;
131
133
  /**
132
134
  * Gives back the most recently connected connection of `plugin`, leaving a handoff marker first;
@@ -305,6 +305,10 @@ let ChannelSupervisor = class ChannelSupervisor {
305
305
  }
306
306
  return out;
307
307
  }
308
+ /** How many long connections this process holds now (replica presence reports it). */
309
+ heldCount() {
310
+ return this.held.size;
311
+ }
308
312
  heldOf(plugin) {
309
313
  let n = 0;
310
314
  for (const h of this.held.values())
@@ -604,7 +604,7 @@ export const ConversationEventSchema = z.discriminatedUnion("type", [
604
604
  /**
605
605
  * Pinned skills (phase B, spec §9.1) in the agent's order: each a skill of `skills` (same
606
606
  * id, version and source) with its SKILL.md body as read at run start or written by
607
- * SkillWrite. Absent means none (every snapshot before phase B).
607
+ * a skill registration. Absent means none (every snapshot before phase B).
608
608
  */
609
609
  pinned: z
610
610
  .array(z.object({
@@ -528,20 +528,16 @@ TitleQueue = __decorate([
528
528
  __param(3, Inject(KERNEL_LOGGER))
529
529
  ], TitleQueue);
530
530
  export { TitleQueue };
531
+ /** The title queue's registration (re-exported: the admin health reads its counts). */
532
+ const TITLE_QUEUE_MODULE = QueueModule.registerQueue(TITLE_QUEUE);
531
533
  /** Conversation titles: the queue and the job (consumed on the worker role). */
532
534
  let TitlesModule = class TitlesModule {
533
535
  };
534
536
  TitlesModule = __decorate([
535
537
  Module({
536
- imports: [
537
- EventLogModule,
538
- ModelsModule,
539
- ExtensionsModule,
540
- UsageReportModule,
541
- QueueModule.registerQueue(TITLE_QUEUE),
542
- ],
538
+ imports: [EventLogModule, ModelsModule, ExtensionsModule, UsageReportModule, TITLE_QUEUE_MODULE],
543
539
  providers: [TitleGenerator, TitleQueue],
544
- exports: [TitleQueue],
540
+ exports: [TitleQueue, TITLE_QUEUE_MODULE],
545
541
  })
546
542
  ], TitlesModule);
547
543
  export { TitlesModule };
@@ -720,6 +720,10 @@ export interface SkillsTable {
720
720
  platform_skill_id: ColumnType<string | null, string | null | undefined, string | null>;
721
721
  /** 0081: a pin keeps the copy on its own stored version (ruling B2). */
722
722
  pinned_version: ColumnType<number | null, number | null | undefined, number | null>;
723
+ /** 0111: the agent a shared org skill came from (cleared when that agent is deleted). */
724
+ origin_agent_id: ColumnType<string | null, string | null | undefined, string | null>;
725
+ /** 0111: the account that maintains a shared org skill. */
726
+ maintainer_account_id: ColumnType<string | null, string | null | undefined, string | null>;
723
727
  }
724
728
  /** 0024_skills.sql:技能的版本。 */
725
729
  export interface SkillVersionsTable {
@@ -760,8 +764,11 @@ export interface SkillChangeRequestsTable {
760
764
  proposed_version: Json<ProposedSkillVersion>;
761
765
  object_prefix: string;
762
766
  requested_by_account: string;
763
- requested_in_conversation: string;
767
+ /** 0112: null for a share request and for a version proposal registered on the web. */
768
+ requested_in_conversation: string | null;
764
769
  status: Generated<"pending" | "approved" | "rejected" | "cancelled">;
770
+ /** 0111: a share of an agent skill with the org, or a new version of a shared skill. */
771
+ kind: Dflt<"share" | "version">;
765
772
  decided_by: string | null;
766
773
  decided_at: TsNullable;
767
774
  created_at: Ts;
@@ -8,6 +8,7 @@ import { Module } from "@nestjs/common";
8
8
  import { ExtensionsModule } from "../extensions/extensions.module.js";
9
9
  import { LocalDevicesModule } from "../local-devices/local-devices.module.js";
10
10
  import { SandboxModule } from "../sandbox/sandbox.module.js";
11
+ import { SandboxFileReader } from "../sandbox/sandbox-file-reader.js";
11
12
  import { DeviceAccess } from "./access.js";
12
13
  import { ConversationDevices } from "./conversation-devices.js";
13
14
  import { DevicesService } from "./devices.service.js";
@@ -24,8 +25,10 @@ DevicesModule = __decorate([
24
25
  // ExtensionsModule: the provider display names of demoted defaults; LocalDevicesModule: deleting
25
26
  // a local device revokes its machine
26
27
  imports: [SandboxModule, ExtensionsModule, LocalDevicesModule],
27
- providers: [DeviceAccess, DevicesService, ConversationDevices],
28
- exports: [DeviceAccess, DevicesService, ConversationDevices],
28
+ // SandboxFileReader lives here, not in SandboxModule: it needs ConversationDevices, and this
29
+ // module already imports SandboxModule (the other way would be a module cycle)
30
+ providers: [DeviceAccess, DevicesService, ConversationDevices, SandboxFileReader],
31
+ exports: [DeviceAccess, DevicesService, ConversationDevices, SandboxFileReader],
29
32
  })
30
33
  ], DevicesModule);
31
34
  export { DevicesModule };
package/dist/errors.d.ts CHANGED
@@ -222,6 +222,8 @@ export declare const ERROR_CODES: {
222
222
  readonly "plugin.upgrade_failed": 502;
223
223
  readonly "setup.already_completed": 409;
224
224
  readonly "setup.token_invalid": 403;
225
+ /** The admin overview's seven-day usage aggregate ran past its statement timeout (spec §5.5). */
226
+ readonly "admin.usage_unavailable": 503;
225
227
  readonly "agent.not_found": 404;
226
228
  readonly "agent.version_conflict": 409;
227
229
  /** The profile was changed since the client read it (`expectedUpdatedAt` is stale). */
@@ -316,6 +318,14 @@ export declare const ERROR_CODES: {
316
318
  readonly "attachment.too_many_pending": 429;
317
319
  readonly "sandbox.unavailable": 503;
318
320
  readonly "sandbox.busy": 409;
321
+ /** A sandbox file path the kernel reads is not absolute or not under the sandbox roots. */
322
+ readonly "sandbox.file_invalid_path": 422;
323
+ /** No file at the sandbox path the kernel reads. */
324
+ readonly "sandbox.file_not_found": 422;
325
+ /** The sandbox path the kernel reads is a directory, not one file. */
326
+ readonly "sandbox.file_is_directory": 422;
327
+ /** The sandbox file the kernel reads is over the caller's size limit. */
328
+ readonly "sandbox.file_too_large": 422;
319
329
  /** The device is missing, deleted, or not the caller's (never 403, so ids cannot be probed). */
320
330
  readonly "device.not_found": 404;
321
331
  /** The cloud device's file system is gone at the provider; reset its files to start empty. */
@@ -355,15 +365,25 @@ export declare const ERROR_CODES: {
355
365
  readonly "skill.not_found": 404;
356
366
  /** Also a skill neither the model nor a person could use (phase D: both invocation flags off). */
357
367
  readonly "skill.invalid_package": 400;
368
+ /** An agent already has `AGENT_SKILLS_MAX` own skills. */
369
+ readonly "skill.agent_cap": 409;
370
+ /** Sharing this agent skill with the org is already waiting for an org admin. */
371
+ readonly "skill.share_pending": 409;
372
+ /** Another conversation's proposed version of this shared skill is waiting for an org admin. */
373
+ readonly "skill.proposal_pending_elsewhere": 409;
374
+ /** A sub-agent conversation tried to register a skill on its agent (only the root registers). */
375
+ readonly "skill.subagent_register": 403;
358
376
  /**
359
377
  * A person invoked a skill (`/name`, phase D) that is not one of the agent's effective skills
360
378
  * (`details: [{ reason: "not_found" }]`) or is hidden from the menu (`not_user_invocable`).
361
379
  */
362
380
  readonly "skill.not_invocable": 400;
363
- /** Creating a skill from text (P3-24, F18): an org-level skill with this name already exists in the org. */
381
+ /**
382
+ * Uploading or sharing a skill: an org-level skill with this name already exists in the org
383
+ * (P3-24, F18).
384
+ */
364
385
  readonly "skill.name_exists": 409;
365
386
  readonly "skill_change.not_found": 404;
366
- readonly "skill_change.not_owner": 403;
367
387
  readonly "skill_change.already_decided": 409;
368
388
  readonly "trajectory.not_found": 404;
369
389
  readonly "eval.run_not_finished": 409;
package/dist/errors.js CHANGED
@@ -222,6 +222,8 @@ export const ERROR_CODES = {
222
222
  "plugin.upgrade_failed": 502,
223
223
  "setup.already_completed": 409,
224
224
  "setup.token_invalid": 403,
225
+ /** The admin overview's seven-day usage aggregate ran past its statement timeout (spec §5.5). */
226
+ "admin.usage_unavailable": 503,
225
227
  "agent.not_found": 404,
226
228
  "agent.version_conflict": 409,
227
229
  /** The profile was changed since the client read it (`expectedUpdatedAt` is stale). */
@@ -316,6 +318,14 @@ export const ERROR_CODES = {
316
318
  "attachment.too_many_pending": 429,
317
319
  "sandbox.unavailable": 503,
318
320
  "sandbox.busy": 409,
321
+ /** A sandbox file path the kernel reads is not absolute or not under the sandbox roots. */
322
+ "sandbox.file_invalid_path": 422,
323
+ /** No file at the sandbox path the kernel reads. */
324
+ "sandbox.file_not_found": 422,
325
+ /** The sandbox path the kernel reads is a directory, not one file. */
326
+ "sandbox.file_is_directory": 422,
327
+ /** The sandbox file the kernel reads is over the caller's size limit. */
328
+ "sandbox.file_too_large": 422,
319
329
  /** The device is missing, deleted, or not the caller's (never 403, so ids cannot be probed). */
320
330
  "device.not_found": 404,
321
331
  /** The cloud device's file system is gone at the provider; reset its files to start empty. */
@@ -355,15 +365,25 @@ export const ERROR_CODES = {
355
365
  "skill.not_found": 404,
356
366
  /** Also a skill neither the model nor a person could use (phase D: both invocation flags off). */
357
367
  "skill.invalid_package": 400,
368
+ /** An agent already has `AGENT_SKILLS_MAX` own skills. */
369
+ "skill.agent_cap": 409,
370
+ /** Sharing this agent skill with the org is already waiting for an org admin. */
371
+ "skill.share_pending": 409,
372
+ /** Another conversation's proposed version of this shared skill is waiting for an org admin. */
373
+ "skill.proposal_pending_elsewhere": 409,
374
+ /** A sub-agent conversation tried to register a skill on its agent (only the root registers). */
375
+ "skill.subagent_register": 403,
358
376
  /**
359
377
  * A person invoked a skill (`/name`, phase D) that is not one of the agent's effective skills
360
378
  * (`details: [{ reason: "not_found" }]`) or is hidden from the menu (`not_user_invocable`).
361
379
  */
362
380
  "skill.not_invocable": 400,
363
- /** Creating a skill from text (P3-24, F18): an org-level skill with this name already exists in the org. */
381
+ /**
382
+ * Uploading or sharing a skill: an org-level skill with this name already exists in the org
383
+ * (P3-24, F18).
384
+ */
364
385
  "skill.name_exists": 409,
365
386
  "skill_change.not_found": 404,
366
- "skill_change.not_owner": 403,
367
387
  "skill_change.already_decided": 409,
368
388
  "trajectory.not_found": 404,
369
389
  "eval.run_not_finished": 409,
@@ -27,6 +27,8 @@ import { EvalRunsService } from "./runs.service.js";
27
27
  import { EvalSetsService } from "./sets.service.js";
28
28
  import { TrajectoryService } from "./trajectory.service.js";
29
29
  import { EVAL_MAX_STALLED_COUNT, EvalWorker } from "./worker.js";
30
+ /** The eval queue's registration (re-exported: the admin health reads its counts). */
31
+ const EVAL_QUEUE_MODULE = QueueModule.registerQueue(EVAL_QUEUE_NAME);
30
32
  /**
31
33
  * Evals: the trajectory viewer, eval sets and cases, eval runs (the producer of
32
34
  * `cap-kernel-evals`) and the run bookkeeping ({@link EvalBudgetModule}, re-exported).
@@ -42,10 +44,17 @@ EvalsModule = __decorate([
42
44
  OrgLockModule,
43
45
  ProvidersModule,
44
46
  EvalBudgetModule,
45
- QueueModule.registerQueue(EVAL_QUEUE_NAME),
47
+ EVAL_QUEUE_MODULE,
46
48
  ],
47
49
  providers: [TrajectoryService, EvalSetsService, EvalRunsService, EvalJobs],
48
- exports: [TrajectoryService, EvalSetsService, EvalRunsService, EvalJobs, EvalBudgetModule],
50
+ exports: [
51
+ TrajectoryService,
52
+ EvalSetsService,
53
+ EvalRunsService,
54
+ EvalJobs,
55
+ EvalBudgetModule,
56
+ EVAL_QUEUE_MODULE,
57
+ ],
49
58
  })
50
59
  ], EvalsModule);
51
60
  export { EvalsModule };
@@ -129,9 +129,9 @@ export function permissionGroups(set) {
129
129
  // older snapshots lack `ui`, `ui.components` and `client` (read as empty / false)
130
130
  const ui = p.ui ?? { views: [], embeds: [], components: [] };
131
131
  const components = ui.components ?? [];
132
- const secretFields = [...set.config.deployment, ...set.config.org]
133
- .filter((f) => f.secret)
134
- .map((f) => f.path || "<root>");
132
+ // Level-prefixed like the config group: a field at both levels (an overridable secret such as
133
+ // `appSecret`) is two items, not one name listed twice.
134
+ const secretFields = ["deployment", "org"].flatMap((level) => set.config[level].filter((f) => f.secret).map((f) => `${level}:${f.path || "<root>"}`));
135
135
  const configFields = ["deployment", "org"].flatMap((level) => set.config[level].map((f) => `${level}:${f.path || "<root>"}`));
136
136
  const secretItems = [...(p.secrets ? ["secret store"] : []), ...secretFields];
137
137
  const groups = [
@@ -8,7 +8,11 @@ import { Module } from "@nestjs/common";
8
8
  import { APP_GUARD } from "@nestjs/core";
9
9
  import { AdminController } from "../admin/admin.controller.js";
10
10
  import { AdminModule } from "../admin/admin.module.js";
11
+ import { AdminAgentsController } from "../admin/admin-agents.controller.js";
12
+ import { AdminConnectorsController } from "../admin/admin-connectors.controller.js";
13
+ import { AdminAuditController } from "../admin/audit.controller.js";
11
14
  import { AdminExtensionsController } from "../admin/extensions.controller.js";
15
+ import { AdminOverviewController } from "../admin/overview.controller.js";
12
16
  import { SetupController } from "../admin/setup.controller.js";
13
17
  import { AgentsController } from "../agents/agents.controller.js";
14
18
  import { AgentsModule } from "../agents/agents.module.js";
@@ -67,7 +71,8 @@ import { ConversationStreamController } from "../runtime/stream.controller.js";
67
71
  import { SetupModule } from "../setup/setup.module.js";
68
72
  import { SetupChecksController } from "../setup/setup-checks.controller.js";
69
73
  import { SetupGuidesController } from "../setup/setup-guides.controller.js";
70
- import { AgentSkillsController, InboxController, SkillChangesController, } from "../skills/changes.controller.js";
74
+ import { AgentSkillsController } from "../skills/agent-skills.controller.js";
75
+ import { InboxController, SkillChangesController } from "../skills/changes.controller.js";
71
76
  import { InvocableSkillsController } from "../skills/invocable.controller.js";
72
77
  import { SkillsController } from "../skills/skills.controller.js";
73
78
  import { SkillsModule } from "../skills/skills.module.js";
@@ -96,6 +101,10 @@ const CONTROLLERS = [
96
101
  InvitationsController,
97
102
  SetupController,
98
103
  AdminController,
104
+ AdminAgentsController,
105
+ AdminConnectorsController,
106
+ AdminAuditController,
107
+ AdminOverviewController,
99
108
  AdminExtensionsController,
100
109
  AdminCatalogController,
101
110
  AdminComponentsController,
@@ -132,8 +132,9 @@ export const DEPLOYMENT_SETTINGS = z.object({
132
132
  /** 组织能否自建连接器(不经平台目录)。 */
133
133
  customConnectors: CustomPolicy.default("allow"),
134
134
  /**
135
- * 组织能否自建技能(不经平台目录):deny 时组织管理员不能上传技能包。agent 自写技能(`SkillWrite`,私有 agent 的
136
- * agent 级技能与共享 agent 经负责人审批的变更)不受此开关约束——那是 agent 定义的一部分,由 R3 / R4 把关。
135
+ * Whether the org can create its own skills (not through the platform catalog): on deny org admins
136
+ * cannot upload skill packages. An agent registering its own skill (`agents.register-skill`) is not
137
+ * governed by it; only sharing a skill to the org is refused.
137
138
  */
138
139
  customSkills: CustomPolicy.default("allow"),
139
140
  /**
@@ -0,0 +1,11 @@
1
+ import { type Principal } from "./principal.js";
2
+ /**
3
+ * Who performs a write on the platform's behalf (a super admin acting on an org they need not be
4
+ * a member of): the account and, for a write made with a personal access token, the token id.
5
+ */
6
+ export interface WriteActor {
7
+ accountId: string;
8
+ tokenId?: string;
9
+ }
10
+ /** The writer of a request: its user and, for a personal access token, the token id. */
11
+ export declare function writeActorOf(principal: Principal): WriteActor;
@@ -0,0 +1,7 @@
1
+ import { patTokenId, requireUser } from "./principal.js";
2
+ /** The writer of a request: its user and, for a personal access token, the token id. */
3
+ export function writeActorOf(principal) {
4
+ const user = requireUser(principal);
5
+ const tokenId = patTokenId(user);
6
+ return { accountId: user.account.id, ...(tokenId !== undefined && { tokenId }) };
7
+ }
@@ -38,7 +38,8 @@ export declare const INBOX_MAX_ITEMS = 100;
38
38
  /**
39
39
  * 收件箱里的一项(按 `type` 区分,形状稳定):
40
40
  * - `tool_approval`:当前用户能处理的待处理工具调用审批(与 `GET /approvals?status=pending` 同一视图);
41
- * - `skill_change`:当前用户作为 agent 负责人能决定的待处理技能变更(M5 T15,R4);
41
+ * - `skill_change`: pending skill share requests and proposed versions of shared skills, when the
42
+ * current user is an org admin (`skillChange.kind`; spec §5.4, §5.5);
42
43
  * - `catalog_request`: pending `enable` requests from members and pending `connector_setup`
43
44
  * requests (`request.kind`; `request.agent` and `request.connectors` set, `item` null; ruling
44
45
  * P4-T6-5), when the current user is an org admin (M6 T26, C22; plugin scope spec §5.1);
@@ -91,6 +91,10 @@ export type PlatformInboxItem = {
91
91
  createdAt: string;
92
92
  notice: PlatformNoticeView;
93
93
  };
94
+ /** Every {@link PlatformInboxItem} type, in the order the admin overview lists them. */
95
+ export declare const PLATFORM_INBOX_TYPES: readonly ["catalog_request", "permission_confirmation", "org_upload_pending", "scope_sync_failed", "platform_migration", "ownership_transferred"];
96
+ /** How many items of each type wait: every type, zeros included. */
97
+ export type PlatformInboxCounts = Record<PlatformInboxItem["type"], number>;
94
98
  /** An open platform notice (`platform_notices`, kind `ownership_transferred`). */
95
99
  export interface PlatformNoticeView {
96
100
  id: string;
@@ -100,12 +104,14 @@ export interface PlatformNoticeView {
100
104
  }
101
105
  /**
102
106
  * `GET /admin/inbox`: newest first, at most {@link PLATFORM_INBOX_MAX_ITEMS}; `truncated`: more;
103
- * `total`: how many items wait in all (also `GET /admin/inbox/count`).
107
+ * `total`: how many items wait in all (also `GET /admin/inbox/count`); `byType`: `total` per
108
+ * type, counted over everything waiting (not the listed window).
104
109
  */
105
110
  export interface PlatformInboxView {
106
111
  items: PlatformInboxItem[];
107
112
  truncated: boolean;
108
113
  total: number;
114
+ byType: PlatformInboxCounts;
109
115
  }
110
116
  /**
111
117
  * The super admin's inbox (plugin scope spec §5.1 rule 5, §13.5): what waits on the platform,
@@ -24,6 +24,15 @@ export const SCOPE_SYNC_FAILURES_LIST_MAX = 200;
24
24
  export const MIGRATION_RUN_STUCK_MS = 60 * 60_000;
25
25
  /** Plugin install statuses of a plugin that is on in the org. */
26
26
  const LIVE_INSTALL = ["installing", "installed", "upgrading"];
27
+ /** Every {@link PlatformInboxItem} type, in the order the admin overview lists them. */
28
+ export const PLATFORM_INBOX_TYPES = [
29
+ "catalog_request",
30
+ "permission_confirmation",
31
+ "org_upload_pending",
32
+ "scope_sync_failed",
33
+ "platform_migration",
34
+ "ownership_transferred",
35
+ ];
27
36
  /**
28
37
  * The super admin's inbox (plugin scope spec §5.1 rule 5, §13.5): what waits on the platform,
29
38
  * gathered from the sources the admin pages read (the 待确认 tab's requests and permission
@@ -96,9 +105,18 @@ let PlatformInboxService = class PlatformInboxService {
96
105
  : a.id > b.id
97
106
  ? 1
98
107
  : 0);
99
- const total = requestCount + pending.length + failureCount + (run === null ? 0 : 1) + noticeCount;
108
+ const orgUploads = pending.filter((i) => i.owner.kind === "org").length;
109
+ const byType = {
110
+ catalog_request: requestCount,
111
+ permission_confirmation: pending.length - orgUploads,
112
+ org_upload_pending: orgUploads,
113
+ scope_sync_failed: failureCount,
114
+ platform_migration: run === null ? 0 : 1,
115
+ ownership_transferred: noticeCount,
116
+ };
117
+ const total = Object.values(byType).reduce((a, b) => a + b, 0);
100
118
  const items = all.slice(0, PLATFORM_INBOX_MAX_ITEMS);
101
- return { items, truncated: total > items.length, total };
119
+ return { items, truncated: total > items.length, total, byType };
102
120
  }
103
121
  /**
104
122
  * How many items wait (the sidebar's count): the same sources, counted where they can be (the
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Global: {@link ReplicaPresence}, on every role (each process reports itself; `KernelLifecycle`
3
+ * starts and stops it), read by the admin health overview.
4
+ */
5
+ export declare class PresenceModule {
6
+ }