@integrity-labs/agt-cli 0.28.37 → 0.28.38
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.
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
requireHost,
|
|
24
24
|
safeWriteJsonAtomic,
|
|
25
25
|
setConfigHash
|
|
26
|
-
} from "../chunk-
|
|
26
|
+
} from "../chunk-IJT4B5PH.js";
|
|
27
27
|
import {
|
|
28
28
|
getProjectDir as getProjectDir2,
|
|
29
29
|
getReadyTasks,
|
|
@@ -106,7 +106,7 @@ import {
|
|
|
106
106
|
} from "../chunk-XWVM4KPK.js";
|
|
107
107
|
|
|
108
108
|
// src/lib/manager-worker.ts
|
|
109
|
-
import { createHash as
|
|
109
|
+
import { createHash as createHash8 } from "crypto";
|
|
110
110
|
import { readFileSync as readFileSync14, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync9, rmSync as rmSync3, readdirSync as readdirSync5, statSync as statSync4, unlinkSync, copyFileSync } from "fs";
|
|
111
111
|
import { execFileSync as syncExecFile } from "child_process";
|
|
112
112
|
import { join as join16, dirname as dirname4 } from "path";
|
|
@@ -4433,11 +4433,140 @@ async function sendTaskNotification(agentCodeName, channel, to, text) {
|
|
|
4433
4433
|
return { ok: false, error_code: `UNKNOWN_CHANNEL:${channel}` };
|
|
4434
4434
|
}
|
|
4435
4435
|
|
|
4436
|
-
// src/lib/manager/scheduler.ts
|
|
4436
|
+
// src/lib/manager/scheduler/tasks.ts
|
|
4437
|
+
function buildSchedulerTaskInput(t) {
|
|
4438
|
+
return {
|
|
4439
|
+
id: t.id,
|
|
4440
|
+
template_id: t.template_id,
|
|
4441
|
+
name: t.name,
|
|
4442
|
+
schedule_kind: t.schedule_kind,
|
|
4443
|
+
schedule_expr: t.schedule_expr ?? null,
|
|
4444
|
+
schedule_every: t.schedule_every ?? null,
|
|
4445
|
+
schedule_at: t.schedule_at ?? null,
|
|
4446
|
+
timezone: t.timezone ?? "UTC",
|
|
4447
|
+
prompt: t.prompt ?? "",
|
|
4448
|
+
session_target: t.session_target ?? "isolated",
|
|
4449
|
+
delivery_mode: t.delivery_mode ?? "none",
|
|
4450
|
+
delivery_policy: t.delivery_policy ?? "always",
|
|
4451
|
+
delivery_channel: t.delivery_channel ?? null,
|
|
4452
|
+
delivery_to: t.delivery_to ?? null,
|
|
4453
|
+
enabled: t.enabled ?? true,
|
|
4454
|
+
triggered_at: t.triggered_at ?? null
|
|
4455
|
+
};
|
|
4456
|
+
}
|
|
4457
|
+
function deriveScheduledTaskNotify(task) {
|
|
4458
|
+
if (task.deliveryChannel !== "msteams") return {};
|
|
4459
|
+
const to = task.deliveryTo;
|
|
4460
|
+
let conversationId = null;
|
|
4461
|
+
if (typeof to === "string" && to.trim().length > 0) {
|
|
4462
|
+
conversationId = to.trim();
|
|
4463
|
+
} else if (to && typeof to === "object") {
|
|
4464
|
+
const cid = to.conversation_id;
|
|
4465
|
+
if (typeof cid === "string" && cid.trim().length > 0) conversationId = cid.trim();
|
|
4466
|
+
}
|
|
4467
|
+
if (!conversationId) return {};
|
|
4468
|
+
return { notify_channel: "msteams", notify_to: conversationId };
|
|
4469
|
+
}
|
|
4470
|
+
function isScheduledViaKanbanEnabled() {
|
|
4471
|
+
const v = process.env["AGT_SCHEDULED_VIA_KANBAN"];
|
|
4472
|
+
return !(v === "0" || v?.toLowerCase() === "false");
|
|
4473
|
+
}
|
|
4474
|
+
|
|
4475
|
+
// src/lib/manager/scheduler/runs.ts
|
|
4437
4476
|
import { createHash as createHash5 } from "crypto";
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4477
|
+
async function startRun(opts) {
|
|
4478
|
+
try {
|
|
4479
|
+
const res = await api.post(
|
|
4480
|
+
"/host/runs/start",
|
|
4481
|
+
opts
|
|
4482
|
+
);
|
|
4483
|
+
return { run_id: res.run_id ?? null, kanban_item_id: res.kanban_item_id ?? null };
|
|
4484
|
+
} catch (err) {
|
|
4485
|
+
const errText = err instanceof Error ? err.message : String(err);
|
|
4486
|
+
const errId = createHash5("sha256").update(errText).digest("hex").slice(0, 12);
|
|
4487
|
+
log(`[runs] start failed for agent_id=${opts.agent_id} source_type=${opts.source_type} error_id=${errId}`);
|
|
4488
|
+
return { run_id: null, kanban_item_id: null };
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4491
|
+
async function finishRun(runId, outcome, options = {}) {
|
|
4492
|
+
try {
|
|
4493
|
+
await api.post("/host/runs/finish", {
|
|
4494
|
+
run_id: runId,
|
|
4495
|
+
outcome,
|
|
4496
|
+
outcome_message: options.outcomeMessage,
|
|
4497
|
+
metadata: options.metadata,
|
|
4498
|
+
complete_kanban_item_id: options.completeKanbanItemId ?? void 0,
|
|
4499
|
+
result: options.result
|
|
4500
|
+
});
|
|
4501
|
+
} catch (err) {
|
|
4502
|
+
const errText = err instanceof Error ? err.message : String(err);
|
|
4503
|
+
const errId = createHash5("sha256").update(errText).digest("hex").slice(0, 12);
|
|
4504
|
+
log(`[runs] finish failed for run_id=${runId} outcome=${outcome} error_id=${errId}`);
|
|
4505
|
+
}
|
|
4506
|
+
}
|
|
4507
|
+
var MAX_PRIOR_RUNS = 5;
|
|
4508
|
+
async function fetchPriorScheduledRuns(agentId, taskId) {
|
|
4509
|
+
try {
|
|
4510
|
+
const data = await api.post("/host/scheduled-tasks/recent-outputs", {
|
|
4511
|
+
agent_id: agentId,
|
|
4512
|
+
task_id: taskId,
|
|
4513
|
+
since_hours: 24,
|
|
4514
|
+
limit: MAX_PRIOR_RUNS
|
|
4515
|
+
});
|
|
4516
|
+
const rows = Array.isArray(data?.runs) ? data.runs.slice(0, MAX_PRIOR_RUNS) : [];
|
|
4517
|
+
return rows.filter((r) => typeof r.output_text === "string" && r.output_text.length > 0).map((r) => ({ startedAt: r.started_at, output: r.output_text }));
|
|
4518
|
+
} catch (err) {
|
|
4519
|
+
const errText = err instanceof Error ? err.message : String(err);
|
|
4520
|
+
const errId = createHash5("sha256").update(errText).digest("hex").slice(0, 12);
|
|
4521
|
+
log(`[runs] prior-runs lookup failed for task_id=${taskId} error_id=${errId}`);
|
|
4522
|
+
return [];
|
|
4523
|
+
}
|
|
4524
|
+
}
|
|
4525
|
+
|
|
4526
|
+
// src/lib/manager/scheduler/state.ts
|
|
4527
|
+
var inFlightClaudeTasks = /* @__PURE__ */ new Set();
|
|
4528
|
+
var claudeTaskConcurrency = /* @__PURE__ */ new Map();
|
|
4529
|
+
var MAX_CLAUDE_CONCURRENCY = 2;
|
|
4530
|
+
var claudeSchedulerStates = /* @__PURE__ */ new Map();
|
|
4531
|
+
var scheduledRunsByCode = /* @__PURE__ */ new Map();
|
|
4532
|
+
var claimedScheduledCards = /* @__PURE__ */ new Set();
|
|
4533
|
+
var completedScheduledCards = /* @__PURE__ */ new Set();
|
|
4534
|
+
function claimScheduledCardDelivery(cardId) {
|
|
4535
|
+
if (claimedScheduledCards.has(cardId)) return false;
|
|
4536
|
+
claimedScheduledCards.add(cardId);
|
|
4537
|
+
return true;
|
|
4538
|
+
}
|
|
4539
|
+
function releaseScheduledCardDelivery(cardId) {
|
|
4540
|
+
claimedScheduledCards.delete(cardId);
|
|
4541
|
+
}
|
|
4542
|
+
function markScheduledCardDeliveryComplete(cardId) {
|
|
4543
|
+
completedScheduledCards.add(cardId);
|
|
4544
|
+
}
|
|
4545
|
+
function trackScheduledRun(codeName, cardId, runId) {
|
|
4546
|
+
let m = scheduledRunsByCode.get(codeName);
|
|
4547
|
+
if (!m) {
|
|
4548
|
+
m = /* @__PURE__ */ new Map();
|
|
4549
|
+
scheduledRunsByCode.set(codeName, m);
|
|
4550
|
+
}
|
|
4551
|
+
m.set(cardId, runId);
|
|
4552
|
+
}
|
|
4553
|
+
function isScheduledCardTracked(cardId) {
|
|
4554
|
+
for (const m of scheduledRunsByCode.values()) {
|
|
4555
|
+
if (m.has(cardId)) return true;
|
|
4556
|
+
}
|
|
4557
|
+
return false;
|
|
4558
|
+
}
|
|
4559
|
+
function closeScheduledRunsForCode(codeName, outcome, reason) {
|
|
4560
|
+
const m = scheduledRunsByCode.get(codeName);
|
|
4561
|
+
if (!m || m.size === 0) return;
|
|
4562
|
+
scheduledRunsByCode.delete(codeName);
|
|
4563
|
+
for (const runId of m.values()) {
|
|
4564
|
+
void finishRun(runId, outcome, { outcomeMessage: reason });
|
|
4565
|
+
}
|
|
4566
|
+
}
|
|
4567
|
+
|
|
4568
|
+
// src/lib/manager/scheduler/kanban-route.ts
|
|
4569
|
+
import { createHash as createHash6 } from "crypto";
|
|
4441
4570
|
|
|
4442
4571
|
// src/lib/delivery-schedule-link.ts
|
|
4443
4572
|
function envSuffixFor2(codeName) {
|
|
@@ -4663,250 +4792,57 @@ async function deliverScheduledTaskOutput(agentCodeName, agentId, rawTarget, bod
|
|
|
4663
4792
|
}
|
|
4664
4793
|
}
|
|
4665
4794
|
|
|
4666
|
-
// src/lib/manager/scheduler.ts
|
|
4667
|
-
function
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
schedule_kind: t.schedule_kind,
|
|
4673
|
-
schedule_expr: t.schedule_expr ?? null,
|
|
4674
|
-
schedule_every: t.schedule_every ?? null,
|
|
4675
|
-
schedule_at: t.schedule_at ?? null,
|
|
4676
|
-
timezone: t.timezone ?? "UTC",
|
|
4677
|
-
prompt: t.prompt ?? "",
|
|
4678
|
-
session_target: t.session_target ?? "isolated",
|
|
4679
|
-
delivery_mode: t.delivery_mode ?? "none",
|
|
4680
|
-
delivery_policy: t.delivery_policy ?? "always",
|
|
4681
|
-
delivery_channel: t.delivery_channel ?? null,
|
|
4682
|
-
delivery_to: t.delivery_to ?? null,
|
|
4683
|
-
enabled: t.enabled ?? true,
|
|
4684
|
-
triggered_at: t.triggered_at ?? null
|
|
4685
|
-
};
|
|
4686
|
-
}
|
|
4687
|
-
var inFlightClaudeTasks = /* @__PURE__ */ new Set();
|
|
4688
|
-
var claudeTaskConcurrency = /* @__PURE__ */ new Map();
|
|
4689
|
-
var MAX_CLAUDE_CONCURRENCY = 2;
|
|
4690
|
-
function claudePidFilePath() {
|
|
4691
|
-
return join14(homedir7(), ".augmented", "manager-claude-pids.json");
|
|
4692
|
-
}
|
|
4693
|
-
var inFlightClaudePids = /* @__PURE__ */ new Map();
|
|
4694
|
-
function registerClaudeSpawn(record) {
|
|
4695
|
-
inFlightClaudePids.set(record.pid, record);
|
|
4795
|
+
// src/lib/manager/scheduler/kanban-route.ts
|
|
4796
|
+
async function deliverScheduledCardResult(codeName, agentId, cardId) {
|
|
4797
|
+
if (!claimScheduledCardDelivery(cardId)) {
|
|
4798
|
+
return completedScheduledCards.has(cardId) ? "terminal" : "in_flight";
|
|
4799
|
+
}
|
|
4800
|
+
let card;
|
|
4696
4801
|
try {
|
|
4697
|
-
|
|
4802
|
+
const resp = await api.post("/host/kanban-item", {
|
|
4803
|
+
agent_id: agentId,
|
|
4804
|
+
item_id: cardId
|
|
4805
|
+
});
|
|
4806
|
+
card = resp.item;
|
|
4698
4807
|
} catch (err) {
|
|
4699
|
-
|
|
4808
|
+
if (err instanceof ApiError && err.status >= 400 && err.status < 500) {
|
|
4809
|
+
log(`[scheduled-kanban] delivery: card ${cardId} on '${codeName}' fetch returned ${err.status} (${err.message}) \u2014 treating as terminal, not retrying`);
|
|
4810
|
+
markScheduledCardDeliveryComplete(cardId);
|
|
4811
|
+
return "terminal";
|
|
4812
|
+
}
|
|
4813
|
+
releaseScheduledCardDelivery(cardId);
|
|
4814
|
+
log(`[scheduled-kanban] delivery fetch failed for card ${cardId} on '${codeName}': ${err.message} \u2014 will retry`);
|
|
4815
|
+
return "retry";
|
|
4700
4816
|
}
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
try {
|
|
4705
|
-
removeSpawn(claudePidFilePath(), pid);
|
|
4706
|
-
} catch {
|
|
4817
|
+
if (!card || card.status !== "done" || card.source_type !== "scheduled_task" || !card.source_ref) {
|
|
4818
|
+
markScheduledCardDeliveryComplete(cardId);
|
|
4819
|
+
return "terminal";
|
|
4707
4820
|
}
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
const resolvedModels = resolveModelChain(refreshData);
|
|
4715
|
-
const modelsHash = createHash5("sha256").update(JSON.stringify(resolvedModels)).digest("hex").slice(0, 16);
|
|
4716
|
-
const combinedHash = `${stableTasksHash}:${boardHash}:${modelsHash}`;
|
|
4717
|
-
const prevHash = agentState.knownTasksHashes.get(agent.agent_id);
|
|
4718
|
-
if (combinedHash !== prevHash) {
|
|
4719
|
-
const taskInputs = tasks.map((t) => buildSchedulerTaskInput(t));
|
|
4720
|
-
const state8 = syncTasksToScheduler(codeName, agent.agent_id, taskInputs);
|
|
4721
|
-
claudeSchedulerStates.set(codeName, state8);
|
|
4722
|
-
agentState.knownTasksHashes.set(agent.agent_id, combinedHash);
|
|
4723
|
-
log(`[claude-scheduler] Tasks synced for '${codeName}' (${taskInputs.length} task(s))`);
|
|
4821
|
+
const state7 = claudeSchedulerStates.get(codeName) ?? loadSchedulerState(codeName);
|
|
4822
|
+
const task = state7.tasks[card.source_ref];
|
|
4823
|
+
if (!task) {
|
|
4824
|
+
log(`[scheduled-kanban] delivery: no scheduler task for source_ref=${card.source_ref} on '${codeName}' \u2014 skipping`);
|
|
4825
|
+
markScheduledCardDeliveryComplete(cardId);
|
|
4826
|
+
return "terminal";
|
|
4724
4827
|
}
|
|
4725
|
-
if (!
|
|
4726
|
-
|
|
4828
|
+
if (!isPlainScheduledTemplate(task.templateId)) {
|
|
4829
|
+
log(`[scheduled-kanban] delivery: card ${cardId} template '${task.templateId}' on '${codeName}' is not a plain scheduled template \u2014 skipping manager delivery`);
|
|
4830
|
+
markScheduledCardDeliveryComplete(cardId);
|
|
4831
|
+
return "terminal";
|
|
4727
4832
|
}
|
|
4728
|
-
const
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
if (
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
let prompt = task.prompt;
|
|
4739
|
-
if (BOARD_INJECT_TEMPLATES.has(task.templateId) && boardItems.length > 0) {
|
|
4740
|
-
const template = PLAN_TEMPLATES.has(task.templateId) ? "morning-plan" : "follow-up";
|
|
4741
|
-
const boardPrefix = formatBoardForPrompt(boardItems, template);
|
|
4742
|
-
prompt = boardPrefix + prompt;
|
|
4743
|
-
}
|
|
4744
|
-
inFlightClaudeTasks.add(task.taskId);
|
|
4745
|
-
claudeTaskConcurrency.set(codeName, (claudeTaskConcurrency.get(codeName) ?? 0) + 1);
|
|
4746
|
-
if (isScheduledViaKanbanEnabled() && isPlainScheduledTemplate(task.templateId)) {
|
|
4747
|
-
await fireScheduledTaskViaKanban(codeName, agent.agent_id, task, prompt);
|
|
4748
|
-
inFlightClaudeTasks.delete(task.taskId);
|
|
4749
|
-
claudeTaskConcurrency.set(codeName, Math.max(0, (claudeTaskConcurrency.get(codeName) ?? 1) - 1));
|
|
4750
|
-
continue;
|
|
4833
|
+
const deliveryPolicy = task.deliveryPolicy ?? "always";
|
|
4834
|
+
if (deliveryPolicy === "never" || deliveryPolicy === "conditional" && card.suppress_delivery !== false) {
|
|
4835
|
+
log(
|
|
4836
|
+
`[scheduled-kanban] Suppressed by delivery_policy=${deliveryPolicy} for card ${cardId} (task '${task.name}') on '${codeName}' \u2014 result recorded on the card only (tombstone)`
|
|
4837
|
+
);
|
|
4838
|
+
if (task.deliveryMode === "announce" && task.deliveryTo) {
|
|
4839
|
+
await reportDeliveryStatus(agentId, task.taskId, {
|
|
4840
|
+
status: "skipped",
|
|
4841
|
+
error_code: "SUPPRESSED_BY_POLICY"
|
|
4842
|
+
});
|
|
4751
4843
|
}
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
inFlightClaudeTasks.delete(task.taskId);
|
|
4755
|
-
claudeTaskConcurrency.set(codeName, Math.max(0, (claudeTaskConcurrency.get(codeName) ?? 1) - 1));
|
|
4756
|
-
});
|
|
4757
|
-
}
|
|
4758
|
-
}
|
|
4759
|
-
function deriveScheduledTaskNotify(task) {
|
|
4760
|
-
if (task.deliveryChannel !== "msteams") return {};
|
|
4761
|
-
const to = task.deliveryTo;
|
|
4762
|
-
let conversationId = null;
|
|
4763
|
-
if (typeof to === "string" && to.trim().length > 0) {
|
|
4764
|
-
conversationId = to.trim();
|
|
4765
|
-
} else if (to && typeof to === "object") {
|
|
4766
|
-
const cid = to.conversation_id;
|
|
4767
|
-
if (typeof cid === "string" && cid.trim().length > 0) conversationId = cid.trim();
|
|
4768
|
-
}
|
|
4769
|
-
if (!conversationId) return {};
|
|
4770
|
-
return { notify_channel: "msteams", notify_to: conversationId };
|
|
4771
|
-
}
|
|
4772
|
-
async function startRun(opts) {
|
|
4773
|
-
try {
|
|
4774
|
-
const res = await api.post(
|
|
4775
|
-
"/host/runs/start",
|
|
4776
|
-
opts
|
|
4777
|
-
);
|
|
4778
|
-
return { run_id: res.run_id ?? null, kanban_item_id: res.kanban_item_id ?? null };
|
|
4779
|
-
} catch (err) {
|
|
4780
|
-
const errText = err instanceof Error ? err.message : String(err);
|
|
4781
|
-
const errId = createHash5("sha256").update(errText).digest("hex").slice(0, 12);
|
|
4782
|
-
log(`[runs] start failed for agent_id=${opts.agent_id} source_type=${opts.source_type} error_id=${errId}`);
|
|
4783
|
-
return { run_id: null, kanban_item_id: null };
|
|
4784
|
-
}
|
|
4785
|
-
}
|
|
4786
|
-
async function finishRun(runId, outcome, options = {}) {
|
|
4787
|
-
try {
|
|
4788
|
-
await api.post("/host/runs/finish", {
|
|
4789
|
-
run_id: runId,
|
|
4790
|
-
outcome,
|
|
4791
|
-
outcome_message: options.outcomeMessage,
|
|
4792
|
-
metadata: options.metadata,
|
|
4793
|
-
complete_kanban_item_id: options.completeKanbanItemId ?? void 0,
|
|
4794
|
-
result: options.result
|
|
4795
|
-
});
|
|
4796
|
-
} catch (err) {
|
|
4797
|
-
const errText = err instanceof Error ? err.message : String(err);
|
|
4798
|
-
const errId = createHash5("sha256").update(errText).digest("hex").slice(0, 12);
|
|
4799
|
-
log(`[runs] finish failed for run_id=${runId} outcome=${outcome} error_id=${errId}`);
|
|
4800
|
-
}
|
|
4801
|
-
}
|
|
4802
|
-
var MAX_PRIOR_RUNS = 5;
|
|
4803
|
-
async function fetchPriorScheduledRuns(agentId, taskId) {
|
|
4804
|
-
try {
|
|
4805
|
-
const data = await api.post("/host/scheduled-tasks/recent-outputs", {
|
|
4806
|
-
agent_id: agentId,
|
|
4807
|
-
task_id: taskId,
|
|
4808
|
-
since_hours: 24,
|
|
4809
|
-
limit: MAX_PRIOR_RUNS
|
|
4810
|
-
});
|
|
4811
|
-
const rows = Array.isArray(data?.runs) ? data.runs.slice(0, MAX_PRIOR_RUNS) : [];
|
|
4812
|
-
return rows.filter((r) => typeof r.output_text === "string" && r.output_text.length > 0).map((r) => ({ startedAt: r.started_at, output: r.output_text }));
|
|
4813
|
-
} catch (err) {
|
|
4814
|
-
const errText = err instanceof Error ? err.message : String(err);
|
|
4815
|
-
const errId = createHash5("sha256").update(errText).digest("hex").slice(0, 12);
|
|
4816
|
-
log(`[runs] prior-runs lookup failed for task_id=${taskId} error_id=${errId}`);
|
|
4817
|
-
return [];
|
|
4818
|
-
}
|
|
4819
|
-
}
|
|
4820
|
-
function isScheduledViaKanbanEnabled() {
|
|
4821
|
-
const v = process.env["AGT_SCHEDULED_VIA_KANBAN"];
|
|
4822
|
-
return !(v === "0" || v?.toLowerCase() === "false");
|
|
4823
|
-
}
|
|
4824
|
-
var scheduledRunsByCode = /* @__PURE__ */ new Map();
|
|
4825
|
-
var claimedScheduledCards = /* @__PURE__ */ new Set();
|
|
4826
|
-
var completedScheduledCards = /* @__PURE__ */ new Set();
|
|
4827
|
-
function claimScheduledCardDelivery(cardId) {
|
|
4828
|
-
if (claimedScheduledCards.has(cardId)) return false;
|
|
4829
|
-
claimedScheduledCards.add(cardId);
|
|
4830
|
-
return true;
|
|
4831
|
-
}
|
|
4832
|
-
function releaseScheduledCardDelivery(cardId) {
|
|
4833
|
-
claimedScheduledCards.delete(cardId);
|
|
4834
|
-
}
|
|
4835
|
-
function markScheduledCardDeliveryComplete(cardId) {
|
|
4836
|
-
completedScheduledCards.add(cardId);
|
|
4837
|
-
}
|
|
4838
|
-
function trackScheduledRun(codeName, cardId, runId) {
|
|
4839
|
-
let m = scheduledRunsByCode.get(codeName);
|
|
4840
|
-
if (!m) {
|
|
4841
|
-
m = /* @__PURE__ */ new Map();
|
|
4842
|
-
scheduledRunsByCode.set(codeName, m);
|
|
4843
|
-
}
|
|
4844
|
-
m.set(cardId, runId);
|
|
4845
|
-
}
|
|
4846
|
-
function isScheduledCardTracked(cardId) {
|
|
4847
|
-
for (const m of scheduledRunsByCode.values()) {
|
|
4848
|
-
if (m.has(cardId)) return true;
|
|
4849
|
-
}
|
|
4850
|
-
return false;
|
|
4851
|
-
}
|
|
4852
|
-
function closeScheduledRunsForCode(codeName, outcome, reason) {
|
|
4853
|
-
const m = scheduledRunsByCode.get(codeName);
|
|
4854
|
-
if (!m || m.size === 0) return;
|
|
4855
|
-
scheduledRunsByCode.delete(codeName);
|
|
4856
|
-
for (const runId of m.values()) {
|
|
4857
|
-
void finishRun(runId, outcome, { outcomeMessage: reason });
|
|
4858
|
-
}
|
|
4859
|
-
}
|
|
4860
|
-
async function deliverScheduledCardResult(codeName, agentId, cardId) {
|
|
4861
|
-
if (!claimScheduledCardDelivery(cardId)) {
|
|
4862
|
-
return completedScheduledCards.has(cardId) ? "terminal" : "in_flight";
|
|
4863
|
-
}
|
|
4864
|
-
let card;
|
|
4865
|
-
try {
|
|
4866
|
-
const resp = await api.post("/host/kanban-item", {
|
|
4867
|
-
agent_id: agentId,
|
|
4868
|
-
item_id: cardId
|
|
4869
|
-
});
|
|
4870
|
-
card = resp.item;
|
|
4871
|
-
} catch (err) {
|
|
4872
|
-
if (err instanceof ApiError && err.status >= 400 && err.status < 500) {
|
|
4873
|
-
log(`[scheduled-kanban] delivery: card ${cardId} on '${codeName}' fetch returned ${err.status} (${err.message}) \u2014 treating as terminal, not retrying`);
|
|
4874
|
-
markScheduledCardDeliveryComplete(cardId);
|
|
4875
|
-
return "terminal";
|
|
4876
|
-
}
|
|
4877
|
-
releaseScheduledCardDelivery(cardId);
|
|
4878
|
-
log(`[scheduled-kanban] delivery fetch failed for card ${cardId} on '${codeName}': ${err.message} \u2014 will retry`);
|
|
4879
|
-
return "retry";
|
|
4880
|
-
}
|
|
4881
|
-
if (!card || card.status !== "done" || card.source_type !== "scheduled_task" || !card.source_ref) {
|
|
4882
|
-
markScheduledCardDeliveryComplete(cardId);
|
|
4883
|
-
return "terminal";
|
|
4884
|
-
}
|
|
4885
|
-
const state7 = claudeSchedulerStates.get(codeName) ?? loadSchedulerState(codeName);
|
|
4886
|
-
const task = state7.tasks[card.source_ref];
|
|
4887
|
-
if (!task) {
|
|
4888
|
-
log(`[scheduled-kanban] delivery: no scheduler task for source_ref=${card.source_ref} on '${codeName}' \u2014 skipping`);
|
|
4889
|
-
markScheduledCardDeliveryComplete(cardId);
|
|
4890
|
-
return "terminal";
|
|
4891
|
-
}
|
|
4892
|
-
if (!isPlainScheduledTemplate(task.templateId)) {
|
|
4893
|
-
log(`[scheduled-kanban] delivery: card ${cardId} template '${task.templateId}' on '${codeName}' is not a plain scheduled template \u2014 skipping manager delivery`);
|
|
4894
|
-
markScheduledCardDeliveryComplete(cardId);
|
|
4895
|
-
return "terminal";
|
|
4896
|
-
}
|
|
4897
|
-
const deliveryPolicy = task.deliveryPolicy ?? "always";
|
|
4898
|
-
if (deliveryPolicy === "never" || deliveryPolicy === "conditional" && card.suppress_delivery !== false) {
|
|
4899
|
-
log(
|
|
4900
|
-
`[scheduled-kanban] Suppressed by delivery_policy=${deliveryPolicy} for card ${cardId} (task '${task.name}') on '${codeName}' \u2014 result recorded on the card only (tombstone)`
|
|
4901
|
-
);
|
|
4902
|
-
if (task.deliveryMode === "announce" && task.deliveryTo) {
|
|
4903
|
-
await reportDeliveryStatus(agentId, task.taskId, {
|
|
4904
|
-
status: "skipped",
|
|
4905
|
-
error_code: "SUPPRESSED_BY_POLICY"
|
|
4906
|
-
});
|
|
4907
|
-
}
|
|
4908
|
-
markScheduledCardDeliveryComplete(cardId);
|
|
4909
|
-
return "delivered";
|
|
4844
|
+
markScheduledCardDeliveryComplete(cardId);
|
|
4845
|
+
return "delivered";
|
|
4910
4846
|
}
|
|
4911
4847
|
if (card.suppress_delivery === true) {
|
|
4912
4848
|
log(`[scheduled-kanban] Suppressing delivery (structured flag) for card ${cardId} (task '${task.name}') on '${codeName}' \u2014 result recorded on the card only`);
|
|
@@ -5022,6 +4958,149 @@ async function fireScheduledTaskViaKanban(codeName, agentId, task, prompt) {
|
|
|
5022
4958
|
}
|
|
5023
4959
|
return routed;
|
|
5024
4960
|
}
|
|
4961
|
+
async function processClaudeTaskResult(codeName, agentId, templateId, rawOutput, delivery) {
|
|
4962
|
+
try {
|
|
4963
|
+
const classification = classifyOutput(rawOutput);
|
|
4964
|
+
if (classification.action === "suppress") {
|
|
4965
|
+
const trimmed = (rawOutput ?? "").trim();
|
|
4966
|
+
const outputHash = trimmed.length === 0 ? "empty" : createHash6("sha256").update(trimmed).digest("hex").slice(0, 12);
|
|
4967
|
+
log(`[claude-scheduler] Suppressing delivery for '${codeName}' (template=${templateId}, task=${delivery?.taskId ?? "n/a"}) \u2014 output_len=${trimmed.length} output_hash=${outputHash}`);
|
|
4968
|
+
if (classification.suppressedNotes) {
|
|
4969
|
+
const notesHash = createHash6("sha256").update(classification.suppressedNotes).digest("hex").slice(0, 12);
|
|
4970
|
+
log(`[claude-scheduler] Suppressed notes for '${codeName}' (task=${delivery?.taskId ?? "n/a"}) \u2014 notes_len=${classification.suppressedNotes.length} notes_hash=${notesHash}`);
|
|
4971
|
+
}
|
|
4972
|
+
if (delivery?.mode === "announce" && delivery.to) {
|
|
4973
|
+
await reportDeliveryStatus(agentId, delivery.taskId, {
|
|
4974
|
+
status: "skipped",
|
|
4975
|
+
error_code: "NO_CONTENT"
|
|
4976
|
+
});
|
|
4977
|
+
}
|
|
4978
|
+
return;
|
|
4979
|
+
}
|
|
4980
|
+
const output = classification.deliverable;
|
|
4981
|
+
if (classification.action === "strip") {
|
|
4982
|
+
log(`[claude-scheduler] Stripped '<no-delivery/>' sentinel from '${codeName}' output (template=${templateId}, task=${delivery?.taskId ?? "n/a"}) \u2014 agent mixed it with real content; delivering the rest.`);
|
|
4983
|
+
}
|
|
4984
|
+
if (STANDUP_TEMPLATES.has(templateId)) {
|
|
4985
|
+
const standup = parseStandupSummary(output);
|
|
4986
|
+
await api.post("/host/agent-status", {
|
|
4987
|
+
agent_code_name: codeName,
|
|
4988
|
+
standup,
|
|
4989
|
+
current_status: "idle"
|
|
4990
|
+
});
|
|
4991
|
+
log(`[claude-scheduler] Standup posted for '${codeName}'`);
|
|
4992
|
+
} else if (TASK_UPDATE_TEMPLATES.has(templateId)) {
|
|
4993
|
+
await api.post("/host/agent-status", {
|
|
4994
|
+
agent_code_name: codeName,
|
|
4995
|
+
current_tasks: output.slice(0, 2e3)
|
|
4996
|
+
});
|
|
4997
|
+
log(`[claude-scheduler] Task update posted for '${codeName}'`);
|
|
4998
|
+
} else if (PLAN_TEMPLATES.has(templateId)) {
|
|
4999
|
+
const planItems = parsePlanItems(output);
|
|
5000
|
+
if (planItems.length > 0) {
|
|
5001
|
+
await api.post("/host/kanban", {
|
|
5002
|
+
agent_id: agentId,
|
|
5003
|
+
add: planItems
|
|
5004
|
+
});
|
|
5005
|
+
log(`[claude-scheduler] Plan items posted for '${codeName}' (${planItems.length} items)`);
|
|
5006
|
+
}
|
|
5007
|
+
} else if (KANBAN_WORK_TEMPLATES.has(templateId)) {
|
|
5008
|
+
const kanbanUpdates = parseKanbanUpdates(output);
|
|
5009
|
+
if (kanbanUpdates.length > 0) {
|
|
5010
|
+
await api.post("/host/kanban", {
|
|
5011
|
+
agent_id: agentId,
|
|
5012
|
+
update: kanbanUpdates
|
|
5013
|
+
});
|
|
5014
|
+
log(`[claude-scheduler] Kanban updates posted for '${codeName}' (${kanbanUpdates.length} updates)`);
|
|
5015
|
+
}
|
|
5016
|
+
}
|
|
5017
|
+
if (delivery?.mode === "announce" && delivery.to) {
|
|
5018
|
+
await deliverScheduledTaskOutput(
|
|
5019
|
+
codeName,
|
|
5020
|
+
agentId,
|
|
5021
|
+
delivery.to,
|
|
5022
|
+
output.slice(0, 4e3),
|
|
5023
|
+
delivery.taskId
|
|
5024
|
+
);
|
|
5025
|
+
}
|
|
5026
|
+
} catch (err) {
|
|
5027
|
+
log(`[claude-scheduler] Failed to post result for '${codeName}': ${err.message}`);
|
|
5028
|
+
}
|
|
5029
|
+
}
|
|
5030
|
+
|
|
5031
|
+
// src/lib/manager/scheduler/execution.ts
|
|
5032
|
+
import { createHash as createHash7 } from "crypto";
|
|
5033
|
+
import { readFileSync as readFileSync12, existsSync as existsSync7 } from "fs";
|
|
5034
|
+
import { homedir as homedir7 } from "os";
|
|
5035
|
+
import { join as join14 } from "path";
|
|
5036
|
+
function claudePidFilePath() {
|
|
5037
|
+
return join14(homedir7(), ".augmented", "manager-claude-pids.json");
|
|
5038
|
+
}
|
|
5039
|
+
var inFlightClaudePids = /* @__PURE__ */ new Map();
|
|
5040
|
+
function registerClaudeSpawn(record) {
|
|
5041
|
+
inFlightClaudePids.set(record.pid, record);
|
|
5042
|
+
try {
|
|
5043
|
+
addSpawn(claudePidFilePath(), record);
|
|
5044
|
+
} catch (err) {
|
|
5045
|
+
log(`[drain] pid-file write failed: ${err.message}`);
|
|
5046
|
+
}
|
|
5047
|
+
}
|
|
5048
|
+
function unregisterClaudeSpawn(pid) {
|
|
5049
|
+
inFlightClaudePids.delete(pid);
|
|
5050
|
+
try {
|
|
5051
|
+
removeSpawn(claudePidFilePath(), pid);
|
|
5052
|
+
} catch {
|
|
5053
|
+
}
|
|
5054
|
+
}
|
|
5055
|
+
async function syncAndCheckClaudeScheduler(agent, tasks, boardItems, refreshData) {
|
|
5056
|
+
const codeName = agent.code_name;
|
|
5057
|
+
const stableTasksHash = createHash7("sha256").update(JSON.stringify(tasks)).digest("hex").slice(0, 16);
|
|
5058
|
+
const boardHash = boardItems.length > 0 ? createHash7("sha256").update(JSON.stringify(boardItems.map((b) => ({ id: b.id, title: b.title, status: b.status, priority: b.priority, deliverable: b.deliverable })))).digest("hex").slice(0, 16) : "empty";
|
|
5059
|
+
const resolvedModels = resolveModelChain(refreshData);
|
|
5060
|
+
const modelsHash = createHash7("sha256").update(JSON.stringify(resolvedModels)).digest("hex").slice(0, 16);
|
|
5061
|
+
const combinedHash = `${stableTasksHash}:${boardHash}:${modelsHash}`;
|
|
5062
|
+
const prevHash = agentState.knownTasksHashes.get(agent.agent_id);
|
|
5063
|
+
if (combinedHash !== prevHash) {
|
|
5064
|
+
const taskInputs = tasks.map((t) => buildSchedulerTaskInput(t));
|
|
5065
|
+
const state8 = syncTasksToScheduler(codeName, agent.agent_id, taskInputs);
|
|
5066
|
+
claudeSchedulerStates.set(codeName, state8);
|
|
5067
|
+
agentState.knownTasksHashes.set(agent.agent_id, combinedHash);
|
|
5068
|
+
log(`[claude-scheduler] Tasks synced for '${codeName}' (${taskInputs.length} task(s))`);
|
|
5069
|
+
}
|
|
5070
|
+
if (!claudeSchedulerStates.has(codeName)) {
|
|
5071
|
+
claudeSchedulerStates.set(codeName, loadSchedulerState(codeName));
|
|
5072
|
+
}
|
|
5073
|
+
const state7 = claudeSchedulerStates.get(codeName);
|
|
5074
|
+
const ready = getReadyTasks(state7, inFlightClaudeTasks);
|
|
5075
|
+
if (ready.length === 0) return;
|
|
5076
|
+
for (const task of ready) {
|
|
5077
|
+
if ((claudeTaskConcurrency.get(codeName) ?? 0) >= MAX_CLAUDE_CONCURRENCY) break;
|
|
5078
|
+
if (KANBAN_WORK_TEMPLATES.has(task.templateId)) {
|
|
5079
|
+
const updated = markTaskFired(codeName, task.taskId, "ok");
|
|
5080
|
+
claudeSchedulerStates.set(codeName, updated);
|
|
5081
|
+
continue;
|
|
5082
|
+
}
|
|
5083
|
+
let prompt = task.prompt;
|
|
5084
|
+
if (BOARD_INJECT_TEMPLATES.has(task.templateId) && boardItems.length > 0) {
|
|
5085
|
+
const template = PLAN_TEMPLATES.has(task.templateId) ? "morning-plan" : "follow-up";
|
|
5086
|
+
const boardPrefix = formatBoardForPrompt(boardItems, template);
|
|
5087
|
+
prompt = boardPrefix + prompt;
|
|
5088
|
+
}
|
|
5089
|
+
inFlightClaudeTasks.add(task.taskId);
|
|
5090
|
+
claudeTaskConcurrency.set(codeName, (claudeTaskConcurrency.get(codeName) ?? 0) + 1);
|
|
5091
|
+
if (isScheduledViaKanbanEnabled() && isPlainScheduledTemplate(task.templateId)) {
|
|
5092
|
+
await fireScheduledTaskViaKanban(codeName, agent.agent_id, task, prompt);
|
|
5093
|
+
inFlightClaudeTasks.delete(task.taskId);
|
|
5094
|
+
claudeTaskConcurrency.set(codeName, Math.max(0, (claudeTaskConcurrency.get(codeName) ?? 1) - 1));
|
|
5095
|
+
continue;
|
|
5096
|
+
}
|
|
5097
|
+
log(`[claude-scheduler] Firing '${task.name}' for '${codeName}'`);
|
|
5098
|
+
executeAndProcessClaudeTask(codeName, agent.agent_id, task, prompt).finally(() => {
|
|
5099
|
+
inFlightClaudeTasks.delete(task.taskId);
|
|
5100
|
+
claudeTaskConcurrency.set(codeName, Math.max(0, (claudeTaskConcurrency.get(codeName) ?? 1) - 1));
|
|
5101
|
+
});
|
|
5102
|
+
}
|
|
5103
|
+
}
|
|
5025
5104
|
async function executeAndProcessClaudeTask(codeName, agentId, task, prompt) {
|
|
5026
5105
|
const projectDir = getProjectDir2(codeName);
|
|
5027
5106
|
const mcpConfigPath = join14(projectDir, ".mcp.json");
|
|
@@ -5159,75 +5238,6 @@ async function executeAndProcessClaudeTask(codeName, agentId, task, prompt) {
|
|
|
5159
5238
|
claudeSchedulerStates.set(codeName, updated);
|
|
5160
5239
|
}
|
|
5161
5240
|
}
|
|
5162
|
-
async function processClaudeTaskResult(codeName, agentId, templateId, rawOutput, delivery) {
|
|
5163
|
-
try {
|
|
5164
|
-
const classification = classifyOutput(rawOutput);
|
|
5165
|
-
if (classification.action === "suppress") {
|
|
5166
|
-
const trimmed = (rawOutput ?? "").trim();
|
|
5167
|
-
const outputHash = trimmed.length === 0 ? "empty" : createHash5("sha256").update(trimmed).digest("hex").slice(0, 12);
|
|
5168
|
-
log(`[claude-scheduler] Suppressing delivery for '${codeName}' (template=${templateId}, task=${delivery?.taskId ?? "n/a"}) \u2014 output_len=${trimmed.length} output_hash=${outputHash}`);
|
|
5169
|
-
if (classification.suppressedNotes) {
|
|
5170
|
-
const notesHash = createHash5("sha256").update(classification.suppressedNotes).digest("hex").slice(0, 12);
|
|
5171
|
-
log(`[claude-scheduler] Suppressed notes for '${codeName}' (task=${delivery?.taskId ?? "n/a"}) \u2014 notes_len=${classification.suppressedNotes.length} notes_hash=${notesHash}`);
|
|
5172
|
-
}
|
|
5173
|
-
if (delivery?.mode === "announce" && delivery.to) {
|
|
5174
|
-
await reportDeliveryStatus(agentId, delivery.taskId, {
|
|
5175
|
-
status: "skipped",
|
|
5176
|
-
error_code: "NO_CONTENT"
|
|
5177
|
-
});
|
|
5178
|
-
}
|
|
5179
|
-
return;
|
|
5180
|
-
}
|
|
5181
|
-
const output = classification.deliverable;
|
|
5182
|
-
if (classification.action === "strip") {
|
|
5183
|
-
log(`[claude-scheduler] Stripped '<no-delivery/>' sentinel from '${codeName}' output (template=${templateId}, task=${delivery?.taskId ?? "n/a"}) \u2014 agent mixed it with real content; delivering the rest.`);
|
|
5184
|
-
}
|
|
5185
|
-
if (STANDUP_TEMPLATES.has(templateId)) {
|
|
5186
|
-
const standup = parseStandupSummary(output);
|
|
5187
|
-
await api.post("/host/agent-status", {
|
|
5188
|
-
agent_code_name: codeName,
|
|
5189
|
-
standup,
|
|
5190
|
-
current_status: "idle"
|
|
5191
|
-
});
|
|
5192
|
-
log(`[claude-scheduler] Standup posted for '${codeName}'`);
|
|
5193
|
-
} else if (TASK_UPDATE_TEMPLATES.has(templateId)) {
|
|
5194
|
-
await api.post("/host/agent-status", {
|
|
5195
|
-
agent_code_name: codeName,
|
|
5196
|
-
current_tasks: output.slice(0, 2e3)
|
|
5197
|
-
});
|
|
5198
|
-
log(`[claude-scheduler] Task update posted for '${codeName}'`);
|
|
5199
|
-
} else if (PLAN_TEMPLATES.has(templateId)) {
|
|
5200
|
-
const planItems = parsePlanItems(output);
|
|
5201
|
-
if (planItems.length > 0) {
|
|
5202
|
-
await api.post("/host/kanban", {
|
|
5203
|
-
agent_id: agentId,
|
|
5204
|
-
add: planItems
|
|
5205
|
-
});
|
|
5206
|
-
log(`[claude-scheduler] Plan items posted for '${codeName}' (${planItems.length} items)`);
|
|
5207
|
-
}
|
|
5208
|
-
} else if (KANBAN_WORK_TEMPLATES.has(templateId)) {
|
|
5209
|
-
const kanbanUpdates = parseKanbanUpdates(output);
|
|
5210
|
-
if (kanbanUpdates.length > 0) {
|
|
5211
|
-
await api.post("/host/kanban", {
|
|
5212
|
-
agent_id: agentId,
|
|
5213
|
-
update: kanbanUpdates
|
|
5214
|
-
});
|
|
5215
|
-
log(`[claude-scheduler] Kanban updates posted for '${codeName}' (${kanbanUpdates.length} updates)`);
|
|
5216
|
-
}
|
|
5217
|
-
}
|
|
5218
|
-
if (delivery?.mode === "announce" && delivery.to) {
|
|
5219
|
-
await deliverScheduledTaskOutput(
|
|
5220
|
-
codeName,
|
|
5221
|
-
agentId,
|
|
5222
|
-
delivery.to,
|
|
5223
|
-
output.slice(0, 4e3),
|
|
5224
|
-
delivery.taskId
|
|
5225
|
-
);
|
|
5226
|
-
}
|
|
5227
|
-
} catch (err) {
|
|
5228
|
-
log(`[claude-scheduler] Failed to post result for '${codeName}': ${err.message}`);
|
|
5229
|
-
}
|
|
5230
|
-
}
|
|
5231
5241
|
|
|
5232
5242
|
// src/lib/wedge-detection.ts
|
|
5233
5243
|
var DEFAULTS = {
|
|
@@ -6438,7 +6448,7 @@ var runningChannelSecretHashes = /* @__PURE__ */ new Map();
|
|
|
6438
6448
|
function projectMcpHash(_codeName, projectDir) {
|
|
6439
6449
|
try {
|
|
6440
6450
|
const raw = readFileSync14(join16(projectDir, ".mcp.json"), "utf-8");
|
|
6441
|
-
return
|
|
6451
|
+
return createHash8("sha256").update(canonicalJson(JSON.parse(raw))).digest("hex");
|
|
6442
6452
|
} catch {
|
|
6443
6453
|
return null;
|
|
6444
6454
|
}
|
|
@@ -6797,7 +6807,7 @@ var cachedMaintenanceWindow = null;
|
|
|
6797
6807
|
var lastVersionCheckAt = 0;
|
|
6798
6808
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
6799
6809
|
var lastResponsivenessProbeAt = 0;
|
|
6800
|
-
var agtCliVersion = true ? "0.28.
|
|
6810
|
+
var agtCliVersion = true ? "0.28.38" : "dev";
|
|
6801
6811
|
function resolveBrewPath(execFileSync4) {
|
|
6802
6812
|
try {
|
|
6803
6813
|
const out = execFileSync4("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
@@ -7936,7 +7946,7 @@ async function pollCycle() {
|
|
|
7936
7946
|
claudeAuth = await detectClaudeAuth();
|
|
7937
7947
|
} catch (err) {
|
|
7938
7948
|
const errText = err instanceof Error ? err.message : String(err);
|
|
7939
|
-
const errId =
|
|
7949
|
+
const errId = createHash8("sha256").update(errText).digest("hex").slice(0, 12);
|
|
7940
7950
|
log(`Claude auth detection failed (error_id=${errId})`);
|
|
7941
7951
|
}
|
|
7942
7952
|
const hostHasClaudeCode = state6.agents.some(
|
|
@@ -9021,7 +9031,7 @@ async function processAgent(agent, agentStates) {
|
|
|
9021
9031
|
const sessionModeForHash = refreshData.agent.session_mode;
|
|
9022
9032
|
const senderPolicyForHash = refreshData.sender_policy ?? null;
|
|
9023
9033
|
const CHANNEL_WRITE_VERSION = 9;
|
|
9024
|
-
const configHash =
|
|
9034
|
+
const configHash = createHash8("sha256").update(
|
|
9025
9035
|
canonicalJson({
|
|
9026
9036
|
writeVersion: CHANNEL_WRITE_VERSION,
|
|
9027
9037
|
config: entry.config,
|
|
@@ -9135,7 +9145,7 @@ async function processAgent(agent, agentStates) {
|
|
|
9135
9145
|
if (channelConfigConverged) {
|
|
9136
9146
|
const hasSenderPolicyChannel = currentChannelIds.has("slack") || currentChannelIds.has("msteams");
|
|
9137
9147
|
const senderPolicyForRestartHash = refreshData.sender_policy ?? null;
|
|
9138
|
-
const senderPolicyHash =
|
|
9148
|
+
const senderPolicyHash = createHash8("sha256").update(canonicalJson({ senderPolicy: senderPolicyForRestartHash })).digest("hex");
|
|
9139
9149
|
const prevSenderPolicyHash = agentState.knownSenderPolicyHashes.get(agent.agent_id);
|
|
9140
9150
|
const senderPolicyDecision = hasSenderPolicyChannel ? decideSenderPolicyRestart({
|
|
9141
9151
|
previousHash: prevSenderPolicyHash,
|
|
@@ -9177,7 +9187,7 @@ async function processAgent(agent, agentStates) {
|
|
|
9177
9187
|
const behaviourSubset = extractMsTeamsBehaviourSubset(
|
|
9178
9188
|
msteamsEntry?.config
|
|
9179
9189
|
);
|
|
9180
|
-
const behaviourHash =
|
|
9190
|
+
const behaviourHash = createHash8("sha256").update(canonicalJson(behaviourSubset)).digest("hex");
|
|
9181
9191
|
const prevBehaviourHash = agentState.knownMsTeamsBehaviourHashes.get(agent.agent_id);
|
|
9182
9192
|
const behaviourDecision = decideSenderPolicyRestart({
|
|
9183
9193
|
previousHash: prevBehaviourHash,
|
|
@@ -9224,7 +9234,7 @@ async function processAgent(agent, agentStates) {
|
|
|
9224
9234
|
const slackBehaviourSubset = extractSlackBehaviourSubset(
|
|
9225
9235
|
slackEntry?.config
|
|
9226
9236
|
);
|
|
9227
|
-
const slackBehaviourHash =
|
|
9237
|
+
const slackBehaviourHash = createHash8("sha256").update(canonicalJson(slackBehaviourSubset)).digest("hex");
|
|
9228
9238
|
const prevSlackBehaviourHash = agentState.knownSlackBehaviourHashes.get(agent.agent_id);
|
|
9229
9239
|
const slackBehaviourDecision = decideSenderPolicyRestart({
|
|
9230
9240
|
previousHash: prevSlackBehaviourHash,
|
|
@@ -9468,10 +9478,10 @@ async function processAgent(agent, agentStates) {
|
|
|
9468
9478
|
desiredEntries.push({ serverId, url, headers: mcpHeaders, name: tk.toolkit_name });
|
|
9469
9479
|
}
|
|
9470
9480
|
const hashBasis = desiredEntries.slice().sort((a, b) => a.serverId.localeCompare(b.serverId)).map((e) => {
|
|
9471
|
-
const headersHash =
|
|
9481
|
+
const headersHash = createHash8("sha256").update(canonicalJson(e.headers ?? {})).digest("hex").slice(0, 16);
|
|
9472
9482
|
return `${e.serverId}|${e.url}|${headersHash}`;
|
|
9473
9483
|
}).join("\n");
|
|
9474
|
-
const mcpHash =
|
|
9484
|
+
const mcpHash = createHash8("sha256").update(hashBasis).digest("hex").slice(0, 16);
|
|
9475
9485
|
const prevMcpHash = agentState.knownManagedMcpHashes.get(agent.agent_id);
|
|
9476
9486
|
const structureHash = managedMcpStructureHash(desiredEntries);
|
|
9477
9487
|
const prevStructureHash = agentState.knownManagedMcpStructure.get(agent.agent_id);
|
|
@@ -9486,7 +9496,7 @@ async function processAgent(agent, agentStates) {
|
|
|
9486
9496
|
if (mcpHash !== prevMcpHash) {
|
|
9487
9497
|
for (const e of desiredEntries) {
|
|
9488
9498
|
frameworkAdapter.writeMcpServer(agent.code_name, e.serverId, { url: e.url, headers: e.headers });
|
|
9489
|
-
const urlHash =
|
|
9499
|
+
const urlHash = createHash8("sha256").update(e.url).digest("hex").slice(0, 12);
|
|
9490
9500
|
log(`[managed-toolkit] ${agent.code_name}: wrote '${e.name}' (serverId=${e.serverId}, url_hash=${urlHash})`);
|
|
9491
9501
|
}
|
|
9492
9502
|
if (frameworkAdapter.removeMcpServer && frameworkAdapter.getMcpPath) {
|
|
@@ -9620,7 +9630,7 @@ async function processAgent(agent, agentStates) {
|
|
|
9620
9630
|
if (frameworkAdapter.installSkillFiles) {
|
|
9621
9631
|
const currentIntegrationSkillIds = /* @__PURE__ */ new Set();
|
|
9622
9632
|
const installedIntegrationSkills = [];
|
|
9623
|
-
const { createHash:
|
|
9633
|
+
const { createHash: createHash9 } = await import("crypto");
|
|
9624
9634
|
const refreshAny = refreshData;
|
|
9625
9635
|
const contexts = refreshAny.integration_contexts ?? refreshAny.plugin_contexts ?? [];
|
|
9626
9636
|
const contextBySlug = /* @__PURE__ */ new Map();
|
|
@@ -9649,7 +9659,7 @@ async function processAgent(agent, agentStates) {
|
|
|
9649
9659
|
)
|
|
9650
9660
|
}));
|
|
9651
9661
|
const bundle = buildIntegrationBundle(renderedScopes);
|
|
9652
|
-
const contentHash =
|
|
9662
|
+
const contentHash = createHash9("sha256").update(bundleFingerprint(bundle.files)).digest("hex").slice(0, 12);
|
|
9653
9663
|
const hashKey = `plugin-skill:${agent.agent_id}:${integrationSkillId}`;
|
|
9654
9664
|
if (agentState.knownSkillHashes.get(hashKey) === contentHash) continue;
|
|
9655
9665
|
frameworkAdapter.installSkillFiles(agent.code_name, integrationSkillId, bundle.files);
|
|
@@ -9708,7 +9718,7 @@ async function processAgent(agent, agentStates) {
|
|
|
9708
9718
|
const plan = planGlobalSkillSync(
|
|
9709
9719
|
refreshAny.global_skills ?? [],
|
|
9710
9720
|
agentState.knownGlobalSkillIds.get(agent.agent_id) ?? /* @__PURE__ */ new Set(),
|
|
9711
|
-
(content) =>
|
|
9721
|
+
(content) => createHash9("sha256").update(content).digest("hex").slice(0, 12),
|
|
9712
9722
|
(skillId) => agentState.knownSkillHashes.get(`global-skill:${agent.agent_id}:${skillId}`)
|
|
9713
9723
|
);
|
|
9714
9724
|
for (const { skillId, content, hash } of plan.installs) {
|
|
@@ -9752,7 +9762,7 @@ async function processAgent(agent, agentStates) {
|
|
|
9752
9762
|
const slug = hook.integration_slug ?? hook.plugin_slug;
|
|
9753
9763
|
if (!slug) continue;
|
|
9754
9764
|
try {
|
|
9755
|
-
const scriptHash =
|
|
9765
|
+
const scriptHash = createHash9("sha256").update(hook.script).digest("hex").slice(0, 12);
|
|
9756
9766
|
const hookKey = `${agent.agent_id}:${frameworkAdapter.id}:plugin-hook:${slug}:on_install`;
|
|
9757
9767
|
if (agentState.knownSkillHashes.get(hookKey) === scriptHash) continue;
|
|
9758
9768
|
const result = await frameworkAdapter.executePluginHook({
|
|
@@ -9767,9 +9777,9 @@ async function processAgent(agent, agentStates) {
|
|
|
9767
9777
|
} else if (result.timedOut) {
|
|
9768
9778
|
log(`Integration hook on_install '${slug}' TIMED OUT for '${agent.code_name}' after ${result.durationMs}ms`);
|
|
9769
9779
|
} else {
|
|
9770
|
-
const stderrHash =
|
|
9780
|
+
const stderrHash = createHash9("sha256").update(result.stderr).digest("hex").slice(0, 12);
|
|
9771
9781
|
const missingCmd = result.exitCode === 127 ? extractCommandNotFound(result.stderr) : null;
|
|
9772
|
-
const missingCmdHash = missingCmd ?
|
|
9782
|
+
const missingCmdHash = missingCmd ? createHash9("sha256").update(missingCmd).digest("hex").slice(0, 8) : null;
|
|
9773
9783
|
log(
|
|
9774
9784
|
`Integration hook on_install '${slug}' exited ${result.exitCode} for '${agent.code_name}' ` + (missingCmdHash ? `[missing_command_hash=${missingCmdHash}] ` : "") + `[stderr_hash=${stderrHash} stderr_len=${result.stderr.length}]`
|
|
9775
9785
|
);
|
|
@@ -10038,10 +10048,10 @@ async function processAgent(agent, agentStates) {
|
|
|
10038
10048
|
} else if (agentFw === "claude-code" && tasks.length > 0) {
|
|
10039
10049
|
await syncAndCheckClaudeScheduler(agent, tasks, boardItems, refreshData);
|
|
10040
10050
|
} else if (frameworkAdapter.syncScheduledTasks && gatewayRunning && gatewayPort) {
|
|
10041
|
-
const stableTasksHash =
|
|
10042
|
-
const boardHash = boardItems.length > 0 ?
|
|
10051
|
+
const stableTasksHash = createHash8("sha256").update(JSON.stringify(tasks)).digest("hex").slice(0, 16);
|
|
10052
|
+
const boardHash = boardItems.length > 0 ? createHash8("sha256").update(JSON.stringify(boardItems.map((b) => ({ id: b.id, title: b.title, status: b.status, priority: b.priority, deliverable: b.deliverable })))).digest("hex").slice(0, 16) : "empty";
|
|
10043
10053
|
const resolvedModels = resolveModelChain(refreshData);
|
|
10044
|
-
const modelsHash =
|
|
10054
|
+
const modelsHash = createHash8("sha256").update(JSON.stringify(resolvedModels)).digest("hex").slice(0, 16);
|
|
10045
10055
|
const combinedHash = `${stableTasksHash}:${boardHash}:${modelsHash}`;
|
|
10046
10056
|
const prevTasksHash = agentState.knownTasksHashes.get(agent.agent_id);
|
|
10047
10057
|
if (combinedHash !== prevTasksHash) {
|
|
@@ -10532,7 +10542,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
|
10532
10542
|
const ctx = getLastFailureContext(codeName);
|
|
10533
10543
|
const recovery = prepareForRespawn(codeName);
|
|
10534
10544
|
const tailSummary = !ctx.tail ? "" : KNOWN_SAFE_TAIL_SIGNATURES.has(ctx.signature) ? `; last pane output (${PANE_TAIL_PREVIEW_LINES} of ~20 lines):
|
|
10535
|
-
${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${
|
|
10545
|
+
${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash8("sha256").update(ctx.tail).digest("hex").slice(0, 12)} (raw at ~/.augmented/${codeName}/pane.log)`;
|
|
10536
10546
|
const sigSummary = ctx.signature !== "unknown" ? `; signature=${ctx.signature}` : "";
|
|
10537
10547
|
const recoverySummary = recovery ? `; recovery=${recovery}` : "";
|
|
10538
10548
|
log(
|
|
@@ -10546,7 +10556,7 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash6("sha256").
|
|
|
10546
10556
|
);
|
|
10547
10557
|
getHostId().then((hostId) => {
|
|
10548
10558
|
if (!hostId) return;
|
|
10549
|
-
const paneTailHash = zombie.paneTail ? `sha256:${
|
|
10559
|
+
const paneTailHash = zombie.paneTail ? `sha256:${createHash8("sha256").update(zombie.paneTail).digest("hex").slice(0, 12)}` : null;
|
|
10550
10560
|
return api.post("/host/events", {
|
|
10551
10561
|
host_id: hostId,
|
|
10552
10562
|
agent_code_name: codeName,
|
|
@@ -10625,7 +10635,7 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash6("sha256").
|
|
|
10625
10635
|
if (!claudeAuthTupleBySession.has(codeName)) {
|
|
10626
10636
|
claudeAuthTupleBySession.set(codeName, currentAuthTuple);
|
|
10627
10637
|
}
|
|
10628
|
-
const stableTasksHash =
|
|
10638
|
+
const stableTasksHash = createHash8("sha256").update(JSON.stringify(tasks)).digest("hex").slice(0, 16);
|
|
10629
10639
|
const prevHash = agentState.knownTasksHashes.get(agent.agent_id);
|
|
10630
10640
|
if (stableTasksHash !== prevHash) {
|
|
10631
10641
|
const taskInputs = tasks.map((t) => buildSchedulerTaskInput(t));
|
|
@@ -11151,7 +11161,7 @@ ${escapeXml(msg.content)}
|
|
|
11151
11161
|
log(`[direct-chat] Reply sent for '${agent.codeName}'`);
|
|
11152
11162
|
} catch (err) {
|
|
11153
11163
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
11154
|
-
const errorId =
|
|
11164
|
+
const errorId = createHash8("sha256").update(errMsg).digest("hex").slice(0, 12);
|
|
11155
11165
|
log(`[direct-chat] Failed to process message for '${agent.codeName}': error_id=${errorId} error=${errMsg.slice(0, 500)}`);
|
|
11156
11166
|
try {
|
|
11157
11167
|
await api.post("/host/direct-chat/reply", {
|
|
@@ -11707,7 +11717,7 @@ async function syncMemories(agent, configDir, log2) {
|
|
|
11707
11717
|
if (!file.endsWith(".md")) continue;
|
|
11708
11718
|
try {
|
|
11709
11719
|
const raw = readFileSync14(join16(memoryDir, file), "utf-8");
|
|
11710
|
-
const fileHash =
|
|
11720
|
+
const fileHash = createHash8("sha256").update(raw).digest("hex").slice(0, 16);
|
|
11711
11721
|
currentHashes.set(file, fileHash);
|
|
11712
11722
|
if (prevHashes.get(file) === fileHash) continue;
|
|
11713
11723
|
const parsed = parseMemoryFile(raw, file.replace(/\.md$/, ""));
|
|
@@ -11745,14 +11755,14 @@ async function syncMemories(agent, configDir, log2) {
|
|
|
11745
11755
|
}
|
|
11746
11756
|
async function downloadMemories(agent, memoryDir, log2, { force }) {
|
|
11747
11757
|
const localFiles = existsSync9(memoryDir) ? readdirSync5(memoryDir).filter((f) => f.endsWith(".md")).sort() : [];
|
|
11748
|
-
const localListHash =
|
|
11758
|
+
const localListHash = createHash8("sha256").update(localFiles.join(",")).digest("hex").slice(0, 16);
|
|
11749
11759
|
const prevLocalHash = lastLocalFileHash.get(agent.agent_id);
|
|
11750
11760
|
const prevDownload = lastDownloadHash.get(agent.agent_id);
|
|
11751
11761
|
try {
|
|
11752
11762
|
const dbMemories = await api.post("/host/memories", {
|
|
11753
11763
|
agent_id: agent.agent_id
|
|
11754
11764
|
});
|
|
11755
|
-
const responseHash =
|
|
11765
|
+
const responseHash = createHash8("sha256").update(JSON.stringify(dbMemories.memories ?? [])).digest("hex").slice(0, 16);
|
|
11756
11766
|
if (!force && prevDownload && prevLocalHash === localListHash && lastDownloadHash.get(agent.agent_id) === responseHash) {
|
|
11757
11767
|
return true;
|
|
11758
11768
|
}
|
|
@@ -11791,7 +11801,7 @@ ${mem.content}
|
|
|
11791
11801
|
}
|
|
11792
11802
|
if (written > 0 || overwritten > 0) {
|
|
11793
11803
|
const updatedFiles = readdirSync5(memoryDir).filter((f) => f.endsWith(".md")).sort();
|
|
11794
|
-
lastLocalFileHash.set(agent.agent_id,
|
|
11804
|
+
lastLocalFileHash.set(agent.agent_id, createHash8("sha256").update(updatedFiles.join(",")).digest("hex").slice(0, 16));
|
|
11795
11805
|
log2(`Memory download for '${agent.code_name}': wrote ${written} new, overwrote ${overwritten} stale`);
|
|
11796
11806
|
}
|
|
11797
11807
|
}
|
|
@@ -12228,7 +12238,7 @@ function deployMcpAssets() {
|
|
|
12228
12238
|
const fileHash = (p) => {
|
|
12229
12239
|
try {
|
|
12230
12240
|
if (!existsSync9(p)) return null;
|
|
12231
|
-
return
|
|
12241
|
+
return createHash8("sha256").update(readFileSync14(p)).digest("hex");
|
|
12232
12242
|
} catch {
|
|
12233
12243
|
return null;
|
|
12234
12244
|
}
|