@markus-global/cli 0.6.2 → 0.6.4
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/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +18 -1
- package/dist/commands/start.js.map +1 -1
- package/dist/markus.mjs +539 -93
- package/dist/web-ui/assets/index-5QUyR8je.js +351 -0
- package/dist/web-ui/assets/index-B9eoccwC.css +1 -0
- package/dist/web-ui/index.html +2 -2
- package/dist/web-ui/logo.png +0 -0
- package/package.json +1 -1
- package/templates/roles/SHARED.md +6 -15
- package/templates/roles/developer/POLICIES.md +1 -1
- package/templates/skills/markus-admin-cli/SKILL.md +9 -1
- package/templates/teams/content-team/team.json +1 -1
- package/templates/teams/dev-squad/team.json +1 -1
- package/templates/teams/engineering-pod/team.json +1 -1
- package/templates/teams/research-lab/team.json +1 -1
- package/templates/teams/startup-team/team.json +1 -1
- package/dist/web-ui/assets/index-Q4_kHftV.css +0 -1
- package/dist/web-ui/assets/index-sJUaZxib.js +0 -351
- package/templates/skills/markus-agent-cli/SKILL.md +0 -69
- package/templates/skills/markus-agent-cli/skill.json +0 -21
- package/templates/skills/markus-cli/SKILL.md +0 -113
- package/templates/skills/markus-cli/skill.json +0 -20
- package/templates/skills/markus-project-cli/SKILL.md +0 -163
- package/templates/skills/markus-project-cli/skill.json +0 -21
- package/templates/skills/markus-skill-cli/SKILL.md +0 -59
- package/templates/skills/markus-skill-cli/skill.json +0 -21
- package/templates/skills/markus-team-cli/SKILL.md +0 -54
- package/templates/skills/markus-team-cli/skill.json +0 -21
package/dist/markus.mjs
CHANGED
|
@@ -5184,12 +5184,45 @@ ${truncatedContent}
|
|
|
5184
5184
|
`;
|
|
5185
5185
|
}
|
|
5186
5186
|
if (updated.length > MEMORY_MD_TOTAL_MAX_CHARS) {
|
|
5187
|
-
log5.warn("MEMORY.md total size exceeds limit,
|
|
5187
|
+
log5.warn("MEMORY.md total size exceeds limit, attempting compression", {
|
|
5188
5188
|
key: key2,
|
|
5189
5189
|
fileSize: updated.length,
|
|
5190
5190
|
limit: MEMORY_MD_TOTAL_MAX_CHARS
|
|
5191
5191
|
});
|
|
5192
|
-
|
|
5192
|
+
const compressed = this.compressLongTermMemory();
|
|
5193
|
+
if (compressed.charsAfter < updated.length) {
|
|
5194
|
+
log5.info("Compression freed space, retrying write", {
|
|
5195
|
+
key: key2,
|
|
5196
|
+
charsFreed: updated.length - compressed.charsAfter
|
|
5197
|
+
});
|
|
5198
|
+
existing = readFileSync4(this.longTermFile, "utf-8");
|
|
5199
|
+
if (existing.includes(sectionHeader)) {
|
|
5200
|
+
const regex = new RegExp(`(## ${key2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})\\n[\\s\\S]*?(?=\\n## |$)`);
|
|
5201
|
+
updated = existing.replace(regex, `${sectionHeader}
|
|
5202
|
+
${truncatedContent}
|
|
5203
|
+
`);
|
|
5204
|
+
} else {
|
|
5205
|
+
updated = existing + `
|
|
5206
|
+
${sectionHeader}
|
|
5207
|
+
${truncatedContent}
|
|
5208
|
+
`;
|
|
5209
|
+
}
|
|
5210
|
+
if (updated.length > MEMORY_MD_TOTAL_MAX_CHARS) {
|
|
5211
|
+
log5.warn("MEMORY.md still exceeds limit even after compression, refusing write", {
|
|
5212
|
+
key: key2,
|
|
5213
|
+
fileSize: updated.length,
|
|
5214
|
+
limit: MEMORY_MD_TOTAL_MAX_CHARS
|
|
5215
|
+
});
|
|
5216
|
+
return;
|
|
5217
|
+
}
|
|
5218
|
+
} else {
|
|
5219
|
+
log5.warn("MEMORY.md still exceeds limit after compression, refusing write", {
|
|
5220
|
+
key: key2,
|
|
5221
|
+
fileSize: updated.length,
|
|
5222
|
+
limit: MEMORY_MD_TOTAL_MAX_CHARS
|
|
5223
|
+
});
|
|
5224
|
+
return;
|
|
5225
|
+
}
|
|
5193
5226
|
}
|
|
5194
5227
|
writeFileSync3(this.longTermFile, updated);
|
|
5195
5228
|
log5.debug("Long-term memory updated", { key: key2, sectionChars: truncatedContent.length, totalChars: updated.length });
|
|
@@ -5365,6 +5398,62 @@ ${tail}`;
|
|
|
5365
5398
|
this.saveDebounce = null;
|
|
5366
5399
|
}, 1e3);
|
|
5367
5400
|
}
|
|
5401
|
+
/** Compress MEMORY.md — truncate oversized sections to prevent context bloat */
|
|
5402
|
+
compressLongTermMemory() {
|
|
5403
|
+
if (!existsSync6(this.longTermFile)) {
|
|
5404
|
+
return { charsBefore: 0, charsAfter: 0, sectionsBefore: 0, sectionsAfter: 0, truncatedChunks: 0 };
|
|
5405
|
+
}
|
|
5406
|
+
const content = readFileSync4(this.longTermFile, "utf-8");
|
|
5407
|
+
const charsBefore = content.length;
|
|
5408
|
+
const lines = content.split("\n");
|
|
5409
|
+
let i = 0;
|
|
5410
|
+
const preambleLines = [];
|
|
5411
|
+
while (i < lines.length && !lines[i].startsWith("## ")) {
|
|
5412
|
+
preambleLines.push(lines[i]);
|
|
5413
|
+
i++;
|
|
5414
|
+
}
|
|
5415
|
+
const sections = [];
|
|
5416
|
+
let currentHeader = "";
|
|
5417
|
+
let currentBody = [];
|
|
5418
|
+
while (i < lines.length) {
|
|
5419
|
+
const line = lines[i];
|
|
5420
|
+
if (line.startsWith("## ")) {
|
|
5421
|
+
if (currentHeader) {
|
|
5422
|
+
sections.push({ headerLine: currentHeader, body: currentBody });
|
|
5423
|
+
}
|
|
5424
|
+
currentHeader = line;
|
|
5425
|
+
currentBody = [];
|
|
5426
|
+
} else {
|
|
5427
|
+
currentBody.push(line);
|
|
5428
|
+
}
|
|
5429
|
+
i++;
|
|
5430
|
+
}
|
|
5431
|
+
if (currentHeader) {
|
|
5432
|
+
sections.push({ headerLine: currentHeader, body: currentBody });
|
|
5433
|
+
}
|
|
5434
|
+
const sectionsBefore = sections.length;
|
|
5435
|
+
let truncatedChunks = 0;
|
|
5436
|
+
const outputLines = [...preambleLines];
|
|
5437
|
+
for (const section4 of sections) {
|
|
5438
|
+
const bodyStr = section4.body.join("\n");
|
|
5439
|
+
if (bodyStr.length > MEMORY_MD_SECTION_MAX_CHARS) {
|
|
5440
|
+
const truncatedBody = bodyStr.slice(0, MEMORY_MD_SECTION_MAX_CHARS);
|
|
5441
|
+
outputLines.push(section4.headerLine, truncatedBody);
|
|
5442
|
+
truncatedChunks++;
|
|
5443
|
+
} else {
|
|
5444
|
+
outputLines.push(section4.headerLine, bodyStr);
|
|
5445
|
+
}
|
|
5446
|
+
}
|
|
5447
|
+
const compressed = outputLines.join("\n");
|
|
5448
|
+
writeFileSync3(this.longTermFile, compressed);
|
|
5449
|
+
return {
|
|
5450
|
+
charsBefore,
|
|
5451
|
+
charsAfter: compressed.length,
|
|
5452
|
+
sectionsBefore,
|
|
5453
|
+
sectionsAfter: sections.length,
|
|
5454
|
+
truncatedChunks
|
|
5455
|
+
};
|
|
5456
|
+
}
|
|
5368
5457
|
};
|
|
5369
5458
|
}
|
|
5370
5459
|
});
|
|
@@ -6455,10 +6544,11 @@ var init_context_engine = __esm({
|
|
|
6455
6544
|
if (opts.assignedTasks && opts.assignedTasks.length > 0) {
|
|
6456
6545
|
const priorityOrder = ["critical", "high", "medium", "low"];
|
|
6457
6546
|
const byPriority = (a, b) => priorityOrder.indexOf(a.priority ?? "medium") - priorityOrder.indexOf(b.priority ?? "medium");
|
|
6547
|
+
const CLOSED_STATUSES = /* @__PURE__ */ new Set(["completed", "cancelled", "failed", "archived", "rejected"]);
|
|
6458
6548
|
const myTasks = opts.assignedTasks.filter((t) => t.assignedAgentId === opts.agentId);
|
|
6459
6549
|
const otherTasks = opts.assignedTasks.filter((t) => t.assignedAgentId !== opts.agentId);
|
|
6460
|
-
const myActive = myTasks.filter((t) => !
|
|
6461
|
-
const myDone = myTasks.filter((t) =>
|
|
6550
|
+
const myActive = myTasks.filter((t) => !CLOSED_STATUSES.has(t.status)).sort(byPriority);
|
|
6551
|
+
const myDone = myTasks.filter((t) => CLOSED_STATUSES.has(t.status));
|
|
6462
6552
|
const MY_TASK_LIMIT = SYSTEM_MY_TASKS_MAX;
|
|
6463
6553
|
const TEAM_TASK_LIMIT = SYSTEM_TEAM_TASKS_MAX;
|
|
6464
6554
|
parts.push("\n## Task Board");
|
|
@@ -6480,8 +6570,8 @@ var init_context_engine = __esm({
|
|
|
6480
6570
|
parts.push(`_(${myDone.length} completed/closed tasks)_`);
|
|
6481
6571
|
}
|
|
6482
6572
|
if (otherTasks.length > 0) {
|
|
6483
|
-
const otherActive = otherTasks.filter((t) => !
|
|
6484
|
-
const otherDone = otherTasks.filter((t) =>
|
|
6573
|
+
const otherActive = otherTasks.filter((t) => !CLOSED_STATUSES.has(t.status)).sort(byPriority);
|
|
6574
|
+
const otherDone = otherTasks.filter((t) => CLOSED_STATUSES.has(t.status));
|
|
6485
6575
|
if (otherActive.length > 0) {
|
|
6486
6576
|
parts.push("### Team Tasks (assigned to others):");
|
|
6487
6577
|
const shown = otherActive.slice(0, TEAM_TASK_LIMIT);
|
|
@@ -6506,7 +6596,7 @@ var init_context_engine = __esm({
|
|
|
6506
6596
|
parts.push("");
|
|
6507
6597
|
parts.push("**Requirements** (governance gate):");
|
|
6508
6598
|
parts.push("- `requirement_propose` \u2192 pending human approval \u2192 approved \u2192 link tasks via `requirement_id`");
|
|
6509
|
-
parts.push("-
|
|
6599
|
+
parts.push("- Every task MUST reference an approved `requirement_id`. Use `requirement_propose` first if no requirement exists.");
|
|
6510
6600
|
parts.push("");
|
|
6511
6601
|
parts.push("**Task lifecycle** \u2014 Create \u2192 Execute \u2192 Review \u2192 Complete:");
|
|
6512
6602
|
parts.push('- **Create**: `task_create` (REQUIRED: `assigned_agent_id`, `reviewer_id`; optional `reviewer_type`: "agent"|"human"). Check `task_list` first to avoid duplicates.');
|
|
@@ -6532,7 +6622,8 @@ var init_context_engine = __esm({
|
|
|
6532
6622
|
parts.push("- `recall_activity` \u2014 query your own past execution logs by task or activity type. Use when you need to review what you did previously (e.g., to answer a follow-up question).");
|
|
6533
6623
|
parts.push("");
|
|
6534
6624
|
parts.push("**Communicating with other agents**:");
|
|
6535
|
-
parts.push("- `agent_send_message` \u2014 send a direct message to a peer agent.
|
|
6625
|
+
parts.push("- `agent_send_message` \u2014 send a direct message to a peer agent. **By default this is asynchronous (fire-and-forget)**: the message enters their mailbox and you continue working without waiting. Set `wait_for_reply: true` only when you need the answer before you can proceed (rare \u2014 prefer async).");
|
|
6626
|
+
parts.push("- A2A messaging is inherently **non-blocking**. You send a message, the recipient processes it on their own schedule, and may reply later via their own `agent_send_message`. Do NOT spin-wait or poll for responses.");
|
|
6536
6627
|
parts.push("- For substantial work requests, create a `task_create` assigned to the target agent instead of asking via message.");
|
|
6537
6628
|
parts.push("- Do NOT use A2A messages for routine task status notifications \u2014 the system handles those automatically.");
|
|
6538
6629
|
}
|
|
@@ -43522,6 +43613,15 @@ var init_attention = __esm({
|
|
|
43522
43613
|
clearPreemptionSignal() {
|
|
43523
43614
|
this.criticalInterruptResolve = void 0;
|
|
43524
43615
|
}
|
|
43616
|
+
/**
|
|
43617
|
+
* Clear the lastYieldDecision flag set by checkYieldPoint().
|
|
43618
|
+
* Used by processing code that handles preemption internally (e.g. task
|
|
43619
|
+
* execution delegates resumption to TaskService) so the attention loop
|
|
43620
|
+
* completes the item normally instead of deferring it.
|
|
43621
|
+
*/
|
|
43622
|
+
clearLastYieldDecision() {
|
|
43623
|
+
this.lastYieldDecision = void 0;
|
|
43624
|
+
}
|
|
43525
43625
|
/**
|
|
43526
43626
|
* Called from the Agent's tool loop at safe yield points (between LLM turns).
|
|
43527
43627
|
* If an interrupt signal is pending, evaluates whether to continue or switch.
|
|
@@ -45629,11 +45729,19 @@ ${notification.stdoutTail}`);
|
|
|
45629
45729
|
}
|
|
45630
45730
|
case "task_status_update": {
|
|
45631
45731
|
if (extra.triggerExecution && item.payload.taskId) {
|
|
45732
|
+
if (typeof extra.onLog !== "function") {
|
|
45733
|
+
log17.info("Skipping resurfaced task execution item (closures lost)", {
|
|
45734
|
+
agentId: this.id,
|
|
45735
|
+
taskId: item.payload.taskId
|
|
45736
|
+
});
|
|
45737
|
+
resolveResponse("");
|
|
45738
|
+
return;
|
|
45739
|
+
}
|
|
45632
45740
|
const taskId2 = item.payload.taskId;
|
|
45633
45741
|
const description = item.payload.content;
|
|
45634
|
-
const onLog = extra.onLog
|
|
45635
|
-
});
|
|
45742
|
+
const onLog = extra.onLog;
|
|
45636
45743
|
await this.executeTask(taskId2, description, onLog, extra.cancelToken, extra.taskProjectContext, extra.executionRound, item.payload.requirementId);
|
|
45744
|
+
this.attentionController.clearLastYieldDecision();
|
|
45637
45745
|
resolveResponse("");
|
|
45638
45746
|
return;
|
|
45639
45747
|
}
|
|
@@ -47127,6 +47235,12 @@ ${chatYield.item.payload.content}`;
|
|
|
47127
47235
|
}
|
|
47128
47236
|
}
|
|
47129
47237
|
async handleMessageStream(userMessage, onEvent, senderId, senderInfo, cancelToken, images, fileNames) {
|
|
47238
|
+
if (cancelToken?.cancelled) {
|
|
47239
|
+
log17.info("Stream cancelled before processing started", { agentId: this.id });
|
|
47240
|
+
if (this.activeTasks.size === 0)
|
|
47241
|
+
this.setStatus("idle");
|
|
47242
|
+
return "";
|
|
47243
|
+
}
|
|
47130
47244
|
if (this.activeTasks.size === 0) {
|
|
47131
47245
|
this.setStatus("working");
|
|
47132
47246
|
}
|
|
@@ -47248,10 +47362,11 @@ ${chatYield.item.payload.content}`;
|
|
|
47248
47362
|
}
|
|
47249
47363
|
if (cancelToken?.cancelled) {
|
|
47250
47364
|
log17.info("Stream cancelled by user during tool loop", { agentId: this.id });
|
|
47251
|
-
if (
|
|
47365
|
+
if (this.currentSessionId) {
|
|
47366
|
+
const content = lastResponseContent ? lastResponseContent + "\n\n[interrupted by user]" : "[interrupted by user]";
|
|
47252
47367
|
this.memory.appendMessage(this.currentSessionId, {
|
|
47253
47368
|
role: "assistant",
|
|
47254
|
-
content
|
|
47369
|
+
content
|
|
47255
47370
|
});
|
|
47256
47371
|
}
|
|
47257
47372
|
if (streamChatActivityId)
|
|
@@ -47271,6 +47386,19 @@ ${chatYield.item.payload.content}`;
|
|
|
47271
47386
|
content: "[Continue from where you left off. Do not repeat what you already said.]"
|
|
47272
47387
|
});
|
|
47273
47388
|
} else {
|
|
47389
|
+
if (cancelToken?.cancelled) {
|
|
47390
|
+
log17.info("Stream cancelled before tool execution", { agentId: this.id });
|
|
47391
|
+
this.memory.appendMessage(this.currentSessionId, {
|
|
47392
|
+
role: "assistant",
|
|
47393
|
+
content: response.content + "\n\n[interrupted by user]",
|
|
47394
|
+
reasoningContent: response.reasoningContent
|
|
47395
|
+
});
|
|
47396
|
+
if (streamChatActivityId)
|
|
47397
|
+
this.endActivity(streamChatActivityId);
|
|
47398
|
+
if (this.activeTasks.size === 0)
|
|
47399
|
+
this.setStatus("idle");
|
|
47400
|
+
return response.content || lastResponseContent || "";
|
|
47401
|
+
}
|
|
47274
47402
|
this.memory.appendMessage(this.currentSessionId, {
|
|
47275
47403
|
role: "assistant",
|
|
47276
47404
|
content: response.content,
|
|
@@ -47373,10 +47501,11 @@ ${streamYield.item.payload.content}`;
|
|
|
47373
47501
|
const updatedMessages = preparedCont.messages;
|
|
47374
47502
|
if (cancelToken?.cancelled) {
|
|
47375
47503
|
log17.info("Stream cancelled before LLM re-call", { agentId: this.id });
|
|
47376
|
-
if (
|
|
47504
|
+
if (this.currentSessionId) {
|
|
47505
|
+
const content = lastResponseContent ? lastResponseContent + "\n\n[interrupted by user]" : "[interrupted by user]";
|
|
47377
47506
|
this.memory.appendMessage(this.currentSessionId, {
|
|
47378
47507
|
role: "assistant",
|
|
47379
|
-
content
|
|
47508
|
+
content
|
|
47380
47509
|
});
|
|
47381
47510
|
}
|
|
47382
47511
|
if (streamChatActivityId)
|
|
@@ -47433,11 +47562,12 @@ ${streamYield.item.payload.content}`;
|
|
|
47433
47562
|
if (streamChatActivityId)
|
|
47434
47563
|
this.endActivity(streamChatActivityId, { success: !cancelToken?.cancelled });
|
|
47435
47564
|
if (cancelToken?.cancelled) {
|
|
47436
|
-
if (
|
|
47565
|
+
if (this.currentSessionId) {
|
|
47437
47566
|
try {
|
|
47567
|
+
const content = lastResponseContent ? lastResponseContent + "\n\n[interrupted by user]" : "[interrupted by user]";
|
|
47438
47568
|
this.memory.appendMessage(this.currentSessionId, {
|
|
47439
47569
|
role: "assistant",
|
|
47440
|
-
content
|
|
47570
|
+
content
|
|
47441
47571
|
});
|
|
47442
47572
|
} catch {
|
|
47443
47573
|
}
|
|
@@ -48732,6 +48862,19 @@ ${escalationReason}`;
|
|
|
48732
48862
|
}
|
|
48733
48863
|
async handleHeartbeat(ctx) {
|
|
48734
48864
|
log17.info("Processing heartbeat check-in");
|
|
48865
|
+
const queuedNonHeartbeat = this.mailbox.getQueuedItems().filter((i) => i.sourceType !== "heartbeat" && i.status === "queued").length;
|
|
48866
|
+
const fingerprint = `q:${queuedNonHeartbeat}`;
|
|
48867
|
+
if (fingerprint === this.lastHeartbeatFingerprint && queuedNonHeartbeat === 0) {
|
|
48868
|
+
this.consecutiveIdleHeartbeats++;
|
|
48869
|
+
log17.info("Heartbeat: no changes detected, skipping LLM call", {
|
|
48870
|
+
consecutiveIdle: this.consecutiveIdleHeartbeats
|
|
48871
|
+
});
|
|
48872
|
+
this.state.lastHeartbeat = (/* @__PURE__ */ new Date()).toISOString();
|
|
48873
|
+
this.metricsCollector.recordHeartbeat(true);
|
|
48874
|
+
return;
|
|
48875
|
+
}
|
|
48876
|
+
this.lastHeartbeatFingerprint = fingerprint;
|
|
48877
|
+
this.consecutiveIdleHeartbeats = 0;
|
|
48735
48878
|
const activityId = this.startActivity("heartbeat", "Heartbeat check-in", {});
|
|
48736
48879
|
let lastHeartbeatSummary = "";
|
|
48737
48880
|
try {
|
|
@@ -49049,12 +49192,15 @@ ${todayLog.slice(0, HEARTBEAT_DAILY_LOG_CHARS)}
|
|
|
49049
49192
|
* 2. Memory dream: prune, deduplicate, merge (once per day)
|
|
49050
49193
|
*/
|
|
49051
49194
|
lastDreamDate = "";
|
|
49195
|
+
lastHeartbeatFingerprint = "";
|
|
49196
|
+
consecutiveIdleHeartbeats = 0;
|
|
49052
49197
|
async consolidateMemory() {
|
|
49053
49198
|
try {
|
|
49054
49199
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
49055
49200
|
const entries2 = this.memory.getEntries();
|
|
49056
|
-
|
|
49057
|
-
|
|
49201
|
+
const dreamKey = entries2.length > 500 ? `${today}_${Math.floor(Date.now() / (6 * 36e5))}` : today;
|
|
49202
|
+
if (entries2.length >= 50 && this.lastDreamDate !== dreamKey) {
|
|
49203
|
+
this.lastDreamDate = dreamKey;
|
|
49058
49204
|
await this.dreamConsolidateMemory(entries2);
|
|
49059
49205
|
this.pruneMemoryMd();
|
|
49060
49206
|
}
|
|
@@ -49068,7 +49214,7 @@ ${todayLog.slice(0, HEARTBEAT_DAILY_LOG_CHARS)}
|
|
|
49068
49214
|
* outdated items, and merge opportunities. Apply changes programmatically.
|
|
49069
49215
|
*/
|
|
49070
49216
|
async dreamConsolidateMemory(entries2) {
|
|
49071
|
-
const MAX_ENTRIES_FOR_LLM =
|
|
49217
|
+
const MAX_ENTRIES_FOR_LLM = 500;
|
|
49072
49218
|
const truncated = entries2.length > MAX_ENTRIES_FOR_LLM;
|
|
49073
49219
|
const batch = truncated ? entries2.slice(-MAX_ENTRIES_FOR_LLM) : entries2;
|
|
49074
49220
|
const entryList = batch.map((e, i) => {
|
|
@@ -49139,8 +49285,8 @@ ${knowledgePreview}
|
|
|
49139
49285
|
return;
|
|
49140
49286
|
}
|
|
49141
49287
|
const rawPlan = JSON.parse(jsonMatch[0]);
|
|
49142
|
-
const MAX_REMOVE_PER_CYCLE =
|
|
49143
|
-
const MAX_MERGE_PER_CYCLE =
|
|
49288
|
+
const MAX_REMOVE_PER_CYCLE = 50;
|
|
49289
|
+
const MAX_MERGE_PER_CYCLE = 20;
|
|
49144
49290
|
const entryIds = new Set(batch.map((e) => e.id));
|
|
49145
49291
|
const plan = {
|
|
49146
49292
|
remove: (rawPlan.remove ?? []).filter((id) => entryIds.has(id)).slice(0, MAX_REMOVE_PER_CYCLE),
|
|
@@ -49240,7 +49386,8 @@ ${promo.content}` : promo.content;
|
|
|
49240
49386
|
}
|
|
49241
49387
|
/**
|
|
49242
49388
|
* Enforce MEMORY.md hygiene: remove daily-report sections (they belong in
|
|
49243
|
-
* daily-logs/), strip leaked LLM <think> blocks, and enforce section
|
|
49389
|
+
* daily-logs/), strip leaked LLM <think> blocks, and enforce section/total
|
|
49390
|
+
* size limits via heuristic compression.
|
|
49244
49391
|
*/
|
|
49245
49392
|
pruneMemoryMd() {
|
|
49246
49393
|
const content = this.memory.getLongTermMemory();
|
|
@@ -49260,6 +49407,52 @@ ${promo.content}` : promo.content;
|
|
|
49260
49407
|
if (!inDailyReport)
|
|
49261
49408
|
afterSectionPrune.push(line);
|
|
49262
49409
|
}
|
|
49410
|
+
const sections = [];
|
|
49411
|
+
let currentHeading = "";
|
|
49412
|
+
let currentBody = [];
|
|
49413
|
+
let sectionStart = 0;
|
|
49414
|
+
for (let i = 0; i <= afterSectionPrune.length; i++) {
|
|
49415
|
+
const line = i < afterSectionPrune.length ? afterSectionPrune[i] : void 0;
|
|
49416
|
+
const isHeading = line !== void 0 && /^#{1,3}\s/.test(line);
|
|
49417
|
+
if (isHeading || line === void 0) {
|
|
49418
|
+
if (currentHeading || currentBody.length > 0) {
|
|
49419
|
+
sections.push({
|
|
49420
|
+
heading: currentHeading,
|
|
49421
|
+
body: currentBody.join("\n").trim(),
|
|
49422
|
+
startIdx: sectionStart
|
|
49423
|
+
});
|
|
49424
|
+
}
|
|
49425
|
+
currentHeading = line ?? "";
|
|
49426
|
+
currentBody = [];
|
|
49427
|
+
sectionStart = i;
|
|
49428
|
+
} else {
|
|
49429
|
+
currentBody.push(line);
|
|
49430
|
+
}
|
|
49431
|
+
}
|
|
49432
|
+
const headingLastIdx = /* @__PURE__ */ new Map();
|
|
49433
|
+
const normalizeHeading = (h) => h.replace(/^#+\s*/, "").trim().toLowerCase();
|
|
49434
|
+
for (let i = 0; i < sections.length; i++) {
|
|
49435
|
+
const key2 = normalizeHeading(sections[i].heading);
|
|
49436
|
+
if (key2)
|
|
49437
|
+
headingLastIdx.set(key2, i);
|
|
49438
|
+
}
|
|
49439
|
+
const deduped = [];
|
|
49440
|
+
for (let i = 0; i < sections.length; i++) {
|
|
49441
|
+
const s = sections[i];
|
|
49442
|
+
const key2 = normalizeHeading(s.heading);
|
|
49443
|
+
if (key2 && headingLastIdx.get(key2) !== i && headingLastIdx.has(key2)) {
|
|
49444
|
+
continue;
|
|
49445
|
+
}
|
|
49446
|
+
deduped.push(s);
|
|
49447
|
+
}
|
|
49448
|
+
afterSectionPrune.length = 0;
|
|
49449
|
+
for (const s of deduped) {
|
|
49450
|
+
if (s.heading)
|
|
49451
|
+
afterSectionPrune.push(s.heading);
|
|
49452
|
+
if (s.body)
|
|
49453
|
+
afterSectionPrune.push(s.body);
|
|
49454
|
+
afterSectionPrune.push("");
|
|
49455
|
+
}
|
|
49263
49456
|
const outputLines = [];
|
|
49264
49457
|
let inThinkBlock = false;
|
|
49265
49458
|
for (const line of afterSectionPrune) {
|
|
@@ -49280,6 +49473,15 @@ ${promo.content}` : promo.content;
|
|
|
49280
49473
|
writeFileSync8(memoryMdPath, pruned + "\n");
|
|
49281
49474
|
log17.info("Pruned MEMORY.md: removed daily-report sections and LLM artifacts", { agentId: this.id });
|
|
49282
49475
|
}
|
|
49476
|
+
const compressed = this.memory.compressLongTermMemory();
|
|
49477
|
+
if (compressed.truncatedChunks > 0) {
|
|
49478
|
+
log17.info("Compressed MEMORY.md during Dream Cycle", {
|
|
49479
|
+
agentId: this.id,
|
|
49480
|
+
charsBefore: compressed.charsBefore,
|
|
49481
|
+
charsAfter: compressed.charsAfter,
|
|
49482
|
+
sectionsTrimmed: compressed.truncatedChunks
|
|
49483
|
+
});
|
|
49484
|
+
}
|
|
49283
49485
|
}
|
|
49284
49486
|
};
|
|
49285
49487
|
}
|
|
@@ -50572,7 +50774,7 @@ function createA2ATools(ctx) {
|
|
|
50572
50774
|
type: "string",
|
|
50573
50775
|
description: 'Required when scope="channel". The channel key (e.g., "group:<teamId>" for team chats, "dm:<id1>_<id2>" for DMs).'
|
|
50574
50776
|
},
|
|
50575
|
-
limit: { type: "number", description: "Number of items to fetch (default
|
|
50777
|
+
limit: { type: "number", description: "Number of items to fetch (default 80, max 200)." },
|
|
50576
50778
|
before: { type: "string", description: "ISO timestamp \u2014 fetch items older than this for pagination. Omit for most recent." }
|
|
50577
50779
|
},
|
|
50578
50780
|
required: ["scope"]
|
|
@@ -50584,11 +50786,11 @@ function createA2ATools(ctx) {
|
|
|
50584
50786
|
if (!channelKey) {
|
|
50585
50787
|
return JSON.stringify({ status: "error", error: 'channel_key is required when scope="channel"' });
|
|
50586
50788
|
}
|
|
50587
|
-
const limit = Math.min(args["limit"] ??
|
|
50789
|
+
const limit = Math.min(args["limit"] ?? 80, 200);
|
|
50588
50790
|
const before2 = args["before"];
|
|
50589
50791
|
try {
|
|
50590
50792
|
const result = await ctx.getChannelMessages(channelKey, limit, before2);
|
|
50591
|
-
const formatted = result.messages.map((m) => `[${m.createdAt}] ${m.senderType === "agent" ? `[agent] ${m.senderName}` : `[human] ${m.senderName}`}: ${m.text.slice(0,
|
|
50793
|
+
const formatted = result.messages.map((m) => `[${m.createdAt}] ${m.senderType === "agent" ? `[agent] ${m.senderName}` : `[human] ${m.senderName}`}: ${m.text.slice(0, 2e3)}`);
|
|
50592
50794
|
return JSON.stringify({
|
|
50593
50795
|
messages: formatted,
|
|
50594
50796
|
count: result.messages.length,
|
|
@@ -51044,6 +51246,15 @@ function createAgentTaskTools(ctx) {
|
|
|
51044
51246
|
items: { type: "string" },
|
|
51045
51247
|
description: "Update the list of task IDs that block this task. Only the task creator can modify this field. Pass an empty array to clear all blockers."
|
|
51046
51248
|
},
|
|
51249
|
+
reviewer_id: {
|
|
51250
|
+
type: "string",
|
|
51251
|
+
description: "New reviewer agent or human ID. Only the task creator or a manager can change the reviewer."
|
|
51252
|
+
},
|
|
51253
|
+
reviewer_type: {
|
|
51254
|
+
type: "string",
|
|
51255
|
+
enum: ["agent", "human"],
|
|
51256
|
+
description: "Whether the new reviewer is an agent or a human user. Required when changing reviewer_id."
|
|
51257
|
+
},
|
|
51047
51258
|
schedule: {
|
|
51048
51259
|
type: "object",
|
|
51049
51260
|
description: 'Update the schedule for a scheduled (recurring) task. Only works on tasks with taskType "scheduled". Set either "every" OR "cron", not both.',
|
|
@@ -51064,20 +51275,30 @@ function createAgentTaskTools(ctx) {
|
|
|
51064
51275
|
const note = args["note"];
|
|
51065
51276
|
const description = args["description"];
|
|
51066
51277
|
const blockedBy = args["blocked_by"];
|
|
51067
|
-
|
|
51278
|
+
const reviewerId = args["reviewer_id"];
|
|
51279
|
+
const reviewerType = args["reviewer_type"];
|
|
51280
|
+
if (blockedBy !== void 0 || reviewerId !== void 0) {
|
|
51068
51281
|
const existing = ctx.getTask ? await ctx.getTask(taskId2) : null;
|
|
51069
51282
|
const createdBy = existing?.["createdBy"];
|
|
51070
|
-
if (createdBy && createdBy !== ctx.agentId) {
|
|
51283
|
+
if (blockedBy !== void 0 && createdBy && createdBy !== ctx.agentId) {
|
|
51071
51284
|
return JSON.stringify({
|
|
51072
51285
|
status: "denied",
|
|
51073
51286
|
error: "Only the task creator can modify blocked_by. You are not the creator of this task."
|
|
51074
51287
|
});
|
|
51075
51288
|
}
|
|
51289
|
+
if (reviewerId !== void 0 && createdBy && createdBy !== ctx.agentId) {
|
|
51290
|
+
return JSON.stringify({
|
|
51291
|
+
status: "denied",
|
|
51292
|
+
error: "Only the task creator or a manager can change the reviewer."
|
|
51293
|
+
});
|
|
51294
|
+
}
|
|
51076
51295
|
}
|
|
51077
|
-
if ((description !== void 0 || blockedBy !== void 0) && ctx.updateTaskFields) {
|
|
51296
|
+
if ((description !== void 0 || blockedBy !== void 0 || reviewerId !== void 0) && ctx.updateTaskFields) {
|
|
51078
51297
|
await ctx.updateTaskFields(taskId2, {
|
|
51079
51298
|
...description !== void 0 ? { description } : {},
|
|
51080
|
-
...blockedBy !== void 0 ? { blockedBy } : {}
|
|
51299
|
+
...blockedBy !== void 0 ? { blockedBy } : {},
|
|
51300
|
+
...reviewerId !== void 0 ? { reviewerId } : {},
|
|
51301
|
+
...reviewerType !== void 0 ? { reviewerType } : {}
|
|
51081
51302
|
});
|
|
51082
51303
|
}
|
|
51083
51304
|
const schedule = args["schedule"];
|
|
@@ -51266,7 +51487,7 @@ function createAgentTaskTools(ctx) {
|
|
|
51266
51487
|
...ctx.addTaskNote ? [
|
|
51267
51488
|
{
|
|
51268
51489
|
name: "task_note",
|
|
51269
|
-
description: "Add a progress note
|
|
51490
|
+
description: "Add a one-way progress note to a task's timeline log without changing its status. Use this to record intermediate findings, decisions, or milestones. For interactive discussion with other agents or humans, use task_comment instead.",
|
|
51270
51491
|
inputSchema: {
|
|
51271
51492
|
type: "object",
|
|
51272
51493
|
properties: {
|
|
@@ -52395,16 +52616,23 @@ function createMemoryTools(ctx) {
|
|
|
52395
52616
|
let entries2 = semResults.map((r) => r.entry);
|
|
52396
52617
|
if (type)
|
|
52397
52618
|
entries2 = entries2.filter((e) => e.type === type);
|
|
52398
|
-
|
|
52399
|
-
|
|
52400
|
-
|
|
52401
|
-
|
|
52402
|
-
|
|
52403
|
-
|
|
52404
|
-
|
|
52405
|
-
|
|
52406
|
-
|
|
52407
|
-
|
|
52619
|
+
if (entries2.length > 0) {
|
|
52620
|
+
log24.debug("Semantic memory search", { agentId: ctx.agentId, query: query2, results: entries2.length });
|
|
52621
|
+
return JSON.stringify({
|
|
52622
|
+
results: entries2.map((e) => ({
|
|
52623
|
+
id: e.id,
|
|
52624
|
+
type: e.type,
|
|
52625
|
+
content: e.content,
|
|
52626
|
+
timestamp: e.timestamp,
|
|
52627
|
+
similarity: semResults.find((r) => r.entry.id === e.id)?.similarity
|
|
52628
|
+
})),
|
|
52629
|
+
count: entries2.length,
|
|
52630
|
+
searchMethod: "semantic"
|
|
52631
|
+
});
|
|
52632
|
+
}
|
|
52633
|
+
log24.info("Semantic search returned 0 results, falling back to substring", {
|
|
52634
|
+
agentId: ctx.agentId,
|
|
52635
|
+
query: query2
|
|
52408
52636
|
});
|
|
52409
52637
|
} catch (err) {
|
|
52410
52638
|
log24.warn("Semantic search failed, falling back to substring", { error: String(err) });
|
|
@@ -52423,7 +52651,8 @@ function createMemoryTools(ctx) {
|
|
|
52423
52651
|
timestamp: e.timestamp,
|
|
52424
52652
|
tags: e.metadata?.tags
|
|
52425
52653
|
})),
|
|
52426
|
-
count: results.length
|
|
52654
|
+
count: results.length,
|
|
52655
|
+
searchMethod: "substring"
|
|
52427
52656
|
});
|
|
52428
52657
|
}
|
|
52429
52658
|
},
|
|
@@ -52496,6 +52725,48 @@ ${content}` : content;
|
|
|
52496
52725
|
log24.info("Agent updated long-term memory", { agentId: ctx.agentId, section: section4, mode, contentLen: content.length });
|
|
52497
52726
|
return JSON.stringify({ status: "updated", section: section4, mode });
|
|
52498
52727
|
}
|
|
52728
|
+
},
|
|
52729
|
+
{
|
|
52730
|
+
name: "memory_delete",
|
|
52731
|
+
description: "Delete specific entries from your memory buffer (memories.json). Use this to clean up outdated, incorrect, or redundant observations. Provide either a list of entry IDs (from memory_list/memory_search) or a tag to remove all entries with that tag. Maximum 20 entries per call.",
|
|
52732
|
+
inputSchema: {
|
|
52733
|
+
type: "object",
|
|
52734
|
+
properties: {
|
|
52735
|
+
ids: {
|
|
52736
|
+
type: "array",
|
|
52737
|
+
items: { type: "string" },
|
|
52738
|
+
description: "Array of memory entry IDs to delete. Use memory_list or memory_search to find IDs."
|
|
52739
|
+
},
|
|
52740
|
+
tag: {
|
|
52741
|
+
type: "string",
|
|
52742
|
+
description: "Delete all entries with this tag. Alternative to specifying individual IDs."
|
|
52743
|
+
}
|
|
52744
|
+
}
|
|
52745
|
+
},
|
|
52746
|
+
async execute(args) {
|
|
52747
|
+
const ids = args["ids"];
|
|
52748
|
+
const tag = args["tag"];
|
|
52749
|
+
if (!ids?.length && !tag) {
|
|
52750
|
+
return JSON.stringify({ status: "error", error: "Provide either ids or tag to delete." });
|
|
52751
|
+
}
|
|
52752
|
+
const MAX_DELETE = 20;
|
|
52753
|
+
let removed = 0;
|
|
52754
|
+
if (ids?.length) {
|
|
52755
|
+
const capped = ids.slice(0, MAX_DELETE);
|
|
52756
|
+
removed = ctx.memory.removeEntries(capped);
|
|
52757
|
+
if (ctx.semanticSearch?.isEnabled()) {
|
|
52758
|
+
for (const id of capped) {
|
|
52759
|
+
ctx.semanticSearch.deleteMemory(id).catch((err) => {
|
|
52760
|
+
log24.warn("Failed to remove memory from semantic index", { error: String(err) });
|
|
52761
|
+
});
|
|
52762
|
+
}
|
|
52763
|
+
}
|
|
52764
|
+
} else if (tag) {
|
|
52765
|
+
removed = ctx.memory.removeEntriesByTag(tag);
|
|
52766
|
+
}
|
|
52767
|
+
log24.info("Agent deleted memories", { agentId: ctx.agentId, removed, byTag: tag ?? null });
|
|
52768
|
+
return JSON.stringify({ status: "deleted", removed });
|
|
52769
|
+
}
|
|
52499
52770
|
}
|
|
52500
52771
|
];
|
|
52501
52772
|
}
|
|
@@ -53143,29 +53414,34 @@ var init_semantic_search = __esm({
|
|
|
53143
53414
|
log27.warn("Failed to index memory entry", { id: entry.id, error: String(err) });
|
|
53144
53415
|
}
|
|
53145
53416
|
}
|
|
53417
|
+
/**
|
|
53418
|
+
* Search indexed memories by semantic similarity.
|
|
53419
|
+
*
|
|
53420
|
+
* Returns an empty array when the service is not enabled.
|
|
53421
|
+
*
|
|
53422
|
+
* @throws {Error} Propagates embedding or vector-store errors to the caller
|
|
53423
|
+
* so that callers (e.g. memory_search tool) can implement their own
|
|
53424
|
+
* fallback strategy. Contrast with {@link indexMemory}, which silently
|
|
53425
|
+
* swallows errors because indexing is best-effort.
|
|
53426
|
+
*/
|
|
53146
53427
|
async search(query2, opts) {
|
|
53147
53428
|
if (!this.enabled)
|
|
53148
53429
|
return [];
|
|
53149
|
-
|
|
53150
|
-
|
|
53151
|
-
|
|
53152
|
-
|
|
53153
|
-
|
|
53154
|
-
|
|
53155
|
-
|
|
53156
|
-
|
|
53157
|
-
|
|
53158
|
-
|
|
53159
|
-
|
|
53160
|
-
|
|
53161
|
-
|
|
53162
|
-
|
|
53163
|
-
|
|
53164
|
-
}));
|
|
53165
|
-
} catch (err) {
|
|
53166
|
-
log27.warn("Semantic search failed, returning empty results", { error: String(err) });
|
|
53167
|
-
return [];
|
|
53168
|
-
}
|
|
53430
|
+
const queryEmbedding = await this.embedding.embed(query2);
|
|
53431
|
+
const results = await this.vectorStore.search(queryEmbedding, {
|
|
53432
|
+
topK: opts?.topK ?? 5,
|
|
53433
|
+
agentId: opts?.agentId,
|
|
53434
|
+
minSimilarity: opts?.minSimilarity ?? 0.3
|
|
53435
|
+
});
|
|
53436
|
+
return results.map((r) => ({
|
|
53437
|
+
entry: {
|
|
53438
|
+
id: r.id,
|
|
53439
|
+
content: r.content,
|
|
53440
|
+
type: r.type,
|
|
53441
|
+
timestamp: ""
|
|
53442
|
+
},
|
|
53443
|
+
similarity: r.similarity
|
|
53444
|
+
}));
|
|
53169
53445
|
}
|
|
53170
53446
|
async deleteMemory(id) {
|
|
53171
53447
|
if (!this.enabled)
|
|
@@ -53711,6 +53987,25 @@ You are ${request.name}.`,
|
|
|
53711
53987
|
"- [ ] Scan recent channel messages for anything requiring attention"
|
|
53712
53988
|
].join("\n"), "utf-8");
|
|
53713
53989
|
}
|
|
53990
|
+
const sessionsDir = join12(agentDataDir, "sessions");
|
|
53991
|
+
const dailyLogsDir = join12(agentDataDir, "daily-logs");
|
|
53992
|
+
const memoryPath = join12(agentDataDir, "MEMORY.md");
|
|
53993
|
+
mkdirSync11(sessionsDir, { recursive: true });
|
|
53994
|
+
mkdirSync11(dailyLogsDir, { recursive: true });
|
|
53995
|
+
if (!existsSync17(memoryPath)) {
|
|
53996
|
+
writeFileSync10(memoryPath, [
|
|
53997
|
+
"# Agent Memory",
|
|
53998
|
+
"",
|
|
53999
|
+
"## Your Knowledge",
|
|
54000
|
+
"",
|
|
54001
|
+
"## procedures",
|
|
54002
|
+
"",
|
|
54003
|
+
"## architecture",
|
|
54004
|
+
"",
|
|
54005
|
+
"## lessons-learned",
|
|
54006
|
+
""
|
|
54007
|
+
].join("\n"), "utf-8");
|
|
54008
|
+
}
|
|
53714
54009
|
const config = {
|
|
53715
54010
|
id,
|
|
53716
54011
|
name: request.name,
|
|
@@ -62237,14 +62532,15 @@ ${c.content}`;
|
|
|
62237
62532
|
reviewer: task.reviewerId
|
|
62238
62533
|
});
|
|
62239
62534
|
if (this.hitlService && request.creatorRole !== "human" && task.status === "pending") {
|
|
62240
|
-
const
|
|
62535
|
+
const creatorId = request.createdBy ?? "system";
|
|
62536
|
+
const creatorName = this.resolveActorName(creatorId, "agent") ?? creatorId;
|
|
62241
62537
|
this.hitlService.requestApprovalAndWait({
|
|
62242
|
-
agentId:
|
|
62538
|
+
agentId: creatorId,
|
|
62243
62539
|
agentName: creatorName,
|
|
62244
62540
|
type: "custom",
|
|
62245
|
-
title:
|
|
62541
|
+
title: task.title,
|
|
62246
62542
|
description: `Agent "${creatorName}" wants to create task "${task.title}" (priority: ${task.priority}).`,
|
|
62247
|
-
details: { taskId: task.id, priority: task.priority }
|
|
62543
|
+
details: { taskId: task.id, priority: task.priority, subType: "task" }
|
|
62248
62544
|
}).then((result) => {
|
|
62249
62545
|
const current = this.tasks.get(task.id);
|
|
62250
62546
|
if (!current || current.status !== "pending")
|
|
@@ -62531,9 +62827,10 @@ ${c.content}`;
|
|
|
62531
62827
|
Deliverables:
|
|
62532
62828
|
${deliverablesSummary}` : ""
|
|
62533
62829
|
].filter(Boolean).join("\n");
|
|
62830
|
+
const assigneeName = this.resolveActorName(task.assignedAgentId, "agent") ?? task.assignedAgentId;
|
|
62534
62831
|
this.hitlService.requestApprovalAndWait({
|
|
62535
62832
|
agentId: task.assignedAgentId,
|
|
62536
|
-
agentName:
|
|
62833
|
+
agentName: assigneeName,
|
|
62537
62834
|
type: "custom",
|
|
62538
62835
|
title: `Review: ${task.title}`,
|
|
62539
62836
|
description,
|
|
@@ -62891,6 +63188,12 @@ Action: ${guidance}` : ""
|
|
|
62891
63188
|
if (data.reviewerType !== void 0)
|
|
62892
63189
|
task.reviewerType = data.reviewerType;
|
|
62893
63190
|
if (data.blockedBy !== void 0) {
|
|
63191
|
+
if (data.blockedBy.length > 0) {
|
|
63192
|
+
const cycle = this.detectBlockedByCycle(id, data.blockedBy);
|
|
63193
|
+
if (cycle) {
|
|
63194
|
+
throw new Error(`Circular dependency detected: ${cycle.join(" \u2192 ")}. Cannot set blocked_by \u2014 this would create a deadlock.`);
|
|
63195
|
+
}
|
|
63196
|
+
}
|
|
62894
63197
|
task.blockedBy = data.blockedBy;
|
|
62895
63198
|
if (this.taskRepo && "updateBlockedBy" in this.taskRepo) {
|
|
62896
63199
|
this.taskRepo.updateBlockedBy(id, data.blockedBy).catch((err) => log50.warn("Failed to persist blockedBy to DB", { error: String(err) }));
|
|
@@ -63015,6 +63318,31 @@ Action: ${guidance}` : ""
|
|
|
63015
63318
|
this.cascadeCancelDependents(task);
|
|
63016
63319
|
}
|
|
63017
63320
|
}
|
|
63321
|
+
/**
|
|
63322
|
+
* Detect cycles in blocked_by dependencies using BFS.
|
|
63323
|
+
* Returns the cycle path if found, or null if no cycle exists.
|
|
63324
|
+
*/
|
|
63325
|
+
detectBlockedByCycle(taskId2, proposedBlockers) {
|
|
63326
|
+
for (const blockerId of proposedBlockers) {
|
|
63327
|
+
const visited = /* @__PURE__ */ new Set();
|
|
63328
|
+
const queue = [{ id: blockerId, path: [taskId2, blockerId] }];
|
|
63329
|
+
while (queue.length > 0) {
|
|
63330
|
+
const { id: current, path } = queue.shift();
|
|
63331
|
+
if (current === taskId2)
|
|
63332
|
+
return path;
|
|
63333
|
+
if (visited.has(current))
|
|
63334
|
+
continue;
|
|
63335
|
+
visited.add(current);
|
|
63336
|
+
const blockerTask = this.tasks.get(current);
|
|
63337
|
+
if (blockerTask?.blockedBy) {
|
|
63338
|
+
for (const nextId of blockerTask.blockedBy) {
|
|
63339
|
+
queue.push({ id: nextId, path: [...path, nextId] });
|
|
63340
|
+
}
|
|
63341
|
+
}
|
|
63342
|
+
}
|
|
63343
|
+
}
|
|
63344
|
+
return null;
|
|
63345
|
+
}
|
|
63018
63346
|
areBlockersSatisfied(task) {
|
|
63019
63347
|
if (!task.blockedBy?.length)
|
|
63020
63348
|
return true;
|
|
@@ -64414,7 +64742,7 @@ var init_builder_service = __esm({
|
|
|
64414
64742
|
const memberRole = member.role ?? "worker";
|
|
64415
64743
|
const memberName = member.name ?? "Agent";
|
|
64416
64744
|
const memberSkills = member.skills ?? [];
|
|
64417
|
-
const memberFilesDir = this.findMemberDir(artDir, memberName, usedMemberDirs);
|
|
64745
|
+
const memberFilesDir = this.findMemberDir(artDir, memberName, usedMemberDirs, member.roleName);
|
|
64418
64746
|
const hasCustomRole = !!memberFilesDir && existsSync24(join20(memberFilesDir, "ROLE.md"));
|
|
64419
64747
|
if (memberFilesDir)
|
|
64420
64748
|
usedMemberDirs.add(memberFilesDir);
|
|
@@ -64459,14 +64787,24 @@ var init_builder_service = __esm({
|
|
|
64459
64787
|
* Find the member directory under artDir/members/ by trying multiple slug strategies.
|
|
64460
64788
|
* Returns the absolute path to the member directory, or null if not found.
|
|
64461
64789
|
*/
|
|
64462
|
-
findMemberDir(artDir, memberName, usedDirs) {
|
|
64790
|
+
findMemberDir(artDir, memberName, usedDirs, roleName) {
|
|
64463
64791
|
const membersBase = join20(artDir, "members");
|
|
64464
64792
|
if (!existsSync24(membersBase))
|
|
64465
64793
|
return null;
|
|
64466
|
-
const
|
|
64467
|
-
|
|
64468
|
-
|
|
64469
|
-
|
|
64794
|
+
const nameSlug = kebab(memberName);
|
|
64795
|
+
if (nameSlug && !/^pkg-/.test(nameSlug)) {
|
|
64796
|
+
const exact = join20(membersBase, nameSlug);
|
|
64797
|
+
if (existsSync24(exact) && !usedDirs.has(exact))
|
|
64798
|
+
return exact;
|
|
64799
|
+
}
|
|
64800
|
+
if (roleName) {
|
|
64801
|
+
const roleSlug = kebab(roleName);
|
|
64802
|
+
if (roleSlug && !/^pkg-/.test(roleSlug)) {
|
|
64803
|
+
const roleDir = join20(membersBase, roleSlug);
|
|
64804
|
+
if (existsSync24(roleDir) && !usedDirs.has(roleDir))
|
|
64805
|
+
return roleDir;
|
|
64806
|
+
}
|
|
64807
|
+
}
|
|
64470
64808
|
try {
|
|
64471
64809
|
for (const entry of readdirSync8(membersBase, { withFileTypes: true })) {
|
|
64472
64810
|
if (!entry.isDirectory())
|
|
@@ -64480,8 +64818,12 @@ var init_builder_service = __esm({
|
|
|
64480
64818
|
try {
|
|
64481
64819
|
const content = readFileSync18(rolePath, "utf-8");
|
|
64482
64820
|
const title = content.match(/^#\s+(.+)$/m)?.[1]?.trim();
|
|
64483
|
-
if (title
|
|
64484
|
-
|
|
64821
|
+
if (title) {
|
|
64822
|
+
const tLower = title.toLowerCase();
|
|
64823
|
+
const nLower = memberName.toLowerCase();
|
|
64824
|
+
if (tLower === nLower || tLower.includes(nLower) || nLower.includes(tLower))
|
|
64825
|
+
return candidateDir;
|
|
64826
|
+
}
|
|
64485
64827
|
} catch {
|
|
64486
64828
|
}
|
|
64487
64829
|
}
|
|
@@ -64997,6 +65339,9 @@ var init_sse_handler = __esm({
|
|
|
64997
65339
|
} else if (this.options.isResume) {
|
|
64998
65340
|
this.sessionId = this.options.sessionId ?? null;
|
|
64999
65341
|
}
|
|
65342
|
+
if (this.sessionId && this.sseBuffer && !this.sseDisconnected) {
|
|
65343
|
+
this.sseBuffer.send({ type: "session_start", sessionId: this.sessionId });
|
|
65344
|
+
}
|
|
65000
65345
|
const reply = await this.options.agent.sendMessageStream(this.options.userText, (event) => this.handleStreamEvent(event), this.options.senderId, this.options.senderInfo, this.cancelToken, this.options.images, this.options.fileNames);
|
|
65001
65346
|
const finalNow = (/* @__PURE__ */ new Date()).toISOString();
|
|
65002
65347
|
let finalThinking;
|
|
@@ -65756,7 +66101,7 @@ var init_api_server = __esm({
|
|
|
65756
66101
|
let channelContext = [];
|
|
65757
66102
|
if (this.storage) {
|
|
65758
66103
|
try {
|
|
65759
|
-
const recent = await this.storage.channelMessageRepo.getMessages(channelKey,
|
|
66104
|
+
const recent = await this.storage.channelMessageRepo.getMessages(channelKey, 80);
|
|
65760
66105
|
channelContext = (recent.messages ?? []).map((m) => ({
|
|
65761
66106
|
role: m.senderType === "agent" ? "assistant" : "user",
|
|
65762
66107
|
content: m.senderType === "agent" ? stripInternalBlocks(m.text) : `[${m.senderName}]: ${m.text}`
|
|
@@ -66326,13 +66671,33 @@ ${cleanText}`,
|
|
|
66326
66671
|
toolEventCollector: toolEvents,
|
|
66327
66672
|
waitForReply: isA2A ? true : void 0
|
|
66328
66673
|
});
|
|
66674
|
+
const emitNoResponse = () => {
|
|
66675
|
+
const evt = {
|
|
66676
|
+
type: "chat:agent_no_response",
|
|
66677
|
+
payload: { channel, agentId: agentId2 },
|
|
66678
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
66679
|
+
};
|
|
66680
|
+
if (channel.startsWith("dm:") || channel.startsWith("notes:")) {
|
|
66681
|
+
const parts = channel.startsWith("notes:") ? [channel.slice(6)] : channel.slice(3).split(":");
|
|
66682
|
+
this.ws.sendToUsers(parts, evt);
|
|
66683
|
+
} else {
|
|
66684
|
+
const humanIds = this.resolveChannelHumanIds(channel);
|
|
66685
|
+
if (humanIds.length > 0)
|
|
66686
|
+
this.ws.sendToUsers(humanIds, evt);
|
|
66687
|
+
else
|
|
66688
|
+
this.ws.broadcast(evt);
|
|
66689
|
+
}
|
|
66690
|
+
};
|
|
66329
66691
|
if (!reply || !reply.trim() || reply.includes("[NO_RESPONSE]")) {
|
|
66692
|
+
emitNoResponse();
|
|
66330
66693
|
return;
|
|
66331
66694
|
}
|
|
66332
66695
|
const { thinking, clean: rawClean } = extractThinkBlocks(reply);
|
|
66333
66696
|
const cleanReply = rawClean.replace(/\[NO_RESPONSE\]/gi, "").trim();
|
|
66334
|
-
if (!cleanReply)
|
|
66697
|
+
if (!cleanReply) {
|
|
66698
|
+
emitNoResponse();
|
|
66335
66699
|
return;
|
|
66700
|
+
}
|
|
66336
66701
|
const metadata = {};
|
|
66337
66702
|
if (thinking.length > 0)
|
|
66338
66703
|
metadata["thinking"] = thinking;
|
|
@@ -66435,7 +66800,7 @@ ${cleanText}`,
|
|
|
66435
66800
|
let channelContext = [];
|
|
66436
66801
|
if (this.storage) {
|
|
66437
66802
|
try {
|
|
66438
|
-
const recent = await this.storage.channelMessageRepo.getMessages(channel,
|
|
66803
|
+
const recent = await this.storage.channelMessageRepo.getMessages(channel, 80);
|
|
66439
66804
|
channelContext = (recent.messages ?? []).map((m) => ({
|
|
66440
66805
|
role: m.senderType === "agent" ? "assistant" : "user",
|
|
66441
66806
|
content: m.senderType === "agent" ? stripInternalBlocks(m.text) : `[${m.senderName}]: ${m.text}`
|
|
@@ -66975,7 +67340,7 @@ ${cleanText}`,
|
|
|
66975
67340
|
if (!this.storage)
|
|
66976
67341
|
return [];
|
|
66977
67342
|
try {
|
|
66978
|
-
const recent = await this.storage.channelMessageRepo.getMessages(channel,
|
|
67343
|
+
const recent = await this.storage.channelMessageRepo.getMessages(channel, 80);
|
|
66979
67344
|
return (recent.messages ?? []).map((m) => ({
|
|
66980
67345
|
role: m.senderType === "agent" ? "assistant" : "user",
|
|
66981
67346
|
content: m.senderType === "agent" ? stripInternalBlocks(m.text) : `[${m.senderName}]: ${m.text}`
|
|
@@ -70309,9 +70674,9 @@ EXPLANATION_END`;
|
|
|
70309
70674
|
writeFileSync16(join22(artDir, "NORMS.md"), norms, "utf-8");
|
|
70310
70675
|
}
|
|
70311
70676
|
const rawMembers = Array.isArray(artifact.team?.members) ? artifact.team.members : Array.isArray(artifact.members) ? artifact.members : [];
|
|
70312
|
-
for (const m of rawMembers) {
|
|
70677
|
+
for (const [idx, m] of rawMembers.entries()) {
|
|
70313
70678
|
const mName = m.name ?? "Agent";
|
|
70314
|
-
const slug = kebab(mName, "
|
|
70679
|
+
const slug = kebab(mName, "member-" + idx);
|
|
70315
70680
|
const memberDir = join22(artDir, "members", slug);
|
|
70316
70681
|
const roleContent = m.roleContent || m.role_md;
|
|
70317
70682
|
const policiesContent = m.policiesContent || m.policies_md;
|
|
@@ -71158,6 +71523,50 @@ EXPLANATION_END`;
|
|
|
71158
71523
|
this.json(res, 200, { success: read ?? false });
|
|
71159
71524
|
return;
|
|
71160
71525
|
}
|
|
71526
|
+
if (path === "/api/activity" && req.method === "GET") {
|
|
71527
|
+
const authUser = await this.requireAuth(req, res);
|
|
71528
|
+
if (!authUser)
|
|
71529
|
+
return;
|
|
71530
|
+
const userId2 = authUser.userId;
|
|
71531
|
+
const limit = Math.min(parseInt(url.searchParams.get("limit") ?? "50", 10), 200);
|
|
71532
|
+
const typeFilter = url.searchParams.get("type") ?? void 0;
|
|
71533
|
+
const items = [];
|
|
71534
|
+
if (!typeFilter || typeFilter === "notification") {
|
|
71535
|
+
const notifications = this.hitlService?.listNotifications(userId2, false, { limit }) ?? [];
|
|
71536
|
+
for (const n of notifications) {
|
|
71537
|
+
items.push({
|
|
71538
|
+
id: n.id,
|
|
71539
|
+
type: n.type,
|
|
71540
|
+
title: n.title,
|
|
71541
|
+
body: n.body,
|
|
71542
|
+
timestamp: n.createdAt,
|
|
71543
|
+
source: "notification",
|
|
71544
|
+
metadata: n.metadata
|
|
71545
|
+
});
|
|
71546
|
+
}
|
|
71547
|
+
}
|
|
71548
|
+
if ((!typeFilter || typeFilter === "task_comment") && this.storage?.taskCommentRepo) {
|
|
71549
|
+
try {
|
|
71550
|
+
const recentComments = this.storage.taskCommentRepo.listRecent?.(limit) ?? [];
|
|
71551
|
+
for (const c of recentComments) {
|
|
71552
|
+
items.push({
|
|
71553
|
+
id: c.id,
|
|
71554
|
+
type: "task_comment",
|
|
71555
|
+
title: `Comment on task ${c.taskId}`,
|
|
71556
|
+
body: typeof c.body === "string" ? c.body.slice(0, 300) : String(c.body ?? ""),
|
|
71557
|
+
timestamp: c.createdAt,
|
|
71558
|
+
source: "task_comment",
|
|
71559
|
+
metadata: { taskId: c.taskId, authorId: c.authorId, authorName: c.authorName }
|
|
71560
|
+
});
|
|
71561
|
+
}
|
|
71562
|
+
} catch {
|
|
71563
|
+
}
|
|
71564
|
+
}
|
|
71565
|
+
items.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
71566
|
+
const page = items.slice(0, limit);
|
|
71567
|
+
this.json(res, 200, { items: page, totalCount: items.length });
|
|
71568
|
+
return;
|
|
71569
|
+
}
|
|
71161
71570
|
if (path === "/api/usage" && req.method === "GET") {
|
|
71162
71571
|
const orgId2 = url.searchParams.get("orgId") ?? "default";
|
|
71163
71572
|
const plan = this.billingService?.getOrgPlan(orgId2);
|
|
@@ -72660,11 +73069,11 @@ EXPLANATION_END`;
|
|
|
72660
73069
|
}
|
|
72661
73070
|
const rolesRoot = rolesCandidates.find((d) => ex(d)) ?? rolesDir;
|
|
72662
73071
|
const files = {};
|
|
72663
|
-
for (const member of tpl.members) {
|
|
73072
|
+
for (const [idx, member] of tpl.members.entries()) {
|
|
72664
73073
|
const roleName = member.roleName;
|
|
72665
73074
|
if (!roleName)
|
|
72666
73075
|
continue;
|
|
72667
|
-
const memberSlug = (member.name ?? roleName
|
|
73076
|
+
const memberSlug = kebab(member.name ?? roleName, "member-" + idx);
|
|
72668
73077
|
const roleDir = resolve13(rolesRoot, roleName);
|
|
72669
73078
|
if (!ex(roleDir))
|
|
72670
73079
|
continue;
|
|
@@ -73793,6 +74202,8 @@ EXPLANATION_END`;
|
|
|
73793
74202
|
exact("/api/notifications", "GET"),
|
|
73794
74203
|
exact("/api/notifications/mark-all-read", "POST"),
|
|
73795
74204
|
startsWith("/api/notifications/", "POST"),
|
|
74205
|
+
// ── Activity feed ─────────────────────────────────────────────────
|
|
74206
|
+
exact("/api/activity", "GET"),
|
|
73796
74207
|
// ── Users ────────────────────────────────────────────────────────────
|
|
73797
74208
|
exact("/api/users", "GET", "POST"),
|
|
73798
74209
|
regex(/^\/api\/users\/[^/]+$/, "PATCH"),
|
|
@@ -74408,7 +74819,7 @@ var init_hitl_service = __esm({
|
|
|
74408
74819
|
this.notify({
|
|
74409
74820
|
targetUserId: opts.targetUserId ?? "all",
|
|
74410
74821
|
type: "approval_request",
|
|
74411
|
-
title:
|
|
74822
|
+
title: opts.title,
|
|
74412
74823
|
body: opts.description,
|
|
74413
74824
|
priority: "high",
|
|
74414
74825
|
actionType: "navigate",
|
|
@@ -75282,13 +75693,14 @@ var init_requirement_service = __esm({
|
|
|
75282
75693
|
}
|
|
75283
75694
|
this.broadcast("requirement:created", req);
|
|
75284
75695
|
if (this.hitlService && req.source === "agent") {
|
|
75696
|
+
const creatorName = this.resolveAgentName(req.createdBy);
|
|
75285
75697
|
this.hitlService.requestApprovalAndWait({
|
|
75286
75698
|
agentId: req.createdBy,
|
|
75287
|
-
agentName:
|
|
75699
|
+
agentName: creatorName,
|
|
75288
75700
|
type: "custom",
|
|
75289
|
-
title:
|
|
75290
|
-
description: `Agent "${
|
|
75291
|
-
details: { requirementId: req.id, priority: req.priority },
|
|
75701
|
+
title: req.title,
|
|
75702
|
+
description: `Agent "${creatorName}" proposed requirement "${req.title}" (priority: ${req.priority}).`,
|
|
75703
|
+
details: { requirementId: req.id, priority: req.priority, subType: "requirement" },
|
|
75292
75704
|
targetUserId: "all"
|
|
75293
75705
|
}).then((result) => {
|
|
75294
75706
|
const current = this.requirements.get(req.id);
|
|
@@ -75424,13 +75836,14 @@ var init_requirement_service = __esm({
|
|
|
75424
75836
|
this.broadcast("requirement:resubmitted", req);
|
|
75425
75837
|
log61.info("Requirement resubmitted for review", { id, hasUpdates: !!updates });
|
|
75426
75838
|
if (this.hitlService && req.source === "agent") {
|
|
75839
|
+
const creatorName = this.resolveAgentName(req.createdBy);
|
|
75427
75840
|
this.hitlService.requestApprovalAndWait({
|
|
75428
75841
|
agentId: req.createdBy,
|
|
75429
|
-
agentName:
|
|
75842
|
+
agentName: creatorName,
|
|
75430
75843
|
type: "custom",
|
|
75431
|
-
title:
|
|
75432
|
-
description: `Agent "${
|
|
75433
|
-
details: { requirementId: req.id, priority: req.priority },
|
|
75844
|
+
title: req.title,
|
|
75845
|
+
description: `Agent "${creatorName}" resubmitted requirement "${req.title}" (priority: ${req.priority}).`,
|
|
75846
|
+
details: { requirementId: req.id, priority: req.priority, subType: "requirement_resubmit" },
|
|
75434
75847
|
targetUserId: "all"
|
|
75435
75848
|
}).then((result) => {
|
|
75436
75849
|
const current = this.requirements.get(req.id);
|
|
@@ -75768,6 +76181,17 @@ var init_requirement_service = __esm({
|
|
|
75768
76181
|
});
|
|
75769
76182
|
}
|
|
75770
76183
|
}
|
|
76184
|
+
resolveAgentName(agentId2) {
|
|
76185
|
+
if (this.agentManager) {
|
|
76186
|
+
try {
|
|
76187
|
+
const agent = this.agentManager.getAgent(agentId2);
|
|
76188
|
+
if (agent)
|
|
76189
|
+
return agent.config?.name ?? agent.name ?? agentId2;
|
|
76190
|
+
} catch {
|
|
76191
|
+
}
|
|
76192
|
+
}
|
|
76193
|
+
return agentId2;
|
|
76194
|
+
}
|
|
75771
76195
|
broadcast(type, data) {
|
|
75772
76196
|
if (this.ws) {
|
|
75773
76197
|
this.ws.broadcast({
|
|
@@ -76919,13 +77343,19 @@ var init_stale_detector = __esm({
|
|
|
76919
77343
|
taskService;
|
|
76920
77344
|
config;
|
|
76921
77345
|
scanInterval;
|
|
76922
|
-
|
|
77346
|
+
onStaleItems;
|
|
77347
|
+
constructor(taskService, config, onStaleItems) {
|
|
76923
77348
|
this.taskService = taskService;
|
|
76924
77349
|
this.config = { ...DEFAULT_CONFIG4, ...config };
|
|
77350
|
+
this.onStaleItems = onStaleItems;
|
|
76925
77351
|
}
|
|
76926
77352
|
start(intervalMs = 36e5) {
|
|
76927
77353
|
this.scanInterval = setInterval(() => {
|
|
76928
|
-
this.scan().
|
|
77354
|
+
this.scan().then((items) => {
|
|
77355
|
+
if (items.length > 0 && this.onStaleItems) {
|
|
77356
|
+
this.onStaleItems(items);
|
|
77357
|
+
}
|
|
77358
|
+
}).catch((err) => log68.warn("Stale scan failed", { error: String(err) }));
|
|
76929
77359
|
}, intervalMs);
|
|
76930
77360
|
log68.info("Stale detector started", { intervalMs });
|
|
76931
77361
|
}
|
|
@@ -82255,6 +82685,21 @@ async function startServer(config, values) {
|
|
|
82255
82685
|
const archiveService = new ArchiveService(taskService, projectService);
|
|
82256
82686
|
archiveService.setRequirementService(requirementService);
|
|
82257
82687
|
archiveService.start();
|
|
82688
|
+
const staleDetector = new StaleDetector(taskService, void 0, (items) => {
|
|
82689
|
+
for (const item of items) {
|
|
82690
|
+
hitlService.notify({
|
|
82691
|
+
targetUserId: "all",
|
|
82692
|
+
type: "system",
|
|
82693
|
+
title: item.type === "review_stale" ? "Stale review" : item.type === "stuck_task" ? "Stuck task" : "Unstarted task",
|
|
82694
|
+
body: item.message,
|
|
82695
|
+
priority: item.type === "review_stale" ? "high" : "normal",
|
|
82696
|
+
actionType: item.taskId ? "navigate" : "none",
|
|
82697
|
+
actionTarget: item.taskId ? JSON.stringify({ path: `/work?openTask=${item.taskId}` }) : void 0,
|
|
82698
|
+
metadata: { taskId: item.taskId, agentId: item.agentId, staleType: item.type }
|
|
82699
|
+
});
|
|
82700
|
+
}
|
|
82701
|
+
});
|
|
82702
|
+
staleDetector.start();
|
|
82258
82703
|
apiServer.setLLMRouter(llmRouter);
|
|
82259
82704
|
apiServer.setConfigPath(values["config"] ?? getDefaultConfigPath());
|
|
82260
82705
|
if (config.hub?.url) apiServer.setHubUrl(config.hub.url);
|
|
@@ -83091,6 +83536,7 @@ ${reason}`;
|
|
|
83091
83536
|
closeStartupLogger();
|
|
83092
83537
|
closeRuntimeLogger();
|
|
83093
83538
|
archiveService.stop();
|
|
83539
|
+
staleDetector.stop();
|
|
83094
83540
|
scheduledTaskRunner.stop();
|
|
83095
83541
|
apiServer.stop();
|
|
83096
83542
|
agentManager.shutdown().then(() => messageRouter.disconnectAll()).then(() => process.exit(0)).catch(() => process.exit(1));
|