agent-relay-server 0.104.5 → 0.105.1

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 (168) hide show
  1. package/docs/openapi.json +141 -1
  2. package/package.json +3 -2
  3. package/public/assets/MessageBubble-CHor4Sxp.js +4 -0
  4. package/public/assets/{MessageBubble-BpiTbbnp.js.map → MessageBubble-CHor4Sxp.js.map} +1 -1
  5. package/public/assets/{PlanGraphPanel-k8Aa98g5.js → PlanGraphPanel-jHfkQBX6.js} +2 -2
  6. package/public/assets/{PlanGraphPanel-k8Aa98g5.js.map → PlanGraphPanel-jHfkQBX6.js.map} +1 -1
  7. package/public/assets/{activity-CxKi5Erb.js → activity-DDXS6LsX.js} +2 -2
  8. package/public/assets/{activity-CxKi5Erb.js.map → activity-DDXS6LsX.js.map} +1 -1
  9. package/public/assets/{agent-profiles-DMZXdN6K.js → agent-profiles-Cn8lCTkg.js} +2 -2
  10. package/public/assets/{agent-profiles-DMZXdN6K.js.map → agent-profiles-Cn8lCTkg.js.map} +1 -1
  11. package/public/assets/agents-DBTdcFFz.js +2 -0
  12. package/public/assets/{agents-BrvpH3gn.js.map → agents-DBTdcFFz.js.map} +1 -1
  13. package/public/assets/{analytics-1fvh-9ok.js → analytics-VximqXQ9.js} +3 -3
  14. package/public/assets/{analytics-1fvh-9ok.js.map → analytics-VximqXQ9.js.map} +1 -1
  15. package/public/assets/{automation-BCgX5WOH.js → automation-L9wq4y5F.js} +2 -2
  16. package/public/assets/{automation-BCgX5WOH.js.map → automation-L9wq4y5F.js.map} +1 -1
  17. package/public/assets/{badge-BLar6FjP.js → badge-D8fABzMs.js} +2 -2
  18. package/public/assets/{badge-BLar6FjP.js.map → badge-D8fABzMs.js.map} +1 -1
  19. package/public/assets/{branch-state-badge-DYA7bU0i.js → branch-state-badge-CcLZ_u86.js} +2 -2
  20. package/public/assets/{branch-state-badge-DYA7bU0i.js.map → branch-state-badge-CcLZ_u86.js.map} +1 -1
  21. package/public/assets/{button-BrIB69qd.js → button-KASZcscA.js} +2 -2
  22. package/public/assets/{button-BrIB69qd.js.map → button-KASZcscA.js.map} +1 -1
  23. package/public/assets/card-Cds0MWME.js +2 -0
  24. package/public/assets/{card-lhnFnBoz.js.map → card-Cds0MWME.js.map} +1 -1
  25. package/public/assets/{channels-CK80oJYG.js → channels-CcSnLzCG.js} +2 -2
  26. package/public/assets/{channels-CK80oJYG.js.map → channels-CcSnLzCG.js.map} +1 -1
  27. package/public/assets/{chat-D_2PEz06.js → chat-BzMj_PO6.js} +2 -2
  28. package/public/assets/{chat-D_2PEz06.js.map → chat-BzMj_PO6.js.map} +1 -1
  29. package/public/assets/{connectors-ChGyht5m.js → connectors-DbPOlQhL.js} +2 -2
  30. package/public/assets/{connectors-ChGyht5m.js.map → connectors-DbPOlQhL.js.map} +1 -1
  31. package/public/assets/copy-button-d9DFNX9v.js +2 -0
  32. package/public/assets/{copy-button-Cd-LbSbg.js.map → copy-button-d9DFNX9v.js.map} +1 -1
  33. package/public/assets/{display-D1xB8NJZ.js → display-BWi8DQCp.js} +2 -2
  34. package/public/assets/display-BWi8DQCp.js.map +1 -0
  35. package/public/assets/{dist-BnbGIUmi.js → dist-BbUJ1Vht.js} +2 -2
  36. package/public/assets/{dist-BnbGIUmi.js.map → dist-BbUJ1Vht.js.map} +1 -1
  37. package/public/assets/{dist-Bp10H3oR.js → dist-BlZvUZZB.js} +2 -2
  38. package/public/assets/{dist-Bp10H3oR.js.map → dist-BlZvUZZB.js.map} +1 -1
  39. package/public/assets/{dist-DDUGmmoi.js → dist-BwwlmyBq.js} +2 -2
  40. package/public/assets/{dist-DDUGmmoi.js.map → dist-BwwlmyBq.js.map} +1 -1
  41. package/public/assets/dist-CBVZk9U8.js +2 -0
  42. package/public/assets/{dist-BEzpMqLy.js.map → dist-CBVZk9U8.js.map} +1 -1
  43. package/public/assets/{dist-B5VvlkhI.js → dist-CgqfdHSt.js} +2 -2
  44. package/public/assets/{dist-B5VvlkhI.js.map → dist-CgqfdHSt.js.map} +1 -1
  45. package/public/assets/dist-DC-TqTz2.js +2 -0
  46. package/public/assets/{dist-30weaXto.js.map → dist-DC-TqTz2.js.map} +1 -1
  47. package/public/assets/{dist-DZ97OosY.js → dist-DOAjFOS4.js} +2 -2
  48. package/public/assets/{dist-DZ97OosY.js.map → dist-DOAjFOS4.js.map} +1 -1
  49. package/public/assets/{dist-BjOBkHHY.js → dist-DTXKbk2K.js} +2 -2
  50. package/public/assets/{dist-BjOBkHHY.js.map → dist-DTXKbk2K.js.map} +1 -1
  51. package/public/assets/dist-DreO5AcZ.js +2 -0
  52. package/public/assets/{dist-Cgy0FmP-.js.map → dist-DreO5AcZ.js.map} +1 -1
  53. package/public/assets/{dist-CQF3Jiqg.js → dist-DybScJmm.js} +2 -2
  54. package/public/assets/{dist-CQF3Jiqg.js.map → dist-DybScJmm.js.map} +1 -1
  55. package/public/assets/{dist-DAl28ejX.js → dist-UhEAFdvZ.js} +2 -2
  56. package/public/assets/{dist-DAl28ejX.js.map → dist-UhEAFdvZ.js.map} +1 -1
  57. package/public/assets/{es2015-B8Z9p8oQ.js → es2015-DUjFyjDp.js} +2 -2
  58. package/public/assets/{es2015-B8Z9p8oQ.js.map → es2015-DUjFyjDp.js.map} +1 -1
  59. package/public/assets/{formatted-body-impl-BJ4WCF5J.js → formatted-body-impl-CFWHLkCY.js} +2 -2
  60. package/public/assets/{formatted-body-impl-BJ4WCF5J.js.map → formatted-body-impl-CFWHLkCY.js.map} +1 -1
  61. package/public/assets/index-BjjzI2LT.css +2 -0
  62. package/public/assets/{index-CN7QZlMb.js → index-aCfZQ7LY.js} +13 -13
  63. package/public/assets/index-aCfZQ7LY.js.map +1 -0
  64. package/public/assets/{input-B_fUHtLD.js → input-CtHfLn1i.js} +2 -2
  65. package/public/assets/{input-B_fUHtLD.js.map → input-CtHfLn1i.js.map} +1 -1
  66. package/public/assets/{insights-BCZRV0hX.js → insights-BuPFE0tf.js} +2 -2
  67. package/public/assets/{insights-BCZRV0hX.js.map → insights-BuPFE0tf.js.map} +1 -1
  68. package/public/assets/{integrations-C7UGs00U.js → integrations-hkSW8CzM.js} +2 -2
  69. package/public/assets/{integrations-C7UGs00U.js.map → integrations-hkSW8CzM.js.map} +1 -1
  70. package/public/assets/{lib-ng6WZYjE.js → lib-l-JkMVR9.js} +2 -2
  71. package/public/assets/{lib-ng6WZYjE.js.map → lib-l-JkMVR9.js.map} +1 -1
  72. package/public/assets/{lucide-react-BYZuAhQs.js → lucide-react-C4eXYCwx.js} +2 -2
  73. package/public/assets/{lucide-react-BYZuAhQs.js.map → lucide-react-C4eXYCwx.js.map} +1 -1
  74. package/public/assets/{maintenance-ChYno3Fm.js → maintenance-wYwJ7w6h.js} +2 -2
  75. package/public/assets/{maintenance-ChYno3Fm.js.map → maintenance-wYwJ7w6h.js.map} +1 -1
  76. package/public/assets/{managed-agents-DjxZQNIR.js → managed-agents-CKaYD93I.js} +4 -4
  77. package/public/assets/{managed-agents-DjxZQNIR.js.map → managed-agents-CKaYD93I.js.map} +1 -1
  78. package/public/assets/{markdown-preview-impl-DFa5HF1H.js → markdown-preview-impl-bf4eDQUM.js} +2 -2
  79. package/public/assets/{markdown-preview-impl-DFa5HF1H.js.map → markdown-preview-impl-bf4eDQUM.js.map} +1 -1
  80. package/public/assets/memory-DIneEza9.js +2 -0
  81. package/public/assets/{memory-CTw6kadT.js.map → memory-DIneEza9.js.map} +1 -1
  82. package/public/assets/{messages-Cjr816D_.js → messages-DP9Vv_wh.js} +2 -2
  83. package/public/assets/{messages-Cjr816D_.js.map → messages-DP9Vv_wh.js.map} +1 -1
  84. package/public/assets/{orchestrators-CdmFKA7K.js → orchestrators-CAnKjxdO.js} +2 -2
  85. package/public/assets/{orchestrators-CdmFKA7K.js.map → orchestrators-CAnKjxdO.js.map} +1 -1
  86. package/public/assets/{overview-DBOnjfpP.js → overview-B98U3C-y.js} +2 -2
  87. package/public/assets/{overview-DBOnjfpP.js.map → overview-B98U3C-y.js.map} +1 -1
  88. package/public/assets/{pairs-BkcdKdF7.js → pairs-DVeeuxhY.js} +2 -2
  89. package/public/assets/{pairs-BkcdKdF7.js.map → pairs-DVeeuxhY.js.map} +1 -1
  90. package/public/assets/{projects-ClKbDQB1.js → projects-vt63bwHN.js} +2 -2
  91. package/public/assets/{projects-ClKbDQB1.js.map → projects-vt63bwHN.js.map} +1 -1
  92. package/public/assets/prompt-settings-Df-0ZMJ5.js +2 -0
  93. package/public/assets/prompt-settings-Df-0ZMJ5.js.map +1 -0
  94. package/public/assets/{react-dom-Dbhs84ug.js → react-dom-BMF4hNzG.js} +2 -2
  95. package/public/assets/{react-dom-Dbhs84ug.js.map → react-dom-BMF4hNzG.js.map} +1 -1
  96. package/public/assets/{registry-Bsf3RtOO.js → registry-DlLZyW7_.js} +2 -2
  97. package/public/assets/{registry-Bsf3RtOO.js.map → registry-DlLZyW7_.js.map} +1 -1
  98. package/public/assets/{security-Iyu5bhJw.js → security-Bo4Ty3lz.js} +2 -2
  99. package/public/assets/{security-Iyu5bhJw.js.map → security-Bo4Ty3lz.js.map} +1 -1
  100. package/public/assets/{select-Bi6tNJDx.js → select-BguZ80M8.js} +2 -2
  101. package/public/assets/{select-Bi6tNJDx.js.map → select-BguZ80M8.js.map} +1 -1
  102. package/public/assets/settings-CpX9Xw2j.js +6 -0
  103. package/public/assets/{settings-CbHp2F9r.js.map → settings-CpX9Xw2j.js.map} +1 -1
  104. package/public/assets/{store-zeyDiSKE.js → store-DUfrGvOE.js} +3 -3
  105. package/public/assets/{store-zeyDiSKE.js.map → store-DUfrGvOE.js.map} +1 -1
  106. package/public/assets/{tasks-CPlf-5Tr.js → tasks-Cg1HXY9l.js} +2 -2
  107. package/public/assets/{tasks-CPlf-5Tr.js.map → tasks-Cg1HXY9l.js.map} +1 -1
  108. package/public/assets/{teams-D2uUNhlY.js → teams-BFcYunxB.js} +2 -2
  109. package/public/assets/{teams-D2uUNhlY.js.map → teams-BFcYunxB.js.map} +1 -1
  110. package/public/assets/{terminal-viewer-impl-uRzbrlXy.js → terminal-viewer-impl-CF2EBa2K.js} +3 -3
  111. package/public/assets/{terminal-viewer-impl-uRzbrlXy.js.map → terminal-viewer-impl-CF2EBa2K.js.map} +1 -1
  112. package/public/assets/types-DorDDvEb.js.map +1 -1
  113. package/public/assets/{use-keyboard-viewport-D8_NYDkh.js → use-keyboard-viewport-RYMALvu1.js} +2 -2
  114. package/public/assets/{use-keyboard-viewport-D8_NYDkh.js.map → use-keyboard-viewport-RYMALvu1.js.map} +1 -1
  115. package/public/assets/user-avatar-D1ffSClz.js +2 -0
  116. package/public/assets/{user-avatar-GFWMuAGK.js.map → user-avatar-D1ffSClz.js.map} +1 -1
  117. package/public/assets/{work-queue-B30SNmuq.js → work-queue-DV36cxjp.js} +2 -2
  118. package/public/assets/{work-queue-B30SNmuq.js.map → work-queue-DV36cxjp.js.map} +1 -1
  119. package/public/assets/workspaces-CIsQqQby.js +3 -0
  120. package/public/assets/workspaces-CIsQqQby.js.map +1 -0
  121. package/public/index.html +22 -22
  122. package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
  123. package/runner/plugins/claude/monitors/relay-monitor.provisioned.mjs +114 -23
  124. package/runner/plugins/claude/monitors/relay-monitor.ts +2 -2
  125. package/runner/src/adapter.ts +4 -1
  126. package/src/agent-lifecycle-events.ts +21 -5
  127. package/src/automations/reconcile.ts +4 -1
  128. package/src/branch-landed.ts +22 -4
  129. package/src/config-store.ts +17 -21
  130. package/src/context-advisory.ts +10 -3
  131. package/src/db/index.ts +1 -0
  132. package/src/db/projects.ts +29 -0
  133. package/src/db/prompt-templates-schema.ts +20 -0
  134. package/src/db/prompt-templates.ts +86 -0
  135. package/src/db/schema.ts +5 -4
  136. package/src/db/tasks.ts +2 -1
  137. package/src/db/workspaces.ts +41 -1
  138. package/src/index.ts +3 -0
  139. package/src/project-instructions.ts +7 -14
  140. package/src/prompt-resolver.ts +530 -0
  141. package/src/routes/index.ts +5 -0
  142. package/src/routes/prompt-templates.ts +60 -0
  143. package/src/routes/workspaces.ts +4 -3
  144. package/src/routing-policy-push.ts +4 -1
  145. package/src/security.ts +3 -0
  146. package/src/self-resume.ts +8 -15
  147. package/src/services/prompt-templates.ts +19 -0
  148. package/src/services/register-agent.ts +2 -0
  149. package/src/services/spawn-issue-activity.ts +107 -0
  150. package/src/spawn-targets.ts +3 -11
  151. package/src/steward.ts +5 -23
  152. package/src/workspace-auto-merge.ts +19 -2
  153. package/src/workspace-phase.ts +15 -28
  154. package/public/assets/MessageBubble-BpiTbbnp.js +0 -4
  155. package/public/assets/agents-BrvpH3gn.js +0 -2
  156. package/public/assets/card-lhnFnBoz.js +0 -2
  157. package/public/assets/copy-button-Cd-LbSbg.js +0 -2
  158. package/public/assets/display-D1xB8NJZ.js.map +0 -1
  159. package/public/assets/dist-30weaXto.js +0 -2
  160. package/public/assets/dist-BEzpMqLy.js +0 -2
  161. package/public/assets/dist-Cgy0FmP-.js +0 -2
  162. package/public/assets/index-BMO_Sblt.css +0 -2
  163. package/public/assets/index-CN7QZlMb.js.map +0 -1
  164. package/public/assets/memory-CTw6kadT.js +0 -2
  165. package/public/assets/settings-CbHp2F9r.js +0 -6
  166. package/public/assets/user-avatar-GFWMuAGK.js +0 -2
  167. package/public/assets/workspaces-CSpmgbPT.js +0 -3
  168. package/public/assets/workspaces-CSpmgbPT.js.map +0 -1
@@ -0,0 +1,86 @@
1
+ import type { PromptTemplate } from "../types";
2
+ import { ValidationError, getDb } from "./connection.ts";
3
+
4
+ // SeedTemplate is owned by the SDK (alongside RUNNER_PROMPT_TEMPLATE_DEFAULTS) so the
5
+ // published runner can consume the seed data without crossing a package boundary (#802).
6
+ import type { SeedTemplate } from "agent-relay-sdk/prompt-template-defaults";
7
+ export type { SeedTemplate };
8
+
9
+ interface PromptTemplateRow {
10
+ slug: string;
11
+ category: string;
12
+ title: string;
13
+ description: string | null;
14
+ template: string;
15
+ default_template: string;
16
+ variables: string;
17
+ updated_at: number;
18
+ created_at: number;
19
+ }
20
+
21
+ export type PromptTemplateRecord = PromptTemplate;
22
+
23
+ function rowToPromptTemplate(row: PromptTemplateRow): PromptTemplateRecord {
24
+ return {
25
+ slug: row.slug,
26
+ category: row.category,
27
+ title: row.title,
28
+ description: row.description ?? undefined,
29
+ template: row.template,
30
+ defaultTemplate: row.default_template,
31
+ variables: JSON.parse(row.variables) as string[],
32
+ updatedAt: row.updated_at,
33
+ createdAt: row.created_at,
34
+ };
35
+ }
36
+
37
+ export function getTemplate(slug: string): PromptTemplateRecord | null {
38
+ const row = getDb().query("SELECT * FROM prompt_templates WHERE slug = ?").get(slug) as PromptTemplateRow | null;
39
+ return row ? rowToPromptTemplate(row) : null;
40
+ }
41
+
42
+ export function listTemplates(category?: string): PromptTemplateRecord[] {
43
+ const rows = (
44
+ category
45
+ ? getDb().query("SELECT * FROM prompt_templates WHERE category = ? ORDER BY category ASC, title ASC, slug ASC").all(category)
46
+ : getDb().query("SELECT * FROM prompt_templates ORDER BY category ASC, title ASC, slug ASC").all()
47
+ ) as PromptTemplateRow[];
48
+ return rows.map(rowToPromptTemplate);
49
+ }
50
+
51
+ export function upsertTemplate(slug: string, template: string): void {
52
+ const now = Date.now();
53
+ const result = getDb().query("UPDATE prompt_templates SET template = ?, updated_at = ? WHERE slug = ?").run(template, now, slug);
54
+ if ((result.changes ?? 0) === 0) throw new ValidationError(`prompt template ${slug} not found`);
55
+ }
56
+
57
+ export function resetTemplate(slug: string): void {
58
+ const now = Date.now();
59
+ const result = getDb().query("UPDATE prompt_templates SET template = default_template, updated_at = ? WHERE slug = ?").run(now, slug);
60
+ if ((result.changes ?? 0) === 0) throw new ValidationError(`prompt template ${slug} not found`);
61
+ }
62
+
63
+ export function seedTemplates(templates: SeedTemplate[]): void {
64
+ const now = Date.now();
65
+ const insert = getDb().query(`
66
+ INSERT OR IGNORE INTO prompt_templates (
67
+ slug, category, title, description, template, default_template, variables, updated_at, created_at
68
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
69
+ `);
70
+ const tx = getDb().transaction((items: SeedTemplate[]) => {
71
+ for (const item of items) {
72
+ insert.run(
73
+ item.slug,
74
+ item.category,
75
+ item.title,
76
+ item.description ?? null,
77
+ item.template,
78
+ item.defaultTemplate,
79
+ JSON.stringify(item.variables),
80
+ now,
81
+ now,
82
+ );
83
+ }
84
+ });
85
+ tx(templates);
86
+ }
package/src/db/schema.ts CHANGED
@@ -17,6 +17,7 @@ import { matchAgents } from "../agent-ref";
17
17
  import { applyConnectionPragmas, getDb, setDb } from "./connection.ts";
18
18
  import { ensureContinuationArchiveSearchSchema } from "./continuation-archives.ts";
19
19
  import { applyMigrations } from "./migrations.ts";
20
+ import { PROMPT_TEMPLATES_SCHEMA_SQL, seedPromptTemplatesIfNeeded } from "./prompt-templates-schema";
20
21
  import type {
21
22
  AgentCard,
22
23
  ActivityEvent,
@@ -506,6 +507,8 @@ export function initDb(path: string = "agent-relay.db"): Database {
506
507
  );
507
508
  CREATE INDEX IF NOT EXISTS idx_config_history_key ON config_history(namespace, key, version);
508
509
 
510
+ ${PROMPT_TEMPLATES_SCHEMA_SQL}
511
+
509
512
  -- Per-user settings (#391, epic #388). The user dimension the global \`config\` table lacks:
510
513
  -- the same \`(namespace, key)\` descriptor address resolves to a per-user value, keyed by the
511
514
  -- resolved \`users.id\` (#393). \`config\` semantics are untouched — \`tier:"user"\` descriptors
@@ -1009,8 +1012,7 @@ export function initDb(path: string = "agent-relay.db"): Database {
1009
1012
  );
1010
1013
  `);
1011
1014
 
1012
- ensureContinuationArchiveSearchSchema();
1013
- applyMigrations();
1015
+ ensureContinuationArchiveSearchSchema(); applyMigrations(); seedPromptTemplatesIfNeeded();
1014
1016
 
1015
1017
  // Bootstrap planner statistics on first run (sqlite_stat1 absent means ANALYZE
1016
1018
  // has never run — the planner would otherwise rely on heuristics only), then
@@ -1018,8 +1020,7 @@ export function initDb(path: string = "agent-relay.db"): Database {
1018
1020
  const hasStats = getDb()
1019
1021
  .query("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'sqlite_stat1'")
1020
1022
  .get();
1021
- if (!hasStats) getDb().run("ANALYZE");
1022
- getDb().run("PRAGMA optimize");
1023
+ if (!hasStats) getDb().run("ANALYZE"); getDb().run("PRAGMA optimize");
1023
1024
 
1024
1025
  return conn;
1025
1026
  }
package/src/db/tasks.ts CHANGED
@@ -21,6 +21,7 @@ import { rowToTask, rowToTaskEvent } from "./mappers.ts";
21
21
  import { claimMessageRow } from "./message-claims.ts";
22
22
  import { TASK_SELECT, insertTaskEvent } from "./task-core.ts";
23
23
  import { settleSingleTargetOnDemandTasks } from "./task-maintenance.ts";
24
+ import { renderTemplate } from "../prompt-resolver";
24
25
  import type {
25
26
  AgentCard,
26
27
  ActivityEvent,
@@ -101,7 +102,7 @@ export function taskMessageBody(task: Pick<Task, "id" | "source" | "severity" |
101
102
  ];
102
103
  if (task.externalUrl) lines.push("", `External: ${task.externalUrl}`);
103
104
  if (task.dedupeKey) lines.push(`Dedupe: ${task.dedupeKey}`);
104
- lines.push("", "Claim this task before working it, then update task status when finished.");
105
+ lines.push("", renderTemplate("task.claim.instruction"));
105
106
  return lines.join("\n");
106
107
  }
107
108
 
@@ -15,6 +15,7 @@ import {
15
15
  import { STALE_TTL_MS, DAY_MS, CLAIM_LEASE_MS, POOL_CLAIM_LEASE_MS, WORKSPACE_MERGE_LEASE_MS } from "../config";
16
16
  import { matchAgents } from "../agent-ref";
17
17
  import { getDb } from "./connection.ts";
18
+ import { resolveProjectsForCwds } from "./projects.ts";
18
19
  import type {
19
20
  AgentCard,
20
21
  ActivityEvent,
@@ -161,7 +162,7 @@ export function upsertWorkspaceFromManagedAgent(agent: ManagedAgent): WorkspaceR
161
162
  // overwrite the true base, and the next land advances a local-only branch that never
162
163
  // reaches origin/main (#285). Trust the existing row's branch/base over registration;
163
164
  // only a brand-new row takes the runner's values.
164
- return upsertWorkspace({
165
+ const result = upsertWorkspace({
165
166
  id: workspace.id,
166
167
  repoRoot: workspace.repoRoot,
167
168
  sourceCwd: workspace.sourceCwd ?? agent.cwd,
@@ -185,6 +186,8 @@ export function upsertWorkspaceFromManagedAgent(agent: ManagedAgent): WorkspaceR
185
186
  tmuxSession: agent.tmuxSession,
186
187
  },
187
188
  });
189
+ if (!existing) emitWorkspaceCreate(result);
190
+ return result;
188
191
  }
189
192
  // Shared-mode occupancy: one row per agent sharing a git repo, with no
190
193
  // worktree. Records true repo occupancy in the Workspaces view (informational
@@ -271,6 +274,25 @@ export function listWorkspaces(filter: { repoRoot?: string; ownerAgentId?: strin
271
274
  return (getDb().query(sql).all(...params) as any[]).map(rowToWorkspace);
272
275
  }
273
276
 
277
+ export function withResolvedWorkspaceProjects(
278
+ workspaces: WorkspaceRecord[],
279
+ filter: { projectId?: string } = {},
280
+ ): WorkspaceRecord[] {
281
+ const projectsByRepoRoot = resolveProjectsForCwds(workspaces.map((workspace) => workspace.repoRoot));
282
+ return workspaces.flatMap((workspace) => {
283
+ const project = projectsByRepoRoot.get(workspace.repoRoot);
284
+ if (filter.projectId && project?.id !== filter.projectId) return [];
285
+ return [{
286
+ ...workspace,
287
+ ...(project ? {
288
+ projectId: project.id,
289
+ projectSlug: project.slug,
290
+ ...(project.title ? { projectTitle: project.title } : {}),
291
+ } : {}),
292
+ }];
293
+ });
294
+ }
295
+
274
296
  export function deleteWorkspace(id: string): boolean {
275
297
  return getDb().query("DELETE FROM workspaces WHERE id = ?").run(id).changes > 0;
276
298
  }
@@ -325,6 +347,24 @@ export function emitWorkspaceChange(workspace: WorkspaceRecord | null | undefine
325
347
  }
326
348
  }
327
349
 
350
+ // Late-bound listeners for a newly created isolated workspace. Fired once per workspace
351
+ // (on the INSERT path), allowing upper layers to record issue activity without a
352
+ // db→service import cycle. index.ts registers the spawn-issue-activity handler.
353
+ type WorkspaceCreateListener = (workspace: WorkspaceRecord) => void;
354
+ const workspaceCreateListeners = new Set<WorkspaceCreateListener>();
355
+ export function onWorkspaceCreate(listener: WorkspaceCreateListener): void {
356
+ workspaceCreateListeners.add(listener);
357
+ }
358
+ function emitWorkspaceCreate(workspace: WorkspaceRecord): void {
359
+ for (const listener of workspaceCreateListeners) {
360
+ try {
361
+ listener(workspace);
362
+ } catch {
363
+ // A create listener must never break a workspace write.
364
+ }
365
+ }
366
+ }
367
+
328
368
  export function updateWorkspaceStatus(id: string, status: WorkspaceStatus, metadata: Record<string, unknown> = {}): WorkspaceRecord | null {
329
369
  const existing = getWorkspace(id);
330
370
  if (!existing) return null;
package/src/index.ts CHANGED
@@ -14,6 +14,8 @@ import { installNotificationSubscriptionRule } from "./notification-subscription
14
14
  import { installQuotaAdvisoryHandler } from "./quota-advisory";
15
15
  import { startPlanLiveBindings } from "./services/plan-live-bindings";
16
16
  import { startRelayScheduler } from "./services/scheduler";
17
+ import { recordWorkspaceIssueActivity } from "./services/spawn-issue-activity";
18
+ import { onWorkspaceCreate } from "./db";
17
19
  import { resolve, sep } from "path";
18
20
  import { gzipSync, brotliCompressSync, constants as zlibConstants } from "node:zlib";
19
21
  import {
@@ -78,6 +80,7 @@ function startServer(): void {
78
80
 
79
81
  assertSafeNetworkConfig(HOST);
80
82
  initDb(DB_PATH);
83
+ onWorkspaceCreate(recordWorkspaceIssueActivity);
81
84
  installQuotaAdvisoryHandler();
82
85
  installNotificationSubscriptionRule();
83
86
  startPlanLiveBindings();
@@ -11,6 +11,7 @@
11
11
  // is that lever: the included-source set is a policy PARAMETER, and the `vanilla`
12
12
  // policy includes `manual`+`promoted` while excluding `ingested`.
13
13
  import { resolveProjectEntries } from "./db";
14
+ import { renderTemplate } from "./prompt-resolver";
14
15
  import { PROJECT_ENTRY_SOURCES, type AgentProfile, type Project, type ProjectEntryKind, type ProjectEntrySource, type RelayInjectionCategory, type ResolvedProjectEntry } from "./types";
15
16
 
16
17
  /** Named source policies — THE additive-from-empty lever (#403 decision 4).
@@ -64,16 +65,6 @@ interface ComposedSpawnInstructions {
64
65
  const DEFAULT_KINDS: readonly ProjectEntryKind[] = ["rule", "fact"];
65
66
  const DEFAULT_MAX_BYTES = 8192;
66
67
  const TRUNCATION_NOTICE = "_(… additional project entries omitted to fit the instruction budget.)_";
67
- const SPAWNED_CHILD_REPORTUP_INSTRUCTION = [
68
- "Agent Relay report-up:",
69
- "You are a spawned child agent. Just respond normally when your turn is complete; Relay automatically delivers your final response to your coordinator. Do not call a messaging tool only to report completion.",
70
- "You have no interactive human at your terminal, so do not use blocking prompts: AskUserQuestion and plan mode/ExitPlanMode will hang with no one to answer. If you need a decision, approval, or clarification, ask your coordinator via Relay and continue once they respond.",
71
- ].join("\n");
72
- const COORDINATOR_CHILD_REPORTUP_INSTRUCTION = [
73
- "Agent Relay child responses:",
74
- "When you spawn child agents, their turn-final responses arrive automatically as Relay messages. Do not put report-up instructions or your own agent id in child briefs.",
75
- ].join("\n");
76
-
77
68
  const KIND_HEADINGS: Record<ProjectEntryKind, string> = {
78
69
  rule: "Rules",
79
70
  fact: "Facts",
@@ -257,19 +248,21 @@ export function composeSpawnInstructionsWithEvents(input: {
257
248
  });
258
249
  }
259
250
  if (input.spawnedBy) {
260
- appendBlocks.push(SPAWNED_CHILD_REPORTUP_INSTRUCTION);
251
+ const instruction = renderTemplate("spawn.reportup.child");
252
+ appendBlocks.push(instruction);
261
253
  relayInjectionEvents.push({
262
254
  category: "instructions",
263
255
  summary: "injected spawned-child report-up instructions",
264
- detail: { source: "spawned-child-report-up", spawnedBy: input.spawnedBy, content: SPAWNED_CHILD_REPORTUP_INSTRUCTION },
256
+ detail: { source: "spawned-child-report-up", spawnedBy: input.spawnedBy, content: instruction },
265
257
  });
266
258
  }
267
259
  if ((input.profile?.maxSpawnedAgents ?? 0) > 0) {
268
- appendBlocks.push(COORDINATOR_CHILD_REPORTUP_INSTRUCTION);
260
+ const instruction = renderTemplate("spawn.reportup.coordinator");
261
+ appendBlocks.push(instruction);
269
262
  relayInjectionEvents.push({
270
263
  category: "instructions",
271
264
  summary: "injected coordinator child-response instructions",
272
- detail: { source: "coordinator-child-report-up", content: COORDINATOR_CHILD_REPORTUP_INSTRUCTION },
265
+ detail: { source: "coordinator-child-report-up", content: instruction },
273
266
  });
274
267
  }
275
268
  const profileSystem = input.profile?.instructions.system?.trim();