@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
@@ -11,8 +11,13 @@ import { Inject, Injectable } from "@nestjs/common";
11
11
  import { z } from "zod";
12
12
  import { AgentsService } from "../../agents/agents.service.js";
13
13
  import { ApiError } from "../../errors.js";
14
+ import { KernelDb } from "../../infra/database/kernel-db.js";
15
+ import { rootConversationOf } from "../../runtime/subagents/child.js";
16
+ import { SandboxFileReader } from "../../sandbox/sandbox-file-reader.js";
17
+ import { AgentSkillsService, isOrgAdmin, } from "../../skills/agent-skills.service.js";
14
18
  import { SkillChangesService } from "../../skills/changes.service.js";
15
19
  import { SkillInvoker } from "../../skills/invoke.js";
20
+ import { SKILL_PACKAGE_MAX_BYTES } from "../../skills/package.js";
16
21
  import { AgentSkillPathParams, AgentSkillsListParams, InvocableSkillsQuerySchema, SkillChangePathParams, } from "../../skills/skills.schemas.js";
17
22
  import { SkillsService } from "../../skills/skills.service.js";
18
23
  import { defineOperation, } from "../operation.js";
@@ -22,6 +27,31 @@ import { named, orElse } from "./summary.js";
22
27
  const AgentId = AgentSkillsListParams.shape.agentId;
23
28
  const DESCRIPTION_MAX = 200;
24
29
  const clip = (s) => s.length > DESCRIPTION_MAX ? `${s.slice(0, DESCRIPTION_MAX)}…` : s;
30
+ /** A sandbox path (the sandbox tools' bound). */
31
+ const FILE_PATH_MAX = 4096;
32
+ const PATH_SHOWN_MAX = 200;
33
+ const shownPath = (p) => p.length > PATH_SHOWN_MAX ? `${p.slice(0, PATH_SHOWN_MAX)}…` : p;
34
+ const subagentRefusal = () => new ApiError("skill.subagent_register", "A sub-agent cannot register skills on its agent; only the agent the person talks with can.");
35
+ const subagentShareRefusal = () => new ApiError("skill.subagent_register", "A sub-agent cannot share skills; only the agent the person talks with can.");
36
+ /** File paths shown on a share card (the rest are counted). */
37
+ const SHARE_FILES_SHOWN = 10;
38
+ /** The model-facing result line of `agents.share-skill`. */
39
+ function sharedText(o, name) {
40
+ return o.kind === "shared"
41
+ ? `Shared skill "${name}" with the organization: it is now an org skill (the person maintains it) that any agent of the org can select; this agent keeps using it.`
42
+ : `Asked the org admins to share skill "${name}" with the organization; it waits for their decision in the inbox. Until then it stays this agent's own skill.`;
43
+ }
44
+ /** The model-facing result line of `agents.register-skill`. */
45
+ function registeredText(o, files) {
46
+ const counted = files === null ? "" : ` (${files} file${files === 1 ? "" : "s"})`;
47
+ if (o.kind === "proposed") {
48
+ return `Proposed version ${o.version} of the org skill "${o.name}"${counted}; it waits for an org admin to approve it in the inbox. Until then only this conversation sees the new version.`;
49
+ }
50
+ const head = `${o.created ? "Created" : "Updated"} skill "${o.name}", version ${o.version}${counted}.`;
51
+ return o.manualOnly
52
+ ? `${head} It is manual-only: it is not in your skill catalog; a person invokes it with /${o.name} in any conversation with this agent.`
53
+ : `${head} It applies to every conversation with this agent from now on; in this run, load it with the Skill tool (skill "${o.name}").`;
54
+ }
25
55
  const skillRow = (s) => ({
26
56
  id: s.id,
27
57
  name: s.name,
@@ -32,21 +62,81 @@ const skillRow = (s) => ({
32
62
  });
33
63
  /**
34
64
  * The skills of one agent and the skill change inbox detail (`Platform` tool, spec §14): reads of
35
- * the agent's own skills and of the invocable ones, detaching a skill, and the detail of a pending
36
- * skill change as the inbox shows it. Deciding a change stays `human-only`.
65
+ * the agent's own skills and of the invocable ones, registering, deleting and sharing a skill,
66
+ * and the detail of a skill change request as the inbox shows it. Deciding a request stays
67
+ * `human-only` (org admins on the web).
37
68
  */
38
69
  let AgentSkillsOperations = class AgentSkillsOperations {
39
70
  registry;
40
71
  agents;
41
72
  skills;
42
73
  changes;
74
+ agentSkills;
43
75
  invoker;
44
- constructor(registry, agents, skills, changes, invoker) {
76
+ files;
77
+ db;
78
+ constructor(registry, agents, skills, changes, agentSkills, invoker, files, db) {
45
79
  this.registry = registry;
46
80
  this.agents = agents;
47
81
  this.skills = skills;
48
82
  this.changes = changes;
83
+ this.agentSkills = agentSkills;
49
84
  this.invoker = invoker;
85
+ this.files = files;
86
+ this.db = db;
87
+ }
88
+ /**
89
+ * Class of registering a skill on the run's agent: `personal` on a private agent the actor owns
90
+ * whose `skills.writePolicy` is not `ask`; otherwise `org` (asked every time, never remembered).
91
+ * Also `org` when the registration may apply a version to a shared org skill at once (the actor
92
+ * is an org admin maintaining one; the package name is not known before the card).
93
+ */
94
+ async registerClass(ctx) {
95
+ const a = await this.agents.get(ctx.orgId, ctx.agentId, ctx.actorAccountId);
96
+ const personal = a.visibility === "private" &&
97
+ a.ownerAccountId === ctx.actorAccountId &&
98
+ a.definition.skills?.writePolicy !== "ask";
99
+ if (!personal)
100
+ return "org";
101
+ return (await this.agentSkills.mayApplyOrgVersions(ctx.orgId, ctx.actorAccountId))
102
+ ? "org"
103
+ : "personal";
104
+ }
105
+ /**
106
+ * The refusals read before anything touches the sandbox (the reader checks only the device):
107
+ * a sub-agent conversation, then an actor who is not the agent's current owner.
108
+ */
109
+ async registerCheck(ctx) {
110
+ if ((await rootConversationOf(this.db.db, ctx.conversationId)) !== ctx.conversationId) {
111
+ throw subagentRefusal();
112
+ }
113
+ await this.agentSkills.assertOwner(ctx.orgId, ctx.agentId, ctx.actorAccountId);
114
+ }
115
+ /**
116
+ * The refusals of sharing read before the card: a sub-agent conversation, an actor who is not
117
+ * the agent's current owner, a skill that is not one of the agent's own.
118
+ */
119
+ async shareCheck(agentId, skillId, ctx) {
120
+ if ((await rootConversationOf(this.db.db, ctx.conversationId)) !== ctx.conversationId) {
121
+ throw subagentShareRefusal();
122
+ }
123
+ await this.agentSkills.assertOwner(ctx.orgId, agentId, ctx.actorAccountId);
124
+ const skill = (await this.skills.listForAgent(ctx.orgId, agentId)).find((s) => s.id === skillId);
125
+ if (!skill)
126
+ throw new ApiError("skill.not_found", "Skill not found");
127
+ return skill;
128
+ }
129
+ /** The file count of a registered version (null for a proposal or when the row is gone). */
130
+ async filesOf(o) {
131
+ if (o.kind !== "version")
132
+ return null;
133
+ const v = await this.db.db
134
+ .selectFrom("skill_versions")
135
+ .select("manifest")
136
+ .where("skill_id", "=", o.skillId)
137
+ .where("version", "=", o.version)
138
+ .executeTakeFirst();
139
+ return v?.manifest.files.length ?? null;
50
140
  }
51
141
  /** Class of deleting a skill: your own private agent is personal; a shared agent is the org's. */
52
142
  async deleteClass(agentId, ctx) {
@@ -168,13 +258,202 @@ let AgentSkillsOperations = class AgentSkillsOperations {
168
258
  };
169
259
  },
170
260
  run: async (input, ctx) => {
171
- await this.changes.deleteAgentSkill(ctx.orgId, input.agentId, input.skillId, ctx.actorAccountId, undefined, viaAgent("agents.delete-skill", ctx));
261
+ await this.agentSkills.deleteAgentSkill(ctx.orgId, input.agentId, input.skillId, ctx.actorAccountId, undefined, viaAgent("agents.delete-skill", ctx));
172
262
  return { id: input.skillId, deleted: true };
173
263
  },
174
264
  }),
265
+ defineOperation({
266
+ id: "agents.register-skill",
267
+ description: "Register a zip package from your sandbox as one of this agent's own skills: a new skill, or the next version of this agent's skill with the same name. Build the package in the sandbox first: a zip with SKILL.md at its root (frontmatter name and description, then the instructions) plus any scripts or references, then pass its absolute path. Only the agent's owner can, and only from the conversation the person talks in (not a sub-agent). The new version applies to every conversation with this agent; in this run, load it with Skill. When the person maintains a shared org skill of the same name (and this agent has no own skill of it), the package is that skill's next version: an org admin applies it at once, anyone else's is proposed to the org admins and only this conversation uses it until approved.",
268
+ input: z.object({ filePath: z.string().min(1).max(FILE_PATH_MAX) }).strict(),
269
+ example: { filePath: "/workspace/out/weekly-report.zip" },
270
+ authz: { orgAction: "agents.create" },
271
+ mirrors: ["POST /api/v1/orgs/:orgId/agents/:agentId/skills"],
272
+ // the agent is always the run's own (ctx.agentId); the zip comes from the sandbox
273
+ omits: ["agentId", "uploadId"],
274
+ extras: ["filePath"],
275
+ confirm: "personal",
276
+ confirmFor: (_i, ctx) => this.registerClass(ctx),
277
+ errors: [
278
+ {
279
+ code: "skill.subagent_register",
280
+ next: "Hand the skill files back to your parent agent; only the agent the person talks with registers skills.",
281
+ },
282
+ {
283
+ code: "forbidden",
284
+ next: "Only the agent's owner can add or update its skills; tell the person, and the owner can register the package (or upload it on the agent's page).",
285
+ },
286
+ {
287
+ code: "agent.not_found",
288
+ next: "This agent is deleted or no longer visible to the person; tell them.",
289
+ },
290
+ {
291
+ code: "sandbox.file_invalid_path",
292
+ next: "Pass the absolute path of the zip inside the sandbox (e.g. under the workspace); check it with Glob.",
293
+ },
294
+ {
295
+ code: "sandbox.file_not_found",
296
+ next: "Check the path with Glob, or build the zip first with Bash (cd into the skill directory, then zip -r ../<name>.zip .).",
297
+ },
298
+ {
299
+ code: "sandbox.file_is_directory",
300
+ next: "Archive the directory into one zip with Bash (zip -r) and pass the zip's path.",
301
+ },
302
+ {
303
+ code: "sandbox.file_too_large",
304
+ next: "The package is over 50 MB; leave out large or generated files, rebuild the zip and retry.",
305
+ },
306
+ {
307
+ code: "skill.invalid_package",
308
+ next: "Fix the package as the message says (SKILL.md at the zip's root with name and description frontmatter, allowed paths and sizes), rebuild the zip and retry.",
309
+ },
310
+ {
311
+ code: "skill.agent_cap",
312
+ next: "Register a new version of an existing skill (same name) instead, or ask the agent's owner to delete one of its skills (agents.skills lists them).",
313
+ },
314
+ {
315
+ code: "skill.name_exists",
316
+ next: "This agent uses an org skill with this name that only its maintainer can update; change the name in SKILL.md, rebuild the zip and register again, or ask the skill's maintainer.",
317
+ },
318
+ {
319
+ code: "catalog.custom_disabled",
320
+ next: "This deployment does not allow custom org skills, so this version of a shared skill cannot be registered; tell the person.",
321
+ },
322
+ {
323
+ code: "skill.proposal_pending_elsewhere",
324
+ next: "Another conversation has a proposed version of this skill waiting for an org admin; wait for that decision, or continue in that conversation.",
325
+ },
326
+ {
327
+ code: "storage.unavailable",
328
+ next: "File storage is unavailable; try again later, or tell the person to contact the super admin.",
329
+ },
330
+ ],
331
+ precheck: (_i, ctx) => precheckBy(() => this.registerCheck(ctx)),
332
+ summary: (i) => ({
333
+ zh: `从 ${shownPath(i.filePath)} 为智能体登记技能`,
334
+ en: `Register a skill on this agent from ${shownPath(i.filePath)}`,
335
+ }),
336
+ run: async (input, ctx) => {
337
+ await this.registerCheck(ctx);
338
+ const zip = await this.files.read({
339
+ orgId: ctx.orgId,
340
+ conversationId: ctx.conversationId,
341
+ actorAccountId: ctx.actorAccountId,
342
+ path: input.filePath,
343
+ maxBytes: SKILL_PACKAGE_MAX_BYTES,
344
+ signal: ctx.signal,
345
+ });
346
+ const o = await this.agentSkills.register({
347
+ orgId: ctx.orgId,
348
+ agentId: ctx.agentId,
349
+ accountId: ctx.actorAccountId,
350
+ conversationId: ctx.conversationId,
351
+ runId: ctx.runId,
352
+ auditDetail: viaAgent("agents.register-skill", ctx),
353
+ }, zip);
354
+ const files = await this.filesOf(o);
355
+ return {
356
+ skill: {
357
+ id: o.skillId,
358
+ name: o.name,
359
+ state: o.kind === "proposed" ? "pending" : o.manualOnly ? "manual" : "active",
360
+ version: o.version,
361
+ ...(files !== null && { files }),
362
+ ...(o.kind === "proposed" && { requestId: o.requestId }),
363
+ },
364
+ message: registeredText(o, files),
365
+ };
366
+ },
367
+ }),
368
+ defineOperation({
369
+ id: "agents.share-skill",
370
+ description: "Share one of an agent's own skills with the organization, so any agent of the org can select it; the agent keeps using it and the person becomes its maintainer (later versions they register on their agents are proposed to the org admins). An org admin shares at once; anyone else's ask waits for an org admin's decision in the inbox. Only the agent's owner can; takes the agent id from agents.list and the skill id from agents.skills.",
371
+ input: z.object({ agentId: AgentId, skillId: AgentSkillPathParams.shape.skillId }).strict(),
372
+ example: {
373
+ agentId: "7c1a5b0e-9a53-4c6b-8d64-2f5a1b0c3e77",
374
+ skillId: "0b9f2c4e-1d3a-4c55-9e0a-7a2f6d1c8b11",
375
+ },
376
+ authz: { orgAction: "agents.create" },
377
+ mirrors: ["POST /api/v1/orgs/:orgId/agents/:agentId/skills/:skillId/share"],
378
+ confirm: "personal",
379
+ // an org admin's share changes the org's skill library at once: an org write, every time
380
+ confirmFor: async (_i, ctx) => (await isOrgAdmin(this.db.db, ctx.orgId, ctx.actorAccountId)) ? "org" : "personal",
381
+ errors: [
382
+ {
383
+ code: "skill.subagent_register",
384
+ next: "Tell your parent agent which skill to share; only the agent the person talks with shares skills.",
385
+ },
386
+ {
387
+ code: "agent.not_found",
388
+ next: "Check the agent id with agents.list: only agents you can see are readable.",
389
+ },
390
+ {
391
+ code: "forbidden",
392
+ next: "Only the agent's owner can share its skills; tell the person, and the owner can share it on the agent's page.",
393
+ },
394
+ {
395
+ code: "skill.not_found",
396
+ next: "Check the skill id with agents.skills: only the agent's own skills can be shared, and a shared one is no longer listed there.",
397
+ },
398
+ {
399
+ code: "skill.name_exists",
400
+ next: "The organization already has a skill with this name; register the package again under another name, then share that skill.",
401
+ },
402
+ {
403
+ code: "catalog.custom_disabled",
404
+ next: "This deployment does not allow custom org skills; the skill stays the agent's own. Tell the person.",
405
+ },
406
+ {
407
+ code: "skill.share_pending",
408
+ next: "Sharing this skill already waits for an org admin; nothing more to do until they decide.",
409
+ },
410
+ ],
411
+ precheck: (i, ctx) => precheckBy(async () => {
412
+ await this.shareCheck(i.agentId, i.skillId, ctx);
413
+ }),
414
+ summary: async (i, ctx) => {
415
+ const skill = await orElse(async () => named((await this.shareCheck(i.agentId, i.skillId, ctx)).name), i.skillId);
416
+ // the card shows the files, so someone has seen what enters the org's skill library
417
+ const files = await orElse(async () => {
418
+ const s = (await this.skills.listForAgent(ctx.orgId, i.agentId)).find((x) => x.id === i.skillId);
419
+ if (!s)
420
+ return "";
421
+ const v = await this.db.db
422
+ .selectFrom("skill_versions")
423
+ .select("manifest")
424
+ .where("skill_id", "=", s.id)
425
+ .where("version", "=", s.currentVersion)
426
+ .executeTakeFirst();
427
+ const paths = v?.manifest.files.map((f) => f.path) ?? [];
428
+ const shown = paths.slice(0, SHARE_FILES_SHOWN).join(", ");
429
+ const more = paths.length - SHARE_FILES_SHOWN;
430
+ return more > 0 ? `${shown} (+${more})` : shown;
431
+ }, "");
432
+ const tail = files === "" ? "" : `: ${files}`;
433
+ return {
434
+ zh: `将技能「${skill}」共享到组织${tail}`,
435
+ en: `Share skill "${skill}" with the organization${tail}`,
436
+ };
437
+ },
438
+ run: async (input, ctx) => {
439
+ const skill = await this.shareCheck(input.agentId, input.skillId, ctx);
440
+ const o = await this.agentSkills.share({
441
+ orgId: ctx.orgId,
442
+ agentId: input.agentId,
443
+ accountId: ctx.actorAccountId,
444
+ auditDetail: viaAgent("agents.share-skill", ctx),
445
+ }, input.skillId);
446
+ return {
447
+ share: o,
448
+ name: skill.name,
449
+ state: o.kind === "shared" ? "shared" : "pending",
450
+ message: sharedText(o, skill.name),
451
+ };
452
+ },
453
+ }),
175
454
  defineOperation({
176
455
  id: "skills.get-change",
177
- description: "Read one skill change a shared agent proposed, as the inbox shows it: the agent, the skill name and description, the version it would become, its file paths and sizes, who asked, its state and who decided. Never file contents: review those on the web, where you also decide it (agents never decide skill changes). Only the agent's owner and the person who asked can read it.",
456
+ description: "Read one skill change request as the inbox shows it: a share of an agent's skill with the org or a proposed version of a shared skill (kind), the agent, the skill name and description, the version, its file paths and sizes, who asked, its state and who decided. Never file contents: review those on the web, where org admins decide it (agents never decide skill changes). Only org admins and the person who asked can read it.",
178
457
  input: z.object({ changeId: SkillChangePathParams.shape.id }).strict(),
179
458
  example: { changeId: "6a1d3e5f-2b4c-4d70-8e91-0f2a4c6e8b13" },
180
459
  // approvals.decide is stricter than the service (a plain-member requester is refused by
@@ -186,7 +465,7 @@ let AgentSkillsOperations = class AgentSkillsOperations {
186
465
  errors: [
187
466
  {
188
467
  code: "skill_change.not_found",
189
- next: "No change with this id is yours to read; the inbox lists those waiting for you (inbox.list).",
468
+ next: "No change with this id is yours to read; org admins see the pending ones in the inbox (inbox.list).",
190
469
  },
191
470
  ],
192
471
  run: async (input, ctx) => {
@@ -194,6 +473,7 @@ let AgentSkillsOperations = class AgentSkillsOperations {
194
473
  return {
195
474
  skillChange: {
196
475
  id: d.id,
476
+ kind: d.kind,
197
477
  name: d.name,
198
478
  state: d.status,
199
479
  description: d.description,
@@ -221,6 +501,9 @@ AgentSkillsOperations = __decorate([
221
501
  __param(1, Inject(AgentsService)),
222
502
  __param(2, Inject(SkillsService)),
223
503
  __param(3, Inject(SkillChangesService)),
224
- __param(4, Inject(SkillInvoker))
504
+ __param(4, Inject(AgentSkillsService)),
505
+ __param(5, Inject(SkillInvoker)),
506
+ __param(6, Inject(SandboxFileReader)),
507
+ __param(7, Inject(KernelDb))
225
508
  ], AgentSkillsOperations);
226
509
  export { AgentSkillsOperations };
@@ -79,12 +79,14 @@ function orgRow(i) {
79
79
  }
80
80
  case "skill_change": {
81
81
  const c = i.skillChange;
82
- const what = c.skillId === null ? "a new skill" : `version ${c.version}`;
82
+ const by = c.requestedByName || "a deleted account";
83
83
  return {
84
84
  ...base,
85
85
  name: c.name,
86
86
  state: c.status,
87
- summary: `Skill change for agent ${c.agent.name} by ${c.requestedByName || "a deleted account"}: ${what}, ${c.files.length} file(s).`,
87
+ summary: c.kind === "share"
88
+ ? `Request by ${by} to share skill ${c.name} of agent ${c.agent.name} with the organization, ${c.files.length} file(s).`
89
+ : `Proposed version ${c.version} of the shared skill ${c.name} by ${by} (agent ${c.agent.name}), ${c.files.length} file(s).`,
88
90
  next: "Review and decide it on the web, in the inbox; agents never decide skill changes.",
89
91
  };
90
92
  }
@@ -254,7 +256,7 @@ let InboxOperations = class InboxOperations {
254
256
  this.registry.register([
255
257
  defineOperation({
256
258
  id: "inbox.list",
257
- description: "List your inbox in this org, newest first, one compact row each (type, what it is about, state, the summary the inbox shows, your next step): tool call approvals waiting for you, skill changes you can decide, and for org admins the members' pending requests and the org's notices; your own decided requests too. Never a call's input. Deciding is done by you on the web; notices can be dismissed with inbox.dismiss-notice. Pages by 50: pass the returned next as params.cursor.",
259
+ description: "List your inbox in this org, newest first, one compact row each (type, what it is about, state, the summary the inbox shows, your next step): tool call approvals waiting for you, and for org admins the skill share requests and proposed skill versions, the members' pending requests and the org's notices; your own decided requests too. Never a call's input. Deciding is done by you on the web; notices can be dismissed with inbox.dismiss-notice. Pages by 50: pass the returned next as params.cursor.",
258
260
  input: z.object({ cursor: Cursor.optional() }).strict(),
259
261
  example: {},
260
262
  authz: { orgAction: "approvals.decide" },
@@ -302,7 +304,7 @@ let InboxOperations = class InboxOperations {
302
304
  }),
303
305
  defineOperation({
304
306
  id: "inbox.platform-list",
305
- description: "List the platform inbox (super admin), newest first, one compact row each (type, what, state, summary, next step): orgs' requests to the platform, plugins waiting for a permission confirmation, this org's uploads waiting for you, failed scope syncs, a failed or stuck components migration and ownership notices. Only the newest 100 items are read (truncated: more wait); listed is how many of them are shown, otherOrgItems how many are items of plugins other orgs own (only counted). total counts everything waiting on the platform, other orgs' items included, as the admin console does. Nothing is decided here: requests and confirmations are done in the admin console. Pages by 50: pass the returned next as params.cursor.",
307
+ description: "List the platform inbox (super admin), newest first, one compact row each (type, what, state, summary, next step): orgs' requests to the platform, plugins waiting for a permission confirmation, this org's uploads waiting for you, failed scope syncs, a failed or stuck components migration and ownership notices. Only the newest 100 items are read (truncated: more wait); listed is how many of them are shown, otherOrgItems how many are items of plugins other orgs own (only counted). total counts everything waiting on the platform, other orgs' items included, as the admin console does; byType splits total by type (everything waiting, not the listed page). Nothing is decided here: requests and confirmations are done in the admin console. Pages by 50: pass the returned next as params.cursor.",
306
308
  input: z.object({ cursor: Cursor.optional() }).strict(),
307
309
  example: {},
308
310
  authz: { superAdmin: true },
@@ -331,6 +333,7 @@ let InboxOperations = class InboxOperations {
331
333
  return {
332
334
  ...page,
333
335
  total: view.total,
336
+ byType: view.byType,
334
337
  listed: shown.length,
335
338
  truncated: view.truncated,
336
339
  otherOrgItems: view.items.length - shown.length,
@@ -30,7 +30,7 @@ const BUILDS = [
30
30
  ];
31
31
  const FAMILIES = [
32
32
  "Platform: this tool, for the org's skills, agents, agent templates, plugins, connectors and requests.",
33
- "Skill and SkillWrite: use and write the skills of this agent.",
33
+ "Skill: use the skills of this agent.",
34
34
  "Agent, SendMessage, SubagentHandback: dispatch and talk to sub-agents.",
35
35
  "Bash, Read, Write, Edit, Glob, Grep, Deliver: work in the conversation's sandbox.",
36
36
  "WebSearch, WebFetch: reach the web through the org's providers.",
@@ -10,7 +10,7 @@ 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 { OwnedItems } from "../../catalog/owned-items.js";
13
- import { SkillCreateFields, SkillIdParams, SkillPathParams, SkillShareableSchema, } from "../../skills/skills.schemas.js";
13
+ import { SkillIdParams, SkillPathParams, SkillShareableSchema, } from "../../skills/skills.schemas.js";
14
14
  import { SkillsService } from "../../skills/skills.service.js";
15
15
  import { defineOperation } from "../operation.js";
16
16
  import { PlatformOperations } from "../registry.js";
@@ -18,7 +18,6 @@ import { precheckBy, viaAgent } from "./errors.js";
18
18
  import { named, orElse } from "./summary.js";
19
19
  const NoInput = z.object({}).strict();
20
20
  const SkillId = z.object({ skillId: SkillPathParams.shape.id.max(100) }).strict();
21
- const Create = SkillCreateFields.shape;
22
21
  const OwnedSkillId = z.object({ skillId: SkillIdParams.shape.id }).strict();
23
22
  /**
24
23
  * Skill library operations of the `Platform` tool (spec §7). The skill body can be long; `get`
@@ -60,61 +59,6 @@ let SkillsOperations = class SkillsOperations {
60
59
  confirm: "read",
61
60
  run: async (input, ctx) => this.skills.get(ctx.orgId, input.skillId),
62
61
  }),
63
- defineOperation({
64
- id: "skills.create",
65
- description: "Create an org skill from text: a name, a one-line description of when to use it, and the SKILL.md body. A skill of that name must not exist yet. Uploads of packages are done on the web.",
66
- // the route's text form, its bounds tightened for the model (a SKILL.md up to 100k chars)
67
- input: z
68
- .object({
69
- name: Create.name.unwrap().max(64),
70
- description: Create.description.unwrap(),
71
- body: Create.body.unwrap().min(1).max(100_000),
72
- shareable: Create.shareable,
73
- })
74
- .strict(),
75
- example: {
76
- name: "weekly-report",
77
- description: "Use when asked to write the weekly status report.",
78
- body: "# Steps\n\n1. Collect this week's updates.\n2. Write the report.",
79
- },
80
- authz: { orgAction: "skills.manage" },
81
- mirrors: ["POST /api/v1/orgs/:orgId/skills"],
82
- // packages are uploaded on the web
83
- omits: ["uploadId"],
84
- confirm: "org",
85
- errors: [
86
- {
87
- code: "skill.name_exists",
88
- next: "A skill of that name exists: pick another name, or read the existing one with skills.get.",
89
- },
90
- {
91
- code: "validation_failed",
92
- next: "Fix the SKILL.md as the message says (name, description, body), then call again.",
93
- },
94
- {
95
- code: "catalog.custom_disabled",
96
- next: {
97
- superAdmin: "The platform does not allow custom skills: allow them in the admin console under Settings, then call again.",
98
- orgAdmin: "The platform does not allow custom skills; ask the super admin, or use a platform skill from skills.list.",
99
- member: "The platform does not allow custom skills; use a platform skill from skills.list, or ask an org admin.",
100
- },
101
- },
102
- ],
103
- precheck: (i, ctx) => precheckBy(() => this.skills.checkCreateFromText(ctx.orgId, i)),
104
- summary: (i) => i.shareable === true
105
- ? {
106
- zh: `新建组织技能「${i.name}」(可共享给其他组织)`,
107
- en: `Create org skill "${i.name}" (shared with other orgs)`,
108
- }
109
- : { zh: `新建组织技能「${i.name}」`, en: `Create org skill "${i.name}"` },
110
- run: async (input, ctx) => this.skills.createFromText(ctx.orgId, ctx.actorAccountId, {
111
- name: input.name,
112
- description: input.description,
113
- body: input.body,
114
- ...(input.shareable !== undefined && { shareable: input.shareable }),
115
- auditDetail: viaAgent("skills.create", ctx),
116
- }),
117
- }),
118
62
  defineOperation({
119
63
  id: "skills.set-shareable",
120
64
  description: "Set whether an org skill may be shared with other orgs. Only the org's own skills can be changed; a skill that came from a platform plugin cannot.",
@@ -1,5 +1,6 @@
1
1
  import { AccountsOperations } from "./ops/accounts.ops.js";
2
2
  import { AdminOperations } from "./ops/admin.ops.js";
3
+ import { AdminItemsOperations } from "./ops/admin-items.ops.js";
3
4
  import { AgentSkillsOperations } from "./ops/agent-skills.ops.js";
4
5
  import { AgentTemplatesOperations } from "./ops/agent-templates.ops.js";
5
6
  import { AgentsOperations } from "./ops/agents.ops.js";
@@ -32,7 +33,7 @@ import { UiOperations } from "./ops/ui.ops.js";
32
33
  * One provider per domain, each taking `PlatformOperations` as its first constructor argument:
33
34
  * the parity test in `tools/checks` builds the operations from it without a Nest app.
34
35
  */
35
- export declare const PLATFORM_OPERATION_PROVIDERS: readonly [typeof PlatformOperationsProvider, typeof SkillsOperations, typeof AgentsOperations, typeof AgentTemplatesOperations, typeof PluginsOperations, typeof MarketplacesOperations, typeof RequestsOperations, typeof ConnectorsOperations, typeof ExtensionsOperations, typeof ConversationsOperations, typeof ComponentsOperations, typeof AccountsOperations, typeof OrgsOperations, typeof AdminOperations, typeof CatalogAdminOperations, typeof OrgPluginsOperations, typeof ExtensionsAdminOperations, typeof ModelsOperations, typeof InboxOperations, typeof ApprovalsOperations, typeof ChannelsOperations, typeof OrgSettingsOperations, typeof EvalsOperations, typeof BackgroundOperations, typeof PreviewersOperations, typeof DevicesOperations, typeof AgentSkillsOperations, typeof ThemesOperations, typeof SetupOperations, typeof UiOperations];
36
+ export declare const PLATFORM_OPERATION_PROVIDERS: readonly [typeof PlatformOperationsProvider, typeof SkillsOperations, typeof AgentsOperations, typeof AgentTemplatesOperations, typeof PluginsOperations, typeof MarketplacesOperations, typeof RequestsOperations, typeof ConnectorsOperations, typeof ExtensionsOperations, typeof ConversationsOperations, typeof ComponentsOperations, typeof AccountsOperations, typeof OrgsOperations, typeof AdminOperations, typeof CatalogAdminOperations, typeof OrgPluginsOperations, typeof ExtensionsAdminOperations, typeof ModelsOperations, typeof InboxOperations, typeof ApprovalsOperations, typeof ChannelsOperations, typeof OrgSettingsOperations, typeof EvalsOperations, typeof BackgroundOperations, typeof PreviewersOperations, typeof DevicesOperations, typeof AgentSkillsOperations, typeof ThemesOperations, typeof SetupOperations, typeof UiOperations, typeof AdminItemsOperations];
36
37
  /**
37
38
  * The `Platform` tool's operations (ruling P2): one provider per domain, each registering its
38
39
  * operations into `PlatformOperations` from `onModuleInit`. It imports the domain modules, which
@@ -5,6 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  import { Module } from "@nestjs/common";
8
+ import { AdminModule } from "../admin/admin.module.js";
8
9
  import { AgentsModule } from "../agents/agents.module.js";
9
10
  import { BackgroundModule } from "../background/background.module.js";
10
11
  import { AppearanceModule } from "../catalog/appearance.service.js";
@@ -26,6 +27,7 @@ import { ThemesModule } from "../themes/themes.module.js";
26
27
  import { ToolsModule } from "../tools/tools.module.js";
27
28
  import { AccountsOperations } from "./ops/accounts.ops.js";
28
29
  import { AdminOperations } from "./ops/admin.ops.js";
30
+ import { AdminItemsOperations } from "./ops/admin-items.ops.js";
29
31
  import { AgentSkillsOperations } from "./ops/agent-skills.ops.js";
30
32
  import { AgentTemplatesOperations } from "./ops/agent-templates.ops.js";
31
33
  import { AgentsOperations } from "./ops/agents.ops.js";
@@ -99,6 +101,8 @@ export const PLATFORM_OPERATION_PROVIDERS = [
99
101
  SetupOperations,
100
102
  // UI slot catalog (UI slots spec §3)
101
103
  UiOperations,
104
+ // every org's agents and connectors, the conversation org's through the tool (spec §4.5)
105
+ AdminItemsOperations,
102
106
  ];
103
107
  /**
104
108
  * The `Platform` tool's operations (ruling P2): one provider per domain, each registering its
@@ -111,6 +115,7 @@ PlatformOpsModule = __decorate([
111
115
  Module({
112
116
  imports: [
113
117
  PlatformModule,
118
+ AdminModule,
114
119
  SkillsModule,
115
120
  AgentsModule,
116
121
  OrgsModule,
@@ -1,5 +1,8 @@
1
1
  import { ADMIN_ROUTES } from "../admin/admin.schemas.js";
2
+ import { ADMIN_ITEM_ROUTES } from "../admin/admin-items.schemas.js";
3
+ import { AUDIT_ROUTES } from "../admin/audit.schemas.js";
2
4
  import { ADMIN_EXTENSION_ROUTES } from "../admin/extensions.schemas.js";
5
+ import { OVERVIEW_ROUTES } from "../admin/overview.schemas.js";
3
6
  import { AGENT_ROUTES } from "../agents/agents.schemas.js";
4
7
  import { AGENT_TEMPLATE_ROUTES } from "../agents/templates.schemas.js";
5
8
  import { BACKGROUND_ROUTES } from "../background/background.schemas.js";
@@ -74,4 +77,9 @@ export const ROUTE_SCHEMAS = [
74
77
  ...SETUP_GUIDE_ROUTES,
75
78
  // UI slots (UI slots spec §3)
76
79
  ...UI_ROUTES,
80
+ // every org's agents and connectors (admin agents and connectors spec §4)
81
+ ...ADMIN_ITEM_ROUTES,
82
+ // the admin overview and the platform-level audit log (admin overview spec §5.2)
83
+ ...OVERVIEW_ROUTES,
84
+ ...AUDIT_ROUTES,
77
85
  ];
@@ -37,6 +37,10 @@ export declare class GatewayRole {
37
37
  * `checkpoint` throws when a stop began meanwhile.
38
38
  */
39
39
  start(checkpoint: () => void): Promise<void>;
40
+ /** The channel long connections this process holds (replica presence). */
41
+ channelsHeld(): number;
42
+ /** The device sockets open on this process (replica presence). */
43
+ devicesConnected(): number;
40
44
  /** Stops watching extension instance changes (the stop's first step). */
41
45
  unwatch(): void;
42
46
  /** Ends the webhook cache's `channel.changed` subscription. */
@@ -82,6 +82,14 @@ let GatewayRole = class GatewayRole {
82
82
  this.unwatchWebhooks = this.host.onChange(() => webhooks.invalidateAll());
83
83
  checkpoint();
84
84
  }
85
+ /** The channel long connections this process holds (replica presence). */
86
+ channelsHeld() {
87
+ return this.supervisor.heldCount();
88
+ }
89
+ /** The device sockets open on this process (replica presence). */
90
+ devicesConnected() {
91
+ return this.devices.connectionCount();
92
+ }
85
93
  /** Stops watching extension instance changes (the stop's first step). */
86
94
  unwatch() {
87
95
  this.unwatchPlugins?.();
@@ -78,14 +78,16 @@ RunJobs = __decorate([
78
78
  __param(3, Inject(KERNEL_LOGGER))
79
79
  ], RunJobs);
80
80
  export { RunJobs };
81
+ /** The run queues' registration (re-exported: the admin health reads their counts). */
82
+ const RUN_QUEUES_MODULE = QueueModule.registerQueue(RUN_QUEUE_NAME, SUBAGENT_RUN_QUEUE_NAME);
81
83
  /** The run queues' producer (the sub-agent dispatch, the run queue and the waits use it). */
82
84
  let RunJobsModule = class RunJobsModule {
83
85
  };
84
86
  RunJobsModule = __decorate([
85
87
  Module({
86
- imports: [QueueModule.registerQueue(RUN_QUEUE_NAME, SUBAGENT_RUN_QUEUE_NAME)],
88
+ imports: [RUN_QUEUES_MODULE],
87
89
  providers: [RunJobs],
88
- exports: [RunJobs],
90
+ exports: [RunJobs, RUN_QUEUES_MODULE],
89
91
  })
90
92
  ], RunJobsModule);
91
93
  export { RunJobsModule };