@capybara-hq/kernel 0.1.0-rc.26 → 0.1.0-rc.28

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 (88) hide show
  1. package/dist/app.js +2 -0
  2. package/dist/catalog/catalog-content.d.ts +18 -1
  3. package/dist/catalog/catalog-content.js +5 -2
  4. package/dist/catalog/catalog-scope.service.js +3 -2
  5. package/dist/catalog/marketplaces.service.d.ts +1 -1
  6. package/dist/catalog/org-catalog.service.js +13 -3
  7. package/dist/catalog/plugin-install.d.ts +37 -16
  8. package/dist/catalog/plugin-install.js +108 -34
  9. package/dist/catalog/plugin-upgrade.js +1 -0
  10. package/dist/channels/actions.d.ts +4 -1
  11. package/dist/channels/actions.js +5 -2
  12. package/dist/channels/cards.d.ts +7 -1
  13. package/dist/channels/cards.js +28 -1
  14. package/dist/channels/phrases.d.ts +15 -0
  15. package/dist/channels/phrases.js +9 -0
  16. package/dist/config/schema.d.ts +8 -0
  17. package/dist/config/schema.js +69 -0
  18. package/dist/conversations/events.d.ts +14 -0
  19. package/dist/conversations/events.js +20 -0
  20. package/dist/db/kernel-schema.d.ts +19 -4
  21. package/dist/errors.d.ts +5 -0
  22. package/dist/errors.js +5 -0
  23. package/dist/git/gateway.d.ts +93 -0
  24. package/dist/git/gateway.js +750 -0
  25. package/dist/git/gateway.route.d.ts +16 -0
  26. package/dist/git/gateway.route.js +41 -0
  27. package/dist/git/git.module.d.ts +10 -0
  28. package/dist/git/git.module.js +31 -0
  29. package/dist/git/identity.d.ts +80 -0
  30. package/dist/git/identity.js +209 -0
  31. package/dist/git/pkt-line.d.ts +53 -0
  32. package/dist/git/pkt-line.js +146 -0
  33. package/dist/git/run-end.d.ts +29 -0
  34. package/dist/git/run-end.js +121 -0
  35. package/dist/git/sources.d.ts +42 -0
  36. package/dist/git/sources.js +134 -0
  37. package/dist/git/tickets.d.ts +69 -0
  38. package/dist/git/tickets.js +194 -0
  39. package/dist/http/api.module.js +3 -0
  40. package/dist/http/app.d.ts +6 -0
  41. package/dist/http/app.js +3 -0
  42. package/dist/infra/config/kernel-config.service.js +2 -0
  43. package/dist/orgs/settings.service.d.ts +21 -0
  44. package/dist/orgs/settings.service.js +18 -0
  45. package/dist/overrides.d.ts +6 -0
  46. package/dist/overrides.js +4 -0
  47. package/dist/platform/exclusions.js +3 -0
  48. package/dist/runtime/run-finish.d.ts +5 -0
  49. package/dist/runtime/run-finish.js +12 -3
  50. package/dist/runtime/runtime.module.js +2 -0
  51. package/dist/runtime/waits.js +7 -5
  52. package/dist/schedules/fire.d.ts +3 -3
  53. package/dist/schedules/fire.js +7 -4
  54. package/dist/schedules/grant-rules.d.ts +40 -0
  55. package/dist/schedules/grant-rules.js +83 -0
  56. package/dist/schedules/scheduled-tasks.service.d.ts +9 -7
  57. package/dist/schedules/scheduled-tasks.service.js +50 -15
  58. package/dist/schedules/types.d.ts +2 -0
  59. package/dist/tools/approvals.service.d.ts +100 -19
  60. package/dist/tools/approvals.service.js +288 -21
  61. package/dist/tools/builtin-names.d.ts +4 -2
  62. package/dist/tools/builtin-names.js +6 -1
  63. package/dist/tools/builtins/schedules.d.ts +11 -9
  64. package/dist/tools/builtins/schedules.js +202 -44
  65. package/dist/tools/connector-account-policies.d.ts +8 -0
  66. package/dist/tools/connector-account-policies.js +8 -0
  67. package/dist/tools/connectors.controller.d.ts +2 -0
  68. package/dist/tools/connectors.controller.js +5 -1
  69. package/dist/tools/connectors.service.d.ts +46 -3
  70. package/dist/tools/connectors.service.js +98 -9
  71. package/dist/tools/credentials.d.ts +18 -0
  72. package/dist/tools/credentials.js +60 -0
  73. package/dist/tools/credentials.module.d.ts +7 -0
  74. package/dist/tools/credentials.module.js +22 -0
  75. package/dist/tools/git-asks.d.ts +113 -0
  76. package/dist/tools/git-asks.js +174 -0
  77. package/dist/tools/git-push-policy.d.ts +89 -0
  78. package/dist/tools/git-push-policy.js +175 -0
  79. package/dist/tools/info.d.ts +3 -3
  80. package/dist/tools/info.js +3 -3
  81. package/dist/tools/network-asks.d.ts +5 -0
  82. package/dist/tools/network-asks.js +9 -2
  83. package/dist/tools/tools.module.js +4 -3
  84. package/package.json +3 -3
  85. package/src/migrations/0126_git_gateway.sql +10 -0
  86. package/src/migrations/0127_git_push_policy.sql +12 -0
  87. package/src/migrations/0128_scheduled_task_plugin_disabled.sql +10 -0
  88. package/src/migrations/0129_connector_plugin_disabled.sql +5 -0
@@ -73,19 +73,52 @@ const RUN_AT_SCHEMA = {
73
73
  };
74
74
  const MODE_SCHEMA = {
75
75
  type: "string",
76
- enum: ["new", "here"],
76
+ enum: ["new", "here", "chat"],
77
77
  description: '"new": every run starts a new conversation with this agent. "here": every run posts the ' +
78
78
  "prompt into this conversation (in an IM direct chat: the chat's current conversation), " +
79
- 'e.g. for "check this again at 3 pm".',
79
+ 'e.g. for "check this again at 3 pm". "chat": every run posts into the IM chat named by ' +
80
+ "chat.",
81
+ };
82
+ const CHAT_SCHEMA = {
83
+ type: "object",
84
+ properties: {
85
+ connectionId: {
86
+ type: "string",
87
+ minLength: 1,
88
+ maxLength: 100,
89
+ description: "The id of the active channel connection that delivers to the chat.",
90
+ },
91
+ chatId: {
92
+ type: "string",
93
+ minLength: 1,
94
+ maxLength: 500,
95
+ description: "The chat's id within that connection, e.g. a DM's user id or a group's id.",
96
+ },
97
+ chatType: {
98
+ type: "string",
99
+ enum: ["dm", "group"],
100
+ description: 'The kind of chat: "dm" (the default) or "group". A group needs a channel with threads.',
101
+ },
102
+ agentId: {
103
+ type: "string",
104
+ minLength: 1,
105
+ maxLength: 100,
106
+ description: "The agent the chat belongs to; defaults to this run's agent. A chat target must use " +
107
+ "the connection's agent.",
108
+ },
109
+ },
110
+ required: ["connectionId", "chatId"],
111
+ additionalProperties: false,
80
112
  };
81
113
  const CREATE_SPEC = {
82
114
  name: BUILTIN.ScheduleCreate,
83
115
  description: "Create a scheduled task for the user: this agent runs the prompt on a schedule (cron) or " +
84
- "once at a time (runAt), in the user's time zone, acting as the user. Use it when the user " +
85
- 'asks for something recurring or later ("every weekday at 9 send me a brief", "remind me at ' +
86
- '3 pm"). Every call asks the user to approve the whole task first. Runs happen without the ' +
87
- "user present: write the prompt so it stands on its own, and list in approvedTools the tools " +
88
- "a run needs without asking. Do not use it for work to do now.",
116
+ "once at a time (runAt), in the user's time zone, acting as the user. Each run starts a new " +
117
+ "conversation, posts into this conversation, or posts into an IM chat the user names with " +
118
+ 'chat. Use it when the user asks for something recurring or later ("every weekday at 9 ' +
119
+ 'send me a brief", "remind me at 3 pm"). Every call asks the user to approve the whole task ' +
120
+ "first. Runs happen without the user present: write the prompt so it stands on its own, and " +
121
+ "list in approvedTools the tools a run needs without asking. Do not use it for work to do now.",
89
122
  keyArgs: ["name"],
90
123
  inputSchema: {
91
124
  type: "object",
@@ -105,6 +138,10 @@ const CREATE_SPEC = {
105
138
  cron: CRON_SCHEMA,
106
139
  runAt: RUN_AT_SCHEMA,
107
140
  mode: MODE_SCHEMA,
141
+ chat: {
142
+ ...CHAT_SCHEMA,
143
+ description: 'For mode "chat": the IM chat every run posts into.',
144
+ },
108
145
  approvedTools: TOOL_KEYS_SCHEMA,
109
146
  },
110
147
  required: ["name", "prompt", "mode"],
@@ -113,11 +150,10 @@ const CREATE_SPEC = {
113
150
  };
114
151
  const UPDATE_SPEC = {
115
152
  name: BUILTIN.ScheduleUpdate,
116
- description: "Change one of the user's scheduled tasks: its name, prompt, schedule, where it runs or its " +
117
- "approved tools. Pass the task id (from ScheduleList or ScheduleCreate) and only the fields " +
118
- "to change; approvedTools replaces the whole list. Every call asks the user to approve the " +
119
- "change first. To pause or stop a task, use ScheduleDelete or tell the user to pause it on " +
120
- "the task page.",
153
+ description: "Change one of the user's scheduled tasks: its name, prompt, schedule, where it runs (a new " +
154
+ "conversation, this conversation or an IM chat, and the agent) or its approved tools. Pass " +
155
+ "the task id and only the fields to change; approvedTools replaces the whole list. Every " +
156
+ "call asks the user to approve the change first. It does not pause, resume or delete a task.",
121
157
  keyArgs: ["id"],
122
158
  inputSchema: {
123
159
  type: "object",
@@ -133,6 +169,10 @@ const UPDATE_SPEC = {
133
169
  cron: CRON_SCHEMA,
134
170
  runAt: RUN_AT_SCHEMA,
135
171
  mode: MODE_SCHEMA,
172
+ chat: {
173
+ ...CHAT_SCHEMA,
174
+ description: 'For mode "chat": the IM chat every run posts into; replaces the target.',
175
+ },
136
176
  approvedTools: {
137
177
  ...TOOL_KEYS_SCHEMA,
138
178
  description: `${TOOL_KEYS_SCHEMA.description} Replaces the task's whole list; pass [] to remove every approved tool, omit it to keep the list.`,
@@ -145,15 +185,21 @@ const UPDATE_SPEC = {
145
185
  const LIST_SPEC = {
146
186
  name: BUILTIN.ScheduleList,
147
187
  description: "List the user's scheduled tasks in this organization: id, name, schedule with its time " +
148
- "zone, status, next run and where runs go. Use it to find a task's id before ScheduleUpdate " +
149
- "or ScheduleDelete, or when the user asks what is scheduled. Read-only.",
188
+ "zone, status, next run and where runs go. Read-only.",
189
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
190
+ };
191
+ const TARGETS_SPEC = {
192
+ name: BUILTIN.ScheduleTargets,
193
+ description: "List the IM chats scheduled task runs can be delivered to for the user in this " +
194
+ "organization: each chat's connection id, chat id, chat type and the connection's agent id, " +
195
+ "with a label naming the connection. Read-only.",
150
196
  inputSchema: { type: "object", properties: {}, additionalProperties: false },
151
197
  };
152
198
  const DELETE_SPEC = {
153
199
  name: BUILTIN.ScheduleDelete,
154
200
  description: "Delete one of the user's scheduled tasks for good: it never runs again; past conversations " +
155
- "stay. Use it when the user asks to stop or remove a task; find the id with ScheduleList. " +
156
- "Only the user's own tasks can be deleted.",
201
+ "stay. Use it when the user asks to stop or remove a task. Only the user's own tasks can be " +
202
+ "deleted.",
157
203
  keyArgs: ["id"],
158
204
  inputSchema: {
159
205
  type: "object",
@@ -165,7 +211,15 @@ const DELETE_SPEC = {
165
211
  },
166
212
  };
167
213
  const ToolKeys = z.array(z.string().min(1).max(200)).max(TOOLS_MAX);
168
- const Mode = z.enum(["new", "here"]);
214
+ const Mode = z.enum(["new", "here", "chat"]);
215
+ const Chat = z
216
+ .object({
217
+ connectionId: z.string().min(1).max(100),
218
+ chatId: z.string().min(1).max(500),
219
+ chatType: z.enum(["dm", "group"]).optional(),
220
+ agentId: z.string().min(1).max(100).optional(),
221
+ })
222
+ .strict();
169
223
  const CreateInput = z
170
224
  .object({
171
225
  name: z.string().min(1).max(NAME_MAX),
@@ -173,6 +227,7 @@ const CreateInput = z
173
227
  cron: z.string().min(1).max(100).optional(),
174
228
  runAt: z.string().min(1).max(40).optional(),
175
229
  mode: Mode,
230
+ chat: Chat.optional(),
176
231
  approvedTools: ToolKeys.optional(),
177
232
  })
178
233
  .strict();
@@ -184,19 +239,24 @@ const UpdateInput = z
184
239
  cron: z.string().min(1).max(100).optional(),
185
240
  runAt: z.string().min(1).max(40).optional(),
186
241
  mode: Mode.optional(),
242
+ chat: Chat.optional(),
187
243
  approvedTools: ToolKeys.optional(),
188
244
  })
189
245
  .strict();
190
246
  const IdInput = z.object({ id: z.string().min(1).max(100) }).strict();
191
247
  // ---- model-facing texts (English, actionable) ----
192
- const INVALID_CREATE_TEXT = "Invalid input; pass name, prompt, mode (new or here), exactly one of cron or runAt, and " +
193
- "optionally approvedTools as an array of tool keys.";
248
+ const INVALID_CREATE_TEXT = "Invalid input; pass name, prompt, mode (new, here or chat), exactly one of cron or runAt, " +
249
+ "chat with mode chat (connectionId, chatId, chatType, agentId), and optionally approvedTools " +
250
+ "as an array of tool keys.";
194
251
  const INVALID_UPDATE_TEXT = "Invalid input; pass the task id and the fields to change (name, prompt, cron or runAt, mode, " +
195
- "approvedTools).";
252
+ "chat, approvedTools).";
196
253
  const INVALID_ID_TEXT = "Invalid input; pass the task id from ScheduleList.";
197
254
  const ONE_TRIGGER_TEXT = "Pass exactly one of cron (a repeating schedule) or runAt (a single run), not both and not neither.";
198
255
  const BOTH_TRIGGERS_TEXT = "Pass at most one of cron or runAt: cron makes the task repeat, runAt makes it run once.";
199
- const NOTHING_TO_UPDATE_TEXT = "Nothing to change; pass at least one of name, prompt, cron, runAt, mode or approvedTools with the id.";
256
+ const NOTHING_TO_UPDATE_TEXT = "Nothing to change; pass at least one of name, prompt, cron, runAt, mode, chat or " +
257
+ "approvedTools with the id.";
258
+ const CHAT_NEEDS_CHAT_TEXT = 'mode "chat" needs a chat; pass chat with connectionId and chatId (and optionally chatType and agentId).';
259
+ const CHAT_ONLY_TEXT = 'chat applies only to mode "chat"; pass mode "chat" with it, or omit it.';
200
260
  export const SCHEDULES_UNAVAILABLE_TEXT = "Scheduled tasks are not available on this server; ask the user to manage the task on the scheduled tasks page instead.";
201
261
  export const SCHEDULES_OFF_TEXT = "Scheduled tasks are turned off in this organization; ask an org admin to turn them on.";
202
262
  /** The card recomputed at run time differs from the one the person approved. */
@@ -226,7 +286,7 @@ export function scheduleErrorText(e, ctx) {
226
286
  case "schedule.invalid_time_zone":
227
287
  return `${m}. Ask the user to set their time zone in their profile, then retry.`;
228
288
  case "schedule.target_invalid":
229
- return `${m}. Use mode "new" to start a new conversation for every run instead.`;
289
+ return `${m}. Fix the target and retry, or use mode "new" to start a new conversation for every run instead.`;
230
290
  case "schedule.grant_not_allowed":
231
291
  return `${m}. Remove it from approvedTools and call again.`;
232
292
  case "forbidden":
@@ -287,7 +347,9 @@ function targetWords(t, currentConversationId) {
287
347
  return { zh: "每次运行新建对话", en: "a new conversation for every run" };
288
348
  if (t.mode === "chat") {
289
349
  const n = clip(t.connectionName, 60);
290
- return { zh: `${n} 的私聊`, en: `the direct chat on ${n}` };
350
+ return t.chat.chatType === "group"
351
+ ? { zh: `${n} 的群聊`, en: `the group chat on ${n}` }
352
+ : { zh: `${n} 的私聊`, en: `the direct chat on ${n}` };
291
353
  }
292
354
  return t.conversationId === currentConversationId
293
355
  ? { zh: "当前对话", en: "this conversation" }
@@ -298,16 +360,25 @@ function toolsWords(keys) {
298
360
  ? { zh: "无", en: "none" }
299
361
  : { zh: keys.join("、"), en: keys.join(", ") };
300
362
  }
363
+ /** The `chat` input with its default kind (`dm`). */
364
+ const normalizedChat = (chat) => ({
365
+ connectionId: chat.connectionId,
366
+ chatId: chat.chatId,
367
+ chatType: chat.chatType ?? "dm",
368
+ });
369
+ /** A chat target with its connection's name for the words. */
370
+ async function chatTargetOf(db, chat) {
371
+ const c = await db
372
+ .selectFrom("channel_connections")
373
+ .select("name")
374
+ .where("id", "=", chat.connectionId)
375
+ .executeTakeFirst();
376
+ return { mode: "chat", chat, connectionName: c?.name ?? chat.connectionId };
377
+ }
301
378
  /** A stored task's target; a chat's connection name is read for the words. */
302
379
  async function storedTarget(db, task) {
303
- if (task.mode === "chat" && task.chat !== null) {
304
- const c = await db
305
- .selectFrom("channel_connections")
306
- .select("name")
307
- .where("id", "=", task.chat.connectionId)
308
- .executeTakeFirst();
309
- return { mode: "chat", chat: task.chat, connectionName: c?.name ?? task.chat.connectionId };
310
- }
380
+ if (task.mode === "chat" && task.chat !== null)
381
+ return chatTargetOf(db, task.chat);
311
382
  if (task.mode === "conversation" && task.conversationId !== null) {
312
383
  return { mode: "conversation", conversationId: task.conversationId };
313
384
  }
@@ -351,7 +422,8 @@ const sameTarget = (a, b) => a.mode === b.mode &&
351
422
  a.conversationId === b.conversationId) &&
352
423
  (a.mode !== "chat" ||
353
424
  (a.chat.connectionId === b.chat.connectionId &&
354
- a.chat.chatId === b.chat.chatId));
425
+ a.chat.chatId === b.chat.chatId &&
426
+ a.chat.chatType === b.chat.chatType));
355
427
  const uniqueSorted = (keys) => [...new Set(keys)].sort();
356
428
  /** cron / runAt as the service's trigger; undefined when neither is given. */
357
429
  function triggerOf(i) {
@@ -361,6 +433,20 @@ function triggerOf(i) {
361
433
  return { kind: "once", at: i.runAt.trim() };
362
434
  return undefined;
363
435
  }
436
+ /** `chat` against the mode on create, as a refusal; null when they agree. */
437
+ function createChatError(i) {
438
+ if (i.mode !== "chat" && i.chat !== undefined)
439
+ return new ToolError(CHAT_ONLY_TEXT);
440
+ if (i.mode === "chat" && i.chat === undefined)
441
+ return new ToolError(CHAT_NEEDS_CHAT_TEXT);
442
+ return null;
443
+ }
444
+ /** `chat` against the mode on update; null when they agree (or chat is absent). */
445
+ function updateChatError(i) {
446
+ if (i.chat === undefined)
447
+ return null;
448
+ return i.mode !== "chat" ? new ToolError(CHAT_ONLY_TEXT) : null;
449
+ }
364
450
  const withZone = (t, timeZone) => t.kind === "cron"
365
451
  ? { kind: "cron", expr: t.expr, timeZone }
366
452
  : { kind: "once", at: t.at, timeZone };
@@ -429,7 +515,7 @@ ScheduleUpdates = __decorate([
429
515
  __param(2, Inject(ScheduledTaskStore))
430
516
  ], ScheduleUpdates);
431
517
  export { ScheduleUpdates };
432
- /** The four schedule builtins. */
518
+ /** The five schedule builtins. */
433
519
  export function scheduleTools(deps) {
434
520
  /**
435
521
  * The service, or a refusal (the tools are hidden then too: {@link unavailable}). Defensive:
@@ -507,17 +593,28 @@ export function scheduleTools(deps) {
507
593
  const zone = await deps.timeZoneOf(ctx.orgId, ctx.conversationId);
508
594
  const target = input.mode === "here"
509
595
  ? await hereTarget(deps.db(), ctx.orgId, ctx.accountId, ctx.conversationId)
510
- : { mode: "new" };
511
- return { trigger, zone, target, tools: uniqueSorted(input.approvedTools ?? []) };
596
+ : input.mode === "chat"
597
+ ? await chatTargetOf(deps.db(), normalizedChat(input.chat))
598
+ : { mode: "new" };
599
+ return {
600
+ trigger,
601
+ zone,
602
+ target,
603
+ tools: uniqueSorted(input.approvedTools ?? []),
604
+ agentId: input.chat?.agentId ?? ctx.agentId,
605
+ };
512
606
  }
513
- function createCard(input, p, conversationId) {
607
+ function createCard(input, p, conversationId, runAgentId) {
514
608
  const when = scheduleWords(withZone(p.trigger, p.zone));
515
609
  const where = targetWords(p.target, conversationId);
516
610
  const tools = toolsWords(p.tools);
517
611
  const name = clip(input.name, NAME_MAX);
612
+ const agent = p.agentId === runAgentId
613
+ ? { zh: "", en: "" }
614
+ : { zh: `;以智能体 ${p.agentId} 运行`, en: `; runs as agent ${p.agentId}` };
518
615
  return {
519
- zh: `创建定时任务「${name}」:${when.zh};运行位置:${where.zh};预先批准的工具:${tools.zh}`,
520
- en: `Create the scheduled task "${name}": ${when.en}; runs in ${where.en}; approved tools: ${tools.en}`,
616
+ zh: `创建定时任务「${name}」:${when.zh};运行位置:${where.zh}${agent.zh};预先批准的工具:${tools.zh}`,
617
+ en: `Create the scheduled task "${name}": ${when.en}; runs in ${where.en}${agent.en}; approved tools: ${tools.en}`,
521
618
  };
522
619
  }
523
620
  const create = {
@@ -532,24 +629,30 @@ export function scheduleTools(deps) {
532
629
  if ((parsed.data.cron === undefined) === (parsed.data.runAt === undefined)) {
533
630
  return deny(key, ONE_TRIGGER_TEXT);
534
631
  }
632
+ const bad = createChatError(parsed.data);
633
+ if (bad !== null)
634
+ return deny(key, bad.message);
535
635
  const p = await createPlan(parsed.data, ctx);
536
- return ask(key, createCard(parsed.data, p, ctx.conversationId));
636
+ return ask(key, createCard(parsed.data, p, ctx.conversationId, ctx.agentId));
537
637
  },
538
638
  async run(raw, ctx) {
539
639
  const parsed = CreateInput.safeParse(raw);
540
640
  if (!parsed.success)
541
641
  throw new ToolError(INVALID_CREATE_TEXT);
542
642
  const input = parsed.data;
643
+ const bad = createChatError(input);
644
+ if (bad !== null)
645
+ throw bad;
543
646
  const svc = await service();
544
647
  const p = await createPlan(input, ctx);
545
648
  if (p.trigger === undefined || (input.cron !== undefined && input.runAt !== undefined)) {
546
649
  throw new ToolError(ONE_TRIGGER_TEXT);
547
650
  }
548
651
  // the zone and target are recomputed now: they must be the ones the person approved
549
- await sameAsApproved(ctx, BUILTIN.ScheduleCreate, createCard(input, p, ctx.conversationId));
652
+ await sameAsApproved(ctx, BUILTIN.ScheduleCreate, createCard(input, p, ctx.conversationId, ctx.agentId));
550
653
  const trigger = p.trigger;
551
654
  const task = await call(() => svc.create(ctx.orgId, ctx.accountId, {
552
- agentId: ctx.agentId,
655
+ agentId: p.agentId,
553
656
  name: input.name,
554
657
  prompt: input.prompt,
555
658
  trigger,
@@ -605,7 +708,9 @@ export function scheduleTools(deps) {
605
708
  const before = await storedTarget(db, task);
606
709
  const after = input.mode === "here"
607
710
  ? await hereTarget(db, ctx.orgId, ctx.accountId, ctx.conversationId)
608
- : { mode: "new" };
711
+ : input.mode === "chat"
712
+ ? await chatTargetOf(db, normalizedChat(input.chat))
713
+ : { mode: "new" };
609
714
  if (!sameTarget(before, after)) {
610
715
  const b = targetWords(before, ctx.conversationId);
611
716
  const a = targetWords(after, ctx.conversationId);
@@ -613,6 +718,10 @@ export function scheduleTools(deps) {
613
718
  en.push(`runs in ${b.en} → ${a.en}`);
614
719
  }
615
720
  }
721
+ if (input.chat?.agentId !== undefined && input.chat.agentId !== task.agentId) {
722
+ zh.push(`智能体 ${clip(task.agentId, 80)} → ${clip(input.chat.agentId, 80)}`);
723
+ en.push(`agent ${clip(task.agentId, 80)} → ${clip(input.chat.agentId, 80)}`);
724
+ }
616
725
  if (input.approvedTools !== undefined) {
617
726
  const next = uniqueSorted(input.approvedTools);
618
727
  const added = next.filter((k) => !grants.includes(k));
@@ -649,6 +758,7 @@ export function scheduleTools(deps) {
649
758
  i.cron === undefined &&
650
759
  i.runAt === undefined &&
651
760
  i.mode === undefined &&
761
+ i.chat === undefined &&
652
762
  i.approvedTools === undefined;
653
763
  const update = {
654
764
  spec: UPDATE_SPEC,
@@ -663,6 +773,9 @@ export function scheduleTools(deps) {
663
773
  if (input.cron !== undefined && input.runAt !== undefined) {
664
774
  return deny(key, BOTH_TRIGGERS_TEXT);
665
775
  }
776
+ const bad = updateChatError(input);
777
+ if (bad !== null)
778
+ return deny(key, bad.message);
666
779
  if (nothingToUpdate(input))
667
780
  return deny(key, NOTHING_TO_UPDATE_TEXT);
668
781
  const task = await ownTask(ctx.orgId, ctx.accountId, input.id);
@@ -679,6 +792,9 @@ export function scheduleTools(deps) {
679
792
  if (input.cron !== undefined && input.runAt !== undefined) {
680
793
  throw new ToolError(BOTH_TRIGGERS_TEXT);
681
794
  }
795
+ const bad = updateChatError(input);
796
+ if (bad !== null)
797
+ throw bad;
682
798
  if (nothingToUpdate(input))
683
799
  throw new ToolError(NOTHING_TO_UPDATE_TEXT);
684
800
  const svc = await service();
@@ -697,11 +813,16 @@ export function scheduleTools(deps) {
697
813
  if (input.mode !== undefined) {
698
814
  const target = input.mode === "here"
699
815
  ? await hereTarget(deps.db(), ctx.orgId, ctx.accountId, ctx.conversationId)
700
- : { mode: "new" };
816
+ : input.mode === "chat"
817
+ ? await chatTargetOf(deps.db(), normalizedChat(input.chat))
818
+ : { mode: "new" };
701
819
  const f = targetFields(target);
702
820
  patch.mode = f.mode;
703
821
  patch.conversationId = f.conversationId ?? null;
704
822
  patch.chat = f.chat ?? null;
823
+ // the chat's agent maps to the service's task agent (the HTTP shape stays flat)
824
+ if (input.chat?.agentId !== undefined)
825
+ patch.agentId = input.chat.agentId;
705
826
  }
706
827
  const zone = task.trigger.timeZone;
707
828
  const updated = await call(() => deps.updates.apply(svc, ctx.orgId, ctx.accountId, task, patch, input.approvedTools), { id: input.id, zone });
@@ -761,6 +882,43 @@ export function scheduleTools(deps) {
761
882
  };
762
883
  },
763
884
  };
885
+ // ---- ScheduleTargets ----
886
+ /** A target's fields in the model-facing line (`ScheduleTargets`). */
887
+ const targetLine = (t) => `- ${clip(t.label, 120)}: connectionId=${t.connectionId}, chatId=${t.chatId}, ` +
888
+ `chatType=${t.chatType}, agentId=${t.agentId} (${clip(t.connectionName, 80)}, ${t.plugin})`;
889
+ const targets = {
890
+ spec: TARGETS_SPEC,
891
+ defaultPolicy: "allow",
892
+ unavailable,
893
+ async run(_raw, ctx) {
894
+ const svc = await service();
895
+ await liveRole(ctx);
896
+ const items = await call(() => svc.chatTargets(ctx.orgId, ctx.accountId), {});
897
+ if (items.length === 0) {
898
+ return {
899
+ content: [
900
+ {
901
+ type: "text",
902
+ text: "The user has no IM chats scheduled task runs can be delivered to in this " +
903
+ "organization; a chat appears once the user has messaged the channel's bot or " +
904
+ "talked in a group.",
905
+ },
906
+ ],
907
+ display: display("没有可送达的聊天", "No delivery chats"),
908
+ };
909
+ }
910
+ return {
911
+ content: [
912
+ {
913
+ type: "text",
914
+ text: `The IM chats this user can deliver scheduled task runs to (${items.length}):\n` +
915
+ items.map(targetLine).join("\n"),
916
+ },
917
+ ],
918
+ display: display(`可送达的聊天(${items.length} 个)`, `Listed ${items.length} delivery chats`),
919
+ };
920
+ },
921
+ };
764
922
  // ---- ScheduleDelete ----
765
923
  const remove = {
766
924
  spec: DELETE_SPEC,
@@ -785,5 +943,5 @@ export function scheduleTools(deps) {
785
943
  };
786
944
  },
787
945
  };
788
- return [create, update, list, remove];
946
+ return [create, update, list, targets, remove];
789
947
  }
@@ -24,6 +24,14 @@ export interface MemberToolPolicyView {
24
24
  }
25
25
  /** Whether a connector's credential is per member: its calls are made with the member's connection. */
26
26
  export declare const personalAuth: (auth: unknown) => boolean;
27
+ /**
28
+ * The tool name of a member's own policy for pushes to one repository on one host through the
29
+ * connector (sandbox git credentials spec §7: the virtual tool `git_push`,
30
+ * `git_push:<host>/<owner>/<repo>`, so an allow never covers another host). No
31
+ * `connector_tools` row backs it, so the 权限 sheet ({@link ConnectorAccountPolicies.read}) does
32
+ * not list it; 「一直允许」 on a git push ask writes it.
33
+ */
34
+ export declare const gitPushPolicyName: (host: string, repo: string) => string;
27
35
  /**
28
36
  * A member's own tool policies for their own connection of a personal-credential connector
29
37
  * (maintainer decision 2026-11-09, the connector card's 权限). They apply to every agent the
@@ -22,6 +22,14 @@ export const personalAuth = (auth) => {
22
22
  const a = auth;
23
23
  return a !== null && a.type !== "none" && a.ownership === "personal";
24
24
  };
25
+ /**
26
+ * The tool name of a member's own policy for pushes to one repository on one host through the
27
+ * connector (sandbox git credentials spec §7: the virtual tool `git_push`,
28
+ * `git_push:<host>/<owner>/<repo>`, so an allow never covers another host). No
29
+ * `connector_tools` row backs it, so the 权限 sheet ({@link ConnectorAccountPolicies.read}) does
30
+ * not list it; 「一直允许」 on a git push ask writes it.
31
+ */
32
+ export const gitPushPolicyName = (host, repo) => `git_push:${host.toLowerCase()}/${repo}`;
25
33
  const notPersonal = () => new ApiError("connector.not_personal", "This connector uses one credential for the whole organization; only a connector each member connects to with their own credential has per-member tool policies");
26
34
  const notFound = () => new ApiError("connector.not_found", "Connector not found");
27
35
  /**
@@ -75,6 +75,7 @@ declare const CreateBody: z.ZodObject<{
75
75
  tokenTypes: z.ZodOptional<z.ZodArray<z.ZodString>>;
76
76
  }, z.core.$strict>], "type">;
77
77
  credential: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
78
+ git: z.ZodOptional<z.ZodType<import("@capybara-hq/plugin-sdk").GitSource, unknown, z.core.$ZodTypeInternals<import("@capybara-hq/plugin-sdk").GitSource, unknown>>>;
78
79
  }, z.core.$strict>;
79
80
  /** 预览(Ruling A20):与创建相同的连接器 / 端点 / 认证 / 凭据,认证可缺省(只读端点描述,不列工具)。 */
80
81
  declare const PreviewBody: z.ZodObject<{
@@ -170,6 +171,7 @@ declare const UpdateBody: z.ZodObject<{
170
171
  }>>;
171
172
  }, z.core.$strict>>>;
172
173
  allowUnannotated: z.ZodOptional<z.ZodBoolean>;
174
+ useForGit: z.ZodOptional<z.ZodBoolean>;
173
175
  }, z.core.$strict>;
174
176
  /**
175
177
  * A connector as the org's pages show it: with its plugin's icon (`icon`, see `withIcons`), its
@@ -7,7 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __param = (this && this.__param) || function (paramIndex, decorator) {
8
8
  return function (target, key) { decorator(target, key, paramIndex); }
9
9
  };
10
- import { ConnectorAuthSchema } from "@capybara-hq/plugin-sdk";
10
+ import { ConnectorAuthSchema, GitSourceSchema } from "@capybara-hq/plugin-sdk";
11
11
  import { Controller, Delete, Get, HttpCode, Inject, Param, Patch, Post, Put } from "@nestjs/common";
12
12
  import { z } from "zod";
13
13
  import { orgIconScope, PluginIcons } from "../extensions/plugin-icons.js";
@@ -47,6 +47,8 @@ const CreateBody = z
47
47
  endpoint: z.record(z.string(), z.unknown()),
48
48
  auth: ConnectorAuthSchema,
49
49
  credential: CredentialValue.optional(),
50
+ /** The git hosts the credential serves in sandboxes (sandbox git credentials spec §6.1). */
51
+ git: GitSourceSchema.optional(),
50
52
  })
51
53
  .strict();
52
54
  /** 预览(Ruling A20):与创建相同的连接器 / 端点 / 认证 / 凭据,认证可缺省(只读端点描述,不列工具)。 */
@@ -76,6 +78,8 @@ const UpdateBody = z
76
78
  .refine((v) => Object.keys(v).length <= 1000, { message: "Too many tools" })
77
79
  .optional(),
78
80
  allowUnannotated: z.boolean().optional(),
81
+ /** Use an org-credential connector for git in sandboxes (spec 2026-11-12 §6.2). */
82
+ useForGit: z.boolean().optional(),
79
83
  })
80
84
  .strict();
81
85
  class ConnectorCreateBody extends createZodDto(CreateBody, "ConnectorCreateBody") {
@@ -1,4 +1,4 @@
1
- import { type AuthHint, type ConnectorOperation, type ToolAnnotations } from "@capybara-hq/plugin-sdk";
1
+ import { type AuthHint, type ConnectorOperation, type GitSource, type ToolAnnotations } from "@capybara-hq/plugin-sdk";
2
2
  import { type ConnectionGuideState, PluginGuides } from "../extensions/plugin-guides.js";
3
3
  import { ExtensionRegistry } from "../extensions/registry.js";
4
4
  import type { WriteActor } from "../identity/write-actor.js";
@@ -71,6 +71,16 @@ export interface ConnectorSummary {
71
71
  platformTemplateId: string | null;
72
72
  /** The plugin (catalog item) its template came from (`connectors.catalog_item_id`), else null. */
73
73
  catalogItemId: string | null;
74
+ /**
75
+ * The git hosts its credential serves in sandboxes (`connectors.git_hosts`; sandbox git
76
+ * credentials spec §6.1), null when it declares none.
77
+ */
78
+ git: GitSource | null;
79
+ /**
80
+ * An org-ownership connector serves as a git source (`connectors.use_for_git`, set by an org
81
+ * admin); personal connectors with `git` always do and keep this false.
82
+ */
83
+ useForGit: boolean;
74
84
  createdBy: string;
75
85
  createdAt: Date;
76
86
  updatedAt: Date;
@@ -90,6 +100,11 @@ export interface CreateConnectorInput {
90
100
  endpoint: unknown;
91
101
  auth: ConnectorAuth;
92
102
  credential?: unknown;
103
+ /**
104
+ * The git hosts the connector's credential serves in sandboxes (a template's `git`, or the
105
+ * create body's; validated by the SDK's `GitSourceSchema`), stored in `connectors.git_hosts`.
106
+ */
107
+ git?: GitSource | undefined;
93
108
  /**
94
109
  * Install from a platform catalog connector template (T26, filled in by the catalog service,
95
110
  * not accepted over HTTP): the connector records its source item and version, the item policy is
@@ -139,6 +154,8 @@ export interface TemplateUpdateInput {
139
154
  auth: ConnectorAuth;
140
155
  /** The template version (itemId, version, templateId, defaultToolPolicies). */
141
156
  template: ConnectorTemplateRef;
157
+ /** The new template's git hosts (template-owned: replaces `git_hosts`; absent clears them). */
158
+ git?: GitSource | undefined;
142
159
  /**
143
160
  * The auth method or the credentials it needs changed: the connector's credentials (org and
144
161
  * personal, OAuth client and tokens) are cleared and `credential` (an org or the actor's static
@@ -194,6 +211,11 @@ export interface UpdateConnectorInput {
194
211
  }> | undefined;
195
212
  /** 把「没有 readOnlyHint / destructiveHint 注解」的工具默认策略批量改为 allow。 */
196
213
  allowUnannotated?: boolean | undefined;
214
+ /**
215
+ * Use this connector's credential for git in sandboxes (sandbox git credentials spec §6.2): only
216
+ * an org-ownership connector that declares git hosts; pushes are then the bot's.
217
+ */
218
+ useForGit?: boolean | undefined;
197
219
  }
198
220
  /** OAuth 安装准备的结果:要存的客户端,以及(客户端凭据流)安装时取到的组织 token。 */
199
221
  export interface OAuthSetup {
@@ -207,7 +229,10 @@ export interface OAuthSetup {
207
229
  export declare class ConnectorsService {
208
230
  private readonly kdb;
209
231
  private readonly registry;
210
- /** Extension manifests (a slug may not take an extension's name). */
232
+ /**
233
+ * Extension manifests: a derived default slug avoids an extension's name (an explicit slug may
234
+ * take one; connector and extension tool keys live in separate namespaces).
235
+ */
211
236
  private readonly extensions;
212
237
  private readonly credentials;
213
238
  private readonly config;
@@ -218,7 +243,10 @@ export declare class ConnectorsService {
218
243
  /** Setup guides of connector templates (spec 2026-11-09-setup-guides-v2-design.md §11). */
219
244
  private readonly guides;
220
245
  constructor(kdb: KernelDb, registry: ToolRegistry,
221
- /** Extension manifests (a slug may not take an extension's name). */
246
+ /**
247
+ * Extension manifests: a derived default slug avoids an extension's name (an explicit slug may
248
+ * take one; connector and extension tool keys live in separate namespaces).
249
+ */
222
250
  extensions: Pick<ExtensionRegistry, "manifests">, credentials: Credentials, config: Pick<KernelConfigService, "get">, drift: ToolDrift,
223
251
  /** The org row lock (orgs/org-lock.ts): joins the caller's transaction. */
224
252
  orgLock: Pick<OrgLock, "lock" | "lockAndAuthorize">, txHost: TransactionHost,
@@ -344,6 +372,21 @@ export declare class ConnectorsService {
344
372
  * global id); else `connector.not_found`.
345
373
  */
346
374
  private liveOrgOf;
375
+ /**
376
+ * Disables a connector because its plugin was switched off in the org (spec §5 rule 8): its
377
+ * endpoint and credentials stay. Only a connector that was active is marked with the plugin
378
+ * (`connectors.disabled_by_plugin_id`), so switching the plugin back on re-enables exactly what
379
+ * this call disabled; one an org admin disabled by hand keeps no marker and stays disabled. The
380
+ * caller holds the org row lock and runs in its transaction.
381
+ */
382
+ disableWithPluginIn(orgId: string, connectorId: string, itemId: string): Promise<void>;
383
+ /**
384
+ * Re-enables a connector the given plugin's switch-off disabled
385
+ * ({@link disableWithPluginIn}) when the plugin is switched back on and its install takes the
386
+ * connector back through the reuse path. A connector an org admin disabled by hand (no marker)
387
+ * or one another state left disabled stays disabled. Returns whether it switched it on.
388
+ */
389
+ enableIfPluginDisabledIn(orgId: string, connectorId: string, itemId: string): Promise<boolean>;
347
390
  /**
348
391
  * Disables or enables a connector on the platform's behalf (a super admin who need not be a
349
392
  * member of the org; the caller checks the super admin): org row lock, the status update, audit