opencode-swarm 7.79.7 → 7.80.0
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/.opencode/skills/brainstorm/SKILL.md +1 -1
- package/.opencode/skills/codebase-review-swarm/SKILL.md +6 -0
- package/.opencode/skills/codebase-review-swarm/references/review-protocol-v8.2.md +5 -5
- package/.opencode/skills/council/SKILL.md +10 -3
- package/.opencode/skills/deep-dive/SKILL.md +4 -2
- package/.opencode/skills/deep-research/SKILL.md +13 -5
- package/.opencode/skills/plan/SKILL.md +1 -1
- package/.opencode/skills/specify/SKILL.md +1 -1
- package/.opencode/skills/swarm-pr-feedback/SKILL.md +18 -0
- package/.opencode/skills/swarm-pr-review/SKILL.md +5 -5
- package/dist/background/completion-observer.d.ts +5 -5
- package/dist/background/pending-delegations.d.ts +58 -17
- package/dist/background/task-envelope.d.ts +5 -0
- package/dist/cli/index.js +183 -16
- package/dist/config/schema.d.ts +2 -0
- package/dist/hooks/knowledge-curator.d.ts +2 -0
- package/dist/hooks/knowledge-types.d.ts +1 -0
- package/dist/index.js +884 -66
- package/dist/memory/schema.d.ts +4 -4
- package/dist/tools/dispatch-lanes.d.ts +118 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/manifest.d.ts +2 -0
- package/dist/tools/tool-metadata.d.ts +8 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -69,7 +69,7 @@ var package_default;
|
|
|
69
69
|
var init_package = __esm(() => {
|
|
70
70
|
package_default = {
|
|
71
71
|
name: "opencode-swarm",
|
|
72
|
-
version: "7.
|
|
72
|
+
version: "7.80.0",
|
|
73
73
|
description: "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
|
|
74
74
|
main: "dist/index.js",
|
|
75
75
|
types: "dist/index.d.ts",
|
|
@@ -683,6 +683,14 @@ var init_tool_metadata = __esm(() => {
|
|
|
683
683
|
description: "dispatch multiple read-only exploration/review lanes concurrently and return a structured join result",
|
|
684
684
|
agents: ["architect"]
|
|
685
685
|
},
|
|
686
|
+
dispatch_lanes_async: {
|
|
687
|
+
description: "launch multiple read-only advisory lanes asynchronously and return a batch id for later collection",
|
|
688
|
+
agents: ["architect"]
|
|
689
|
+
},
|
|
690
|
+
collect_lane_results: {
|
|
691
|
+
description: "collect or poll results for a dispatch_lanes_async advisory batch without advancing workflow gates",
|
|
692
|
+
agents: ["architect"]
|
|
693
|
+
},
|
|
686
694
|
summarize_work: {
|
|
687
695
|
description: "emit a short structured summary of completed work (key decisions, assumptions, risks, constraints) at task completion; rolls up per phase for architecture-supervisor review. Advisory, never blocks.",
|
|
688
696
|
agents: [
|
|
@@ -15967,7 +15975,8 @@ var init_schema = __esm(() => {
|
|
|
15967
15975
|
}).optional(),
|
|
15968
15976
|
enrichment: exports_external.object({
|
|
15969
15977
|
max_calls_per_day: exports_external.number().int().min(0).max(1000).default(30),
|
|
15970
|
-
quota_window: exports_external.enum(["utc", "local"]).default("utc")
|
|
15978
|
+
quota_window: exports_external.enum(["utc", "local"]).default("utc"),
|
|
15979
|
+
batch_size: exports_external.number().int().min(1).max(100).optional()
|
|
15971
15980
|
}).default({ max_calls_per_day: 30, quota_window: "utc" })
|
|
15972
15981
|
});
|
|
15973
15982
|
MemoryConfigSchema = exports_external.object({
|
|
@@ -23590,6 +23599,21 @@ function rememberStandardWorktreeDispatch(dispatch) {
|
|
|
23590
23599
|
function hasStandardWorktreeDispatchCapacity() {
|
|
23591
23600
|
return standardWorktreeByCallID.size < MAX_TRACKED_STANDARD_WORKTREE_CALLS;
|
|
23592
23601
|
}
|
|
23602
|
+
function hasInFlightStandardWorktreeDispatch(parentSessionID) {
|
|
23603
|
+
for (const dispatch of standardWorktreeByCallID.values()) {
|
|
23604
|
+
if (dispatch.parentSessionID === parentSessionID)
|
|
23605
|
+
return true;
|
|
23606
|
+
}
|
|
23607
|
+
return false;
|
|
23608
|
+
}
|
|
23609
|
+
function handleStandardWorktreeFailure(parentSessionID, policy, message) {
|
|
23610
|
+
if (policy === "required")
|
|
23611
|
+
throw new Error(message);
|
|
23612
|
+
if (hasInFlightStandardWorktreeDispatch(parentSessionID)) {
|
|
23613
|
+
throw new Error(`STANDARD_WORKTREE_ISOLATION_UNSAFE: ${message} ` + `Sibling coder task(s) are isolated in worktrees for this session, so ` + `dispatching this coder un-isolated in the main tree would risk a ` + `merge-back collision. Wait for in-flight coder task(s) to be reviewed ` + `and merged, then retry.`);
|
|
23614
|
+
}
|
|
23615
|
+
serializeStandardWorktreeDispatches(parentSessionID, message);
|
|
23616
|
+
}
|
|
23593
23617
|
function serializeStandardWorktreeDispatches(sessionID, message) {
|
|
23594
23618
|
rememberStandardWorktreeSerializationSession(sessionID);
|
|
23595
23619
|
const session = ensureAgentSession(sessionID);
|
|
@@ -23643,9 +23667,7 @@ async function precreateStandardWorktreeSession(args2) {
|
|
|
23643
23667
|
const client = swarmState.opencodeClient;
|
|
23644
23668
|
if (!client) {
|
|
23645
23669
|
const message = "STANDARD_WORKTREE_ISOLATION_UNAVAILABLE: OpenCode SDK client is unavailable; standard parallel coder work cannot be isolated.";
|
|
23646
|
-
|
|
23647
|
-
throw new Error(message);
|
|
23648
|
-
serializeStandardWorktreeDispatches(args2.parentSessionID, message);
|
|
23670
|
+
handleStandardWorktreeFailure(args2.parentSessionID, worktreeConfig.policy, message);
|
|
23649
23671
|
return;
|
|
23650
23672
|
}
|
|
23651
23673
|
const provisionResult = await _internals12.provisionWorktree(args2.directory, args2.taskId, args2.parentSessionID, {
|
|
@@ -23654,10 +23676,8 @@ async function precreateStandardWorktreeSession(args2) {
|
|
|
23654
23676
|
mergeStrategy: worktreeConfig.merge_strategy
|
|
23655
23677
|
});
|
|
23656
23678
|
if ("error" in provisionResult) {
|
|
23657
|
-
const message = `STANDARD_WORKTREE_PROVISION_FAILED: ${provisionResult.error}
|
|
23658
|
-
|
|
23659
|
-
throw new Error(message);
|
|
23660
|
-
serializeStandardWorktreeDispatches(args2.parentSessionID, `${message}.`);
|
|
23679
|
+
const message = `STANDARD_WORKTREE_PROVISION_FAILED: ${provisionResult.error}.`;
|
|
23680
|
+
handleStandardWorktreeFailure(args2.parentSessionID, worktreeConfig.policy, message);
|
|
23661
23681
|
return;
|
|
23662
23682
|
}
|
|
23663
23683
|
const createResult = await client.session.create({
|
|
@@ -23671,10 +23691,8 @@ async function precreateStandardWorktreeSession(args2) {
|
|
|
23671
23691
|
await _internals12.removeWorktree(provisionResult.worktreePath, args2.directory).catch(() => {});
|
|
23672
23692
|
const createError = createResult.error;
|
|
23673
23693
|
const detail = typeof createError === "string" ? createError : JSON.stringify(createError ?? "missing session id");
|
|
23674
|
-
const message = `STANDARD_WORKTREE_SESSION_CREATE_FAILED: ${detail}
|
|
23675
|
-
|
|
23676
|
-
throw new Error(message);
|
|
23677
|
-
serializeStandardWorktreeDispatches(args2.parentSessionID, `${message}.`);
|
|
23694
|
+
const message = `STANDARD_WORKTREE_SESSION_CREATE_FAILED: ${detail}.`;
|
|
23695
|
+
handleStandardWorktreeFailure(args2.parentSessionID, worktreeConfig.policy, message);
|
|
23678
23696
|
return;
|
|
23679
23697
|
}
|
|
23680
23698
|
args2.outputArgs.task_id = createResult.data.id;
|
|
@@ -42738,7 +42756,47 @@ function parseTaskEnvelope(text) {
|
|
|
42738
42756
|
const state = match[2];
|
|
42739
42757
|
if (!sessionId)
|
|
42740
42758
|
return null;
|
|
42741
|
-
|
|
42759
|
+
const summary = extractTagText(text, "summary");
|
|
42760
|
+
const resultRaw = state === "error" ? extractTagText(text, "task_error") : extractTagText(text, "task_result");
|
|
42761
|
+
const bounded = boundEnvelopeText(resultRaw);
|
|
42762
|
+
return {
|
|
42763
|
+
sessionId,
|
|
42764
|
+
state,
|
|
42765
|
+
...summary !== undefined ? { summary } : {},
|
|
42766
|
+
...state === "error" && bounded ? {
|
|
42767
|
+
errorText: bounded.text,
|
|
42768
|
+
resultChars: bounded.chars,
|
|
42769
|
+
resultTruncated: bounded.truncated
|
|
42770
|
+
} : {},
|
|
42771
|
+
...state !== "error" && bounded ? {
|
|
42772
|
+
resultText: bounded.text,
|
|
42773
|
+
resultChars: bounded.chars,
|
|
42774
|
+
resultTruncated: bounded.truncated
|
|
42775
|
+
} : {}
|
|
42776
|
+
};
|
|
42777
|
+
}
|
|
42778
|
+
function extractTagText(text, tag) {
|
|
42779
|
+
const re = new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`);
|
|
42780
|
+
const match = text.match(re);
|
|
42781
|
+
if (!match)
|
|
42782
|
+
return;
|
|
42783
|
+
return match[1];
|
|
42784
|
+
}
|
|
42785
|
+
function boundEnvelopeText(text) {
|
|
42786
|
+
if (text === undefined)
|
|
42787
|
+
return;
|
|
42788
|
+
if (text.length <= MAX_TASK_ENVELOPE_TEXT_CHARS) {
|
|
42789
|
+
return { text, chars: text.length, truncated: false };
|
|
42790
|
+
}
|
|
42791
|
+
const omitted = text.length - MAX_TASK_ENVELOPE_TEXT_CHARS;
|
|
42792
|
+
const suffix = `
|
|
42793
|
+
[... ${omitted} chars truncated by task-envelope ...]`;
|
|
42794
|
+
const maxContent = Math.max(0, MAX_TASK_ENVELOPE_TEXT_CHARS - suffix.length);
|
|
42795
|
+
return {
|
|
42796
|
+
text: `${text.slice(0, maxContent)}${suffix}`,
|
|
42797
|
+
chars: text.length,
|
|
42798
|
+
truncated: true
|
|
42799
|
+
};
|
|
42742
42800
|
}
|
|
42743
42801
|
function extractDispatchIds(output) {
|
|
42744
42802
|
let subagentSessionId = null;
|
|
@@ -42768,7 +42826,7 @@ function extractDispatchIds(output) {
|
|
|
42768
42826
|
}
|
|
42769
42827
|
return { subagentSessionId, jobId };
|
|
42770
42828
|
}
|
|
42771
|
-
var TASK_ENVELOPE_RE;
|
|
42829
|
+
var TASK_ENVELOPE_RE, MAX_TASK_ENVELOPE_TEXT_CHARS = 20000;
|
|
42772
42830
|
var init_task_envelope = __esm(() => {
|
|
42773
42831
|
TASK_ENVELOPE_RE = /<task\s+id="([^"]+)"\s+state="(running|completed|error)"\s*>/;
|
|
42774
42832
|
});
|
|
@@ -42779,7 +42837,10 @@ __export(exports_pending_delegations, {
|
|
|
42779
42837
|
sweepStaleDelegations: () => sweepStaleDelegations,
|
|
42780
42838
|
recordPendingDelegation: () => recordPendingDelegation,
|
|
42781
42839
|
readDelegations: () => readDelegations,
|
|
42840
|
+
findOpenAsyncLaneBatches: () => findOpenAsyncLaneBatches,
|
|
42782
42841
|
findByCorrelationId: () => findByCorrelationId,
|
|
42842
|
+
findByBatchId: () => findByBatchId,
|
|
42843
|
+
appendDelegationTransition: () => appendDelegationTransition,
|
|
42783
42844
|
BACKGROUND_DELEGATIONS_FILE: () => BACKGROUND_DELEGATIONS_FILE
|
|
42784
42845
|
});
|
|
42785
42846
|
import * as fs15 from "node:fs";
|
|
@@ -42836,7 +42897,7 @@ function appendRecord(directory, record2) {
|
|
|
42836
42897
|
async function recordPendingDelegation(directory, input, options = {}) {
|
|
42837
42898
|
const now = Date.now();
|
|
42838
42899
|
const record2 = {
|
|
42839
|
-
schemaVersion: 1,
|
|
42900
|
+
schemaVersion: input.batchId ? 2 : 1,
|
|
42840
42901
|
correlationId: input.correlationId,
|
|
42841
42902
|
jobId: input.jobId,
|
|
42842
42903
|
subagentSessionId: input.subagentSessionId,
|
|
@@ -42848,7 +42909,13 @@ async function recordPendingDelegation(directory, input, options = {}) {
|
|
|
42848
42909
|
evidenceTaskId: input.evidenceTaskId,
|
|
42849
42910
|
status: "pending",
|
|
42850
42911
|
createdAt: now,
|
|
42851
|
-
updatedAt: now
|
|
42912
|
+
updatedAt: now,
|
|
42913
|
+
...input.batchId ? { batchId: input.batchId } : {},
|
|
42914
|
+
...input.laneId ? { laneId: input.laneId } : {},
|
|
42915
|
+
...input.mode ? { mode: input.mode } : {},
|
|
42916
|
+
...input.promptHash ? { promptHash: input.promptHash } : {},
|
|
42917
|
+
...input.workspace ? { workspace: input.workspace } : {},
|
|
42918
|
+
...input.generation !== undefined ? { generation: input.generation } : {}
|
|
42852
42919
|
};
|
|
42853
42920
|
try {
|
|
42854
42921
|
await withEvidenceLock(directory, BACKGROUND_DELEGATIONS_FILE, STORE_LOCK_AGENT, STORE_LOCK_TASK, async () => {
|
|
@@ -42863,10 +42930,49 @@ async function recordPendingDelegation(directory, input, options = {}) {
|
|
|
42863
42930
|
return null;
|
|
42864
42931
|
}
|
|
42865
42932
|
}
|
|
42933
|
+
async function appendDelegationTransition(directory, correlationId, transition) {
|
|
42934
|
+
const now = Date.now();
|
|
42935
|
+
try {
|
|
42936
|
+
let next = null;
|
|
42937
|
+
await withEvidenceLock(directory, BACKGROUND_DELEGATIONS_FILE, STORE_LOCK_AGENT, STORE_LOCK_TASK, async () => {
|
|
42938
|
+
const current = findByCorrelationId(directory, correlationId);
|
|
42939
|
+
if (!current)
|
|
42940
|
+
return;
|
|
42941
|
+
if (isTerminal(current.status) && transition.status !== "consumed") {
|
|
42942
|
+
next = current;
|
|
42943
|
+
return;
|
|
42944
|
+
}
|
|
42945
|
+
next = {
|
|
42946
|
+
...current,
|
|
42947
|
+
schemaVersion: current.schemaVersion === 1 ? 2 : current.schemaVersion,
|
|
42948
|
+
status: transition.status,
|
|
42949
|
+
updatedAt: now,
|
|
42950
|
+
...transition.completedAt !== undefined ? { completedAt: transition.completedAt } : transition.status === "completed" || transition.status === "error" ? { completedAt: now } : {},
|
|
42951
|
+
...transition.result ? { result: transition.result } : {}
|
|
42952
|
+
};
|
|
42953
|
+
appendRecord(directory, next);
|
|
42954
|
+
});
|
|
42955
|
+
return next;
|
|
42956
|
+
} catch (err2) {
|
|
42957
|
+
warn(`[background] appendDelegationTransition failed: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
42958
|
+
return null;
|
|
42959
|
+
}
|
|
42960
|
+
}
|
|
42961
|
+
function findByBatchId(directory, batchId, opts) {
|
|
42962
|
+
if (!batchId)
|
|
42963
|
+
return [];
|
|
42964
|
+
return readDelegations(directory).filter((record2) => record2.batchId === batchId && (opts?.parentSessionId === undefined || record2.parentSessionId === opts.parentSessionId));
|
|
42965
|
+
}
|
|
42966
|
+
function findOpenAsyncLaneBatches(directory) {
|
|
42967
|
+
return readDelegations(directory).filter((record2) => record2.batchId !== undefined && (record2.status === "pending" || record2.status === "running"));
|
|
42968
|
+
}
|
|
42969
|
+
function isTerminal(status) {
|
|
42970
|
+
return status === "completed" || status === "error" || status === "cancelled" || status === "stale" || status === "consumed";
|
|
42971
|
+
}
|
|
42866
42972
|
function sweepStaleLocked(directory, timeoutMs, now) {
|
|
42867
42973
|
let swept = 0;
|
|
42868
42974
|
for (const record2 of readDelegations(directory)) {
|
|
42869
|
-
if (record2.status !== "pending")
|
|
42975
|
+
if (record2.status !== "pending" && record2.status !== "running")
|
|
42870
42976
|
continue;
|
|
42871
42977
|
if (now - record2.updatedAt <= timeoutMs)
|
|
42872
42978
|
continue;
|
|
@@ -42889,14 +42995,28 @@ async function sweepStaleDelegations(directory, timeoutMs) {
|
|
|
42889
42995
|
return 0;
|
|
42890
42996
|
}
|
|
42891
42997
|
}
|
|
42892
|
-
var BACKGROUND_DELEGATIONS_FILE = "background-delegations.jsonl", STORE_LOCK_AGENT = "background", STORE_LOCK_TASK = "background-delegations", RecordSchema;
|
|
42998
|
+
var BACKGROUND_DELEGATIONS_FILE = "background-delegations.jsonl", STORE_LOCK_AGENT = "background", STORE_LOCK_TASK = "background-delegations", ResultSchema, WorkspaceSchema, RecordSchema;
|
|
42893
42999
|
var init_pending_delegations = __esm(() => {
|
|
42894
43000
|
init_zod();
|
|
42895
43001
|
init_lock();
|
|
42896
43002
|
init_utils2();
|
|
42897
43003
|
init_logger();
|
|
43004
|
+
ResultSchema = exports_external.object({
|
|
43005
|
+
text: exports_external.string().optional(),
|
|
43006
|
+
error: exports_external.string().optional(),
|
|
43007
|
+
chars: exports_external.number(),
|
|
43008
|
+
truncated: exports_external.boolean(),
|
|
43009
|
+
digest: exports_external.string()
|
|
43010
|
+
}).strict();
|
|
43011
|
+
WorkspaceSchema = exports_external.object({
|
|
43012
|
+
directory: exports_external.string(),
|
|
43013
|
+
gitHead: exports_external.string().nullable(),
|
|
43014
|
+
dirtyHash: exports_external.string().nullable(),
|
|
43015
|
+
prHeadSha: exports_external.string().nullable(),
|
|
43016
|
+
scope: exports_external.string().nullable()
|
|
43017
|
+
}).strict();
|
|
42898
43018
|
RecordSchema = exports_external.object({
|
|
42899
|
-
schemaVersion: exports_external.literal(1),
|
|
43019
|
+
schemaVersion: exports_external.union([exports_external.literal(1), exports_external.literal(2)]),
|
|
42900
43020
|
correlationId: exports_external.string().min(1),
|
|
42901
43021
|
jobId: exports_external.string().nullable(),
|
|
42902
43022
|
subagentSessionId: exports_external.string().min(1),
|
|
@@ -42906,9 +43026,25 @@ var init_pending_delegations = __esm(() => {
|
|
|
42906
43026
|
swarmPrefixedAgent: exports_external.string(),
|
|
42907
43027
|
planTaskId: exports_external.string().nullable(),
|
|
42908
43028
|
evidenceTaskId: exports_external.string().nullable(),
|
|
42909
|
-
status: exports_external.enum([
|
|
43029
|
+
status: exports_external.enum([
|
|
43030
|
+
"pending",
|
|
43031
|
+
"running",
|
|
43032
|
+
"completed",
|
|
43033
|
+
"error",
|
|
43034
|
+
"cancelled",
|
|
43035
|
+
"stale",
|
|
43036
|
+
"consumed"
|
|
43037
|
+
]),
|
|
42910
43038
|
createdAt: exports_external.number(),
|
|
42911
|
-
updatedAt: exports_external.number()
|
|
43039
|
+
updatedAt: exports_external.number(),
|
|
43040
|
+
batchId: exports_external.string().optional(),
|
|
43041
|
+
laneId: exports_external.string().optional(),
|
|
43042
|
+
mode: exports_external.string().optional(),
|
|
43043
|
+
promptHash: exports_external.string().optional(),
|
|
43044
|
+
workspace: WorkspaceSchema.optional(),
|
|
43045
|
+
generation: exports_external.number().optional(),
|
|
43046
|
+
result: ResultSchema.optional(),
|
|
43047
|
+
completedAt: exports_external.number().optional()
|
|
42912
43048
|
}).strict();
|
|
42913
43049
|
});
|
|
42914
43050
|
|
|
@@ -43470,6 +43606,14 @@ function createDelegationGateHook(config2, directory) {
|
|
|
43470
43606
|
const session = ensureAgentSession(input.sessionID);
|
|
43471
43607
|
if (!session || !session.taskWorkflowStates)
|
|
43472
43608
|
return;
|
|
43609
|
+
const plan = await _internals20.loadPlanJsonOnly(directory);
|
|
43610
|
+
const profile = plan?.execution_profile;
|
|
43611
|
+
const parallelEnabled = profile?.parallelization_enabled === true;
|
|
43612
|
+
const maxConcurrent = profile?.max_concurrent_tasks ?? 10;
|
|
43613
|
+
const effectiveMaxConcurrent = session.maxConcurrencyOverride ?? maxConcurrent;
|
|
43614
|
+
const parallelModeActive = parallelEnabled && effectiveMaxConcurrent > 1 && !hasActiveLeanTurbo(input.sessionID);
|
|
43615
|
+
const planTaskIds = plan ? new Set(plan.phases.flatMap((phase) => phase.tasks.map((t) => t.id))) : new Set;
|
|
43616
|
+
const incomingCoderTaskId = resolveDelegatedPlanTaskId(args2, planTaskIds);
|
|
43473
43617
|
for (const [taskId, state] of session.taskWorkflowStates) {
|
|
43474
43618
|
if (state !== "coder_delegated")
|
|
43475
43619
|
continue;
|
|
@@ -43487,24 +43631,29 @@ function createDelegationGateHook(config2, directory) {
|
|
|
43487
43631
|
if (!isTier3)
|
|
43488
43632
|
continue;
|
|
43489
43633
|
}
|
|
43634
|
+
if (parallelModeActive && incomingCoderTaskId && taskId !== incomingCoderTaskId) {
|
|
43635
|
+
continue;
|
|
43636
|
+
}
|
|
43490
43637
|
throw new Error(`REVIEWER_GATE_VIOLATION: Cannot re-delegate to coder without reviewer delegation. ` + `Task ${taskId} state: coder_delegated. Delegate to reviewer first. ` + `If this is stale state from a prior session, run /swarm reset-session to clear workflow state.`);
|
|
43491
43638
|
}
|
|
43639
|
+
if (parallelModeActive) {
|
|
43640
|
+
let coderDelegatedCount = 0;
|
|
43641
|
+
for (const s of session.taskWorkflowStates.values()) {
|
|
43642
|
+
if (s === "coder_delegated")
|
|
43643
|
+
coderDelegatedCount++;
|
|
43644
|
+
}
|
|
43645
|
+
if (coderDelegatedCount >= effectiveMaxConcurrent) {
|
|
43646
|
+
throw new Error(`PARALLEL_SLOTS_EXHAUSTED: ${coderDelegatedCount} coder task(s) are awaiting review ` + `(max_concurrent_tasks=${effectiveMaxConcurrent}). Dispatch reviewer/test_engineer for an ` + `in-flight coder task before starting another coder.`);
|
|
43647
|
+
}
|
|
43648
|
+
}
|
|
43492
43649
|
if (standardWorktreeSerializationSessions.has(input.sessionID)) {
|
|
43493
43650
|
throw new Error("STANDARD_WORKTREE_ISOLATION_SERIALIZED: prior standard worktree isolation setup failed in this session; wait for the active coder task to finish before dispatching another coder.");
|
|
43494
43651
|
}
|
|
43495
|
-
const plan = await _internals20.loadPlanJsonOnly(directory);
|
|
43496
43652
|
if (!plan)
|
|
43497
43653
|
return;
|
|
43498
|
-
|
|
43499
|
-
const parallelEnabled = profile?.parallelization_enabled === true;
|
|
43500
|
-
const maxConcurrent = profile?.max_concurrent_tasks ?? 10;
|
|
43501
|
-
const effectiveMaxConcurrent = session.maxConcurrencyOverride ?? maxConcurrent;
|
|
43502
|
-
if (!parallelEnabled || effectiveMaxConcurrent <= 1)
|
|
43654
|
+
if (!parallelModeActive)
|
|
43503
43655
|
return;
|
|
43504
|
-
|
|
43505
|
-
return;
|
|
43506
|
-
const planTaskIds = new Set(plan.phases.flatMap((phase) => phase.tasks.map((task) => task.id)));
|
|
43507
|
-
const resolvedTaskId = resolveDelegatedPlanTaskId(args2, planTaskIds);
|
|
43656
|
+
const resolvedTaskId = incomingCoderTaskId;
|
|
43508
43657
|
await precreateStandardWorktreeSession({
|
|
43509
43658
|
config: config2,
|
|
43510
43659
|
directory,
|
|
@@ -67829,6 +67978,35 @@ function buildV3EnrichmentPrompt(lesson, category, tags) {
|
|
|
67829
67978
|
].join(`
|
|
67830
67979
|
`);
|
|
67831
67980
|
}
|
|
67981
|
+
function buildV3BatchEnrichmentPrompt(lessons) {
|
|
67982
|
+
const lessonLines = lessons.map((item, idx) => `${idx + 1}. LESSON: ${item.lesson}
|
|
67983
|
+
CATEGORY: ${item.category}
|
|
67984
|
+
TAGS: ${item.tags.join(", ")}`).join(`
|
|
67985
|
+
`);
|
|
67986
|
+
return [
|
|
67987
|
+
"Convert each prose lesson below into an actionable knowledge directive.",
|
|
67988
|
+
"Output ONLY a JSON array (no code fences, no commentary).",
|
|
67989
|
+
`The array length MUST be exactly ${lessons.length}.`,
|
|
67990
|
+
"Each array element at position i maps to lesson i (1-indexed above).",
|
|
67991
|
+
"",
|
|
67992
|
+
"For EACH element, mandatory requirements:",
|
|
67993
|
+
"- At least ONE scope field non-empty:",
|
|
67994
|
+
' "applies_to_agents": string[] — roles from: architect, coder, reviewer, test_engineer, sme, docs, designer, critic, curator',
|
|
67995
|
+
' "applies_to_tools": string[] — tool names from: edit, write, patch, bash, read, grep, glob',
|
|
67996
|
+
"- At least ONE predicate field non-empty:",
|
|
67997
|
+
' "forbidden_actions": string[] | "required_actions": string[] | "verification_checks": string[]',
|
|
67998
|
+
"",
|
|
67999
|
+
"Optional per element:",
|
|
68000
|
+
'"triggers": string[], "directive_priority": "low" | "medium" | "high" | "critical"',
|
|
68001
|
+
"",
|
|
68002
|
+
"Example array:",
|
|
68003
|
+
'[{"applies_to_agents":["coder"],"required_actions":["run focused tests before commit"],"directive_priority":"high"}]',
|
|
68004
|
+
"",
|
|
68005
|
+
"LESSONS:",
|
|
68006
|
+
lessonLines
|
|
68007
|
+
].join(`
|
|
68008
|
+
`);
|
|
68009
|
+
}
|
|
67832
68010
|
function parseV3EnrichmentResponse(text) {
|
|
67833
68011
|
if (!text || typeof text !== "string") {
|
|
67834
68012
|
return { missing: ["valid JSON object"] };
|
|
@@ -67869,6 +68047,109 @@ function parseV3EnrichmentResponse(text) {
|
|
|
67869
68047
|
}
|
|
67870
68048
|
return { fields };
|
|
67871
68049
|
}
|
|
68050
|
+
function parseV3BatchEnrichmentResponse(text, expectedLength) {
|
|
68051
|
+
const empty = Array.from({ length: expectedLength }, () => null);
|
|
68052
|
+
if (!text || typeof text !== "string") {
|
|
68053
|
+
return { fields: empty, missing: ["valid JSON array"] };
|
|
68054
|
+
}
|
|
68055
|
+
const start2 = text.indexOf("[");
|
|
68056
|
+
const end = text.lastIndexOf("]");
|
|
68057
|
+
if (start2 < 0 || end <= start2) {
|
|
68058
|
+
return { fields: empty, missing: ["valid JSON array"] };
|
|
68059
|
+
}
|
|
68060
|
+
let parsed;
|
|
68061
|
+
try {
|
|
68062
|
+
parsed = JSON.parse(text.slice(start2, end + 1));
|
|
68063
|
+
} catch {
|
|
68064
|
+
return { fields: empty, missing: ["valid JSON array"] };
|
|
68065
|
+
}
|
|
68066
|
+
if (!Array.isArray(parsed)) {
|
|
68067
|
+
return { fields: empty, missing: ["valid JSON array"] };
|
|
68068
|
+
}
|
|
68069
|
+
const fields = Array.from({ length: expectedLength }, () => null);
|
|
68070
|
+
const missing = [];
|
|
68071
|
+
for (let i2 = 0;i2 < expectedLength; i2++) {
|
|
68072
|
+
const item = parsed[i2];
|
|
68073
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) {
|
|
68074
|
+
missing.push(`item ${i2 + 1}: valid JSON object`);
|
|
68075
|
+
continue;
|
|
68076
|
+
}
|
|
68077
|
+
const raw = item;
|
|
68078
|
+
const candidate = {};
|
|
68079
|
+
for (const key of ENRICHMENT_ALLOWED_FIELDS) {
|
|
68080
|
+
if (raw[key] !== undefined) {
|
|
68081
|
+
candidate[key] = raw[key];
|
|
68082
|
+
}
|
|
68083
|
+
}
|
|
68084
|
+
const shape = validateActionableFields(candidate);
|
|
68085
|
+
if (!shape.valid) {
|
|
68086
|
+
missing.push(`item ${i2 + 1}: ${shape.errors.join("; ")}`);
|
|
68087
|
+
continue;
|
|
68088
|
+
}
|
|
68089
|
+
const actionability = validateActionability(candidate);
|
|
68090
|
+
if (!actionability.actionable) {
|
|
68091
|
+
const expected = [];
|
|
68092
|
+
if (actionability.reason === "missing_predicate" || actionability.reason === "missing_predicate_and_scope") {
|
|
68093
|
+
expected.push("a non-empty predicate field (forbidden_actions, required_actions, or verification_checks)");
|
|
68094
|
+
}
|
|
68095
|
+
if (actionability.reason === "missing_scope" || actionability.reason === "missing_predicate_and_scope") {
|
|
68096
|
+
expected.push("a non-empty scope field (applies_to_agents or applies_to_tools)");
|
|
68097
|
+
}
|
|
68098
|
+
missing.push(`item ${i2 + 1}: ${expected.join("; ")}`);
|
|
68099
|
+
continue;
|
|
68100
|
+
}
|
|
68101
|
+
fields[i2] = candidate;
|
|
68102
|
+
}
|
|
68103
|
+
if (parsed.length < expectedLength) {
|
|
68104
|
+
missing.push(`expected ${expectedLength} items but got ${parsed.length}`);
|
|
68105
|
+
} else if (parsed.length > expectedLength) {
|
|
68106
|
+
warn(`[knowledge-curator] parseV3BatchEnrichmentResponse received ${parsed.length} items but expected ${expectedLength}; extras will be discarded`);
|
|
68107
|
+
missing.push(`got ${parsed.length} items but only first ${expectedLength} will be used; extras discarded`);
|
|
68108
|
+
}
|
|
68109
|
+
return { fields, missing };
|
|
68110
|
+
}
|
|
68111
|
+
async function enrichLessonsToV3Batched(params) {
|
|
68112
|
+
const quota = params.quota ?? { maxCalls: 10, window: "utc" };
|
|
68113
|
+
const out2 = Array.from({ length: params.lessons.length }, () => null);
|
|
68114
|
+
const batchSize = params.batchSize ?? ENRICHMENT_BATCH_SIZE;
|
|
68115
|
+
for (let start2 = 0;start2 < params.lessons.length; start2 += batchSize) {
|
|
68116
|
+
const batch = params.lessons.slice(start2, start2 + batchSize);
|
|
68117
|
+
const prompt = buildV3BatchEnrichmentPrompt(batch);
|
|
68118
|
+
let userInput = prompt;
|
|
68119
|
+
let best = Array.from({ length: batch.length }, () => null);
|
|
68120
|
+
let retryHint = "";
|
|
68121
|
+
for (let attempt = 0;attempt < 2; attempt++) {
|
|
68122
|
+
try {
|
|
68123
|
+
const reservation = await reserveQuota(params.directory, {
|
|
68124
|
+
nCalls: 1,
|
|
68125
|
+
maxCalls: quota.maxCalls,
|
|
68126
|
+
window: quota.window,
|
|
68127
|
+
scope: "knowledge-enrichment"
|
|
68128
|
+
});
|
|
68129
|
+
if (!reservation.allowed)
|
|
68130
|
+
break;
|
|
68131
|
+
const response = await params.llmDelegate("", userInput, AbortSignal.timeout(ENRICHMENT_LLM_TIMEOUT_MS));
|
|
68132
|
+
const parsed = parseV3BatchEnrichmentResponse(response, batch.length);
|
|
68133
|
+
best = best.map((current, idx) => current ?? parsed.fields[idx]);
|
|
68134
|
+
const unresolved = best.map((fields, idx) => ({ fields, idx })).filter((item) => item.fields === null).map((item) => item.idx + 1);
|
|
68135
|
+
if (unresolved.length === 0)
|
|
68136
|
+
break;
|
|
68137
|
+
retryHint = parsed.missing.join("; ");
|
|
68138
|
+
const resolvedList = best.map((fields, idx) => ({ fields, idx })).filter((item) => item.fields !== null).map((item) => item.idx + 1);
|
|
68139
|
+
const preserveClause = resolvedList.length > 0 ? `Preserve the already-valid entries for items ${resolvedList.join(", ")} exactly as you returned them previously. ` : "";
|
|
68140
|
+
userInput = `${prompt}
|
|
68141
|
+
|
|
68142
|
+
RETRY: your last output still missed valid directives for items ${unresolved.join(", ")}. ${retryHint} ${preserveClause}Return a full JSON array with valid entries for every item.`;
|
|
68143
|
+
} catch (err2) {
|
|
68144
|
+
warn(`[knowledge-curator] v3 batch enrichment attempt ${attempt + 1} failed: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
68145
|
+
}
|
|
68146
|
+
}
|
|
68147
|
+
for (let i2 = 0;i2 < best.length; i2++) {
|
|
68148
|
+
out2[start2 + i2] = best[i2];
|
|
68149
|
+
}
|
|
68150
|
+
}
|
|
68151
|
+
return out2;
|
|
68152
|
+
}
|
|
67872
68153
|
async function enrichLessonToV3(params) {
|
|
67873
68154
|
const quota = params.quota ?? { maxCalls: 10, window: "utc" };
|
|
67874
68155
|
const prompt = buildV3EnrichmentPrompt(params.lesson, params.category, params.tags);
|
|
@@ -68006,6 +68287,7 @@ async function curateAndStoreSwarm(lessons, projectName, phaseInfo, directory, c
|
|
|
68006
68287
|
const snapshotPlusNew = [...snapshot];
|
|
68007
68288
|
const toAdd = [];
|
|
68008
68289
|
const pendingReinforcementIds = new Set;
|
|
68290
|
+
const pendingBatchEnrichment = [];
|
|
68009
68291
|
for (const lesson of lessons) {
|
|
68010
68292
|
const tags = inferTags(lesson);
|
|
68011
68293
|
let category = "process";
|
|
@@ -68068,20 +68350,10 @@ async function curateAndStoreSwarm(lessons, projectName, phaseInfo, directory, c
|
|
|
68068
68350
|
project_name: projectName,
|
|
68069
68351
|
auto_generated: true
|
|
68070
68352
|
};
|
|
68071
|
-
|
|
68353
|
+
const actionability = validateActionability(entry);
|
|
68072
68354
|
if (!actionability.actionable && options?.llmDelegate) {
|
|
68073
|
-
|
|
68074
|
-
|
|
68075
|
-
llmDelegate: options.llmDelegate,
|
|
68076
|
-
lesson,
|
|
68077
|
-
category,
|
|
68078
|
-
tags,
|
|
68079
|
-
quota: options.enrichmentQuota
|
|
68080
|
-
});
|
|
68081
|
-
if (enriched) {
|
|
68082
|
-
Object.assign(entry, enriched);
|
|
68083
|
-
actionability = validateActionability(entry);
|
|
68084
|
-
}
|
|
68355
|
+
pendingBatchEnrichment.push({ entry, lesson, category, tags });
|
|
68356
|
+
continue;
|
|
68085
68357
|
}
|
|
68086
68358
|
if (!actionability.actionable) {
|
|
68087
68359
|
quarantined++;
|
|
@@ -68098,6 +68370,41 @@ async function curateAndStoreSwarm(lessons, projectName, phaseInfo, directory, c
|
|
|
68098
68370
|
toAdd.push(entry);
|
|
68099
68371
|
snapshotPlusNew.push(entry);
|
|
68100
68372
|
}
|
|
68373
|
+
if (pendingBatchEnrichment.length > 0 && options?.llmDelegate) {
|
|
68374
|
+
const enrichedBatch = await enrichLessonsToV3Batched({
|
|
68375
|
+
directory,
|
|
68376
|
+
llmDelegate: options.llmDelegate,
|
|
68377
|
+
lessons: pendingBatchEnrichment.map((item) => ({
|
|
68378
|
+
lesson: item.lesson,
|
|
68379
|
+
category: item.category,
|
|
68380
|
+
tags: item.tags
|
|
68381
|
+
})),
|
|
68382
|
+
quota: options.enrichmentQuota,
|
|
68383
|
+
batchSize: config3.enrichment?.batch_size
|
|
68384
|
+
});
|
|
68385
|
+
for (let i2 = 0;i2 < pendingBatchEnrichment.length; i2++) {
|
|
68386
|
+
const pending = pendingBatchEnrichment[i2];
|
|
68387
|
+
const enriched = enrichedBatch[i2];
|
|
68388
|
+
if (enriched) {
|
|
68389
|
+
Object.assign(pending.entry, enriched);
|
|
68390
|
+
}
|
|
68391
|
+
const actionability = validateActionability(pending.entry);
|
|
68392
|
+
if (!actionability.actionable) {
|
|
68393
|
+
quarantined++;
|
|
68394
|
+
try {
|
|
68395
|
+
await appendUnactionable(directory, pending.entry, actionability.reason ?? "unactionable");
|
|
68396
|
+
} catch {}
|
|
68397
|
+
await appendCuratorSkippedEvent(directory, {
|
|
68398
|
+
entry_id: pending.entry.id,
|
|
68399
|
+
lesson: pending.lesson,
|
|
68400
|
+
reason: actionability.reason ?? "unactionable"
|
|
68401
|
+
});
|
|
68402
|
+
continue;
|
|
68403
|
+
}
|
|
68404
|
+
toAdd.push(pending.entry);
|
|
68405
|
+
snapshotPlusNew.push(pending.entry);
|
|
68406
|
+
}
|
|
68407
|
+
}
|
|
68101
68408
|
try {
|
|
68102
68409
|
const insights = await consumeInsightCandidates(directory);
|
|
68103
68410
|
for (const cand of insights) {
|
|
@@ -68301,7 +68608,7 @@ function createKnowledgeCuratorHook(directory, config3, options = {}) {
|
|
|
68301
68608
|
};
|
|
68302
68609
|
return safeHook(handler);
|
|
68303
68610
|
}
|
|
68304
|
-
var seenRetroSections, MAX_TRACKED_RETRO_SECTIONS = 500, ENRICHMENT_ALLOWED_FIELDS, ENRICHMENT_LLM_TIMEOUT_MS = 60000, MESO_INSIGHT_BATCH_LIMIT = 20, KNOWLEDGE_CATEGORIES, OUTCOME_PROMOTION_BLOCK = -0.3, _internals31;
|
|
68611
|
+
var seenRetroSections, MAX_TRACKED_RETRO_SECTIONS = 500, ENRICHMENT_ALLOWED_FIELDS, ENRICHMENT_LLM_TIMEOUT_MS = 60000, ENRICHMENT_BATCH_SIZE = 6, MESO_INSIGHT_BATCH_LIMIT = 20, KNOWLEDGE_CATEGORIES, OUTCOME_PROMOTION_BLOCK = -0.3, _internals31;
|
|
68305
68612
|
var init_knowledge_curator = __esm(() => {
|
|
68306
68613
|
init_skill_improver_quota();
|
|
68307
68614
|
init_synonym_map();
|
|
@@ -95301,7 +95608,7 @@ Present all three items together in a single message. One message, defaults pre-
|
|
|
95301
95608
|
|
|
95302
95609
|
**1. QA Gates** — accept defaults or customize (the eleven gates listed above).
|
|
95303
95610
|
|
|
95304
|
-
**2. Parallel Coders** — "How many coders should run in parallel? (default: 1, range: 1-4)"
|
|
95611
|
+
**2. Parallel Coders** — Parallel coders each run in their own isolated git worktree (a separate working directory on its own branch); each coder's work is committed and merged back to the main tree automatically when it finishes, so concurrent coders never overwrite each other's files. This is safe and faster — but only for tasks whose declared file scopes do NOT overlap. Before you ask, INSPECT the plan's tasks: group the dependency-ready tasks whose file scopes are disjoint, and let your RECOMMENDED count be the number of such independent groups, clamped to the 1-4 range. If task scopes overlap or you cannot determine them, recommend 1 (serial). File-scope disjointness is your recommendation to make, not a runtime-enforced guarantee: if overlapping tasks run in parallel a merge conflict will preserve the work in its worktree and surface an advisory, but it stalls progress — so prefer serial whenever you are unsure. Ask: "How many coders should run in parallel? (default: 1, range: 1-4; my recommendation: <N>, because <independent task groups>)"
|
|
95305
95612
|
|
|
95306
95613
|
**3. Commit Frequency** — "Commit frequency for completed tasks? (default: phase-level only; optional per-task checkpoint commit after each task completion)"
|
|
95307
95614
|
|
|
@@ -95313,7 +95620,7 @@ Wait for the user to answer all three in a single reply. Then apply:
|
|
|
95313
95620
|
## Pending Parallelization Config
|
|
95314
95621
|
- parallelization_enabled: true
|
|
95315
95622
|
- max_concurrent_tasks: <user's number>
|
|
95316
|
-
- council_parallel:
|
|
95623
|
+
- council_parallel: false
|
|
95317
95624
|
- locked: true
|
|
95318
95625
|
- recorded_at: <ISO timestamp>
|
|
95319
95626
|
\`\`\`
|
|
@@ -95650,8 +95957,8 @@ If a tool modifies a file, it is a CODER tool. Delegate.
|
|
|
95650
95957
|
- Rationale: declare_scope persists the allowed set to disk (.swarm/scopes/scope-\${taskId}.json) so it survives cross-process delegation. Without a call, the coder or test_engineer process reads an empty scope and every Edit/Write is denied.
|
|
95651
95958
|
<!-- BEHAVIORAL_GUIDANCE_END -->
|
|
95652
95959
|
2. ONE agent per message. Send, STOP, wait for response.
|
|
95653
|
-
Exception: Stage B reviewer/test_engineer gate agents for the SAME completed coder task may be dispatched together before waiting when both gates are required.
|
|
95654
|
-
|
|
95960
|
+
Exception: Stage B reviewer/test_engineer gate agents for the SAME completed coder task may be dispatched together before waiting when both gates are required. This exception NEVER applies to coder delegations. Preserve ONE task per coder call.
|
|
95961
|
+
Separate parallel-mode exception (distinct from the Stage B exception above, and the ONLY case where more than one coder may be dispatched before waiting): when an active \`[PARALLEL EXECUTION PROFILE]\` directive is present in your context (parallelization_enabled=true), you MAY dispatch multiple {{AGENT_PREFIX}}coder agents in a single message — up to the stated max_concurrent_tasks — but ONLY for distinct, dependency-ready tasks whose declared file scopes do NOT overlap. Each coder still requires its own \`declare_scope\` call and carries exactly ONE task (Rule 3 still holds: never batch multiple objectives into one coder). Parallel coders each run in an isolated git worktree, so their writes never collide and are merged back automatically. If no \`[PARALLEL EXECUTION PROFILE]\` directive is present, dispatch coders one at a time.
|
|
95655
95962
|
3. ONE task per {{AGENT_PREFIX}}coder call. Never batch.
|
|
95656
95963
|
3a. PRE-DELEGATION SCOPE CALL (required): BEFORE every {{AGENT_PREFIX}}coder delegation, you MUST call \`declare_scope\` with { taskId, files } listing the exact file(s) this task will modify (including generated/lockfile paths). No \`declare_scope\` call → no coder delegation. See Rule 1a.
|
|
95657
95964
|
3b. PRE-DELEGATION SCOPE CALL (test_engineer): BEFORE any {{AGENT_PREFIX}}test_engineer delegation that will CREATE or MODIFY test files, you MUST call \`declare_scope\` with { taskId, files } listing the exact test file path(s) to write. Omitting this call leaves the write scope undeclared and will block the write. See Rule 1a.
|
|
@@ -96582,6 +96889,7 @@ RULES:
|
|
|
96582
96889
|
- Respect CONSTRAINT
|
|
96583
96890
|
- No web searches or documentation lookups — but DO use the search tool for cross-codebase pattern lookup before using any function
|
|
96584
96891
|
- Verify all import paths exist before using them
|
|
96892
|
+
- WORKTREE ISOLATION: when the orchestrator runs coders in parallel, you may be working inside an isolated git worktree — a separate working directory on its own branch. Work exactly as normal: read and edit files at the paths you are given. Your changes are committed and merged back to the main tree automatically when you finish. If a merge conflict arises during merge-back, your work is preserved in its worktree and an advisory is surfaced to the orchestrator — your changes are never lost. Do NOT run git worktree/branch/checkout/merge commands yourself, and do NOT switch directories. Stay strictly within your declared FILE scope so coders working in sibling worktrees never collide with you.
|
|
96585
96893
|
|
|
96586
96894
|
## ANTI-HALLUCINATION PROTOCOL (MANDATORY)
|
|
96587
96895
|
Before importing ANY function, type, or class from an existing project module:
|
|
@@ -106830,6 +107138,7 @@ init_worker();
|
|
|
106830
107138
|
init_logger();
|
|
106831
107139
|
init_pending_delegations();
|
|
106832
107140
|
init_task_envelope();
|
|
107141
|
+
import { createHash as createHash11 } from "node:crypto";
|
|
106833
107142
|
function createBackgroundCompletionObserver(opts) {
|
|
106834
107143
|
const { config: config3, directory } = opts;
|
|
106835
107144
|
const event = async (input) => {
|
|
@@ -106854,16 +107163,37 @@ function createBackgroundCompletionObserver(opts) {
|
|
|
106854
107163
|
const pending = findByCorrelationId(directory, envelope.sessionId);
|
|
106855
107164
|
const parentSessionId = typeof part.sessionID === "string" ? part.sessionID : "unknown";
|
|
106856
107165
|
if (!pending) {
|
|
106857
|
-
log(`[background] observed synthetic completion (state=${envelope.state}) for subagent ${envelope.sessionId} in parent ${parentSessionId} with NO matching pending record — ignored
|
|
107166
|
+
log(`[background] observed synthetic completion (state=${envelope.state}) for subagent ${envelope.sessionId} in parent ${parentSessionId} with NO matching pending record — ignored`);
|
|
107167
|
+
return;
|
|
107168
|
+
}
|
|
107169
|
+
if (pending.parentSessionId !== parentSessionId) {
|
|
107170
|
+
warn(`[background] observed synthetic completion for ${envelope.sessionId} with parent mismatch: expected=${pending.parentSessionId} observed=${parentSessionId}; ignored`);
|
|
107171
|
+
return;
|
|
107172
|
+
}
|
|
107173
|
+
if (pending.status !== "pending" && pending.status !== "running") {
|
|
107174
|
+
log(`[background] observed duplicate/late completion for ${envelope.sessionId}; current status=${pending.status}; ignored`);
|
|
106858
107175
|
return;
|
|
106859
107176
|
}
|
|
106860
|
-
|
|
107177
|
+
const text = envelope.state === "error" ? envelope.errorText ?? "" : envelope.resultText ?? "";
|
|
107178
|
+
await appendDelegationTransition(directory, envelope.sessionId, {
|
|
107179
|
+
status: envelope.state === "error" ? "error" : "completed",
|
|
107180
|
+
result: {
|
|
107181
|
+
...envelope.state === "error" ? { error: text } : { text },
|
|
107182
|
+
chars: envelope.resultChars ?? text.length,
|
|
107183
|
+
truncated: envelope.resultTruncated ?? false,
|
|
107184
|
+
digest: digest(text)
|
|
107185
|
+
}
|
|
107186
|
+
});
|
|
107187
|
+
log(`[background] observed trusted completion (state=${envelope.state}) correlated to pending delegation: ` + `agent=${pending.normalizedAgent} task=${pending.evidenceTaskId ?? pending.planTaskId ?? "unknown"} ` + `parent=${pending.parentSessionId} observedParent=${parentSessionId} pendingStatus=${pending.status} ` + "(advisory ledger update only — no gate effect)");
|
|
106861
107188
|
} catch (err2) {
|
|
106862
107189
|
warn(`[background] completion observer error: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
106863
107190
|
}
|
|
106864
107191
|
};
|
|
106865
107192
|
return { event };
|
|
106866
107193
|
}
|
|
107194
|
+
function digest(text) {
|
|
107195
|
+
return createHash11("sha256").update(text).digest("hex");
|
|
107196
|
+
}
|
|
106867
107197
|
|
|
106868
107198
|
// src/index.ts
|
|
106869
107199
|
init_pr_subscriptions();
|
|
@@ -117074,7 +117404,7 @@ init_knowledge_events();
|
|
|
117074
117404
|
// src/hooks/knowledge-injector.ts
|
|
117075
117405
|
init_schema();
|
|
117076
117406
|
init_manager();
|
|
117077
|
-
import { createHash as
|
|
117407
|
+
import { createHash as createHash15 } from "node:crypto";
|
|
117078
117408
|
|
|
117079
117409
|
// src/services/run-memory.ts
|
|
117080
117410
|
import * as crypto10 from "node:crypto";
|
|
@@ -117578,7 +117908,7 @@ function createKnowledgeInjectorHook(directory, config3, modelLimitOverrides = {
|
|
|
117578
117908
|
ctx.taskId ?? "",
|
|
117579
117909
|
(ctx.filePaths ?? []).slice(0, 8).join(",")
|
|
117580
117910
|
].join("|");
|
|
117581
|
-
return
|
|
117911
|
+
return createHash15("sha1").update(parts2).digest("hex").slice(0, 16);
|
|
117582
117912
|
}
|
|
117583
117913
|
let lastSeenCacheKey = null;
|
|
117584
117914
|
let cachedInjectionText = null;
|
|
@@ -125574,6 +125904,9 @@ var diff_summary = createSwarmTool({
|
|
|
125574
125904
|
}
|
|
125575
125905
|
});
|
|
125576
125906
|
|
|
125907
|
+
// src/tools/dispatch-lanes.ts
|
|
125908
|
+
import { createHash as createHash16 } from "node:crypto";
|
|
125909
|
+
|
|
125577
125910
|
// node_modules/yocto-queue/index.js
|
|
125578
125911
|
class Node2 {
|
|
125579
125912
|
value;
|
|
@@ -125732,6 +126065,7 @@ function validateConcurrency(concurrency) {
|
|
|
125732
126065
|
|
|
125733
126066
|
// src/tools/dispatch-lanes.ts
|
|
125734
126067
|
init_zod();
|
|
126068
|
+
init_pending_delegations();
|
|
125735
126069
|
init_constants();
|
|
125736
126070
|
init_schema();
|
|
125737
126071
|
|
|
@@ -125796,6 +126130,12 @@ var MAX_TIMEOUT_MS2 = 1800000;
|
|
|
125796
126130
|
var MAX_LANE_OUTPUT_CHARS = 20000;
|
|
125797
126131
|
var MAX_ERROR_CHARS = 200;
|
|
125798
126132
|
var ERROR_TRUNCATION_SUFFIX = "...";
|
|
126133
|
+
var MAX_BATCH_ID_CHARS = 120;
|
|
126134
|
+
var DEFAULT_ASYNC_STALE_TIMEOUT_MS = 30 * 60000;
|
|
126135
|
+
var DEFAULT_COLLECT_TIMEOUT_MS = DEFAULT_ASYNC_STALE_TIMEOUT_MS;
|
|
126136
|
+
var MAX_COLLECT_TIMEOUT_MS = 60 * 60000;
|
|
126137
|
+
var COLLECT_POLL_INTERVAL_MS = 500;
|
|
126138
|
+
var MAX_COLLECT_POLL_INTERVAL_MS = 1e4;
|
|
125799
126139
|
var AGENT_NAME_SEPARATORS = ["_", "-", " "];
|
|
125800
126140
|
var READ_ONLY_LANE_ROLES = new Set([
|
|
125801
126141
|
"explorer",
|
|
@@ -125847,11 +126187,25 @@ var DispatchLanesArgsSchema = exports_external.object({
|
|
|
125847
126187
|
max_concurrent: exports_external.number().int().min(1).max(MAX_LANES).optional().describe("Maximum lanes in flight at once; defaults to lane count"),
|
|
125848
126188
|
timeout_ms: exports_external.number().int().min(10).max(MAX_TIMEOUT_MS2).optional().describe("Per-lane session create/prompt timeout in milliseconds")
|
|
125849
126189
|
});
|
|
126190
|
+
var DispatchLanesAsyncArgsSchema = DispatchLanesArgsSchema.extend({
|
|
126191
|
+
batch_id: exports_external.string().min(1).max(MAX_BATCH_ID_CHARS).regex(/^[A-Za-z0-9][A-Za-z0-9_.:-]*$/).optional().describe("Stable async batch id for later collection; generated when omitted"),
|
|
126192
|
+
mode: exports_external.string().min(1).max(80).optional().describe("Advisory workflow mode, such as deep-dive or swarm-pr-review"),
|
|
126193
|
+
pr_head_sha: exports_external.string().min(1).max(80).optional(),
|
|
126194
|
+
scope: exports_external.string().min(1).max(500).optional()
|
|
126195
|
+
});
|
|
126196
|
+
var CollectLaneResultsArgsSchema = exports_external.object({
|
|
126197
|
+
batch_id: exports_external.string().min(1).max(MAX_BATCH_ID_CHARS),
|
|
126198
|
+
wait: exports_external.boolean().optional().describe("Poll until all lanes settle or timeout"),
|
|
126199
|
+
timeout_ms: exports_external.number().int().min(0).max(MAX_COLLECT_TIMEOUT_MS).optional().describe("Total wait budget when wait=true"),
|
|
126200
|
+
include_pending: exports_external.boolean().optional(),
|
|
126201
|
+
cancel_pending: exports_external.boolean().optional().describe("Abort and mark pending/running lanes cancelled")
|
|
126202
|
+
});
|
|
125850
126203
|
var _internals81 = {
|
|
125851
126204
|
getSessionOps: () => swarmState.opencodeClient?.session ?? null,
|
|
125852
126205
|
getGeneratedAgentNames: () => swarmState.generatedAgentNames,
|
|
125853
126206
|
createParallelDispatcher,
|
|
125854
|
-
now: () => Date.now()
|
|
126207
|
+
now: () => Date.now(),
|
|
126208
|
+
sleep: sleep2
|
|
125855
126209
|
};
|
|
125856
126210
|
async function executeDispatchLanes(args2, directory, context = {}) {
|
|
125857
126211
|
const parsed = DispatchLanesArgsSchema.safeParse(args2);
|
|
@@ -125893,6 +126247,322 @@ async function executeDispatchLanes(args2, directory, context = {}) {
|
|
|
125893
126247
|
dispatcher.shutdown();
|
|
125894
126248
|
}
|
|
125895
126249
|
}
|
|
126250
|
+
async function executeDispatchLanesAsync(args2, directory, context = {}) {
|
|
126251
|
+
const parsed = DispatchLanesAsyncArgsSchema.safeParse(args2);
|
|
126252
|
+
if (!parsed.success) {
|
|
126253
|
+
return asyncFailureResult({
|
|
126254
|
+
failure_class: "invalid_args",
|
|
126255
|
+
message: "Invalid dispatch_lanes_async arguments",
|
|
126256
|
+
errors: parsed.error.issues.map((issue3) => `${issue3.path.join(".")}: ${issue3.message}`)
|
|
126257
|
+
});
|
|
126258
|
+
}
|
|
126259
|
+
const duplicateLaneIds = findDuplicateLaneIds(parsed.data.lanes);
|
|
126260
|
+
if (duplicateLaneIds.length > 0) {
|
|
126261
|
+
return asyncFailureResult({
|
|
126262
|
+
failure_class: "invalid_args",
|
|
126263
|
+
message: "Lane IDs must be unique within one dispatch_lanes_async batch",
|
|
126264
|
+
errors: duplicateLaneIds.map((id) => `Duplicate lane id: ${id}`)
|
|
126265
|
+
});
|
|
126266
|
+
}
|
|
126267
|
+
const session = _internals81.getSessionOps();
|
|
126268
|
+
if (!session || typeof session.promptAsync !== "function") {
|
|
126269
|
+
return asyncFailureResult({
|
|
126270
|
+
failure_class: "no_client",
|
|
126271
|
+
message: "OpenCode session promptAsync client is not available"
|
|
126272
|
+
});
|
|
126273
|
+
}
|
|
126274
|
+
const lanes = parsed.data.lanes;
|
|
126275
|
+
const batchId = parsed.data.batch_id ?? makeBatchId();
|
|
126276
|
+
if (findByBatchId(directory, batchId).length > 0) {
|
|
126277
|
+
return asyncFailureResult({
|
|
126278
|
+
failure_class: "invalid_args",
|
|
126279
|
+
message: `Async lane batch already exists: ${batchId}`,
|
|
126280
|
+
errors: [`batch_id must be unique: ${batchId}`]
|
|
126281
|
+
});
|
|
126282
|
+
}
|
|
126283
|
+
const maxConcurrent = Math.min(parsed.data.max_concurrent ?? lanes.length, lanes.length, MAX_LANES);
|
|
126284
|
+
const timeoutMs = parsed.data.timeout_ms ?? DEFAULT_TIMEOUT_MS3;
|
|
126285
|
+
const dispatcher = _internals81.createParallelDispatcher({
|
|
126286
|
+
enabled: true,
|
|
126287
|
+
maxConcurrentTasks: maxConcurrent,
|
|
126288
|
+
evidenceLockTimeoutMs: 0
|
|
126289
|
+
});
|
|
126290
|
+
const limit = pLimit(maxConcurrent);
|
|
126291
|
+
try {
|
|
126292
|
+
const laneResults = await Promise.all(lanes.map((lane) => limit(() => launchAsyncLane({
|
|
126293
|
+
session,
|
|
126294
|
+
dispatcher,
|
|
126295
|
+
lane,
|
|
126296
|
+
directory,
|
|
126297
|
+
timeoutMs,
|
|
126298
|
+
context,
|
|
126299
|
+
batchId,
|
|
126300
|
+
mode: parsed.data.mode,
|
|
126301
|
+
prHeadSha: parsed.data.pr_head_sha,
|
|
126302
|
+
scope: parsed.data.scope
|
|
126303
|
+
}))));
|
|
126304
|
+
const failed = laneResults.filter((lane) => lane.status === "failed");
|
|
126305
|
+
const rejected = laneResults.filter((lane) => lane.status === "rejected");
|
|
126306
|
+
const pending = laneResults.filter((lane) => lane.status === "pending");
|
|
126307
|
+
return {
|
|
126308
|
+
success: failed.length === 0 && rejected.length === 0,
|
|
126309
|
+
batch_id: batchId,
|
|
126310
|
+
dispatched: laneResults.length,
|
|
126311
|
+
pending: pending.length,
|
|
126312
|
+
failed: failed.length,
|
|
126313
|
+
rejected: rejected.length,
|
|
126314
|
+
max_concurrent: maxConcurrent,
|
|
126315
|
+
timeout_ms: timeoutMs,
|
|
126316
|
+
lane_results: laneResults
|
|
126317
|
+
};
|
|
126318
|
+
} finally {
|
|
126319
|
+
dispatcher.shutdown();
|
|
126320
|
+
}
|
|
126321
|
+
}
|
|
126322
|
+
async function executeCollectLaneResults(args2, directory, context = {}) {
|
|
126323
|
+
const parsed = CollectLaneResultsArgsSchema.safeParse(args2);
|
|
126324
|
+
if (!parsed.success) {
|
|
126325
|
+
return collectFailureResult({
|
|
126326
|
+
failure_class: "invalid_args",
|
|
126327
|
+
batch_id: "",
|
|
126328
|
+
message: "Invalid collect_lane_results arguments",
|
|
126329
|
+
errors: parsed.error.issues.map((issue3) => `${issue3.path.join(".")}: ${issue3.message}`)
|
|
126330
|
+
});
|
|
126331
|
+
}
|
|
126332
|
+
const session = _internals81.getSessionOps();
|
|
126333
|
+
if (!session || typeof session.messages !== "function") {
|
|
126334
|
+
return collectFailureResult({
|
|
126335
|
+
failure_class: "no_client",
|
|
126336
|
+
batch_id: parsed.data.batch_id,
|
|
126337
|
+
message: "OpenCode session messages client is not available"
|
|
126338
|
+
});
|
|
126339
|
+
}
|
|
126340
|
+
const timeoutMs = parsed.data.timeout_ms ?? DEFAULT_COLLECT_TIMEOUT_MS;
|
|
126341
|
+
const deadline = _internals81.now() + timeoutMs;
|
|
126342
|
+
const batchFilter = context.sessionID !== undefined ? { parentSessionId: context.sessionID } : undefined;
|
|
126343
|
+
await sweepStaleDelegations(directory, DEFAULT_ASYNC_STALE_TIMEOUT_MS);
|
|
126344
|
+
let records = findByBatchId(directory, parsed.data.batch_id, batchFilter);
|
|
126345
|
+
if (records.length === 0) {
|
|
126346
|
+
return collectFailureResult({
|
|
126347
|
+
failure_class: "not_found",
|
|
126348
|
+
batch_id: parsed.data.batch_id,
|
|
126349
|
+
message: `No async lane batch found for ${parsed.data.batch_id}`
|
|
126350
|
+
});
|
|
126351
|
+
}
|
|
126352
|
+
let keepPolling = true;
|
|
126353
|
+
let pollIntervalMs = COLLECT_POLL_INTERVAL_MS;
|
|
126354
|
+
while (keepPolling) {
|
|
126355
|
+
await collectOnce(session, directory, records, parsed.data.cancel_pending === true);
|
|
126356
|
+
await sweepStaleDelegations(directory, DEFAULT_ASYNC_STALE_TIMEOUT_MS);
|
|
126357
|
+
records = findByBatchId(directory, parsed.data.batch_id, batchFilter);
|
|
126358
|
+
if (allSettled(records) || parsed.data.wait !== true) {
|
|
126359
|
+
keepPolling = false;
|
|
126360
|
+
continue;
|
|
126361
|
+
}
|
|
126362
|
+
if (_internals81.now() >= deadline) {
|
|
126363
|
+
keepPolling = false;
|
|
126364
|
+
continue;
|
|
126365
|
+
}
|
|
126366
|
+
await _internals81.sleep(Math.min(pollIntervalMs, Math.max(0, deadline - _internals81.now())));
|
|
126367
|
+
pollIntervalMs = nextCollectPollInterval(pollIntervalMs);
|
|
126368
|
+
}
|
|
126369
|
+
return buildCollectResult(parsed.data.batch_id, records, parsed.data.include_pending === true);
|
|
126370
|
+
}
|
|
126371
|
+
async function launchAsyncLane(args2) {
|
|
126372
|
+
const validation2 = validateLaneAgent(args2.lane.agent, args2.context);
|
|
126373
|
+
const role = validation2.role;
|
|
126374
|
+
const startedAt = isoNow();
|
|
126375
|
+
if (!validation2.ok) {
|
|
126376
|
+
return {
|
|
126377
|
+
id: args2.lane.id,
|
|
126378
|
+
agent: args2.lane.agent,
|
|
126379
|
+
role,
|
|
126380
|
+
status: "rejected",
|
|
126381
|
+
started_at: startedAt,
|
|
126382
|
+
completed_at: isoNow(),
|
|
126383
|
+
error: validation2.error
|
|
126384
|
+
};
|
|
126385
|
+
}
|
|
126386
|
+
const decision = args2.dispatcher.dispatch(args2.lane.id);
|
|
126387
|
+
if (decision.action !== "dispatch") {
|
|
126388
|
+
return {
|
|
126389
|
+
id: args2.lane.id,
|
|
126390
|
+
agent: args2.lane.agent,
|
|
126391
|
+
role,
|
|
126392
|
+
status: "failed",
|
|
126393
|
+
started_at: startedAt,
|
|
126394
|
+
completed_at: isoNow(),
|
|
126395
|
+
error: `dispatcher ${decision.action}: ${decision.reason}`
|
|
126396
|
+
};
|
|
126397
|
+
}
|
|
126398
|
+
try {
|
|
126399
|
+
const createTimeoutMessage = `Lane "${args2.lane.id}" session.create timed out after ${args2.timeoutMs}ms`;
|
|
126400
|
+
const createPromise = args2.session.create({
|
|
126401
|
+
query: { directory: args2.directory }
|
|
126402
|
+
});
|
|
126403
|
+
let createTimedOut = false;
|
|
126404
|
+
createPromise.then((createResult2) => {
|
|
126405
|
+
if (createTimedOut && createResult2.data?.id) {
|
|
126406
|
+
scheduleSessionCleanup(args2.session, createResult2.data.id);
|
|
126407
|
+
}
|
|
126408
|
+
}).catch(() => {
|
|
126409
|
+
return;
|
|
126410
|
+
});
|
|
126411
|
+
const createResult = await withTimeout2(createPromise, args2.timeoutMs, createTimeoutMessage).catch((error93) => {
|
|
126412
|
+
if (formatError3(error93) === createTimeoutMessage) {
|
|
126413
|
+
createTimedOut = true;
|
|
126414
|
+
}
|
|
126415
|
+
throw error93;
|
|
126416
|
+
});
|
|
126417
|
+
const sessionId = createResult.data?.id;
|
|
126418
|
+
if (!sessionId) {
|
|
126419
|
+
return failedLane(args2.lane, role, startedAt, `session.create failed: ${formatError3(createResult.error)}`, decision.slot.slotId, decision.slot.runId);
|
|
126420
|
+
}
|
|
126421
|
+
const pendingRecord = await recordPendingDelegation(args2.directory, {
|
|
126422
|
+
correlationId: sessionId,
|
|
126423
|
+
jobId: null,
|
|
126424
|
+
subagentSessionId: sessionId,
|
|
126425
|
+
parentSessionId: args2.context.sessionID ?? `dispatch_lanes_async:${args2.batchId}`,
|
|
126426
|
+
callID: args2.batchId,
|
|
126427
|
+
normalizedAgent: role,
|
|
126428
|
+
swarmPrefixedAgent: args2.lane.agent,
|
|
126429
|
+
planTaskId: null,
|
|
126430
|
+
evidenceTaskId: null,
|
|
126431
|
+
batchId: args2.batchId,
|
|
126432
|
+
laneId: args2.lane.id,
|
|
126433
|
+
mode: args2.mode ?? "advisory",
|
|
126434
|
+
promptHash: promptHash(args2.lane, args2.directory, args2.batchId),
|
|
126435
|
+
workspace: {
|
|
126436
|
+
directory: args2.directory,
|
|
126437
|
+
gitHead: null,
|
|
126438
|
+
dirtyHash: null,
|
|
126439
|
+
prHeadSha: args2.prHeadSha ?? null,
|
|
126440
|
+
scope: args2.scope ?? null
|
|
126441
|
+
},
|
|
126442
|
+
generation: 1
|
|
126443
|
+
}, { staleTimeoutMs: DEFAULT_ASYNC_STALE_TIMEOUT_MS });
|
|
126444
|
+
if (!pendingRecord) {
|
|
126445
|
+
cleanupAsyncLaunchSession(args2.session, sessionId);
|
|
126446
|
+
return failedLane(args2.lane, role, startedAt, "Failed to record async lane in background delegation ledger", decision.slot.slotId, decision.slot.runId);
|
|
126447
|
+
}
|
|
126448
|
+
const promptController = new AbortController;
|
|
126449
|
+
let promptResult;
|
|
126450
|
+
try {
|
|
126451
|
+
promptResult = await withTimeout2(args2.session.promptAsync({
|
|
126452
|
+
path: { id: sessionId },
|
|
126453
|
+
query: { directory: args2.directory },
|
|
126454
|
+
body: {
|
|
126455
|
+
agent: args2.lane.agent,
|
|
126456
|
+
tools: buildReadOnlyTools(),
|
|
126457
|
+
parts: [{ type: "text", text: args2.lane.prompt }]
|
|
126458
|
+
},
|
|
126459
|
+
signal: promptController.signal
|
|
126460
|
+
}), args2.timeoutMs, `Lane "${args2.lane.id}" session.promptAsync timed out after ${args2.timeoutMs}ms`, promptController);
|
|
126461
|
+
} catch (error93) {
|
|
126462
|
+
const message = formatError3(error93);
|
|
126463
|
+
await appendDelegationTransition(args2.directory, sessionId, {
|
|
126464
|
+
status: "error",
|
|
126465
|
+
result: {
|
|
126466
|
+
error: message,
|
|
126467
|
+
chars: message.length,
|
|
126468
|
+
truncated: false,
|
|
126469
|
+
digest: digestText(message)
|
|
126470
|
+
}
|
|
126471
|
+
});
|
|
126472
|
+
cleanupAsyncLaunchSession(args2.session, sessionId);
|
|
126473
|
+
return failedLane(args2.lane, role, startedAt, message, decision.slot.slotId, decision.slot.runId);
|
|
126474
|
+
}
|
|
126475
|
+
if (promptResult.error) {
|
|
126476
|
+
const error93 = `session.promptAsync failed: ${formatError3(promptResult.error)}`;
|
|
126477
|
+
await appendDelegationTransition(args2.directory, sessionId, {
|
|
126478
|
+
status: "error",
|
|
126479
|
+
result: {
|
|
126480
|
+
error: error93,
|
|
126481
|
+
chars: error93.length,
|
|
126482
|
+
truncated: false,
|
|
126483
|
+
digest: digestText(error93)
|
|
126484
|
+
}
|
|
126485
|
+
});
|
|
126486
|
+
cleanupAsyncLaunchSession(args2.session, sessionId);
|
|
126487
|
+
return failedLane(args2.lane, role, startedAt, error93, decision.slot.slotId, decision.slot.runId);
|
|
126488
|
+
}
|
|
126489
|
+
await appendDelegationTransition(args2.directory, sessionId, {
|
|
126490
|
+
status: "running"
|
|
126491
|
+
});
|
|
126492
|
+
return {
|
|
126493
|
+
id: args2.lane.id,
|
|
126494
|
+
agent: args2.lane.agent,
|
|
126495
|
+
role,
|
|
126496
|
+
status: "pending",
|
|
126497
|
+
session_id: sessionId,
|
|
126498
|
+
slot_id: decision.slot.slotId,
|
|
126499
|
+
run_id: decision.slot.runId,
|
|
126500
|
+
started_at: startedAt,
|
|
126501
|
+
completed_at: isoNow()
|
|
126502
|
+
};
|
|
126503
|
+
} catch (error93) {
|
|
126504
|
+
return failedLane(args2.lane, role, startedAt, formatError3(error93), decision.slot.slotId, decision.slot.runId);
|
|
126505
|
+
} finally {
|
|
126506
|
+
args2.dispatcher.releaseSlot(decision.slot.slotId);
|
|
126507
|
+
}
|
|
126508
|
+
}
|
|
126509
|
+
async function collectOnce(session, directory, records, cancelPending) {
|
|
126510
|
+
for (const record3 of records) {
|
|
126511
|
+
if (record3.status !== "pending" && record3.status !== "running")
|
|
126512
|
+
continue;
|
|
126513
|
+
if (cancelPending) {
|
|
126514
|
+
if (typeof session.abort === "function") {
|
|
126515
|
+
await session.abort({ path: { id: record3.subagentSessionId } }).catch(() => {
|
|
126516
|
+
return;
|
|
126517
|
+
});
|
|
126518
|
+
}
|
|
126519
|
+
await appendDelegationTransition(directory, record3.correlationId, {
|
|
126520
|
+
status: "cancelled"
|
|
126521
|
+
});
|
|
126522
|
+
continue;
|
|
126523
|
+
}
|
|
126524
|
+
let messages;
|
|
126525
|
+
try {
|
|
126526
|
+
messages = await session.messages({
|
|
126527
|
+
path: { id: record3.subagentSessionId },
|
|
126528
|
+
query: { directory, limit: 50 }
|
|
126529
|
+
});
|
|
126530
|
+
} catch {
|
|
126531
|
+
continue;
|
|
126532
|
+
}
|
|
126533
|
+
if (!messages.data)
|
|
126534
|
+
continue;
|
|
126535
|
+
const text = extractLastAssistantText(messages.data);
|
|
126536
|
+
if (!text)
|
|
126537
|
+
continue;
|
|
126538
|
+
const bounded = boundLaneOutput(text);
|
|
126539
|
+
await appendDelegationTransition(directory, record3.correlationId, {
|
|
126540
|
+
status: "completed",
|
|
126541
|
+
result: {
|
|
126542
|
+
text: bounded.output,
|
|
126543
|
+
chars: bounded.output_chars,
|
|
126544
|
+
truncated: bounded.output_truncated,
|
|
126545
|
+
digest: digestText(text)
|
|
126546
|
+
}
|
|
126547
|
+
});
|
|
126548
|
+
}
|
|
126549
|
+
}
|
|
126550
|
+
function extractLastAssistantText(messages) {
|
|
126551
|
+
for (let i2 = messages.length - 1;i2 >= 0; i2--) {
|
|
126552
|
+
const message = messages[i2];
|
|
126553
|
+
if (message.info?.role !== "assistant")
|
|
126554
|
+
continue;
|
|
126555
|
+
const text = extractText3(message.parts);
|
|
126556
|
+
if (text.trim().length > 0)
|
|
126557
|
+
return text;
|
|
126558
|
+
}
|
|
126559
|
+
return "";
|
|
126560
|
+
}
|
|
126561
|
+
function nextCollectPollInterval(currentMs) {
|
|
126562
|
+
if (currentMs <= 0)
|
|
126563
|
+
return COLLECT_POLL_INTERVAL_MS;
|
|
126564
|
+
return Math.min(currentMs * 2, MAX_COLLECT_POLL_INTERVAL_MS);
|
|
126565
|
+
}
|
|
125896
126566
|
async function runLane(session, dispatcher, lane, directory, timeoutMs, context) {
|
|
125897
126567
|
const validation2 = validateLaneAgent(lane.agent, context);
|
|
125898
126568
|
const role = validation2.role;
|
|
@@ -125993,6 +126663,49 @@ function buildResult(laneResults, maxConcurrent, timeoutMs) {
|
|
|
125993
126663
|
lane_results: laneResults
|
|
125994
126664
|
};
|
|
125995
126665
|
}
|
|
126666
|
+
function buildCollectResult(batchId, records, includePending) {
|
|
126667
|
+
const laneResults = records.filter((record3) => includePending || record3.status !== "pending" && record3.status !== "running").map(recordToLaneResult);
|
|
126668
|
+
const completed = records.filter((record3) => record3.status === "completed");
|
|
126669
|
+
const failed = records.filter((record3) => record3.status === "error");
|
|
126670
|
+
const cancelled = records.filter((record3) => record3.status === "cancelled");
|
|
126671
|
+
const stale = records.filter((record3) => record3.status === "stale");
|
|
126672
|
+
const pending = records.filter((record3) => record3.status === "pending" || record3.status === "running");
|
|
126673
|
+
const consumed = records.filter((record3) => record3.status === "consumed");
|
|
126674
|
+
return {
|
|
126675
|
+
success: pending.length === 0 && failed.length === 0 && cancelled.length === 0 && stale.length === 0,
|
|
126676
|
+
batch_id: batchId,
|
|
126677
|
+
total: records.length,
|
|
126678
|
+
completed: completed.length,
|
|
126679
|
+
failed: failed.length,
|
|
126680
|
+
cancelled: cancelled.length,
|
|
126681
|
+
stale: stale.length,
|
|
126682
|
+
pending: pending.length,
|
|
126683
|
+
consumed: consumed.length,
|
|
126684
|
+
all_settled: pending.length === 0,
|
|
126685
|
+
lane_results: laneResults
|
|
126686
|
+
};
|
|
126687
|
+
}
|
|
126688
|
+
function recordToLaneResult(record3) {
|
|
126689
|
+
const status = record3.status === "error" ? "failed" : record3.status === "running" ? "pending" : record3.status;
|
|
126690
|
+
return {
|
|
126691
|
+
id: record3.laneId ?? record3.correlationId,
|
|
126692
|
+
agent: record3.swarmPrefixedAgent,
|
|
126693
|
+
role: record3.normalizedAgent,
|
|
126694
|
+
status,
|
|
126695
|
+
session_id: record3.subagentSessionId,
|
|
126696
|
+
started_at: new Date(record3.createdAt).toISOString(),
|
|
126697
|
+
completed_at: new Date(record3.completedAt ?? record3.updatedAt).toISOString(),
|
|
126698
|
+
...record3.result?.text !== undefined ? {
|
|
126699
|
+
output: record3.result.text,
|
|
126700
|
+
output_chars: record3.result.chars,
|
|
126701
|
+
output_truncated: record3.result.truncated
|
|
126702
|
+
} : {},
|
|
126703
|
+
...record3.result?.error !== undefined ? { error: record3.result.error } : {}
|
|
126704
|
+
};
|
|
126705
|
+
}
|
|
126706
|
+
function allSettled(records) {
|
|
126707
|
+
return records.every((record3) => record3.status !== "pending" && record3.status !== "running");
|
|
126708
|
+
}
|
|
125996
126709
|
function failedLane(lane, role, startedAt, error93, slotId, runId, sessionId) {
|
|
125997
126710
|
return {
|
|
125998
126711
|
id: lane.id,
|
|
@@ -126095,6 +126808,40 @@ function failureResult(args2) {
|
|
|
126095
126808
|
errors: args2.errors
|
|
126096
126809
|
};
|
|
126097
126810
|
}
|
|
126811
|
+
function asyncFailureResult(args2) {
|
|
126812
|
+
return {
|
|
126813
|
+
success: false,
|
|
126814
|
+
failure_class: args2.failure_class,
|
|
126815
|
+
message: args2.message,
|
|
126816
|
+
batch_id: null,
|
|
126817
|
+
dispatched: 0,
|
|
126818
|
+
pending: 0,
|
|
126819
|
+
failed: 0,
|
|
126820
|
+
rejected: 0,
|
|
126821
|
+
max_concurrent: 0,
|
|
126822
|
+
timeout_ms: 0,
|
|
126823
|
+
lane_results: [],
|
|
126824
|
+
errors: args2.errors
|
|
126825
|
+
};
|
|
126826
|
+
}
|
|
126827
|
+
function collectFailureResult(args2) {
|
|
126828
|
+
return {
|
|
126829
|
+
success: false,
|
|
126830
|
+
failure_class: args2.failure_class,
|
|
126831
|
+
message: args2.message,
|
|
126832
|
+
batch_id: args2.batch_id,
|
|
126833
|
+
total: 0,
|
|
126834
|
+
completed: 0,
|
|
126835
|
+
failed: 0,
|
|
126836
|
+
cancelled: 0,
|
|
126837
|
+
stale: 0,
|
|
126838
|
+
pending: 0,
|
|
126839
|
+
consumed: 0,
|
|
126840
|
+
all_settled: false,
|
|
126841
|
+
lane_results: [],
|
|
126842
|
+
errors: args2.errors
|
|
126843
|
+
};
|
|
126844
|
+
}
|
|
126098
126845
|
function findDuplicateLaneIds(lanes) {
|
|
126099
126846
|
const seen = new Set;
|
|
126100
126847
|
const duplicates = new Set;
|
|
@@ -126110,6 +126857,14 @@ function scheduleSessionCleanup(session, sessionId) {
|
|
|
126110
126857
|
return;
|
|
126111
126858
|
});
|
|
126112
126859
|
}
|
|
126860
|
+
function cleanupAsyncLaunchSession(session, sessionId) {
|
|
126861
|
+
if (typeof session.abort === "function") {
|
|
126862
|
+
session.abort({ path: { id: sessionId } }).catch(() => {
|
|
126863
|
+
return;
|
|
126864
|
+
});
|
|
126865
|
+
}
|
|
126866
|
+
scheduleSessionCleanup(session, sessionId);
|
|
126867
|
+
}
|
|
126113
126868
|
async function withTimeout2(promise3, timeoutMs, message, controller) {
|
|
126114
126869
|
let timeout;
|
|
126115
126870
|
try {
|
|
@@ -126120,9 +126875,6 @@ async function withTimeout2(promise3, timeoutMs, message, controller) {
|
|
|
126120
126875
|
controller?.abort();
|
|
126121
126876
|
reject(new Error(message));
|
|
126122
126877
|
}, timeoutMs);
|
|
126123
|
-
if (typeof timeout.unref === "function") {
|
|
126124
|
-
timeout.unref();
|
|
126125
|
-
}
|
|
126126
126878
|
})
|
|
126127
126879
|
]);
|
|
126128
126880
|
} finally {
|
|
@@ -126150,6 +126902,29 @@ function boundErrorString(text) {
|
|
|
126150
126902
|
function isoNow() {
|
|
126151
126903
|
return new Date(_internals81.now()).toISOString();
|
|
126152
126904
|
}
|
|
126905
|
+
function makeBatchId() {
|
|
126906
|
+
return `lanes-${_internals81.now().toString(36)}`;
|
|
126907
|
+
}
|
|
126908
|
+
function promptHash(lane, directory, batchId) {
|
|
126909
|
+
return digestText(JSON.stringify({
|
|
126910
|
+
batchId,
|
|
126911
|
+
laneId: lane.id,
|
|
126912
|
+
agent: lane.agent,
|
|
126913
|
+
directory,
|
|
126914
|
+
prompt: lane.prompt.replace(/\r\n/g, `
|
|
126915
|
+
`)
|
|
126916
|
+
}));
|
|
126917
|
+
}
|
|
126918
|
+
function digestText(text) {
|
|
126919
|
+
return createHash16("sha256").update(text).digest("hex");
|
|
126920
|
+
}
|
|
126921
|
+
function sleep2(ms) {
|
|
126922
|
+
if (ms <= 0)
|
|
126923
|
+
return Promise.resolve();
|
|
126924
|
+
return new Promise((resolve57) => {
|
|
126925
|
+
setTimeout(resolve57, ms);
|
|
126926
|
+
});
|
|
126927
|
+
}
|
|
126153
126928
|
var dispatch_lanes = createSwarmTool({
|
|
126154
126929
|
description: "Dispatch multiple read-only exploration/review lanes concurrently through OpenCode sessions and return a structured join result.",
|
|
126155
126930
|
args: {
|
|
@@ -126164,12 +126939,53 @@ var dispatch_lanes = createSwarmTool({
|
|
|
126164
126939
|
return JSON.stringify(result, null, 2);
|
|
126165
126940
|
}
|
|
126166
126941
|
});
|
|
126942
|
+
var dispatch_lanes_async = createSwarmTool({
|
|
126943
|
+
description: "Launch multiple read-only advisory lanes with OpenCode promptAsync and return immediately with a batch id for collect_lane_results.",
|
|
126944
|
+
args: {
|
|
126945
|
+
lanes: DispatchLanesAsyncArgsSchema.shape.lanes,
|
|
126946
|
+
max_concurrent: DispatchLanesAsyncArgsSchema.shape.max_concurrent,
|
|
126947
|
+
timeout_ms: DispatchLanesAsyncArgsSchema.shape.timeout_ms,
|
|
126948
|
+
batch_id: DispatchLanesAsyncArgsSchema.shape.batch_id,
|
|
126949
|
+
mode: DispatchLanesAsyncArgsSchema.shape.mode,
|
|
126950
|
+
pr_head_sha: DispatchLanesAsyncArgsSchema.shape.pr_head_sha,
|
|
126951
|
+
scope: DispatchLanesAsyncArgsSchema.shape.scope
|
|
126952
|
+
},
|
|
126953
|
+
execute: async (args2, directory, ctx) => {
|
|
126954
|
+
const result = await executeDispatchLanesAsync(args2, directory, {
|
|
126955
|
+
callerAgent: getContextAgent(ctx),
|
|
126956
|
+
sessionID: getContextSessionID(ctx)
|
|
126957
|
+
});
|
|
126958
|
+
return JSON.stringify(result, null, 2);
|
|
126959
|
+
}
|
|
126960
|
+
});
|
|
126961
|
+
var collect_lane_results = createSwarmTool({
|
|
126962
|
+
description: "Collect or poll results for a dispatch_lanes_async batch; this is the required join barrier for advisory lane workflows and does not advance workflow gates.",
|
|
126963
|
+
args: {
|
|
126964
|
+
batch_id: CollectLaneResultsArgsSchema.shape.batch_id,
|
|
126965
|
+
wait: CollectLaneResultsArgsSchema.shape.wait,
|
|
126966
|
+
timeout_ms: CollectLaneResultsArgsSchema.shape.timeout_ms,
|
|
126967
|
+
include_pending: CollectLaneResultsArgsSchema.shape.include_pending,
|
|
126968
|
+
cancel_pending: CollectLaneResultsArgsSchema.shape.cancel_pending
|
|
126969
|
+
},
|
|
126970
|
+
execute: async (args2, directory, ctx) => {
|
|
126971
|
+
const result = await executeCollectLaneResults(args2, directory, {
|
|
126972
|
+
sessionID: getContextSessionID(ctx)
|
|
126973
|
+
});
|
|
126974
|
+
return JSON.stringify(result, null, 2);
|
|
126975
|
+
}
|
|
126976
|
+
});
|
|
126167
126977
|
function getContextAgent(ctx) {
|
|
126168
126978
|
if (!ctx || typeof ctx !== "object")
|
|
126169
126979
|
return;
|
|
126170
126980
|
const value = ctx.agent;
|
|
126171
126981
|
return typeof value === "string" ? value : undefined;
|
|
126172
126982
|
}
|
|
126983
|
+
function getContextSessionID(ctx) {
|
|
126984
|
+
if (!ctx || typeof ctx !== "object")
|
|
126985
|
+
return;
|
|
126986
|
+
const value = ctx.sessionID;
|
|
126987
|
+
return typeof value === "string" ? value : undefined;
|
|
126988
|
+
}
|
|
126173
126989
|
|
|
126174
126990
|
// src/tools/manifest.ts
|
|
126175
126991
|
init_doc_scan();
|
|
@@ -126850,11 +127666,11 @@ var external_skill_delete = createSwarmTool({
|
|
|
126850
127666
|
// src/tools/external-skill-discover.ts
|
|
126851
127667
|
init_zod();
|
|
126852
127668
|
init_loader();
|
|
126853
|
-
import { createHash as
|
|
127669
|
+
import { createHash as createHash18, randomUUID as randomUUID12 } from "node:crypto";
|
|
126854
127670
|
|
|
126855
127671
|
// src/services/external-skill-validator.ts
|
|
126856
127672
|
init_knowledge_validator();
|
|
126857
|
-
import { createHash as
|
|
127673
|
+
import { createHash as createHash17 } from "node:crypto";
|
|
126858
127674
|
var PROMPT_INJECTION_PATTERNS = [
|
|
126859
127675
|
{
|
|
126860
127676
|
pattern: /system\s*:/i,
|
|
@@ -127389,7 +128205,7 @@ function evaluateCandidate(candidate, options) {
|
|
|
127389
128205
|
}
|
|
127390
128206
|
var _internals84 = {
|
|
127391
128207
|
getTimestamp: () => new Date().toISOString(),
|
|
127392
|
-
computeSha256: (content) =>
|
|
128208
|
+
computeSha256: (content) => createHash17("sha256").update(content).digest("hex"),
|
|
127393
128209
|
stripMarkdownCodeForUnsafeScan
|
|
127394
128210
|
};
|
|
127395
128211
|
|
|
@@ -127411,7 +128227,7 @@ var _internals85 = {
|
|
|
127411
128227
|
return { content, finalUrl: response.url };
|
|
127412
128228
|
},
|
|
127413
128229
|
getTimestamp: () => new Date().toISOString(),
|
|
127414
|
-
computeSha256: (content) =>
|
|
128230
|
+
computeSha256: (content) => createHash18("sha256").update(content).digest("hex"),
|
|
127415
128231
|
uuid: () => randomUUID12()
|
|
127416
128232
|
};
|
|
127417
128233
|
var SOURCE_TRUST_LEVELS = {
|
|
@@ -127797,7 +128613,7 @@ var external_skill_list = createSwarmTool({
|
|
|
127797
128613
|
// src/tools/external-skill-promote.ts
|
|
127798
128614
|
init_zod();
|
|
127799
128615
|
init_loader();
|
|
127800
|
-
import { createHash as
|
|
128616
|
+
import { createHash as createHash19 } from "node:crypto";
|
|
127801
128617
|
import * as fs95 from "node:fs/promises";
|
|
127802
128618
|
import * as path149 from "node:path";
|
|
127803
128619
|
init_create_tool();
|
|
@@ -127962,7 +128778,7 @@ var external_skill_promote = createSwarmTool({
|
|
|
127962
128778
|
}
|
|
127963
128779
|
throw writeErr;
|
|
127964
128780
|
}
|
|
127965
|
-
const promotedContentHash =
|
|
128781
|
+
const promotedContentHash = createHash19("sha256").update(skillMarkdown).digest("hex");
|
|
127966
128782
|
const prePromotionHistory = candidate.evaluation_history;
|
|
127967
128783
|
const lastPrePromotionEntry = prePromotionHistory.length > 0 ? prePromotionHistory[prePromotionHistory.length - 1] : undefined;
|
|
127968
128784
|
const originalEvaluation = lastPrePromotionEntry ? {
|
|
@@ -145435,7 +146251,7 @@ import * as zlib from "node:zlib";
|
|
|
145435
146251
|
// src/evidence/documents.ts
|
|
145436
146252
|
init_utils2();
|
|
145437
146253
|
init_redaction();
|
|
145438
|
-
import { createHash as
|
|
146254
|
+
import { createHash as createHash21 } from "node:crypto";
|
|
145439
146255
|
import { appendFile as appendFile17, mkdir as mkdir34 } from "node:fs/promises";
|
|
145440
146256
|
import * as path189 from "node:path";
|
|
145441
146257
|
var EVIDENCE_CACHE_FILE = "evidence-cache/documents.jsonl";
|
|
@@ -145479,7 +146295,7 @@ function createEvidenceDocumentRecord(input, defaultCapturedAt) {
|
|
|
145479
146295
|
};
|
|
145480
146296
|
}
|
|
145481
146297
|
function createEvidenceDocumentId(input) {
|
|
145482
|
-
const hash4 =
|
|
146298
|
+
const hash4 = createHash21("sha256").update([
|
|
145483
146299
|
input.sourceType,
|
|
145484
146300
|
input.query ?? "",
|
|
145485
146301
|
input.title ?? "",
|
|
@@ -147276,6 +148092,8 @@ var TOOL_MANIFEST = defineHandlers({
|
|
|
147276
148092
|
swarm_memory_propose: () => swarm_memory_propose,
|
|
147277
148093
|
swarm_command: () => swarm_command,
|
|
147278
148094
|
dispatch_lanes: () => dispatch_lanes,
|
|
148095
|
+
dispatch_lanes_async: () => dispatch_lanes_async,
|
|
148096
|
+
collect_lane_results: () => collect_lane_results,
|
|
147279
148097
|
summarize_work: () => summarize_work,
|
|
147280
148098
|
write_architecture_supervisor_evidence: () => write_architecture_supervisor_evidence,
|
|
147281
148099
|
lean_turbo_plan_lanes: () => lean_turbo_plan_lanes,
|