@markus-global/cli 0.6.3 → 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 +298 -39
- 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 +3 -6
- package/templates/roles/developer/POLICIES.md +1 -1
- package/dist/web-ui/assets/index-C97PujBE.js +0 -351
- package/dist/web-ui/assets/index-Q4_kHftV.css +0 -1
package/dist/markus.mjs
CHANGED
|
@@ -6544,10 +6544,11 @@ var init_context_engine = __esm({
|
|
|
6544
6544
|
if (opts.assignedTasks && opts.assignedTasks.length > 0) {
|
|
6545
6545
|
const priorityOrder = ["critical", "high", "medium", "low"];
|
|
6546
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"]);
|
|
6547
6548
|
const myTasks = opts.assignedTasks.filter((t) => t.assignedAgentId === opts.agentId);
|
|
6548
6549
|
const otherTasks = opts.assignedTasks.filter((t) => t.assignedAgentId !== opts.agentId);
|
|
6549
|
-
const myActive = myTasks.filter((t) => !
|
|
6550
|
-
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));
|
|
6551
6552
|
const MY_TASK_LIMIT = SYSTEM_MY_TASKS_MAX;
|
|
6552
6553
|
const TEAM_TASK_LIMIT = SYSTEM_TEAM_TASKS_MAX;
|
|
6553
6554
|
parts.push("\n## Task Board");
|
|
@@ -6569,8 +6570,8 @@ var init_context_engine = __esm({
|
|
|
6569
6570
|
parts.push(`_(${myDone.length} completed/closed tasks)_`);
|
|
6570
6571
|
}
|
|
6571
6572
|
if (otherTasks.length > 0) {
|
|
6572
|
-
const otherActive = otherTasks.filter((t) => !
|
|
6573
|
-
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));
|
|
6574
6575
|
if (otherActive.length > 0) {
|
|
6575
6576
|
parts.push("### Team Tasks (assigned to others):");
|
|
6576
6577
|
const shown = otherActive.slice(0, TEAM_TASK_LIMIT);
|
|
@@ -6595,7 +6596,7 @@ var init_context_engine = __esm({
|
|
|
6595
6596
|
parts.push("");
|
|
6596
6597
|
parts.push("**Requirements** (governance gate):");
|
|
6597
6598
|
parts.push("- `requirement_propose` \u2192 pending human approval \u2192 approved \u2192 link tasks via `requirement_id`");
|
|
6598
|
-
parts.push("-
|
|
6599
|
+
parts.push("- Every task MUST reference an approved `requirement_id`. Use `requirement_propose` first if no requirement exists.");
|
|
6599
6600
|
parts.push("");
|
|
6600
6601
|
parts.push("**Task lifecycle** \u2014 Create \u2192 Execute \u2192 Review \u2192 Complete:");
|
|
6601
6602
|
parts.push('- **Create**: `task_create` (REQUIRED: `assigned_agent_id`, `reviewer_id`; optional `reviewer_type`: "agent"|"human"). Check `task_list` first to avoid duplicates.');
|
|
@@ -6621,7 +6622,8 @@ var init_context_engine = __esm({
|
|
|
6621
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).");
|
|
6622
6623
|
parts.push("");
|
|
6623
6624
|
parts.push("**Communicating with other agents**:");
|
|
6624
|
-
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.");
|
|
6625
6627
|
parts.push("- For substantial work requests, create a `task_create` assigned to the target agent instead of asking via message.");
|
|
6626
6628
|
parts.push("- Do NOT use A2A messages for routine task status notifications \u2014 the system handles those automatically.");
|
|
6627
6629
|
}
|
|
@@ -48860,6 +48862,19 @@ ${escalationReason}`;
|
|
|
48860
48862
|
}
|
|
48861
48863
|
async handleHeartbeat(ctx) {
|
|
48862
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;
|
|
48863
48878
|
const activityId = this.startActivity("heartbeat", "Heartbeat check-in", {});
|
|
48864
48879
|
let lastHeartbeatSummary = "";
|
|
48865
48880
|
try {
|
|
@@ -49177,12 +49192,15 @@ ${todayLog.slice(0, HEARTBEAT_DAILY_LOG_CHARS)}
|
|
|
49177
49192
|
* 2. Memory dream: prune, deduplicate, merge (once per day)
|
|
49178
49193
|
*/
|
|
49179
49194
|
lastDreamDate = "";
|
|
49195
|
+
lastHeartbeatFingerprint = "";
|
|
49196
|
+
consecutiveIdleHeartbeats = 0;
|
|
49180
49197
|
async consolidateMemory() {
|
|
49181
49198
|
try {
|
|
49182
49199
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
49183
49200
|
const entries2 = this.memory.getEntries();
|
|
49184
|
-
|
|
49185
|
-
|
|
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;
|
|
49186
49204
|
await this.dreamConsolidateMemory(entries2);
|
|
49187
49205
|
this.pruneMemoryMd();
|
|
49188
49206
|
}
|
|
@@ -49196,7 +49214,7 @@ ${todayLog.slice(0, HEARTBEAT_DAILY_LOG_CHARS)}
|
|
|
49196
49214
|
* outdated items, and merge opportunities. Apply changes programmatically.
|
|
49197
49215
|
*/
|
|
49198
49216
|
async dreamConsolidateMemory(entries2) {
|
|
49199
|
-
const MAX_ENTRIES_FOR_LLM =
|
|
49217
|
+
const MAX_ENTRIES_FOR_LLM = 500;
|
|
49200
49218
|
const truncated = entries2.length > MAX_ENTRIES_FOR_LLM;
|
|
49201
49219
|
const batch = truncated ? entries2.slice(-MAX_ENTRIES_FOR_LLM) : entries2;
|
|
49202
49220
|
const entryList = batch.map((e, i) => {
|
|
@@ -49267,8 +49285,8 @@ ${knowledgePreview}
|
|
|
49267
49285
|
return;
|
|
49268
49286
|
}
|
|
49269
49287
|
const rawPlan = JSON.parse(jsonMatch[0]);
|
|
49270
|
-
const MAX_REMOVE_PER_CYCLE =
|
|
49271
|
-
const MAX_MERGE_PER_CYCLE =
|
|
49288
|
+
const MAX_REMOVE_PER_CYCLE = 50;
|
|
49289
|
+
const MAX_MERGE_PER_CYCLE = 20;
|
|
49272
49290
|
const entryIds = new Set(batch.map((e) => e.id));
|
|
49273
49291
|
const plan = {
|
|
49274
49292
|
remove: (rawPlan.remove ?? []).filter((id) => entryIds.has(id)).slice(0, MAX_REMOVE_PER_CYCLE),
|
|
@@ -49389,6 +49407,52 @@ ${promo.content}` : promo.content;
|
|
|
49389
49407
|
if (!inDailyReport)
|
|
49390
49408
|
afterSectionPrune.push(line);
|
|
49391
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
|
+
}
|
|
49392
49456
|
const outputLines = [];
|
|
49393
49457
|
let inThinkBlock = false;
|
|
49394
49458
|
for (const line of afterSectionPrune) {
|
|
@@ -50710,7 +50774,7 @@ function createA2ATools(ctx) {
|
|
|
50710
50774
|
type: "string",
|
|
50711
50775
|
description: 'Required when scope="channel". The channel key (e.g., "group:<teamId>" for team chats, "dm:<id1>_<id2>" for DMs).'
|
|
50712
50776
|
},
|
|
50713
|
-
limit: { type: "number", description: "Number of items to fetch (default
|
|
50777
|
+
limit: { type: "number", description: "Number of items to fetch (default 80, max 200)." },
|
|
50714
50778
|
before: { type: "string", description: "ISO timestamp \u2014 fetch items older than this for pagination. Omit for most recent." }
|
|
50715
50779
|
},
|
|
50716
50780
|
required: ["scope"]
|
|
@@ -50722,11 +50786,11 @@ function createA2ATools(ctx) {
|
|
|
50722
50786
|
if (!channelKey) {
|
|
50723
50787
|
return JSON.stringify({ status: "error", error: 'channel_key is required when scope="channel"' });
|
|
50724
50788
|
}
|
|
50725
|
-
const limit = Math.min(args["limit"] ??
|
|
50789
|
+
const limit = Math.min(args["limit"] ?? 80, 200);
|
|
50726
50790
|
const before2 = args["before"];
|
|
50727
50791
|
try {
|
|
50728
50792
|
const result = await ctx.getChannelMessages(channelKey, limit, before2);
|
|
50729
|
-
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)}`);
|
|
50730
50794
|
return JSON.stringify({
|
|
50731
50795
|
messages: formatted,
|
|
50732
50796
|
count: result.messages.length,
|
|
@@ -51182,6 +51246,15 @@ function createAgentTaskTools(ctx) {
|
|
|
51182
51246
|
items: { type: "string" },
|
|
51183
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."
|
|
51184
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
|
+
},
|
|
51185
51258
|
schedule: {
|
|
51186
51259
|
type: "object",
|
|
51187
51260
|
description: 'Update the schedule for a scheduled (recurring) task. Only works on tasks with taskType "scheduled". Set either "every" OR "cron", not both.',
|
|
@@ -51202,20 +51275,30 @@ function createAgentTaskTools(ctx) {
|
|
|
51202
51275
|
const note = args["note"];
|
|
51203
51276
|
const description = args["description"];
|
|
51204
51277
|
const blockedBy = args["blocked_by"];
|
|
51205
|
-
|
|
51278
|
+
const reviewerId = args["reviewer_id"];
|
|
51279
|
+
const reviewerType = args["reviewer_type"];
|
|
51280
|
+
if (blockedBy !== void 0 || reviewerId !== void 0) {
|
|
51206
51281
|
const existing = ctx.getTask ? await ctx.getTask(taskId2) : null;
|
|
51207
51282
|
const createdBy = existing?.["createdBy"];
|
|
51208
|
-
if (createdBy && createdBy !== ctx.agentId) {
|
|
51283
|
+
if (blockedBy !== void 0 && createdBy && createdBy !== ctx.agentId) {
|
|
51209
51284
|
return JSON.stringify({
|
|
51210
51285
|
status: "denied",
|
|
51211
51286
|
error: "Only the task creator can modify blocked_by. You are not the creator of this task."
|
|
51212
51287
|
});
|
|
51213
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
|
+
}
|
|
51214
51295
|
}
|
|
51215
|
-
if ((description !== void 0 || blockedBy !== void 0) && ctx.updateTaskFields) {
|
|
51296
|
+
if ((description !== void 0 || blockedBy !== void 0 || reviewerId !== void 0) && ctx.updateTaskFields) {
|
|
51216
51297
|
await ctx.updateTaskFields(taskId2, {
|
|
51217
51298
|
...description !== void 0 ? { description } : {},
|
|
51218
|
-
...blockedBy !== void 0 ? { blockedBy } : {}
|
|
51299
|
+
...blockedBy !== void 0 ? { blockedBy } : {},
|
|
51300
|
+
...reviewerId !== void 0 ? { reviewerId } : {},
|
|
51301
|
+
...reviewerType !== void 0 ? { reviewerType } : {}
|
|
51219
51302
|
});
|
|
51220
51303
|
}
|
|
51221
51304
|
const schedule = args["schedule"];
|
|
@@ -51404,7 +51487,7 @@ function createAgentTaskTools(ctx) {
|
|
|
51404
51487
|
...ctx.addTaskNote ? [
|
|
51405
51488
|
{
|
|
51406
51489
|
name: "task_note",
|
|
51407
|
-
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.",
|
|
51408
51491
|
inputSchema: {
|
|
51409
51492
|
type: "object",
|
|
51410
51493
|
properties: {
|
|
@@ -52642,6 +52725,48 @@ ${content}` : content;
|
|
|
52642
52725
|
log24.info("Agent updated long-term memory", { agentId: ctx.agentId, section: section4, mode, contentLen: content.length });
|
|
52643
52726
|
return JSON.stringify({ status: "updated", section: section4, mode });
|
|
52644
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
|
+
}
|
|
52645
52770
|
}
|
|
52646
52771
|
];
|
|
52647
52772
|
}
|
|
@@ -62407,14 +62532,15 @@ ${c.content}`;
|
|
|
62407
62532
|
reviewer: task.reviewerId
|
|
62408
62533
|
});
|
|
62409
62534
|
if (this.hitlService && request.creatorRole !== "human" && task.status === "pending") {
|
|
62410
|
-
const
|
|
62535
|
+
const creatorId = request.createdBy ?? "system";
|
|
62536
|
+
const creatorName = this.resolveActorName(creatorId, "agent") ?? creatorId;
|
|
62411
62537
|
this.hitlService.requestApprovalAndWait({
|
|
62412
|
-
agentId:
|
|
62538
|
+
agentId: creatorId,
|
|
62413
62539
|
agentName: creatorName,
|
|
62414
62540
|
type: "custom",
|
|
62415
|
-
title:
|
|
62541
|
+
title: task.title,
|
|
62416
62542
|
description: `Agent "${creatorName}" wants to create task "${task.title}" (priority: ${task.priority}).`,
|
|
62417
|
-
details: { taskId: task.id, priority: task.priority }
|
|
62543
|
+
details: { taskId: task.id, priority: task.priority, subType: "task" }
|
|
62418
62544
|
}).then((result) => {
|
|
62419
62545
|
const current = this.tasks.get(task.id);
|
|
62420
62546
|
if (!current || current.status !== "pending")
|
|
@@ -62701,9 +62827,10 @@ ${c.content}`;
|
|
|
62701
62827
|
Deliverables:
|
|
62702
62828
|
${deliverablesSummary}` : ""
|
|
62703
62829
|
].filter(Boolean).join("\n");
|
|
62830
|
+
const assigneeName = this.resolveActorName(task.assignedAgentId, "agent") ?? task.assignedAgentId;
|
|
62704
62831
|
this.hitlService.requestApprovalAndWait({
|
|
62705
62832
|
agentId: task.assignedAgentId,
|
|
62706
|
-
agentName:
|
|
62833
|
+
agentName: assigneeName,
|
|
62707
62834
|
type: "custom",
|
|
62708
62835
|
title: `Review: ${task.title}`,
|
|
62709
62836
|
description,
|
|
@@ -63061,6 +63188,12 @@ Action: ${guidance}` : ""
|
|
|
63061
63188
|
if (data.reviewerType !== void 0)
|
|
63062
63189
|
task.reviewerType = data.reviewerType;
|
|
63063
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
|
+
}
|
|
63064
63197
|
task.blockedBy = data.blockedBy;
|
|
63065
63198
|
if (this.taskRepo && "updateBlockedBy" in this.taskRepo) {
|
|
63066
63199
|
this.taskRepo.updateBlockedBy(id, data.blockedBy).catch((err) => log50.warn("Failed to persist blockedBy to DB", { error: String(err) }));
|
|
@@ -63185,6 +63318,31 @@ Action: ${guidance}` : ""
|
|
|
63185
63318
|
this.cascadeCancelDependents(task);
|
|
63186
63319
|
}
|
|
63187
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
|
+
}
|
|
63188
63346
|
areBlockersSatisfied(task) {
|
|
63189
63347
|
if (!task.blockedBy?.length)
|
|
63190
63348
|
return true;
|
|
@@ -65943,7 +66101,7 @@ var init_api_server = __esm({
|
|
|
65943
66101
|
let channelContext = [];
|
|
65944
66102
|
if (this.storage) {
|
|
65945
66103
|
try {
|
|
65946
|
-
const recent = await this.storage.channelMessageRepo.getMessages(channelKey,
|
|
66104
|
+
const recent = await this.storage.channelMessageRepo.getMessages(channelKey, 80);
|
|
65947
66105
|
channelContext = (recent.messages ?? []).map((m) => ({
|
|
65948
66106
|
role: m.senderType === "agent" ? "assistant" : "user",
|
|
65949
66107
|
content: m.senderType === "agent" ? stripInternalBlocks(m.text) : `[${m.senderName}]: ${m.text}`
|
|
@@ -66513,13 +66671,33 @@ ${cleanText}`,
|
|
|
66513
66671
|
toolEventCollector: toolEvents,
|
|
66514
66672
|
waitForReply: isA2A ? true : void 0
|
|
66515
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
|
+
};
|
|
66516
66691
|
if (!reply || !reply.trim() || reply.includes("[NO_RESPONSE]")) {
|
|
66692
|
+
emitNoResponse();
|
|
66517
66693
|
return;
|
|
66518
66694
|
}
|
|
66519
66695
|
const { thinking, clean: rawClean } = extractThinkBlocks(reply);
|
|
66520
66696
|
const cleanReply = rawClean.replace(/\[NO_RESPONSE\]/gi, "").trim();
|
|
66521
|
-
if (!cleanReply)
|
|
66697
|
+
if (!cleanReply) {
|
|
66698
|
+
emitNoResponse();
|
|
66522
66699
|
return;
|
|
66700
|
+
}
|
|
66523
66701
|
const metadata = {};
|
|
66524
66702
|
if (thinking.length > 0)
|
|
66525
66703
|
metadata["thinking"] = thinking;
|
|
@@ -66622,7 +66800,7 @@ ${cleanText}`,
|
|
|
66622
66800
|
let channelContext = [];
|
|
66623
66801
|
if (this.storage) {
|
|
66624
66802
|
try {
|
|
66625
|
-
const recent = await this.storage.channelMessageRepo.getMessages(channel,
|
|
66803
|
+
const recent = await this.storage.channelMessageRepo.getMessages(channel, 80);
|
|
66626
66804
|
channelContext = (recent.messages ?? []).map((m) => ({
|
|
66627
66805
|
role: m.senderType === "agent" ? "assistant" : "user",
|
|
66628
66806
|
content: m.senderType === "agent" ? stripInternalBlocks(m.text) : `[${m.senderName}]: ${m.text}`
|
|
@@ -67162,7 +67340,7 @@ ${cleanText}`,
|
|
|
67162
67340
|
if (!this.storage)
|
|
67163
67341
|
return [];
|
|
67164
67342
|
try {
|
|
67165
|
-
const recent = await this.storage.channelMessageRepo.getMessages(channel,
|
|
67343
|
+
const recent = await this.storage.channelMessageRepo.getMessages(channel, 80);
|
|
67166
67344
|
return (recent.messages ?? []).map((m) => ({
|
|
67167
67345
|
role: m.senderType === "agent" ? "assistant" : "user",
|
|
67168
67346
|
content: m.senderType === "agent" ? stripInternalBlocks(m.text) : `[${m.senderName}]: ${m.text}`
|
|
@@ -71345,6 +71523,50 @@ EXPLANATION_END`;
|
|
|
71345
71523
|
this.json(res, 200, { success: read ?? false });
|
|
71346
71524
|
return;
|
|
71347
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
|
+
}
|
|
71348
71570
|
if (path === "/api/usage" && req.method === "GET") {
|
|
71349
71571
|
const orgId2 = url.searchParams.get("orgId") ?? "default";
|
|
71350
71572
|
const plan = this.billingService?.getOrgPlan(orgId2);
|
|
@@ -73980,6 +74202,8 @@ EXPLANATION_END`;
|
|
|
73980
74202
|
exact("/api/notifications", "GET"),
|
|
73981
74203
|
exact("/api/notifications/mark-all-read", "POST"),
|
|
73982
74204
|
startsWith("/api/notifications/", "POST"),
|
|
74205
|
+
// ── Activity feed ─────────────────────────────────────────────────
|
|
74206
|
+
exact("/api/activity", "GET"),
|
|
73983
74207
|
// ── Users ────────────────────────────────────────────────────────────
|
|
73984
74208
|
exact("/api/users", "GET", "POST"),
|
|
73985
74209
|
regex(/^\/api\/users\/[^/]+$/, "PATCH"),
|
|
@@ -74595,7 +74819,7 @@ var init_hitl_service = __esm({
|
|
|
74595
74819
|
this.notify({
|
|
74596
74820
|
targetUserId: opts.targetUserId ?? "all",
|
|
74597
74821
|
type: "approval_request",
|
|
74598
|
-
title:
|
|
74822
|
+
title: opts.title,
|
|
74599
74823
|
body: opts.description,
|
|
74600
74824
|
priority: "high",
|
|
74601
74825
|
actionType: "navigate",
|
|
@@ -75469,13 +75693,14 @@ var init_requirement_service = __esm({
|
|
|
75469
75693
|
}
|
|
75470
75694
|
this.broadcast("requirement:created", req);
|
|
75471
75695
|
if (this.hitlService && req.source === "agent") {
|
|
75696
|
+
const creatorName = this.resolveAgentName(req.createdBy);
|
|
75472
75697
|
this.hitlService.requestApprovalAndWait({
|
|
75473
75698
|
agentId: req.createdBy,
|
|
75474
|
-
agentName:
|
|
75699
|
+
agentName: creatorName,
|
|
75475
75700
|
type: "custom",
|
|
75476
|
-
title:
|
|
75477
|
-
description: `Agent "${
|
|
75478
|
-
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" },
|
|
75479
75704
|
targetUserId: "all"
|
|
75480
75705
|
}).then((result) => {
|
|
75481
75706
|
const current = this.requirements.get(req.id);
|
|
@@ -75611,13 +75836,14 @@ var init_requirement_service = __esm({
|
|
|
75611
75836
|
this.broadcast("requirement:resubmitted", req);
|
|
75612
75837
|
log61.info("Requirement resubmitted for review", { id, hasUpdates: !!updates });
|
|
75613
75838
|
if (this.hitlService && req.source === "agent") {
|
|
75839
|
+
const creatorName = this.resolveAgentName(req.createdBy);
|
|
75614
75840
|
this.hitlService.requestApprovalAndWait({
|
|
75615
75841
|
agentId: req.createdBy,
|
|
75616
|
-
agentName:
|
|
75842
|
+
agentName: creatorName,
|
|
75617
75843
|
type: "custom",
|
|
75618
|
-
title:
|
|
75619
|
-
description: `Agent "${
|
|
75620
|
-
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" },
|
|
75621
75847
|
targetUserId: "all"
|
|
75622
75848
|
}).then((result) => {
|
|
75623
75849
|
const current = this.requirements.get(req.id);
|
|
@@ -75955,6 +76181,17 @@ var init_requirement_service = __esm({
|
|
|
75955
76181
|
});
|
|
75956
76182
|
}
|
|
75957
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
|
+
}
|
|
75958
76195
|
broadcast(type, data) {
|
|
75959
76196
|
if (this.ws) {
|
|
75960
76197
|
this.ws.broadcast({
|
|
@@ -77106,13 +77343,19 @@ var init_stale_detector = __esm({
|
|
|
77106
77343
|
taskService;
|
|
77107
77344
|
config;
|
|
77108
77345
|
scanInterval;
|
|
77109
|
-
|
|
77346
|
+
onStaleItems;
|
|
77347
|
+
constructor(taskService, config, onStaleItems) {
|
|
77110
77348
|
this.taskService = taskService;
|
|
77111
77349
|
this.config = { ...DEFAULT_CONFIG4, ...config };
|
|
77350
|
+
this.onStaleItems = onStaleItems;
|
|
77112
77351
|
}
|
|
77113
77352
|
start(intervalMs = 36e5) {
|
|
77114
77353
|
this.scanInterval = setInterval(() => {
|
|
77115
|
-
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) }));
|
|
77116
77359
|
}, intervalMs);
|
|
77117
77360
|
log68.info("Stale detector started", { intervalMs });
|
|
77118
77361
|
}
|
|
@@ -82442,6 +82685,21 @@ async function startServer(config, values) {
|
|
|
82442
82685
|
const archiveService = new ArchiveService(taskService, projectService);
|
|
82443
82686
|
archiveService.setRequirementService(requirementService);
|
|
82444
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();
|
|
82445
82703
|
apiServer.setLLMRouter(llmRouter);
|
|
82446
82704
|
apiServer.setConfigPath(values["config"] ?? getDefaultConfigPath());
|
|
82447
82705
|
if (config.hub?.url) apiServer.setHubUrl(config.hub.url);
|
|
@@ -83278,6 +83536,7 @@ ${reason}`;
|
|
|
83278
83536
|
closeStartupLogger();
|
|
83279
83537
|
closeRuntimeLogger();
|
|
83280
83538
|
archiveService.stop();
|
|
83539
|
+
staleDetector.stop();
|
|
83281
83540
|
scheduledTaskRunner.stop();
|
|
83282
83541
|
apiServer.stop();
|
|
83283
83542
|
agentManager.shutdown().then(() => messageRouter.disconnectAll()).then(() => process.exit(0)).catch(() => process.exit(1));
|