@ouro.bot/cli 0.1.0-alpha.2 → 0.1.0-alpha.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.
- package/AdoptionSpecialist.ouro/agent.json +70 -9
- package/AdoptionSpecialist.ouro/psyche/SOUL.md +5 -2
- package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
- package/assets/ouroboros.png +0 -0
- package/dist/heart/config.js +66 -4
- package/dist/heart/core.js +75 -2
- package/dist/heart/daemon/daemon-cli.js +507 -29
- package/dist/heart/daemon/daemon-entry.js +13 -5
- package/dist/heart/daemon/daemon.js +42 -9
- package/dist/heart/daemon/hatch-animation.js +35 -0
- package/dist/heart/daemon/hatch-flow.js +2 -11
- package/dist/heart/daemon/hatch-specialist.js +6 -1
- package/dist/heart/daemon/ouro-bot-wrapper.js +4 -3
- package/dist/heart/daemon/ouro-path-installer.js +177 -0
- package/dist/heart/daemon/ouro-uti.js +11 -2
- package/dist/heart/daemon/process-manager.js +1 -1
- package/dist/heart/daemon/runtime-logging.js +9 -5
- package/dist/heart/daemon/runtime-metadata.js +118 -0
- package/dist/heart/daemon/sense-manager.js +266 -0
- package/dist/heart/daemon/specialist-orchestrator.js +129 -0
- package/dist/heart/daemon/specialist-prompt.js +98 -0
- package/dist/heart/daemon/specialist-tools.js +237 -0
- package/dist/heart/daemon/subagent-installer.js +10 -1
- package/dist/heart/identity.js +77 -1
- package/dist/heart/providers/anthropic.js +19 -2
- package/dist/heart/sense-truth.js +61 -0
- package/dist/heart/streaming.js +99 -21
- package/dist/mind/bundle-manifest.js +58 -0
- package/dist/mind/friends/channel.js +8 -0
- package/dist/mind/friends/types.js +1 -1
- package/dist/mind/prompt.js +77 -3
- package/dist/nerves/cli-logging.js +15 -2
- package/dist/repertoire/ado-client.js +4 -2
- package/dist/repertoire/coding/feedback.js +134 -0
- package/dist/repertoire/coding/index.js +4 -1
- package/dist/repertoire/coding/manager.js +61 -2
- package/dist/repertoire/coding/spawner.js +3 -3
- package/dist/repertoire/coding/tools.js +41 -2
- package/dist/repertoire/data/ado-endpoints.json +188 -0
- package/dist/repertoire/tools-base.js +69 -5
- package/dist/repertoire/tools-teams.js +57 -4
- package/dist/repertoire/tools.js +44 -11
- package/dist/senses/bluebubbles-client.js +433 -0
- package/dist/senses/bluebubbles-entry.js +11 -0
- package/dist/senses/bluebubbles-media.js +244 -0
- package/dist/senses/bluebubbles-model.js +253 -0
- package/dist/senses/bluebubbles-mutation-log.js +76 -0
- package/dist/senses/bluebubbles.js +421 -0
- package/dist/senses/cli.js +293 -133
- package/dist/senses/debug-activity.js +107 -0
- package/dist/senses/teams.js +173 -54
- package/package.json +11 -4
- package/subagents/work-doer.md +26 -24
- package/subagents/work-merger.md +24 -30
- package/subagents/work-planner.md +34 -25
- 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.
|
|
455
|
-
stderrTail: normalizedSession.
|
|
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.
|
|
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
|
|
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) =>
|
|
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:
|
|
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
|
}
|