@ouro.bot/cli 0.1.0-alpha.2 → 0.1.0-alpha.21

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 (58) hide show
  1. package/AdoptionSpecialist.ouro/agent.json +70 -9
  2. package/AdoptionSpecialist.ouro/psyche/SOUL.md +5 -2
  3. package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
  4. package/assets/ouroboros.png +0 -0
  5. package/dist/heart/config.js +66 -4
  6. package/dist/heart/core.js +75 -2
  7. package/dist/heart/daemon/daemon-cli.js +523 -33
  8. package/dist/heart/daemon/daemon-entry.js +13 -5
  9. package/dist/heart/daemon/daemon-runtime-sync.js +90 -0
  10. package/dist/heart/daemon/daemon.js +42 -9
  11. package/dist/heart/daemon/hatch-animation.js +35 -0
  12. package/dist/heart/daemon/hatch-flow.js +2 -11
  13. package/dist/heart/daemon/hatch-specialist.js +6 -1
  14. package/dist/heart/daemon/ouro-bot-wrapper.js +4 -3
  15. package/dist/heart/daemon/ouro-path-installer.js +178 -0
  16. package/dist/heart/daemon/ouro-uti.js +11 -2
  17. package/dist/heart/daemon/process-manager.js +1 -1
  18. package/dist/heart/daemon/runtime-logging.js +9 -5
  19. package/dist/heart/daemon/runtime-metadata.js +118 -0
  20. package/dist/heart/daemon/sense-manager.js +266 -0
  21. package/dist/heart/daemon/specialist-orchestrator.js +129 -0
  22. package/dist/heart/daemon/specialist-prompt.js +98 -0
  23. package/dist/heart/daemon/specialist-tools.js +237 -0
  24. package/dist/heart/daemon/subagent-installer.js +10 -1
  25. package/dist/heart/daemon/wrapper-publish-guard.js +48 -0
  26. package/dist/heart/identity.js +77 -1
  27. package/dist/heart/providers/anthropic.js +19 -2
  28. package/dist/heart/sense-truth.js +61 -0
  29. package/dist/heart/streaming.js +99 -21
  30. package/dist/mind/bundle-manifest.js +58 -0
  31. package/dist/mind/friends/channel.js +8 -0
  32. package/dist/mind/friends/types.js +1 -1
  33. package/dist/mind/prompt.js +77 -3
  34. package/dist/nerves/cli-logging.js +15 -2
  35. package/dist/repertoire/ado-client.js +4 -2
  36. package/dist/repertoire/coding/feedback.js +134 -0
  37. package/dist/repertoire/coding/index.js +4 -1
  38. package/dist/repertoire/coding/manager.js +61 -2
  39. package/dist/repertoire/coding/spawner.js +3 -3
  40. package/dist/repertoire/coding/tools.js +41 -2
  41. package/dist/repertoire/data/ado-endpoints.json +188 -0
  42. package/dist/repertoire/tools-base.js +69 -5
  43. package/dist/repertoire/tools-teams.js +57 -4
  44. package/dist/repertoire/tools.js +44 -11
  45. package/dist/senses/bluebubbles-client.js +433 -0
  46. package/dist/senses/bluebubbles-entry.js +11 -0
  47. package/dist/senses/bluebubbles-media.js +244 -0
  48. package/dist/senses/bluebubbles-model.js +253 -0
  49. package/dist/senses/bluebubbles-mutation-log.js +76 -0
  50. package/dist/senses/bluebubbles.js +421 -0
  51. package/dist/senses/cli.js +293 -133
  52. package/dist/senses/debug-activity.js +107 -0
  53. package/dist/senses/teams.js +173 -54
  54. package/package.json +11 -4
  55. package/subagents/work-doer.md +26 -24
  56. package/subagents/work-merger.md +24 -30
  57. package/subagents/work-planner.md +34 -25
  58. package/dist/inner-worker-entry.js +0 -4
@@ -63,6 +63,8 @@ function isPidAlive(pid) {
63
63
  function cloneSession(session) {
64
64
  return {
65
65
  ...session,
66
+ stdoutTail: session.stdoutTail,
67
+ stderrTail: session.stderrTail,
66
68
  failure: session.failure
67
69
  ? {
68
70
  ...session.failure,
@@ -115,6 +117,7 @@ function defaultFailureDiagnostics(code, signal, command, args, stdoutTail, stde
115
117
  }
116
118
  class CodingSessionManager {
117
119
  records = new Map();
120
+ listeners = new Map();
118
121
  spawnProcess;
119
122
  nowIso;
120
123
  maxRestarts;
@@ -158,6 +161,8 @@ class CodingSessionManager {
158
161
  scopeFile: normalizedRequest.scopeFile,
159
162
  stateFile: normalizedRequest.stateFile,
160
163
  status: "spawning",
164
+ stdoutTail: "",
165
+ stderrTail: "",
161
166
  pid: null,
162
167
  startedAt: now,
163
168
  lastActivityAt: now,
@@ -188,6 +193,7 @@ class CodingSessionManager {
188
193
  meta: { id, runner: normalizedRequest.runner, pid: session.pid },
189
194
  });
190
195
  this.persistState();
196
+ this.notifyListeners(id, { kind: "spawned", session: cloneSession(session) });
191
197
  return cloneSession(session);
192
198
  }
193
199
  listSessions() {
@@ -199,6 +205,20 @@ class CodingSessionManager {
199
205
  const record = this.records.get(sessionId);
200
206
  return record ? cloneSession(record.session) : null;
201
207
  }
208
+ subscribe(sessionId, listener) {
209
+ const listeners = this.listeners.get(sessionId) ?? new Set();
210
+ listeners.add(listener);
211
+ this.listeners.set(sessionId, listeners);
212
+ return () => {
213
+ const current = this.listeners.get(sessionId);
214
+ if (!current)
215
+ return;
216
+ current.delete(listener);
217
+ if (current.size === 0) {
218
+ this.listeners.delete(sessionId);
219
+ }
220
+ };
221
+ }
202
222
  sendInput(sessionId, input) {
203
223
  const record = this.records.get(sessionId);
204
224
  if (!record || !record.process) {
@@ -234,6 +254,7 @@ class CodingSessionManager {
234
254
  meta: { id: sessionId },
235
255
  });
236
256
  this.persistState();
257
+ this.notifyListeners(sessionId, { kind: "killed", session: cloneSession(record.session) });
237
258
  return { ok: true, message: `killed ${sessionId}` };
238
259
  }
239
260
  checkStalls(nowMs = Date.now()) {
@@ -254,6 +275,7 @@ class CodingSessionManager {
254
275
  message: "coding session stalled",
255
276
  meta: { id: record.session.id, elapsedMs: elapsed },
256
277
  });
278
+ this.notifyListeners(record.session.id, { kind: "stalled", session: cloneSession(record.session) });
257
279
  if (record.request.autoRestartOnStall !== false && record.session.restartCount < this.maxRestarts) {
258
280
  this.restartSession(record, "stalled");
259
281
  }
@@ -297,18 +319,23 @@ class CodingSessionManager {
297
319
  }
298
320
  onOutput(record, text, stream) {
299
321
  record.session.lastActivityAt = this.nowIso();
322
+ let updateKind = "progress";
300
323
  if (stream === "stdout") {
301
324
  record.stdoutTail = appendTail(record.stdoutTail, text);
325
+ record.session.stdoutTail = record.stdoutTail;
302
326
  }
303
327
  else {
304
328
  record.stderrTail = appendTail(record.stderrTail, text);
329
+ record.session.stderrTail = record.stderrTail;
305
330
  }
306
331
  if (text.includes("status: NEEDS_REVIEW") || text.includes("❌ blocked")) {
307
332
  record.session.status = "waiting_input";
333
+ updateKind = "waiting_input";
308
334
  }
309
335
  if (text.includes("✅ all units complete")) {
310
336
  record.session.status = "completed";
311
337
  record.session.endedAt = this.nowIso();
338
+ updateKind = "completed";
312
339
  }
313
340
  (0, runtime_1.emitNervesEvent)({
314
341
  component: "repertoire",
@@ -317,6 +344,12 @@ class CodingSessionManager {
317
344
  meta: { id: record.session.id, status: record.session.status },
318
345
  });
319
346
  this.persistState();
347
+ this.notifyListeners(record.session.id, {
348
+ kind: updateKind,
349
+ session: cloneSession(record.session),
350
+ stream,
351
+ text,
352
+ });
320
353
  }
321
354
  onExit(record, code, signal) {
322
355
  if (!record.process)
@@ -334,6 +367,7 @@ class CodingSessionManager {
334
367
  record.session.status = "completed";
335
368
  record.session.endedAt = this.nowIso();
336
369
  this.persistState();
370
+ this.notifyListeners(record.session.id, { kind: "completed", session: cloneSession(record.session) });
337
371
  return;
338
372
  }
339
373
  if (record.request.autoRestartOnCrash !== false && record.session.restartCount < this.maxRestarts) {
@@ -351,6 +385,7 @@ class CodingSessionManager {
351
385
  meta: { id: record.session.id, code, signal, command: record.command },
352
386
  });
353
387
  this.persistState();
388
+ this.notifyListeners(record.session.id, { kind: "failed", session: cloneSession(record.session) });
354
389
  }
355
390
  restartSession(record, reason) {
356
391
  const replacement = normalizeSpawnResult(this.spawnProcess(record.request));
@@ -359,6 +394,8 @@ class CodingSessionManager {
359
394
  record.args = [...replacement.args];
360
395
  record.stdoutTail = "";
361
396
  record.stderrTail = "";
397
+ record.session.stdoutTail = "";
398
+ record.session.stderrTail = "";
362
399
  record.session.pid = replacement.process.pid ?? null;
363
400
  record.session.restartCount += 1;
364
401
  record.session.status = "running";
@@ -375,6 +412,26 @@ class CodingSessionManager {
375
412
  });
376
413
  this.persistState();
377
414
  }
415
+ notifyListeners(sessionId, update) {
416
+ const listeners = this.listeners.get(sessionId);
417
+ if (!listeners || listeners.size === 0)
418
+ return;
419
+ for (const listener of listeners) {
420
+ void Promise.resolve(listener(update)).catch((error) => {
421
+ (0, runtime_1.emitNervesEvent)({
422
+ level: "warn",
423
+ component: "repertoire",
424
+ event: "repertoire.coding_feedback_listener_error",
425
+ message: "coding session listener failed",
426
+ meta: {
427
+ sessionId,
428
+ kind: update.kind,
429
+ reason: error instanceof Error ? error.message : String(error),
430
+ },
431
+ });
432
+ });
433
+ }
434
+ }
378
435
  loadPersistedState() {
379
436
  if (!this.existsSync(this.stateFilePath)) {
380
437
  return;
@@ -433,6 +490,8 @@ class CodingSessionManager {
433
490
  ...session,
434
491
  taskRef: session.taskRef ?? normalizedRequest.taskRef,
435
492
  failure: session.failure ?? null,
493
+ stdoutTail: session.stdoutTail ?? session.failure?.stdoutTail ?? "",
494
+ stderrTail: session.stderrTail ?? session.failure?.stderrTail ?? "",
436
495
  };
437
496
  if (typeof normalizedSession.pid === "number") {
438
497
  const alive = this.pidAlive(normalizedSession.pid);
@@ -451,8 +510,8 @@ class CodingSessionManager {
451
510
  process: null,
452
511
  command: normalizedSession.failure?.command ?? "restored",
453
512
  args: normalizedSession.failure ? [...normalizedSession.failure.args] : [],
454
- stdoutTail: normalizedSession.failure?.stdoutTail ?? "",
455
- stderrTail: normalizedSession.failure?.stderrTail ?? "",
513
+ stdoutTail: normalizedSession.stdoutTail,
514
+ stderrTail: normalizedSession.stderrTail,
456
515
  });
457
516
  this.sequence = Math.max(this.sequence, extractSequence(normalizedSession.id));
458
517
  }
@@ -43,11 +43,11 @@ function buildCommandArgs(runner, workdir) {
43
43
  command: "claude",
44
44
  args: [
45
45
  "-p",
46
+ "--verbose",
47
+ "--no-session-persistence",
46
48
  "--dangerously-skip-permissions",
47
49
  "--add-dir",
48
50
  workdir,
49
- "--input-format",
50
- "stream-json",
51
51
  "--output-format",
52
52
  "stream-json",
53
53
  ],
@@ -91,7 +91,7 @@ function spawnCodingProcess(request, deps = {}) {
91
91
  cwd: request.workdir,
92
92
  stdio: ["pipe", "pipe", "pipe"],
93
93
  });
94
- proc.stdin.write(`${prompt}\n`);
94
+ proc.stdin.end(`${prompt}\n`);
95
95
  (0, runtime_1.emitNervesEvent)({
96
96
  component: "repertoire",
97
97
  event: "repertoire.coding_spawn_end",
@@ -61,6 +61,20 @@ const codingStatusTool = {
61
61
  },
62
62
  },
63
63
  };
64
+ const codingTailTool = {
65
+ type: "function",
66
+ function: {
67
+ name: "coding_tail",
68
+ description: "show recent stdout/stderr tail for a coding session in a readable format",
69
+ parameters: {
70
+ type: "object",
71
+ properties: {
72
+ sessionId: { type: "string" },
73
+ },
74
+ required: ["sessionId"],
75
+ },
76
+ },
77
+ };
64
78
  const codingSendInputTool = {
65
79
  type: "function",
66
80
  function: {
@@ -93,7 +107,7 @@ const codingKillTool = {
93
107
  exports.codingToolDefinitions = [
94
108
  {
95
109
  tool: codingSpawnTool,
96
- handler: async (args) => {
110
+ handler: async (args, ctx) => {
97
111
  emitCodingToolEvent("coding_spawn");
98
112
  const rawRunner = requireArg(args, "runner");
99
113
  if (!rawRunner)
@@ -122,7 +136,19 @@ exports.codingToolDefinitions = [
122
136
  const stateFile = optionalArg(args, "stateFile");
123
137
  if (stateFile)
124
138
  request.stateFile = stateFile;
125
- const session = await (0, index_1.getCodingSessionManager)().spawnSession(request);
139
+ const manager = (0, index_1.getCodingSessionManager)();
140
+ const session = await manager.spawnSession(request);
141
+ if (args.runner === "codex" && args.taskRef) {
142
+ (0, runtime_1.emitNervesEvent)({
143
+ component: "repertoire",
144
+ event: "repertoire.coding_codex_spawned",
145
+ message: "spawned codex coding session",
146
+ meta: { sessionId: session.id, taskRef: args.taskRef },
147
+ });
148
+ }
149
+ if (ctx?.codingFeedback) {
150
+ (0, index_1.attachCodingSessionFeedback)(manager, session, ctx.codingFeedback);
151
+ }
126
152
  return JSON.stringify(session);
127
153
  },
128
154
  },
@@ -141,6 +167,19 @@ exports.codingToolDefinitions = [
141
167
  return JSON.stringify(session);
142
168
  },
143
169
  },
170
+ {
171
+ tool: codingTailTool,
172
+ handler: (args) => {
173
+ emitCodingToolEvent("coding_tail");
174
+ const sessionId = requireArg(args, "sessionId");
175
+ if (!sessionId)
176
+ return "sessionId is required";
177
+ const session = (0, index_1.getCodingSessionManager)().getSession(sessionId);
178
+ if (!session)
179
+ return `session not found: ${sessionId}`;
180
+ return (0, index_1.formatCodingTail)(session);
181
+ },
182
+ },
144
183
  {
145
184
  tool: codingSendInputTool,
146
185
  handler: (args) => {
@@ -35,6 +35,12 @@
35
35
  "description": "Delete a work item (moves to recycle bin)",
36
36
  "params": "destroy (boolean, permanently delete)"
37
37
  },
38
+ {
39
+ "path": "/{project}/_apis/wit/workitemtypes",
40
+ "method": "GET",
41
+ "description": "List all work item types available in a project (Bug, Task, Epic, User Story, etc.)",
42
+ "params": ""
43
+ },
38
44
  {
39
45
  "path": "/_apis/git/repositories",
40
46
  "method": "GET",
@@ -118,5 +124,187 @@
118
124
  "method": "GET",
119
125
  "description": "List saved work item queries (shared and personal)",
120
126
  "params": "$depth, $expand"
127
+ },
128
+ {
129
+ "path": "/_apis/groupentitlements?api-version=7.1",
130
+ "method": "GET",
131
+ "host": "vsaex.dev.azure.com",
132
+ "description": "List group entitlements (group rules that auto-assign licenses). Use host vsaex.dev.azure.com.",
133
+ "params": ""
134
+ },
135
+ {
136
+ "path": "/_apis/groupentitlements?api-version=7.1",
137
+ "method": "POST",
138
+ "host": "vsaex.dev.azure.com",
139
+ "description": "Create a group entitlement rule — maps an AAD group to an access level (e.g. Basic) and project membership. All members of the AAD group automatically get the specified license. Use host vsaex.dev.azure.com. This is the best way to bulk-provision users.",
140
+ "params": "body: { group: { origin: 'aad', originId: '<AAD-group-object-id>', subjectKind: 'group' }, licenseRule: { licensingSource: 'account', accountLicenseType: 'express', licenseDisplayName: 'Basic' }, projectEntitlements: [{ group: { groupType: 'projectContributor' }, projectRef: { id: '<project-id>' } }] }"
141
+ },
142
+ {
143
+ "path": "/_apis/groupentitlements/{groupId}?api-version=7.1",
144
+ "method": "GET",
145
+ "host": "vsaex.dev.azure.com",
146
+ "description": "Get a specific group entitlement by ID. Use host vsaex.dev.azure.com.",
147
+ "params": ""
148
+ },
149
+ {
150
+ "path": "/_apis/groupentitlements/{groupId}?api-version=7.1",
151
+ "method": "PATCH",
152
+ "host": "vsaex.dev.azure.com",
153
+ "description": "Update a group entitlement (change license rule, project access). Use host vsaex.dev.azure.com.",
154
+ "params": "JSON Patch array: [{op, path, value}]"
155
+ },
156
+ {
157
+ "path": "/_apis/groupentitlements/{groupId}?api-version=7.1",
158
+ "method": "DELETE",
159
+ "host": "vsaex.dev.azure.com",
160
+ "description": "Delete a group entitlement rule. Use host vsaex.dev.azure.com.",
161
+ "params": ""
162
+ },
163
+ {
164
+ "path": "/_apis/memberentitlementmanagement/memberentitlements?api-version=7.1-preview.3",
165
+ "method": "GET",
166
+ "host": "vsapm.dev.azure.com",
167
+ "description": "List individual member entitlements (users and their access levels). Use host vsapm.dev.azure.com. For bulk provisioning, prefer the Group Entitlements API on vsaex.dev.azure.com instead.",
168
+ "params": "$top, $skip, $filter, $orderBy, $select"
169
+ },
170
+ {
171
+ "path": "/_apis/memberentitlementmanagement/memberentitlements?api-version=7.1-preview.3",
172
+ "method": "POST",
173
+ "host": "vsapm.dev.azure.com",
174
+ "description": "Add a single member entitlement. Use host vsapm.dev.azure.com. For bulk provisioning, prefer the Group Entitlements API on vsaex.dev.azure.com instead.",
175
+ "params": "body: { accessLevel: { accountLicenseType: 'express'|'stakeholder', licensingSource: 'account' }, user: { principalName: 'user@domain.com', subjectKind: 'user' }, projectEntitlements: [{ group: { groupType: 'projectContributor' }, projectRef: { id: projectId } }] }"
176
+ },
177
+ {
178
+ "path": "/_apis/memberentitlementmanagement/memberentitlements/{memberId}?api-version=7.1-preview.3",
179
+ "method": "PATCH",
180
+ "host": "vsapm.dev.azure.com",
181
+ "description": "Update a member entitlement (change access level, project access). Use host vsapm.dev.azure.com.",
182
+ "params": "JSON Patch array: [{op, path, value}]"
183
+ },
184
+ {
185
+ "path": "/_apis/memberentitlementmanagement/memberentitlements/{memberId}?api-version=7.1-preview.3",
186
+ "method": "DELETE",
187
+ "host": "vsapm.dev.azure.com",
188
+ "description": "Remove a member entitlement (revoke user access). Use host vsapm.dev.azure.com.",
189
+ "params": ""
190
+ },
191
+ {
192
+ "path": "/_apis/graph/users?api-version=7.1-preview.1",
193
+ "method": "GET",
194
+ "host": "vssps.dev.azure.com",
195
+ "description": "List users in the organization (Graph API). Use host vssps.dev.azure.com. IMPORTANT: include the full path with api-version as shown.",
196
+ "params": "subjectTypes (aad, msa, etc.), continuationToken"
197
+ },
198
+ {
199
+ "path": "/_apis/graph/groups?api-version=7.1-preview.1",
200
+ "method": "GET",
201
+ "host": "vssps.dev.azure.com",
202
+ "description": "List groups in the organization. Use host vssps.dev.azure.com. IMPORTANT: include the full path with api-version as shown.",
203
+ "params": "subjectTypes, continuationToken"
204
+ },
205
+ {
206
+ "path": "/_apis/graph/memberships/{subjectDescriptor}?api-version=7.1-preview.1",
207
+ "method": "GET",
208
+ "host": "vssps.dev.azure.com",
209
+ "description": "List group memberships for a user or group. Use host vssps.dev.azure.com. IMPORTANT: include the full path with api-version as shown.",
210
+ "params": "direction (up = groups user belongs to, down = members of group)"
211
+ },
212
+ {
213
+ "path": "/_apis/graph/memberships/{subjectDescriptor}/{containerDescriptor}?api-version=7.1-preview.1",
214
+ "method": "PUT",
215
+ "host": "vssps.dev.azure.com",
216
+ "description": "Add a user to a group. Use host vssps.dev.azure.com. IMPORTANT: include the full path with api-version as shown.",
217
+ "params": ""
218
+ },
219
+ {
220
+ "path": "/_apis/graph/memberships/{subjectDescriptor}/{containerDescriptor}?api-version=7.1-preview.1",
221
+ "method": "DELETE",
222
+ "host": "vssps.dev.azure.com",
223
+ "description": "Remove a user from a group. Use host vssps.dev.azure.com. IMPORTANT: include the full path with api-version as shown.",
224
+ "params": ""
225
+ },
226
+ {
227
+ "path": "/_apis/projects/{projectId}/teams",
228
+ "method": "GET",
229
+ "description": "List teams in a project",
230
+ "params": "$top, $skip"
231
+ },
232
+ {
233
+ "path": "/_apis/projects/{projectId}/teams/{teamId}",
234
+ "method": "GET",
235
+ "description": "Get a specific team by ID",
236
+ "params": ""
237
+ },
238
+ {
239
+ "path": "/_apis/projects/{projectId}/teams",
240
+ "method": "POST",
241
+ "description": "Create a new team in a project",
242
+ "params": "name, description"
243
+ },
244
+ {
245
+ "path": "/_apis/projects/{projectId}/teams/{teamId}/members",
246
+ "method": "GET",
247
+ "description": "List members of a team",
248
+ "params": "$top, $skip"
249
+ },
250
+ {
251
+ "path": "/{project}/{team}/_apis/work/teamsettings/iterations",
252
+ "method": "GET",
253
+ "description": "List iterations (sprints) for a team",
254
+ "params": "$timeframe (current, past, future)"
255
+ },
256
+ {
257
+ "path": "/{project}/{team}/_apis/work/teamsettings/iterations",
258
+ "method": "POST",
259
+ "description": "Add an iteration to a team's sprint schedule",
260
+ "params": "id (iteration node ID)"
261
+ },
262
+ {
263
+ "path": "/{project}/{team}/_apis/work/teamsettings/iterations/{iterationId}",
264
+ "method": "DELETE",
265
+ "description": "Remove an iteration from a team's sprint schedule",
266
+ "params": ""
267
+ },
268
+ {
269
+ "path": "/{project}/_apis/wit/classificationnodes/iterations",
270
+ "method": "GET",
271
+ "description": "List iteration path tree (project-level iteration nodes)",
272
+ "params": "$depth"
273
+ },
274
+ {
275
+ "path": "/{project}/_apis/wit/classificationnodes/iterations",
276
+ "method": "POST",
277
+ "description": "Create a new iteration node (sprint)",
278
+ "params": "name, attributes: { startDate, finishDate }"
279
+ },
280
+ {
281
+ "path": "/{project}/_apis/wit/classificationnodes/areas",
282
+ "method": "GET",
283
+ "description": "List area path tree (project-level area nodes)",
284
+ "params": "$depth"
285
+ },
286
+ {
287
+ "path": "/{project}/_apis/wit/classificationnodes/areas",
288
+ "method": "POST",
289
+ "description": "Create a new area path node",
290
+ "params": "name"
291
+ },
292
+ {
293
+ "path": "/{project}/_apis/wit/classificationnodes/{structureGroup}/{path}",
294
+ "method": "DELETE",
295
+ "description": "Delete a classification node (area or iteration). structureGroup is 'areas' or 'iterations'.",
296
+ "params": "$reclassifyId (move items to this node before deleting)"
297
+ },
298
+ {
299
+ "path": "/_apis/hooks/subscriptions",
300
+ "method": "GET",
301
+ "description": "List service hook subscriptions (webhooks for events)",
302
+ "params": ""
303
+ },
304
+ {
305
+ "path": "/_apis/hooks/subscriptions",
306
+ "method": "POST",
307
+ "description": "Create a service hook subscription (webhook)",
308
+ "params": "publisherId, eventType, consumerId, consumerActionId, publisherInputs, consumerInputs"
121
309
  }
122
310
  ]
@@ -46,6 +46,25 @@ const tasks_1 = require("./tasks");
46
46
  const tools_1 = require("./coding/tools");
47
47
  const memory_1 = require("../mind/memory");
48
48
  const postIt = (msg) => `post-it from past you:\n${msg}`;
49
+ function normalizeOptionalText(value) {
50
+ if (typeof value !== "string")
51
+ return null;
52
+ const trimmed = value.trim();
53
+ return trimmed.length > 0 ? trimmed : null;
54
+ }
55
+ function buildTaskCreateInput(args) {
56
+ return {
57
+ title: args.title,
58
+ type: args.type,
59
+ category: args.category,
60
+ body: args.body,
61
+ status: normalizeOptionalText(args.status) ?? undefined,
62
+ validator: normalizeOptionalText(args.validator),
63
+ requester: normalizeOptionalText(args.requester),
64
+ cadence: normalizeOptionalText(args.cadence),
65
+ scheduledAt: normalizeOptionalText(args.scheduledAt),
66
+ };
67
+ }
49
68
  exports.baseToolDefinitions = [
50
69
  {
51
70
  tool: {
@@ -75,7 +94,11 @@ exports.baseToolDefinitions = [
75
94
  },
76
95
  },
77
96
  },
78
- handler: (a) => (fs.writeFileSync(a.path, a.content, "utf-8"), "ok"),
97
+ handler: (a) => {
98
+ fs.mkdirSync(path.dirname(a.path), { recursive: true });
99
+ fs.writeFileSync(a.path, a.content, "utf-8");
100
+ return "ok";
101
+ },
79
102
  },
80
103
  {
81
104
  tool: {
@@ -235,7 +258,7 @@ exports.baseToolDefinitions = [
235
258
  },
236
259
  handler: (a) => {
237
260
  try {
238
- const result = (0, child_process_1.spawnSync)("claude", ["-p", "--dangerously-skip-permissions", "--add-dir", "."], {
261
+ const result = (0, child_process_1.spawnSync)("claude", ["-p", "--no-session-persistence", "--dangerously-skip-permissions", "--add-dir", "."], {
239
262
  input: a.prompt,
240
263
  encoding: "utf-8",
241
264
  timeout: 60000,
@@ -394,7 +417,7 @@ exports.baseToolDefinitions = [
394
417
  type: "function",
395
418
  function: {
396
419
  name: "task_create",
397
- description: "create a new task in the bundle task system",
420
+ description: "create a new task in the bundle task system. optionally set `scheduledAt` for a one-time reminder or `cadence` for recurring daemon-scheduled work.",
398
421
  parameters: {
399
422
  type: "object",
400
423
  properties: {
@@ -402,18 +425,59 @@ exports.baseToolDefinitions = [
402
425
  type: { type: "string", enum: ["one-shot", "ongoing", "habit"] },
403
426
  category: { type: "string" },
404
427
  body: { type: "string" },
428
+ status: { type: "string" },
429
+ validator: { type: "string" },
430
+ requester: { type: "string" },
431
+ scheduledAt: { type: "string", description: "ISO timestamp for a one-time scheduled run/reminder" },
432
+ cadence: { type: "string", description: "recurrence like 30m, 1h, 1d, or cron" },
405
433
  },
406
434
  required: ["title", "type", "category", "body"],
407
435
  },
408
436
  },
409
437
  },
410
438
  handler: (a) => {
439
+ try {
440
+ const created = (0, tasks_1.getTaskModule)().createTask(buildTaskCreateInput(a));
441
+ return `created: ${created}`;
442
+ }
443
+ catch (error) {
444
+ return `error: ${error instanceof Error ? error.message : String(error)}`;
445
+ }
446
+ },
447
+ },
448
+ {
449
+ tool: {
450
+ type: "function",
451
+ function: {
452
+ name: "schedule_reminder",
453
+ description: "create a scheduled reminder or recurring daemon job. use `scheduledAt` for one-time reminders and `cadence` for recurring reminders. this writes canonical task fields that the daemon reconciles into OS-level jobs.",
454
+ parameters: {
455
+ type: "object",
456
+ properties: {
457
+ title: { type: "string" },
458
+ body: { type: "string" },
459
+ category: { type: "string" },
460
+ scheduledAt: { type: "string", description: "ISO timestamp for a one-time reminder" },
461
+ cadence: { type: "string", description: "recurrence like 30m, 1h, 1d, or cron" },
462
+ },
463
+ required: ["title", "body"],
464
+ },
465
+ },
466
+ },
467
+ handler: (a) => {
468
+ const scheduledAt = normalizeOptionalText(a.scheduledAt);
469
+ const cadence = normalizeOptionalText(a.cadence);
470
+ if (!scheduledAt && !cadence) {
471
+ return "error: provide scheduledAt or cadence";
472
+ }
411
473
  try {
412
474
  const created = (0, tasks_1.getTaskModule)().createTask({
413
475
  title: a.title,
414
- type: a.type,
415
- category: a.category,
476
+ type: cadence ? "habit" : "one-shot",
477
+ category: normalizeOptionalText(a.category) ?? "reminder",
416
478
  body: a.body,
479
+ scheduledAt,
480
+ cadence,
417
481
  });
418
482
  return `created: ${created}`;
419
483
  }