@quantiya/codevibe-claude-plugin 2.0.41 → 2.0.43
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/.claude-plugin/plugin.json +1 -1
- package/node_modules/@quantiya/codevibe-core/dist/index.js +454 -445
- package/node_modules/@quantiya/codevibe-core/dist/local-executor/class-b-consumer.d.ts +5 -5
- package/node_modules/@quantiya/codevibe-core/dist/local-executor/hook-bridge.d.ts +1 -1
- package/node_modules/@quantiya/codevibe-core/dist/local-executor/local-executor-impl.d.ts +53 -20
- package/node_modules/@quantiya/codevibe-core/dist/local-executor/types.d.ts +8 -2
- package/node_modules/@quantiya/codevibe-core/dist/local-executor/workspace-shadow.d.ts +16 -0
- package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/cli.js +1302 -374
- package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/command-intent.d.ts +18 -0
- package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/components/InputBar.d.ts +1 -0
- package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/gate-decision-submit.d.ts +26 -5
- package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/index.d.ts +17 -0
- package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/non-tty-fallback.d.ts +1 -1
- package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/quorum-loop.d.ts +224 -51
- package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/reducer.d.ts +7 -0
- package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/slash-routes/continuation.d.ts +5 -0
- package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/task-label.d.ts +25 -0
- package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/types.d.ts +83 -0
- package/node_modules/@quantiya/codevibe-core/dist/substrate-launch/engage-substrate.d.ts +2 -0
- package/node_modules/@quantiya/codevibe-core/package.json +1 -1
- package/package.json +2 -2
|
@@ -7955,6 +7955,39 @@ function createExplicitTtyExitCoordinator(deps) {
|
|
|
7955
7955
|
// src/orchestration-shell/reducer.ts
|
|
7956
7956
|
var import_ulid = require("ulid");
|
|
7957
7957
|
|
|
7958
|
+
// src/orchestration-shell/task-label.ts
|
|
7959
|
+
var GROUP_GATE_PREFIX = "group:";
|
|
7960
|
+
function shortTaskId(taskId) {
|
|
7961
|
+
return taskId.length > 8 ? taskId.slice(0, 8) : taskId;
|
|
7962
|
+
}
|
|
7963
|
+
function taskLabel(state, taskId) {
|
|
7964
|
+
let direct = state.taskOrdinals.get(taskId);
|
|
7965
|
+
if (direct)
|
|
7966
|
+
return direct.retryOf ? `#${direct.ordinal} retry` : `#${direct.ordinal}`;
|
|
7967
|
+
if (taskId.startsWith(GROUP_GATE_PREFIX)) {
|
|
7968
|
+
let group = state.taskOrdinals.get(taskId.slice(GROUP_GATE_PREFIX.length));
|
|
7969
|
+
if (group) return `#${group.ordinal}`;
|
|
7970
|
+
}
|
|
7971
|
+
let team = state.team;
|
|
7972
|
+
if (team && team.taskGroupId) {
|
|
7973
|
+
let group = state.taskOrdinals.get(team.taskGroupId);
|
|
7974
|
+
if (group) {
|
|
7975
|
+
for (let [trackIndex, track] of team.tracks)
|
|
7976
|
+
if (track.taskId === taskId) return `#${group.ordinal}.${trackIndex + 1}`;
|
|
7977
|
+
}
|
|
7978
|
+
}
|
|
7979
|
+
return shortTaskId(taskId);
|
|
7980
|
+
}
|
|
7981
|
+
function taskIdForOrdinal(state, ordinal) {
|
|
7982
|
+
for (let [id, record] of state.taskOrdinals)
|
|
7983
|
+
if (record.ordinal === ordinal) return id;
|
|
7984
|
+
return null;
|
|
7985
|
+
}
|
|
7986
|
+
function endedTaskAdvisory(described, taskId) {
|
|
7987
|
+
let ended = described.find((t) => t.taskId === taskId && t.outcome === "discarded");
|
|
7988
|
+
return ended ? `Task ${ended.label} ended on this side \u2014 its workspace copy was discarded; accepting this review applies nothing. Choose Abort (or Cancel) and re-run the request.` : null;
|
|
7989
|
+
}
|
|
7990
|
+
|
|
7958
7991
|
// src/orchestration-shell/web/sanitize.ts
|
|
7959
7992
|
function sanitizeForTerminal(input) {
|
|
7960
7993
|
if (!input) return "";
|
|
@@ -8270,6 +8303,23 @@ function liveLabel(event) {
|
|
|
8270
8303
|
// src/orchestration-shell/reducer.ts
|
|
8271
8304
|
var CONVERSATION_BUFFER_MAX = 500;
|
|
8272
8305
|
function reducer(state, action) {
|
|
8306
|
+
return normalizeActiveGatePrompt(reduceAction(state, action));
|
|
8307
|
+
}
|
|
8308
|
+
function isAnswerableGatePrompt(entry) {
|
|
8309
|
+
if (entry.kind !== "gate-prompt" || entry.final !== !1) return !1;
|
|
8310
|
+
let phase = entry.uiState.phase;
|
|
8311
|
+
return phase === "awaiting-number" || phase === "awaiting-notes";
|
|
8312
|
+
}
|
|
8313
|
+
function normalizeActiveGatePrompt(state) {
|
|
8314
|
+
let current = state.activeGatePromptId;
|
|
8315
|
+
if (current !== null) {
|
|
8316
|
+
let entry = state.conversation.find((e) => e.id === current);
|
|
8317
|
+
if (entry && isAnswerableGatePrompt(entry)) return state;
|
|
8318
|
+
}
|
|
8319
|
+
let next = state.conversation.find(isAnswerableGatePrompt)?.id ?? null;
|
|
8320
|
+
return next === current ? state : { ...state, activeGatePromptId: next };
|
|
8321
|
+
}
|
|
8322
|
+
function reduceAction(state, action) {
|
|
8273
8323
|
switch (action.type) {
|
|
8274
8324
|
case "USER_INPUT":
|
|
8275
8325
|
return reduceUserInput(state, action.text, action.attachments, action.imagePaths);
|
|
@@ -8302,6 +8352,16 @@ function reducer(state, action) {
|
|
|
8302
8352
|
return reducePlannerOfferPresented(state, action.offer);
|
|
8303
8353
|
case "CLEAR_PENDING_PLANNER_OFFER":
|
|
8304
8354
|
return { ...state, pendingPlannerOffer: null };
|
|
8355
|
+
case "TASK_ORDINAL_ASSIGNED":
|
|
8356
|
+
return reduceTaskOrdinalAssigned(state, action.taskId, action.kind, action.retryOf);
|
|
8357
|
+
case "APPLY_CONFLICT_PRESENTED":
|
|
8358
|
+
return reduceApplyConflictPresented(state, action.menu);
|
|
8359
|
+
case "CLEAR_PENDING_APPLY_CONFLICT": {
|
|
8360
|
+
if (action.taskId === void 0) return { ...state, pendingApplyConflicts: /* @__PURE__ */ new Map() };
|
|
8361
|
+
if (!state.pendingApplyConflicts.has(action.taskId)) return state;
|
|
8362
|
+
let rest = new Map(state.pendingApplyConflicts);
|
|
8363
|
+
return rest.delete(action.taskId), { ...state, pendingApplyConflicts: rest };
|
|
8364
|
+
}
|
|
8305
8365
|
case "STRUCTURAL_SUMMARY_GENERATED":
|
|
8306
8366
|
return {
|
|
8307
8367
|
...state,
|
|
@@ -8545,13 +8605,13 @@ function reduceReviewerStatusLine(state, event) {
|
|
|
8545
8605
|
function reduceTaskProgress(state, event) {
|
|
8546
8606
|
let s = state;
|
|
8547
8607
|
if (MILESTONE_SCROLLBACK_PHASES.has(event.phase)) {
|
|
8548
|
-
let milestone = {
|
|
8608
|
+
let milestoneTaskId = "taskId" in event && typeof event.taskId == "string" ? event.taskId : null, milestoneLabel = milestoneTaskId ? taskLabel(state, milestoneTaskId) : "", milestonePrefix = milestoneLabel.startsWith("#") ? `[Task ${milestoneLabel}] ` : "", milestone = {
|
|
8549
8609
|
kind: "advisory",
|
|
8550
8610
|
id: (0, import_ulid.ulid)(),
|
|
8551
8611
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8552
8612
|
final: !0,
|
|
8553
8613
|
source: "shell",
|
|
8554
|
-
text: renderProgressLine(event)
|
|
8614
|
+
text: `${milestonePrefix}${renderProgressLine(event)}`,
|
|
8555
8615
|
// R7 A4 (Stage-2 r4 Codex MED) — the halt_notice echo is DESKTOP-ONLY by
|
|
8556
8616
|
// contract: `localOnly` keeps it out of the turn mirror + handoff briefs
|
|
8557
8617
|
// (index.ts outbound-context filters), so operational halt text can never
|
|
@@ -8732,6 +8792,43 @@ function reducePlannerOfferPresented(state, offer) {
|
|
|
8732
8792
|
pendingClarification: null
|
|
8733
8793
|
};
|
|
8734
8794
|
}
|
|
8795
|
+
function reduceTaskOrdinalAssigned(state, taskId, kind, retryOf) {
|
|
8796
|
+
if (state.taskOrdinals.has(taskId)) return state;
|
|
8797
|
+
let taskOrdinals = new Map(state.taskOrdinals);
|
|
8798
|
+
return taskOrdinals.set(taskId, {
|
|
8799
|
+
ordinal: state.nextTaskOrdinal,
|
|
8800
|
+
kind,
|
|
8801
|
+
...retryOf ? { retryOf } : {}
|
|
8802
|
+
}), { ...state, taskOrdinals, nextTaskOrdinal: state.nextTaskOrdinal + 1 };
|
|
8803
|
+
}
|
|
8804
|
+
var APPLY_CONFLICT_MENU_HINT = "Reply with a number 1-2 (or /task retry <n> / /task discard <n> while another review is open); other text closes this menu.";
|
|
8805
|
+
function reduceApplyConflictPresented(state, menu) {
|
|
8806
|
+
let text2 = [
|
|
8807
|
+
...menu.conflicts.map(
|
|
8808
|
+
(c) => ` ${c.path} \u2014 ${c.cause === "session-task" ? `changed by task ${c.byTaskLabel ?? "?"}` : c.cause === "write-error" ? "could not be written" : "changed outside CodeVibe"}`
|
|
8809
|
+
),
|
|
8810
|
+
"",
|
|
8811
|
+
" 1. Retry as a new task \u2014 re-run the request on a fresh snapshot (reviewed and approved again)",
|
|
8812
|
+
" 2. Discard \u2014 keep your files as they are",
|
|
8813
|
+
"",
|
|
8814
|
+
APPLY_CONFLICT_MENU_HINT
|
|
8815
|
+
].join(`
|
|
8816
|
+
`), advisoryEntry = {
|
|
8817
|
+
kind: "advisory",
|
|
8818
|
+
id: (0, import_ulid.ulid)(),
|
|
8819
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8820
|
+
final: !0,
|
|
8821
|
+
source: "shell",
|
|
8822
|
+
text: text2,
|
|
8823
|
+
// Desktop-only by contract (design §3 C3 / §9): never mirrored, never folded
|
|
8824
|
+
// into a workflow-handoff brief.
|
|
8825
|
+
localOnly: !0
|
|
8826
|
+
}, withEntry = appendConversation(state, advisoryEntry), menus = new Map(state.pendingApplyConflicts);
|
|
8827
|
+
return menus.set(menu.taskId, { ...menu, conversationEntryId: advisoryEntry.id }), {
|
|
8828
|
+
...withEntry,
|
|
8829
|
+
pendingApplyConflicts: menus
|
|
8830
|
+
};
|
|
8831
|
+
}
|
|
8735
8832
|
function reduceEventReceived(state, event, role) {
|
|
8736
8833
|
if (state.conversation.some(
|
|
8737
8834
|
(entry2) => entry2.kind === "subagent-event" && entry2.event.eventId === event.eventId
|
|
@@ -8841,13 +8938,18 @@ function findUnfinalizedGatePromptIndex(state, taskId) {
|
|
|
8841
8938
|
}
|
|
8842
8939
|
return -1;
|
|
8843
8940
|
}
|
|
8844
|
-
function makeGatePromptPanel(envelope) {
|
|
8941
|
+
function makeGatePromptPanel(envelope, state) {
|
|
8942
|
+
let label = state ? taskLabel(state, envelope.taskId) : "";
|
|
8845
8943
|
return {
|
|
8846
8944
|
kind: "gate-panel",
|
|
8847
8945
|
id: (0, import_ulid.ulid)(),
|
|
8848
8946
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8849
8947
|
final: !0,
|
|
8850
|
-
panel: {
|
|
8948
|
+
panel: {
|
|
8949
|
+
variant: "prompt",
|
|
8950
|
+
envelope,
|
|
8951
|
+
...label.startsWith("#") ? { taskLabel: `Task ${label}` } : {}
|
|
8952
|
+
}
|
|
8851
8953
|
};
|
|
8852
8954
|
}
|
|
8853
8955
|
function makeGateSummaryPanel(reviewSummary) {
|
|
@@ -8876,7 +8978,7 @@ function promoteQueuedGatePrompt(state, idx, target) {
|
|
|
8876
8978
|
...state.conversation.slice(idx + 1)
|
|
8877
8979
|
], promoted = appendConversation(
|
|
8878
8980
|
{ ...state, conversation: nextConversation },
|
|
8879
|
-
makeGatePromptPanel(head)
|
|
8981
|
+
makeGatePromptPanel(head, state)
|
|
8880
8982
|
);
|
|
8881
8983
|
return promotedReviewSummary !== void 0 && promotedReviewSummary.rounds.length > 0 && (promoted = appendConversation(promoted, makeGateSummaryPanel(promotedReviewSummary))), promoted;
|
|
8882
8984
|
}
|
|
@@ -8909,7 +9011,7 @@ function reduceGatePromptReceived(state, envelope) {
|
|
|
8909
9011
|
queue: [],
|
|
8910
9012
|
uiState: durableResolution ? { phase: "resolved", resolvedBy: "durable", resolution: durableResolution } : isClaimed ? { phase: "claimed", decisionClaimId: state.gateActionClaims.get(envelope.gateId) ?? null } : { phase: "awaiting-number" }
|
|
8911
9013
|
}, withControl = appendConversation(state, entry);
|
|
8912
|
-
return appendConversation(withControl, makeGatePromptPanel(envelope));
|
|
9014
|
+
return appendConversation(withControl, makeGatePromptPanel(envelope, state));
|
|
8913
9015
|
}
|
|
8914
9016
|
function reduceGatePromptDecisionFenced(state, promptEntryId, gateId, decisionAttemptId, canonicalRequestHash, decisionClaimId) {
|
|
8915
9017
|
let idx = state.conversation.findIndex(
|
|
@@ -9326,6 +9428,12 @@ function buildInitialState(args) {
|
|
|
9326
9428
|
// A1a #3/#4: the lightweight local interactive offer. null until the shell
|
|
9327
9429
|
// presents a team-decomposition / build-transition offer.
|
|
9328
9430
|
pendingPlannerOffer: null,
|
|
9431
|
+
// P46a D1/C3: in-session task numbers (one counter, starts at 1) + the
|
|
9432
|
+
// desktop-local apply-conflict menu (null until a promote collides).
|
|
9433
|
+
taskOrdinals: /* @__PURE__ */ new Map(),
|
|
9434
|
+
nextTaskOrdinal: 1,
|
|
9435
|
+
pendingApplyConflicts: /* @__PURE__ */ new Map(),
|
|
9436
|
+
activeGatePromptId: null,
|
|
9329
9437
|
inputHistory: [],
|
|
9330
9438
|
// CP-1.d §4.8 LOCK: structuralSummary stays null until session-start
|
|
9331
9439
|
// generation completes; structuralSummaryError populated on failure.
|
|
@@ -10687,8 +10795,8 @@ function formatHaltReason3(reason) {
|
|
|
10687
10795
|
function formatPromptKind2(promptKind) {
|
|
10688
10796
|
return promptKind === ORCHESTRATION_ESCALATED_GATE_PROMPT_KIND ? "Escalated review" : promptKind === ORCHESTRATION_FINAL_APPROVAL_PROMPT_KIND ? "Final approval" : promptKind === CONTINUATION_OFFER_HANDOFF_PROMPT_KIND ? "Continue with which agent?" : promptKind;
|
|
10689
10797
|
}
|
|
10690
|
-
function renderPromptVariant(envelope) {
|
|
10691
|
-
let { ink } = getInkRuntime(), { Box, Text } = ink, haltReasonText = envelope.promptKind === ORCHESTRATION_ESCALATED_GATE_PROMPT_KIND ? formatHaltReason3(envelope.reason) : null, kindLabel = formatPromptKind2(envelope.promptKind), children = [], context = GateContextPanel({ envelope });
|
|
10798
|
+
function renderPromptVariant(envelope, taskLabel2) {
|
|
10799
|
+
let { ink } = getInkRuntime(), { Box, Text } = ink, haltReasonText = envelope.promptKind === ORCHESTRATION_ESCALATED_GATE_PROMPT_KIND ? formatHaltReason3(envelope.reason) : null, kindLabel = taskLabel2 ? `${formatPromptKind2(envelope.promptKind)} \xB7 ${taskLabel2}` : formatPromptKind2(envelope.promptKind), children = [], context = GateContextPanel({ envelope });
|
|
10692
10800
|
context && children.push(
|
|
10693
10801
|
React11.createElement(
|
|
10694
10802
|
Box,
|
|
@@ -10758,7 +10866,7 @@ function renderPromptVariant(envelope) {
|
|
|
10758
10866
|
}
|
|
10759
10867
|
function GatePanelEntry(props) {
|
|
10760
10868
|
let { panel } = props.entry;
|
|
10761
|
-
return panel.variant === "prompt" ? renderPromptVariant(panel.envelope) : renderReviewSummarySection(panel.reviewSummary);
|
|
10869
|
+
return panel.variant === "prompt" ? renderPromptVariant(panel.envelope, panel.taskLabel) : renderReviewSummarySection(panel.reviewSummary);
|
|
10762
10870
|
}
|
|
10763
10871
|
|
|
10764
10872
|
// src/orchestration-shell/components/ConversationPane.tsx
|
|
@@ -17006,7 +17114,7 @@ function InputBar(props) {
|
|
|
17006
17114
|
)
|
|
17007
17115
|
);
|
|
17008
17116
|
let placeholder = props.placeholder ?? "";
|
|
17009
|
-
gatePromptMode?.kind === "awaiting-number" ? placeholder = `Type a number 1..${gatePromptMode.maxOption}` : gatePromptMode?.kind === "awaiting-notes" && (placeholder = "Type your notes + Enter");
|
|
17117
|
+
gatePromptMode?.kind === "awaiting-number" ? placeholder = gatePromptMode.taskLabel ? `Type a number 1..${gatePromptMode.maxOption} \xB7 ${gatePromptMode.taskLabel}` : `Type a number 1..${gatePromptMode.maxOption}` : gatePromptMode?.kind === "awaiting-notes" && (placeholder = "Type your notes + Enter");
|
|
17010
17118
|
let shown = displayValue(value), cur = clampCursor2(cursor, shown.length), valueWidth = Math.max(1, columns - 6), win = computeInputWindow(shown, cur, valueWidth, scrollRef.current);
|
|
17011
17119
|
scrollRef.current = win.scroll;
|
|
17012
17120
|
let origin = rowOrigin ?? { left: 0, top: 0 };
|
|
@@ -17383,13 +17491,20 @@ function decisionToGroupOutcome(decision) {
|
|
|
17383
17491
|
// src/orchestration-shell/gate-decision-submit.ts
|
|
17384
17492
|
var import_uuid3 = require("uuid");
|
|
17385
17493
|
var defaultDecisionAttemptReceiptStore = new DecisionAttemptReceiptStore();
|
|
17386
|
-
function findActiveGatePromptEntry(conversation) {
|
|
17387
|
-
|
|
17388
|
-
let entry
|
|
17389
|
-
|
|
17390
|
-
|
|
17494
|
+
function findActiveGatePromptEntry(conversation, activeGatePromptId = null) {
|
|
17495
|
+
if (activeGatePromptId !== null) {
|
|
17496
|
+
for (let entry of conversation)
|
|
17497
|
+
if (entry.id === activeGatePromptId && entry.kind === "gate-prompt" && entry.final === !1)
|
|
17498
|
+
return entry;
|
|
17391
17499
|
}
|
|
17392
|
-
|
|
17500
|
+
let oldestOpen = null;
|
|
17501
|
+
for (let entry of conversation) {
|
|
17502
|
+
if (entry.kind !== "gate-prompt" || entry.final !== !1) continue;
|
|
17503
|
+
let phase = entry.uiState.phase;
|
|
17504
|
+
if (phase === "awaiting-number" || phase === "awaiting-notes") return entry;
|
|
17505
|
+
oldestOpen ??= entry;
|
|
17506
|
+
}
|
|
17507
|
+
return oldestOpen;
|
|
17393
17508
|
}
|
|
17394
17509
|
function getStoreSessionId(store) {
|
|
17395
17510
|
return store.getState().session.sessionId;
|
|
@@ -18019,7 +18134,7 @@ var readline = __toESM(require("readline"));
|
|
|
18019
18134
|
function plannerDecisionRenderEnabled(env = process.env) {
|
|
18020
18135
|
return env.CODEVIBE_PLANNER_DEBUG === "1";
|
|
18021
18136
|
}
|
|
18022
|
-
function renderEntryAsLine(entry) {
|
|
18137
|
+
function renderEntryAsLine(entry, labelFor) {
|
|
18023
18138
|
switch (entry.kind) {
|
|
18024
18139
|
case "user-message":
|
|
18025
18140
|
return `> ${entry.text}`;
|
|
@@ -18040,9 +18155,9 @@ function renderEntryAsLine(entry) {
|
|
|
18040
18155
|
case "advisory":
|
|
18041
18156
|
return `[${entry.source}] ${entry.text}`;
|
|
18042
18157
|
case "gate-prompt": {
|
|
18043
|
-
let lines = [], halt = entry.envelope.reason ? ` (${entry.envelope.reason})` : "";
|
|
18158
|
+
let lines = [], halt = entry.envelope.reason ? ` (${entry.envelope.reason})` : "", label = labelFor?.(entry.envelope.taskId);
|
|
18044
18159
|
lines.push(
|
|
18045
|
-
`[gate-prompt ${entry.envelope.promptKind}${halt}] ${entry.envelope.summary ?? ""}`
|
|
18160
|
+
`[gate-prompt ${entry.envelope.promptKind}${halt}]${label ? ` [${label}]` : ""} ${entry.envelope.summary ?? ""}`
|
|
18046
18161
|
);
|
|
18047
18162
|
for (let i = 0; i < entry.envelope.options.length; i++) {
|
|
18048
18163
|
let opt = entry.envelope.options[i];
|
|
@@ -18086,8 +18201,13 @@ async function runLineLogFallback(args) {
|
|
|
18086
18201
|
let newEntries = state.conversation.slice(printedUpTo);
|
|
18087
18202
|
printedUpTo = state.conversation.length;
|
|
18088
18203
|
for (let entry of newEntries)
|
|
18089
|
-
entry.kind !== "gate-panel" && (entry.kind === "planner-decision" && !plannerDecisionRenderEnabled() || out.write(
|
|
18090
|
-
|
|
18204
|
+
entry.kind !== "gate-panel" && (entry.kind === "planner-decision" && !plannerDecisionRenderEnabled() || out.write(
|
|
18205
|
+
renderEntryAsLine(entry, (taskId) => {
|
|
18206
|
+
let label = taskLabel(state, taskId);
|
|
18207
|
+
return label.startsWith("#") ? `Task ${label}` : void 0;
|
|
18208
|
+
}) + `
|
|
18209
|
+
`
|
|
18210
|
+
));
|
|
18091
18211
|
}), rl = readline.createInterface({ input: inp, output: out, terminal: !1 }), onAbort = null;
|
|
18092
18212
|
args.signal && (args.signal.aborted ? rl.close() : (onAbort = () => {
|
|
18093
18213
|
try {
|
|
@@ -18146,13 +18266,32 @@ function buildTrackLabelByTaskId(team) {
|
|
|
18146
18266
|
entry.taskId && map.set(entry.taskId, `Track ${displayTrackNumber(idx)}`);
|
|
18147
18267
|
return map.size > 0 ? map : null;
|
|
18148
18268
|
}
|
|
18149
|
-
function
|
|
18269
|
+
function buildGateLabelByTaskId(state) {
|
|
18270
|
+
let trackLabels = buildTrackLabelByTaskId(state.team), map = new Map(trackLabels ?? []);
|
|
18271
|
+
for (let entry of state.conversation) {
|
|
18272
|
+
if (entry.kind !== "gate-prompt") continue;
|
|
18273
|
+
let id = entry.envelope.taskId;
|
|
18274
|
+
if (map.has(id) && map.get(id).startsWith("Task ")) continue;
|
|
18275
|
+
let label = taskLabel(state, id);
|
|
18276
|
+
if (!label.startsWith("#")) continue;
|
|
18277
|
+
let track = trackLabels?.get(id);
|
|
18278
|
+
map.set(id, track ? `Task ${label} \xB7 ${track}` : `Task ${label}`);
|
|
18279
|
+
}
|
|
18280
|
+
return map.size > 0 ? map : null;
|
|
18281
|
+
}
|
|
18282
|
+
function deriveGatePromptMode(active, gateActionRecoveryBlocked = !1, labelByTaskId = null) {
|
|
18150
18283
|
if (!active) return null;
|
|
18151
18284
|
if (gateActionRecoveryBlocked && active.uiState.phase !== "resolved")
|
|
18152
18285
|
return { kind: "submitting" };
|
|
18153
18286
|
switch (active.uiState.phase) {
|
|
18154
|
-
case "awaiting-number":
|
|
18155
|
-
|
|
18287
|
+
case "awaiting-number": {
|
|
18288
|
+
let taskLabel2 = labelByTaskId?.get(active.envelope.taskId);
|
|
18289
|
+
return {
|
|
18290
|
+
kind: "awaiting-number",
|
|
18291
|
+
maxOption: active.envelope.options.length,
|
|
18292
|
+
...taskLabel2 ? { taskLabel: taskLabel2 } : {}
|
|
18293
|
+
};
|
|
18294
|
+
}
|
|
18156
18295
|
case "awaiting-notes":
|
|
18157
18296
|
return { kind: "awaiting-notes", decisionDraft: active.uiState.decisionDraft };
|
|
18158
18297
|
case "submitting":
|
|
@@ -18184,7 +18323,7 @@ function OrchestrationApp(props) {
|
|
|
18184
18323
|
}, [wizardOpen, isRawModeSupported]);
|
|
18185
18324
|
let staticOrderRef = React18.useRef(null);
|
|
18186
18325
|
staticOrderRef.current || (staticOrderRef.current = makeStaticOrderAccum());
|
|
18187
|
-
let trackLabelByTaskId =
|
|
18326
|
+
let trackLabelByTaskId = buildGateLabelByTaskId(state), staticConversation = plannerDecisionRenderEnabled() ? state.conversation : state.conversation.filter((e) => e.kind !== "planner-decision");
|
|
18188
18327
|
appendNewlyFinal(staticOrderRef.current, staticConversation, (entry) => ({
|
|
18189
18328
|
key: entry.id,
|
|
18190
18329
|
element: renderConversationEntry(entry, trackLabelByTaskId),
|
|
@@ -18195,9 +18334,10 @@ function OrchestrationApp(props) {
|
|
|
18195
18334
|
...staticOrderRef.current.items
|
|
18196
18335
|
], nonFinal = state.conversation.filter(
|
|
18197
18336
|
(e) => !e.final
|
|
18198
|
-
), activeGateEntry = findActiveGatePromptEntry(state.conversation), gatePromptMode = deriveGatePromptMode(
|
|
18337
|
+
), activeGateEntry = findActiveGatePromptEntry(state.conversation, state.activeGatePromptId), gatePromptMode = deriveGatePromptMode(
|
|
18199
18338
|
activeGateEntry,
|
|
18200
|
-
state.gateActionRecoveryBlocked
|
|
18339
|
+
state.gateActionRecoveryBlocked,
|
|
18340
|
+
trackLabelByTaskId
|
|
18201
18341
|
), terminalRows = process.stdout.rows ?? 24, FIXED_CHROME_ROWS = 8, teamReserveRows = state.team ? 5 + state.team.tracks.size : 0, dropdownFits = terminalRows - FIXED_CHROME_ROWS - teamReserveRows >= DROPDOWN_MAX_ROWS + MIN_LIVE_ROWS, dropdownActive = state.progress === null && gatePromptMode === null && state.reviewerWizard === null && dropdownFits, dropdownReserveRows = dropdownActive ? DROPDOWN_MAX_ROWS : 0, wizardReserveRows = state.reviewerWizard ? WIZARD_MAX_ROWS : 0, { rendered: cappedNonFinal, hiddenCount } = capLiveEntries(
|
|
18202
18342
|
nonFinal,
|
|
18203
18343
|
terminalRows,
|
|
@@ -18616,11 +18756,11 @@ function renderLocalGemmaPlannerPrompt(input, options) {
|
|
|
18616
18756
|
"Never refuse merely because the user asks about local repository files, the current directory, the workspace, or the project. Refuse only for clearly unsafe requests such as exposing secrets/credentials, destructive root/home deletion, malware, bypassing auth/paywalls, or exfiltration.",
|
|
18617
18757
|
"",
|
|
18618
18758
|
"Routing rules:",
|
|
18619
|
-
'- start_task: user asks to create, add, edit, fix, implement, refactor, test, run tests, or review a SPECIFIC, BOUNDED change \u2014 such as their pending diff, staged/uncommitted changes, a named file, or a pull request. (A BROAD "review/audit the WHOLE codebase for bugs" is read-only understanding \u2192 familiarize, NOT start_task \u2014 see below.) If the user says current directory, current working directory, repo root, workspace, ".", "./", or an absolute path, treat the target as sufficiently specified. If the requested content is obvious, such as a JavaScript hello-world file, do not ask for extra content. A SINGLE task is start_task even when it has multiple steps; choose team_decompose ONLY when the user explicitly asks for parallel work (see below).',
|
|
18759
|
+
'- start_task: user asks to create, add, edit, fix, implement, refactor, test, run tests, or review a SPECIFIC, BOUNDED change \u2014 such as their pending diff, staged/uncommitted changes, a named file, or a pull request. (A BROAD "review/audit the WHOLE codebase for bugs" is read-only understanding \u2192 familiarize, NOT start_task \u2014 see below.) If the user says current directory, current working directory, repo root, workspace, ".", "./", or an absolute path, treat the target as sufficiently specified. If the requested content is obvious, such as a JavaScript hello-world file, do not ask for extra content. A SINGLE task is start_task even when it has multiple steps; choose team_decompose ONLY when the user explicitly asks for parallel work (see below). This ALSO includes requests that fetch/check an external URL or search online AND write, save, create, or update local files, reports, or code (e.g. "check https://... and write a report into report.md" -> start_task).',
|
|
18620
18760
|
'- team_decompose: user EXPLICITLY asks to split the work into MULTIPLE PARALLEL tasks or tracks, run an "agent team", do things "in parallel", or describes 2+ INDEPENDENT pieces (typically touching different files) to run concurrently. Prefer team_decompose over start_task whenever the request names an agent team or parallel/separate tracks. A single multi-step task is start_task, NOT team_decompose.',
|
|
18621
18761
|
'- familiarize: user asks to read, inspect, understand, explain, or summarize the current project, codebase, repository, folder, files, or working directory without asking for a mutation. This ALSO covers a BROAD read-only REVIEW or AUDIT of the WHOLE repository/codebase \u2014 e.g. "review the codebase for bugs", "audit the repo for issues", "look over the whole project for problems": reviewing the ENTIRE repo for bugs/quality/security is a read-only understanding task, so it is familiarize, NOT start_task. (A BOUNDED review of a specific pending DIFF or named file is start_task instead.)',
|
|
18622
18762
|
`- brainstorm: user asks to explore options, tradeoffs, risks, architecture directions, or recommendations for THIS project's design or implementation before deciding what to design or implement. brainstorm is NOT for opinions or analysis of a web page, article, or answer from earlier in this session \u2014 that is advisory_response. Use brainstorm for exploratory prompts such as "brainstorm ways to build offline support" or "compare approaches to create a local context store". Do NOT use brainstorm when the user asks for immediate mutation, a design artifact, a hard gate, tests, review, commit, deploy, or release; choose the workflow action or ask one clarifying question.`,
|
|
18623
|
-
'- browse: user asks to read/open/fetch/summarize a specific web URL (http/https), OR to look something up on the web / search online / find the latest on a topic. Put any explicit URL(s) in "browseUrls" (array) and, when there is no URL, put the search query in "browseQuery". This route fetches the page (or searches) on the user machine and answers from the content; it is NOT a familiarize (which reads the LOCAL repo) and NOT advisory_response.',
|
|
18763
|
+
'- browse: user asks a read-only question to read/open/fetch/summarize a specific web URL (http/https), OR to look something up on the web / search online / find the latest on a topic, WITHOUT asking to create, write, or modify any local files. If the user asks to fetch a URL or search AND save/write/create local files or reports, choose start_task instead. Put any explicit URL(s) in "browseUrls" (array) and, when there is no URL, put the search query in "browseQuery". This route fetches the page (or searches) on the user machine and answers from the content; it is NOT a familiarize (which reads the LOCAL repo) and NOT advisory_response.',
|
|
18624
18764
|
options?.isSubprocessRunner ? `- advisory_response: user asks a general question that does not require repository context or file changes. Put a COMPLETE, natural, conversational ANSWER to the question in the "advisory_summary" field (a full helpful reply, like a chat assistant \u2014 NOT a one-line label or a restatement of the question); explain core principles accurately: for search/pathfinding, start with the start node in the open set, use strictly standard A* terminology (open set and closed set only; never invent other sets like missed set or turn set), and compare with Dijkstra's algorithm; explain that a more accurate, higher admissible heuristic (closer to the true remaining cost) guides the search more directly to the goal and expands fewer nodes, whereas a smaller or zero heuristic (like Dijkstra's algorithm) explores in all directions and expands more nodes; a heuristic must be admissible (never overestimate the true distance) to guarantee an optimal shortest path; write all mathematical expressions in clean plain text like f(n) = g(n) + h(n) (never use LaTeX math notation, \\text{}, math mode $, or backslashes); format the entire explanation in clean, well-structured markdown prose paragraphs separated by blank lines (do not use bullet lists, numbered sub-lists, or backslash line breaks; write complete narrative paragraphs); never use tab characters or \\t; never use double quotes inside advisory_summary (use single quotes ' if quoting terms); keep "rationale" a short internal classification reason. Answer directly and warmly, e.g. "Yes \u2014 I can \u2026".` : '- advisory_response: user asks a general question or capability question. Put a concise, natural, 1-sentence direct answer or definition in "advisory_summary" (e.g. "Yes, I can write Rust" or "A* is a best-first pathfinding algorithm that expands nodes by f(n) = g(n) + h(n)"); do NOT write preambles like "Here is..." or conversational labels; keep "rationale" a short reason. The downstream local advisory engine generates the full answer.',
|
|
18625
18765
|
options?.isSubprocessRunner ? '- FOLLOW-UP ON EARLIER CONTENT: when the user asks for your thoughts, opinion, analysis, critique, or a deeper explanation of something already fetched or answered earlier in this session (a web page that was read, a search result, a previous reply) \u2014 e.g. "I am looking for your thoughts", "what do you think about that", "go deeper on that", "is that right?" \u2014 choose advisory_response and put the COMPLETE reply in advisory_summary (no downstream answerer runs for this runner). Never brainstorm, familiarize, or browse again for such a follow-up.' : `- FOLLOW-UP ON EARLIER CONTENT: when the user asks for your thoughts, opinion, analysis, critique, or a deeper explanation of something already fetched or answered earlier in this session (a web page that was read, a search result, a previous reply) \u2014 e.g. "I am looking for your thoughts", "what do you think about that", "go deeper on that", "is that right?" \u2014 choose advisory_response with a one-sentence advisory_summary (the shell's answerer writes the full reply from the page and the conversation). Never brainstorm, familiarize, or browse again for such a follow-up.`,
|
|
18626
18766
|
options?.isSubprocessRunner ? "- summarize_current_status: user asks what changed, what the last task did, current progress, or workflow status." : "- Questions about progress, what changed, what the last task did, or workflow status are advisory_response too: the shell attaches its own status record to the answer, so never invent task history.",
|
|
@@ -18658,6 +18798,8 @@ function renderLocalGemmaPlannerPrompt(input, options) {
|
|
|
18658
18798
|
'User: "look up the React 19 release notes online" -> {"action":"browse","rationale":"web lookup","browseQuery":"React 19 release notes"}',
|
|
18659
18799
|
'User: "what is the latest LTS version of Node.js?" -> {"action":"browse","rationale":"freshness lookup needs current web info, not stale knowledge","browseQuery":"latest LTS version Node.js"}',
|
|
18660
18800
|
'User: "what is the current stable version of Python?" -> {"action":"browse","rationale":"current version is a freshness web lookup","browseQuery":"current stable version Python"}',
|
|
18801
|
+
'User: "check https://registry.npmjs.org/@quantiya/codevibe-core/latest and write a report into report.md" -> {"action":"start_task","rationale":"check external URL and write report file"}',
|
|
18802
|
+
'User: "Please use your codevibe_web_search tool to search for \\"Quantiya AI\\", then use codevibe_web_fetch to read https://quantiya.ai/ and write a 3-bullet summary of the site into a file named claude-web-test.md." -> {"action":"start_task","rationale":"search web and write summary file"}',
|
|
18661
18803
|
`User: "fyi every FLAGS.md row should also say what the default is" (session.currentTaskState is in_progress) -> {"action":"advisory_response","rationale":"a convention stated while a task runs; acknowledge, no second task","advisory_summary":"Noted \u2014 FLAGS.md rows will also state each flag's default."}`,
|
|
18662
18804
|
'User: "scratch that \u2014 descriptions should not end with a period" (session.currentTaskState is in_progress) -> {"action":"advisory_response","rationale":"retracts an earlier rule while a task runs","advisory_summary":"Understood \u2014 descriptions will not end with a period."}',
|
|
18663
18805
|
'User: "also add a --cv-quiet flag to release.sh" (session.currentTaskState is in_progress) -> {"action":"start_task","rationale":"asks for a new deliverable while a task runs"}',
|
|
@@ -21326,6 +21468,57 @@ function buildCommandIntentMetadata(text2) {
|
|
|
21326
21468
|
if (intent)
|
|
21327
21469
|
return { command_intent: buildCommandIntentEnvelope(intent) };
|
|
21328
21470
|
}
|
|
21471
|
+
function isWebAccessNeeded(text2) {
|
|
21472
|
+
return !text2 || typeof text2 != "string" ? !1 : /https?:\/\/[^\s]+/i.test(text2) || /\bwww\.[^\s]+/i.test(text2) ? !0 : /(?:^|[.?!;])\s*(?:please\s+)?(?:implement|build|create|add|support|develop)\b[\s\S]{0,50}?\b(?:feature|functionality|function|button|capability|endpoint|command|module|ui|api|service)\b/i.test(
|
|
21473
|
+
text2
|
|
21474
|
+
) || /\b(?:feature|functionality|function|capability)\b[\s\S]{0,40}?\b(?:to|that|which)\s+(?:search|browse|look\s+up)\b/i.test(
|
|
21475
|
+
text2
|
|
21476
|
+
) || /(?:实现|添加|开发|支持|做个|写个)[\s\S]{0,30}?(?:功能|特性)/.test(text2) || /(?:功能|特性)[\s\S]{0,30}?(?:实现|添加|开发|支持)/.test(text2) || /(?:機能|기능)[\s\S]{0,30}?(?:実装|追加|作成|開発|구현|추가)/.test(text2) || /(?:実装|追加|作成|開発|구현|추가)[\s\S]{0,30}?(?:機能|기능)/.test(text2) ? !1 : !!(/(?:^|[.?!;])\s*(?:please\s+)?(?:search|browse)\s+(?:the\s+(?:web|internet)\b|online)(?:\s+(?:for|about)\b|\s*[,.;?!]?$)/i.test(
|
|
21477
|
+
text2
|
|
21478
|
+
) || /(?:^|[.?!;])\s*(?:please\s+)?look\s+up\s+(?!where\b|how\b|who\b|why\b)[\w\s.-]{1,60}?\s+(?:online|on\s+the\s+web\b|on\s+the\s+internet\b)(?:\s+(?:for|about)\b|\s*[,.;?!]?$)/i.test(
|
|
21479
|
+
text2
|
|
21480
|
+
) && !/\bonline\s+(?:checkout|users?|status|banking|store|shop|service|mode|game|player|account|system|portal|flow|documentation)\b/i.test(
|
|
21481
|
+
text2
|
|
21482
|
+
) || /(?:^|[.?!;])\s*(?:please\s+)?(?:use|try|run|perform|do)\s+(?:a\s+)?(?:web|internet|online)\s+search(?:\s+(?:for|about|on)\b|\s*[,.;?!]?$)(?!\s+(?:api|service|endpoint|tests?|feature|sdk|library|component|function|code)\b)/i.test(
|
|
21483
|
+
text2
|
|
21484
|
+
) || /(?:^|[.?!;])\s*(?:please\s+)?(?:google\s+(?:this|it|that)\b|search\s+(?:on\s+google|google)\s+for\b)/i.test(
|
|
21485
|
+
text2
|
|
21486
|
+
) || /(?:^|[.?!;,。!?;])\s*(?:请|帮我|麻烦)?\s*(?:(?:去?上网|在网上|去官网|在官网|在官方网站)(?:搜索|查找|查阅|查一下|搜一下)(?!到|过|出|的|结果)|去网上搜(?:一下|索)|谷歌一下|(?:查阅|查找|搜索)(?:官网|官方网站)(?:的)?(?:文档|说明|api)|(?:在线|官网|联网)搜索一下)/i.test(
|
|
21487
|
+
text2
|
|
21488
|
+
) || /(?:^|[.?!;])\s*(?:(?:ネット|オンライン)で\s*(?:検索して(?:みて|ください)?|調べて(?:みて|ください)?|探して(?:みて|ください)?)(?=$|[\s。、.!?])|ウェブ検索で\s*[\u4E00-\u9FFF\u30A0-\u30FF\w ]{1,20}を(?:調べて|探して))|(?:^|[.?!;])\s*[\w\s\u3040-\u30FF\u4E00-\u9FFF]{0,20}?ググ[るっ](?=$|[\s.?!;。~])/i.test(
|
|
21489
|
+
text2
|
|
21490
|
+
) || /(?:^|[.?!;])\s*(?:[\w\s가-힣]{0,40}?\s+)?(?:인터넷|온라인|구글)에서\s*(?:검색해(?:봐|줘|요)|찾아(?:봐|줘)|조회해(?:봐|줘))(?=$|[\s.?!;。~])/i.test(
|
|
21491
|
+
text2
|
|
21492
|
+
));
|
|
21493
|
+
}
|
|
21494
|
+
function hasFileMutationIntent(text2) {
|
|
21495
|
+
if (!text2 || typeof text2 != "string") return !1;
|
|
21496
|
+
let stripped = stripLeadingAgentControlToken(text2).trim(), textWithoutUrls = stripped.replace(/https?:\/\/[^\s]+/gi, " ").trim();
|
|
21497
|
+
if (/\b(?:how\s+(?:to|do\s+I|can\s+I)|explain\s+how\s+to|tell\s+me\s+how\s+to)\b/i.test(stripped) || /(?:^|[.?!;])\s*(?:what\s+(?:is|are)\b|c[oó]mo\s+(?:hacer|puedo|se\s+puede|escribir|guardar|crear)|comment\s+(?:faire|écrire|sauvegarder|créer|enregistrer)|wie\s+(?:kann\s+ich|macht\s+man|schreibt\s+man|speichert\s+man|erstellt\s+man))\b/i.test(stripped) || /(?:如何|怎么|怎样|告诉我(?:如何|怎么|怎样)|解释如何|请问|什么是)/.test(stripped) || /(?:どうやって|方法(?:を(?:教えて|説明して))|やり方を教えて|でしょうか|とは(?:何|なん)ですか)/.test(stripped) || /(?:어떻게|방법을?\s*(?:알려줘|설명해줘)|법\s*설명|하는지\s*알려줘)/.test(stripped))
|
|
21498
|
+
return !1;
|
|
21499
|
+
if (/>{1,2}\s*[\w./-]+\b/.test(textWithoutUrls))
|
|
21500
|
+
return !0;
|
|
21501
|
+
let fileExt = "(?:md|markdown|txt|json|csv|ts|js|tsx|jsx|py|html|css|yaml|yml|log|sh|xml|toml|sql|env)", writeToFilePatternEn = new RegExp(
|
|
21502
|
+
`\\b(?:write|save|create|dump|output|put|export|store|record|guardar|escribir|crear|enregistrer|\xE9crire|cr\xE9er|sauvegarder|speichern|schreiben|erstellen)\\b[\\s\\S]*?\\b(?:into|to|in|as|en|dans|auf|zu)\\b(?!\\s+(?:chat|console|stdout|screen|terminal)\\b)[\\s\\S]*?(?:(?:a|an|the|un|une|ein|eine|el|la)?\\s*file(?:\\s+named|\\s+called)?\\s+)?[\\w./-]+\\.${fileExt}\\b`,
|
|
21503
|
+
"i"
|
|
21504
|
+
), writeToFilePatternGermanic = new RegExp(
|
|
21505
|
+
`\\b(?:in|auf|zu|into|to)\\s+[\\w./-]+\\.${fileExt}[\\s\\S]*?\\b(?:speichern|schreiben|erstellen|ablegen)\\b`,
|
|
21506
|
+
"i"
|
|
21507
|
+
), namedFilePatternEn = /\b(?:into|to|in|en|dans|auf|zu)\s+(?:a|an|un|une|ein|eine|el|la)?\s*file(?:\s+(?:named|called))?\s+[\w./-]+\b/i, createFilePatternEn = /\b(?:create|add|touch)\s+(?:a|an|the)?\s*(?:new\s+)?(?:file|markdown\s+file|script)\b/i, directFileVerbPatternEn = new RegExp(
|
|
21508
|
+
`\\b(?:create|write|touch|add|crear|escribir|cr\xE9er|\xE9crire|erstellen)\\s+[\\w./-]+\\.${fileExt}\\b`,
|
|
21509
|
+
"i"
|
|
21510
|
+
), writeToFilePatternZh = new RegExp(
|
|
21511
|
+
`(?:\u5199\u5165|\u4FDD\u5B58\u5230?|\u521B\u5EFA|\u751F\u6210|\u8F93\u51FA\u5230|\u5B58\u5165|\u5BFC\u51FA\u5230?|\u8BB0\u5F55\u5230?)[\\s\\S]*?[\\w./-]+\\.${fileExt}\\b`,
|
|
21512
|
+
"i"
|
|
21513
|
+
), writeToFilePatternJa = new RegExp(
|
|
21514
|
+
`(?:[\\w./-]+\\.${fileExt}[\\s\\S]*?(?:\u306B|\u3078)?\\s*(?:\u66F8\u304D\u51FA|\u4FDD\u5B58|\u4F5C\u6210|\u751F\u6210|\u51FA\u529B|\u8A18\u9332|\u30A8\u30AF\u30B9\u30DD\u30FC\u30C8)|(?:\u66F8\u304D\u51FA|\u4FDD\u5B58|\u4F5C\u6210|\u751F\u6210|\u51FA\u529B|\u8A18\u9332|\u30A8\u30AF\u30B9\u30DD\u30FC\u30C8)[\\s\\S]*?[\\w./-]+\\.${fileExt})`,
|
|
21515
|
+
"i"
|
|
21516
|
+
), writeToFilePatternKo = new RegExp(
|
|
21517
|
+
`(?:[\\w./-]+\\.${fileExt}[\\s\\S]*?(?:\uC5D0|\uC73C\uB85C|\uB85C)?\\s*(?:\uC800\uC7A5|\uC791\uC131|\uC0DD\uC131|\uAE30\uB85D|\uCD9C\uB825|\uB0B4\uBCF4\uB0B4\uAE30)|(?:\uC800\uC7A5|\uC791\uC131|\uC0DD\uC131|\uAE30\uB85D|\uCD9C\uB825|\uB0B4\uBCF4\uB0B4\uAE30)[\\s\\S]*?[\\w./-]+\\.${fileExt})`,
|
|
21518
|
+
"i"
|
|
21519
|
+
);
|
|
21520
|
+
return writeToFilePatternEn.test(textWithoutUrls) || writeToFilePatternGermanic.test(textWithoutUrls) || namedFilePatternEn.test(textWithoutUrls) || createFilePatternEn.test(textWithoutUrls) || directFileVerbPatternEn.test(textWithoutUrls) || writeToFilePatternZh.test(textWithoutUrls) || writeToFilePatternJa.test(textWithoutUrls) || writeToFilePatternKo.test(textWithoutUrls);
|
|
21521
|
+
}
|
|
21329
21522
|
|
|
21330
21523
|
// src/orchestration-shell/route-browse.ts
|
|
21331
21524
|
var RETAINED_PAGE_MAX_CHARS = 12e4, NO_MODEL_PREFIX = "Local CodeVibe model is required to read and summarize web pages.", RUN_INSTALL = "Run `codevibe model install` from a Pro/Max account, then ask again. No hosted model was called and no code was changed.";
|
|
@@ -21390,17 +21583,29 @@ function fetchErrorMessage(err, url) {
|
|
|
21390
21583
|
async function readUrl(deps, runner, url) {
|
|
21391
21584
|
let { store, userPrompt, signal } = deps, dispUrl = sanitizeForTerminal(url);
|
|
21392
21585
|
advise(store, `Reading ${dispUrl}\u2026`);
|
|
21393
|
-
let body, finalUrl, fetch2 = deps.guardedFetchFn ?? guardedFetch;
|
|
21586
|
+
let body, finalUrl, contentType = "", fetch2 = deps.guardedFetchFn ?? guardedFetch;
|
|
21394
21587
|
try {
|
|
21395
|
-
let res = await fetch2(url, signal);
|
|
21396
|
-
body = res.body, finalUrl = res.finalUrl;
|
|
21588
|
+
let res = await fetch2(url, signal, { allowJson: !0 });
|
|
21589
|
+
body = res.body, finalUrl = res.finalUrl, contentType = res.contentType;
|
|
21397
21590
|
} catch (err) {
|
|
21398
21591
|
if (signal?.aborted) return;
|
|
21399
21592
|
let suffix = deps.delegateAnswer ? " The mentioned agent was not consulted." : "";
|
|
21400
21593
|
err instanceof FetchError ? advise(store, `${fetchErrorMessage(err, dispUrl)}${suffix}`) : advise(store, `Couldn't read ${dispUrl} (${sanitizeForTerminal(err.message)}). No code was changed.${suffix}`);
|
|
21401
21594
|
return;
|
|
21402
21595
|
}
|
|
21403
|
-
let dispFinal = sanitizeForTerminal(finalUrl),
|
|
21596
|
+
let dispFinal = sanitizeForTerminal(finalUrl), title = dispFinal, text2 = "";
|
|
21597
|
+
if (contentType.toLowerCase().includes("application/json"))
|
|
21598
|
+
try {
|
|
21599
|
+
let parsed = JSON.parse(body);
|
|
21600
|
+
text2 = JSON.stringify(parsed, null, 2);
|
|
21601
|
+
} catch {
|
|
21602
|
+
text2 = body;
|
|
21603
|
+
}
|
|
21604
|
+
else {
|
|
21605
|
+
let extracted = await htmlToText(body);
|
|
21606
|
+
title = extracted.title || dispFinal, text2 = extracted.text || "";
|
|
21607
|
+
}
|
|
21608
|
+
let safeTitle = sanitizeForTerminal(title).slice(0, 200), fullSafeText = sanitizeForTerminal(text2);
|
|
21404
21609
|
if (signal?.aborted) return;
|
|
21405
21610
|
if (!fullSafeText.trim()) {
|
|
21406
21611
|
let suffix = deps.delegateAnswer ? " The mentioned agent was not consulted." : "";
|
|
@@ -21529,7 +21734,19 @@ async function readSearchResults(deps, runner, query, results) {
|
|
|
21529
21734
|
advise(store, `Reading top ${targetResults.length} web pages in parallel\u2026`);
|
|
21530
21735
|
let fetch2 = deps.guardedFetchFn ?? guardedFetch, fetchPromises = targetResults.map(async (res) => {
|
|
21531
21736
|
try {
|
|
21532
|
-
let fetched = await fetch2(res.url, signal
|
|
21737
|
+
let fetched = await fetch2(res.url, signal, { allowJson: !0 }), title = res.title || "", text2 = "";
|
|
21738
|
+
if ((fetched.contentType || "").toLowerCase().includes("application/json"))
|
|
21739
|
+
try {
|
|
21740
|
+
let parsed = JSON.parse(fetched.body);
|
|
21741
|
+
text2 = JSON.stringify(parsed, null, 2);
|
|
21742
|
+
} catch {
|
|
21743
|
+
text2 = fetched.body;
|
|
21744
|
+
}
|
|
21745
|
+
else {
|
|
21746
|
+
let extracted = await htmlToText(fetched.body);
|
|
21747
|
+
title = extracted.title || title, text2 = extracted.text || "";
|
|
21748
|
+
}
|
|
21749
|
+
let safeTitle = sanitizeForTerminal(title).slice(0, 200), rawText = text2 || "";
|
|
21533
21750
|
return rawText.trim() ? {
|
|
21534
21751
|
finalUrl: fetched.finalUrl,
|
|
21535
21752
|
safeTitle,
|
|
@@ -26367,7 +26584,7 @@ async function requestSubcommand(deps, rawTarget) {
|
|
|
26367
26584
|
return { exitCode: 1, stdout: "Continuation request is unavailable in this session." };
|
|
26368
26585
|
let ctx = deps.getActiveRequestContext();
|
|
26369
26586
|
if (!ctx)
|
|
26370
|
-
return { exitCode: 1, stdout: "No active task to hand off." };
|
|
26587
|
+
return { exitCode: 1, stdout: deps.explainNoActiveRequest?.() ?? "No active task to hand off." };
|
|
26371
26588
|
try {
|
|
26372
26589
|
return await deps.request(ctx, targetAgent) ? {
|
|
26373
26590
|
exitCode: 0,
|
|
@@ -31273,7 +31490,7 @@ function stripAgyWebPlugin(workdir) {
|
|
|
31273
31490
|
function buildImplementorArgv(agent, mode, workdir, options) {
|
|
31274
31491
|
switch (agent) {
|
|
31275
31492
|
case "CLAUDE": {
|
|
31276
|
-
if (
|
|
31493
|
+
if (!!options?.mcpConfigPath) {
|
|
31277
31494
|
let disallowedTools = mode === "plan" ? "WebFetch,WebSearch,Bash,Agent,Task,Workflow,SendMessage,ListAgents" : "WebFetch,WebSearch,Agent,Task,Workflow,SendMessage,ListAgents", baseTools = mode === "plan" ? "Read,Grep,Glob" : "Read,Grep,Glob,Edit,Write,Bash", allowedTools = `${baseTools},mcp__codevibe-web__codevibe_web_search,mcp__codevibe-web__codevibe_web_fetch`;
|
|
31278
31495
|
return {
|
|
31279
31496
|
argv: [
|
|
@@ -31310,7 +31527,7 @@ function buildImplementorArgv(agent, mode, workdir, options) {
|
|
|
31310
31527
|
let sandbox = mode === "plan" ? "read-only" : "workspace-write", lastMessagePath = import_node_path.default.join(
|
|
31311
31528
|
import_node_os.default.tmpdir(),
|
|
31312
31529
|
`quorum-impl-codex-${process.pid}-${(0, import_uuid4.v4)()}.txt`
|
|
31313
|
-
), webEnabled =
|
|
31530
|
+
), webEnabled = !!options?.mcpConfigPath, mcpServer = webEnabled ? extractMcpServerConfig(options?.mcpConfigPath) : null;
|
|
31314
31531
|
return webEnabled && mcpServer ? {
|
|
31315
31532
|
argv: [
|
|
31316
31533
|
"codex",
|
|
@@ -31360,7 +31577,7 @@ function buildImplementorArgv(agent, mode, workdir, options) {
|
|
|
31360
31577
|
throw new Error(
|
|
31361
31578
|
"buildImplementorArgv: ANTIGRAVITY requires an absolute workdir (agy is workspace-centric \u2014 --add-dir is its only view of the tree); the call site must thread it (AGY-2.0 D3)"
|
|
31362
31579
|
);
|
|
31363
|
-
let webEnabled =
|
|
31580
|
+
let webEnabled = !!options?.mcpConfigPath, mcpServer = webEnabled ? extractMcpServerConfig(options?.mcpConfigPath) : null;
|
|
31364
31581
|
return webEnabled && mcpServer && setupAgyWebPlugin(workdir, mcpServer), {
|
|
31365
31582
|
argv: [...agyImplementorArgvPrefix(mode), workdir, "--print", ""],
|
|
31366
31583
|
capture: { kind: "stdout" }
|
|
@@ -31709,8 +31926,8 @@ var HookBridge = class {
|
|
|
31709
31926
|
// EXECUTOR_REFUSAL (Class A audit) and THEN LOCAL_AUTHORITY_REFUSAL (shell-
|
|
31710
31927
|
// visible). Reverse order would race the user-visible message ahead of the
|
|
31711
31928
|
// audit trail. Tests assert the ordering invariant.
|
|
31712
|
-
async bridgeAuthorityRefusal(err, args) {
|
|
31713
|
-
let ctx = this.contextOrNull();
|
|
31929
|
+
async bridgeAuthorityRefusal(err, args, taskCtx) {
|
|
31930
|
+
let ctx = taskCtx ?? this.contextOrNull();
|
|
31714
31931
|
ctx !== null && await this.deps.emitter.emitExecutorRefusal(ctx, {
|
|
31715
31932
|
refusal: err.refusal,
|
|
31716
31933
|
refusedMessageId: args.refusedMessageId
|
|
@@ -32292,7 +32509,7 @@ var ClassBConsumer = class {
|
|
|
32292
32509
|
}
|
|
32293
32510
|
}
|
|
32294
32511
|
async handlePolicyRejection(packet, envelopeTaskId) {
|
|
32295
|
-
let payload = packet.payload, ctx = this.deps.getContext(), hostedRecovery = payload?.recommendedRecovery, recommendedRecovery = hostedRecovery === "reauthorize_locally" || hostedRecovery === "abort_task" || hostedRecovery === "retry_after_resync" ? hostedRecovery : "ask_user";
|
|
32512
|
+
let payload = packet.payload, ctx = envelopeTaskId ? { ...this.deps.getContext(), taskId: envelopeTaskId } : this.deps.getContext(), hostedRecovery = payload?.recommendedRecovery, recommendedRecovery = hostedRecovery === "reauthorize_locally" || hostedRecovery === "abort_task" || hostedRecovery === "retry_after_resync" ? hostedRecovery : "ask_user";
|
|
32296
32513
|
await this.deps.emitter.emitExecutorRefusal(ctx, {
|
|
32297
32514
|
refusal: {
|
|
32298
32515
|
category: "policy_rejection",
|
|
@@ -38547,6 +38764,23 @@ var WorkspaceShadow = class _WorkspaceShadow {
|
|
|
38547
38764
|
)
|
|
38548
38765
|
);
|
|
38549
38766
|
}
|
|
38767
|
+
/**
|
|
38768
|
+
* P46a C1 — the real tree's current content-hash + mode for a workspace-
|
|
38769
|
+
* relative path, read exactly as the promote compare-and-set reads it
|
|
38770
|
+
* (`readRealFileMetadata`; `{ hash: null, mode: null }` when absent). Used to
|
|
38771
|
+
* record a promoted path's post-image and to attribute a later collision.
|
|
38772
|
+
*/
|
|
38773
|
+
async readRealMetadata(rel) {
|
|
38774
|
+
return assertSafeRelativePath(rel), assertNotInternalShadowPath(rel), readRealFileMetadata(this.workingDir, rel, this.workspaceRootAuthority);
|
|
38775
|
+
}
|
|
38776
|
+
/**
|
|
38777
|
+
* P46a C2 — the real tree's current text content for a workspace-relative
|
|
38778
|
+
* path (`null` when absent), for the retry brief's "files changed since your
|
|
38779
|
+
* first attempt" diff. Bounded by `MAX_REVIEW_FILE_BYTES`; never logged.
|
|
38780
|
+
*/
|
|
38781
|
+
async readRealFileText(rel) {
|
|
38782
|
+
return assertSafeRelativePath(rel), assertNotInternalShadowPath(rel), (await readRealFileForManifest(this.workingDir, rel, this.workspaceRootAuthority)).content;
|
|
38783
|
+
}
|
|
38550
38784
|
/** Read the crash-durable reviewed artifact, or null for an unreviewed snapshot. */
|
|
38551
38785
|
async readReviewedDiff() {
|
|
38552
38786
|
return (await this.readStateMarker())?.reviewedDiff?.map((file) => ({ ...file })) ?? null;
|
|
@@ -47457,7 +47691,15 @@ function makeNoopTrackHandle(agentKind) {
|
|
|
47457
47691
|
}
|
|
47458
47692
|
var LocalExecutorImpl = class {
|
|
47459
47693
|
constructor(deps) {
|
|
47460
|
-
|
|
47694
|
+
/**
|
|
47695
|
+
* P46a B2 — PER-TASK authority scopes: each task's TaskAuthorized path scope
|
|
47696
|
+
* merged over the seeded scope (`mergeAuthorityScope`), keyed by task id. A
|
|
47697
|
+
* spawn reads its OWN task's scope, never a union; authority never
|
|
47698
|
+
* accumulates across tasks. Replaces the singleton `this.scope`/`this.taskId`
|
|
47699
|
+
* pair, which made the SECOND concurrent task's spawn run under the FIRST
|
|
47700
|
+
* task's authorized paths (and mis-attributed its audit context).
|
|
47701
|
+
*/
|
|
47702
|
+
this.scopeByTask = /* @__PURE__ */ new Map();
|
|
47461
47703
|
// CP-12 W2.b — per-track registry (idempotent N-track spawn) + a snapshot of
|
|
47462
47704
|
// the most-recent TrackAssigned per track (for state-dir + scope on
|
|
47463
47705
|
// MergeGate). EMPTY for single-track sessions (never touched).
|
|
@@ -47501,7 +47743,7 @@ var LocalExecutorImpl = class {
|
|
|
47501
47743
|
// re-drain from a duplicate without relying on wall-clock resolution.
|
|
47502
47744
|
this._lastMergeGateEmitMs = 0;
|
|
47503
47745
|
this.memberReviewEvidenceByTask = /* @__PURE__ */ new Map();
|
|
47504
|
-
this.sessionId = deps.sessionId, this.substrateEngager = deps.substrateEngager, this.strictBadgeSink = deps.strictBadgeSink, this.
|
|
47746
|
+
this.sessionId = deps.sessionId, this.substrateEngager = deps.substrateEngager, this.strictBadgeSink = deps.strictBadgeSink, this.baseScope = deps.initialScope ?? makeEmptyScope(), this.baseCtx = deps.baseCtx, this.adapter = deps.adapter, this.emitShellEvent = deps.emitShellEvent, this.teamDeps = deps, this.onTeamMergeAuditSummary = deps.onTeamMergeAuditSummary, this.logger = deps.logger ?? { warn: (m, x) => console.warn(m, x) }, this.teamModeEnabled = !!(deps.spawnTrackImplementor && deps.appsyncClient && deps.repoRoot && deps.withSnapshotMergeGateFence), this.teamModeEnabled && typeof this.emitShellEvent == "function" && this.sessionId && this.replayPendingTeamDeliveries().catch((err) => {
|
|
47505
47747
|
this.logger.warn("[LocalExecutorImpl] startup background replay of pending team deliveries failed (non-fatal)", {
|
|
47506
47748
|
err: err?.message
|
|
47507
47749
|
});
|
|
@@ -47529,8 +47771,8 @@ var LocalExecutorImpl = class {
|
|
|
47529
47771
|
)), this.bridge = new HookBridge({
|
|
47530
47772
|
emitter: this.emitter,
|
|
47531
47773
|
emitShellEvent: this.emitShellEvent,
|
|
47532
|
-
getAuthorityScope: () => this.
|
|
47533
|
-
getCurrentTaskId: () =>
|
|
47774
|
+
getAuthorityScope: () => this.baseScope,
|
|
47775
|
+
getCurrentTaskId: () => null,
|
|
47534
47776
|
getContextWithoutTask: () => this.baseCtx,
|
|
47535
47777
|
adapter: this.adapter
|
|
47536
47778
|
}), this.classBConsumer = new ClassBConsumer({
|
|
@@ -47538,7 +47780,7 @@ var LocalExecutorImpl = class {
|
|
|
47538
47780
|
emitShellEvent: this.emitShellEvent,
|
|
47539
47781
|
getContext: () => this.contextOrFallback(),
|
|
47540
47782
|
advanceAuthorityScope: (newScope, taskId) => {
|
|
47541
|
-
this.
|
|
47783
|
+
this.scopeByTask.set(taskId, mergeAuthorityScope(this.baseScope, newScope));
|
|
47542
47784
|
},
|
|
47543
47785
|
notifyPolicyRejection: (detail, category, recommendedRecovery, rejectedTaskId) => {
|
|
47544
47786
|
this.emitShellEvent({
|
|
@@ -47630,19 +47872,20 @@ var LocalExecutorImpl = class {
|
|
|
47630
47872
|
* is responsible for routing to `bridgeAuthorityErrorToShellRefusal` /
|
|
47631
47873
|
* `HookBridge.bridgeAuthorityRefusal` per LOCK #C4-3.
|
|
47632
47874
|
*/
|
|
47633
|
-
async enforceAuthority(action) {
|
|
47875
|
+
async enforceAuthority(action, taskId) {
|
|
47876
|
+
let scope = this.scopeFor(taskId);
|
|
47634
47877
|
switch (action.kind) {
|
|
47635
47878
|
case "Write":
|
|
47636
|
-
await enforceWrite(
|
|
47879
|
+
await enforceWrite(scope, action.path);
|
|
47637
47880
|
return;
|
|
47638
47881
|
case "Read":
|
|
47639
|
-
await enforceRead(
|
|
47882
|
+
await enforceRead(scope, action.path);
|
|
47640
47883
|
return;
|
|
47641
47884
|
case "Network":
|
|
47642
|
-
enforceNetwork(
|
|
47885
|
+
enforceNetwork(scope);
|
|
47643
47886
|
return;
|
|
47644
47887
|
case "Command":
|
|
47645
|
-
enforceCommand(
|
|
47888
|
+
enforceCommand(scope, action.argv);
|
|
47646
47889
|
return;
|
|
47647
47890
|
default: {
|
|
47648
47891
|
let _exhaustive = action;
|
|
@@ -47651,36 +47894,64 @@ var LocalExecutorImpl = class {
|
|
|
47651
47894
|
}
|
|
47652
47895
|
}
|
|
47653
47896
|
/**
|
|
47654
|
-
*
|
|
47897
|
+
* P46a B2 — resolve the authority scope a caller runs under: the task's OWN
|
|
47898
|
+
* merged scope once ITS TaskAuthorized arrived, else the seeded scope (before
|
|
47899
|
+
* authorization, for a task this executor never saw authorized, and for a
|
|
47900
|
+
* task-less caller). Never a union across tasks.
|
|
47901
|
+
*/
|
|
47902
|
+
scopeFor(taskId) {
|
|
47903
|
+
return (taskId ? this.scopeByTask.get(taskId) : void 0) ?? this.baseScope;
|
|
47904
|
+
}
|
|
47905
|
+
/**
|
|
47906
|
+
* Read-only view of the SEEDED authority scope per master §9:1286 (P46a B2:
|
|
47907
|
+
* `authorityScope()` keeps today's value at its only production caller — the
|
|
47908
|
+
* continuation writer's clamp, frozen at wiring — which IS the seeded scope).
|
|
47655
47909
|
* Returns a deep-frozen copy so callers cannot mutate the LE's state.
|
|
47656
47910
|
*/
|
|
47657
47911
|
authorityScope() {
|
|
47658
47912
|
return Object.freeze({
|
|
47659
|
-
writeScopes: [...this.
|
|
47660
|
-
readScopes: [...this.
|
|
47661
|
-
networkAllowed: this.
|
|
47662
|
-
commandAllowlist: [...this.
|
|
47663
|
-
expiresAt: this.
|
|
47913
|
+
writeScopes: [...this.baseScope.writeScopes],
|
|
47914
|
+
readScopes: [...this.baseScope.readScopes],
|
|
47915
|
+
networkAllowed: this.baseScope.networkAllowed,
|
|
47916
|
+
commandAllowlist: [...this.baseScope.commandAllowlist],
|
|
47917
|
+
expiresAt: this.baseScope.expiresAt
|
|
47918
|
+
});
|
|
47919
|
+
}
|
|
47920
|
+
/** P46a B2 (Stage 1 r1 F7) — drop a task's merged scope when the task ends. */
|
|
47921
|
+
forgetTaskScope(taskId) {
|
|
47922
|
+
this.scopeByTask.delete(taskId);
|
|
47923
|
+
}
|
|
47924
|
+
/** P46a B2 — read-only view of ONE task's merged scope (the seeded scope when unauthorized). */
|
|
47925
|
+
authorityScopeForTask(taskId) {
|
|
47926
|
+
let scope = this.scopeFor(taskId);
|
|
47927
|
+
return Object.freeze({
|
|
47928
|
+
writeScopes: [...scope.writeScopes],
|
|
47929
|
+
readScopes: [...scope.readScopes],
|
|
47930
|
+
networkAllowed: scope.networkAllowed,
|
|
47931
|
+
commandAllowlist: [...scope.commandAllowlist],
|
|
47932
|
+
expiresAt: scope.expiresAt
|
|
47664
47933
|
});
|
|
47665
47934
|
}
|
|
47666
47935
|
// --- Authority surface (re-exports through this class for the bound scope) ---
|
|
47667
|
-
|
|
47668
|
-
|
|
47936
|
+
// P46a B2 — each takes an optional task id and resolves the seeded scope
|
|
47937
|
+
// without one (none has a production caller today).
|
|
47938
|
+
enforceCommand(argv, taskId) {
|
|
47939
|
+
enforceCommand(this.scopeFor(taskId), argv);
|
|
47669
47940
|
}
|
|
47670
|
-
enforceNetwork() {
|
|
47671
|
-
enforceNetwork(this.
|
|
47941
|
+
enforceNetwork(taskId) {
|
|
47942
|
+
enforceNetwork(this.scopeFor(taskId));
|
|
47672
47943
|
}
|
|
47673
|
-
async enforceWrite(p) {
|
|
47674
|
-
return enforceWrite(this.
|
|
47944
|
+
async enforceWrite(p, taskId) {
|
|
47945
|
+
return enforceWrite(this.scopeFor(taskId), p);
|
|
47675
47946
|
}
|
|
47676
|
-
async enforceRead(p) {
|
|
47677
|
-
return enforceRead(this.
|
|
47947
|
+
async enforceRead(p, taskId) {
|
|
47948
|
+
return enforceRead(this.scopeFor(taskId), p);
|
|
47678
47949
|
}
|
|
47679
|
-
async safeWriteFile(p, data) {
|
|
47680
|
-
return safeWriteFile(this.
|
|
47950
|
+
async safeWriteFile(p, data, taskId) {
|
|
47951
|
+
return safeWriteFile(this.scopeFor(taskId), p, data);
|
|
47681
47952
|
}
|
|
47682
|
-
async safeReadFile(p, opts) {
|
|
47683
|
-
return safeReadFile(this.
|
|
47953
|
+
async safeReadFile(p, opts, taskId) {
|
|
47954
|
+
return safeReadFile(this.scopeFor(taskId), p, opts);
|
|
47684
47955
|
}
|
|
47685
47956
|
// --- Spawn surface (wires lifecycle hooks to the emitter) ---
|
|
47686
47957
|
// Per master CP-1 §9:1281-1283 — all three spawn methods take full
|
|
@@ -47711,11 +47982,11 @@ var LocalExecutorImpl = class {
|
|
|
47711
47982
|
signal: advisoryAdmission.controller.signal
|
|
47712
47983
|
});
|
|
47713
47984
|
try {
|
|
47714
|
-
let {
|
|
47985
|
+
let spawnTaskScope = this.scopeFor(promptBoundArgs.taskId ?? null), {
|
|
47715
47986
|
args: launchArgs,
|
|
47716
47987
|
onExit,
|
|
47717
47988
|
substrateEngaged
|
|
47718
|
-
} = await this.engageSubstrateForSpawn(promptBoundArgs,
|
|
47989
|
+
} = await this.engageSubstrateForSpawn(promptBoundArgs, spawnTaskScope), outerBoundaryConfines = substrateEngaged || isTrustedContainerBoundary(), finalArgs = {
|
|
47719
47990
|
...launchArgs,
|
|
47720
47991
|
argv: applyOuterBoundarySandbox(
|
|
47721
47992
|
relocateCodexLastMessageForSubstrate(
|
|
@@ -47726,7 +47997,7 @@ var LocalExecutorImpl = class {
|
|
|
47726
47997
|
launchArgs.agentKind ?? this.adapter,
|
|
47727
47998
|
outerBoundaryConfines
|
|
47728
47999
|
)
|
|
47729
|
-
}, spawnScope = outerBoundaryConfines ? withBoundaryConfinedCommandAuthority(
|
|
48000
|
+
}, spawnScope = outerBoundaryConfines ? withBoundaryConfinedCommandAuthority(spawnTaskScope) : spawnTaskScope, handle = await this.spawnWithLifecycle(
|
|
47730
48001
|
finalArgs,
|
|
47731
48002
|
(a) => spawnImplementor(spawnScope, a),
|
|
47732
48003
|
onExit
|
|
@@ -47744,7 +48015,7 @@ var LocalExecutorImpl = class {
|
|
|
47744
48015
|
async spawnHealthProbe(args) {
|
|
47745
48016
|
if (this.workspaceShutdown)
|
|
47746
48017
|
throw new Error("LocalExecutor session shutdown has fenced new health probes");
|
|
47747
|
-
return this.spawnWithLifecycle(args, (a) => spawnHealthProbe(this.
|
|
48018
|
+
return this.spawnWithLifecycle(args, (a) => spawnHealthProbe(this.baseScope, a));
|
|
47748
48019
|
}
|
|
47749
48020
|
/**
|
|
47750
48021
|
* Publish advisory ownership synchronously before the first spawn await.
|
|
@@ -47847,7 +48118,7 @@ var LocalExecutorImpl = class {
|
|
|
47847
48118
|
);
|
|
47848
48119
|
return { args, substrateEngaged: !1 };
|
|
47849
48120
|
}
|
|
47850
|
-
let effectiveTaskId = args.taskId ??
|
|
48121
|
+
let effectiveTaskId = args.taskId ?? null;
|
|
47851
48122
|
if (effectiveTaskId === null) {
|
|
47852
48123
|
if (args.requireConfined)
|
|
47853
48124
|
throw new SpawnConfinementUnavailable(
|
|
@@ -47859,7 +48130,7 @@ var LocalExecutorImpl = class {
|
|
|
47859
48130
|
"none"
|
|
47860
48131
|
), { args, substrateEngaged: !1 };
|
|
47861
48132
|
}
|
|
47862
|
-
let result = await this.substrateEngager({
|
|
48133
|
+
let isWebActive = args.webTurnActive === !0, result = await this.substrateEngager({
|
|
47863
48134
|
taskId: effectiveTaskId,
|
|
47864
48135
|
agentKind,
|
|
47865
48136
|
workdir: args.workingDir,
|
|
@@ -47868,7 +48139,8 @@ var LocalExecutorImpl = class {
|
|
|
47868
48139
|
// A1d (design §8.1) — thread the spawn's confinement demand so the engager
|
|
47869
48140
|
// can build the WRITE-CONFINED resolver row for a broker-less agent
|
|
47870
48141
|
// (GEMINI/ANTIGRAVITY) rather than immediately returning `reduced_trust`.
|
|
47871
|
-
requireConfined: args.requireConfined === !0
|
|
48142
|
+
requireConfined: args.requireConfined === !0,
|
|
48143
|
+
webBrowsingActive: isWebActive
|
|
47872
48144
|
});
|
|
47873
48145
|
if (result.mode === "reduced_trust") {
|
|
47874
48146
|
if (args.requireConfined)
|
|
@@ -47997,7 +48269,7 @@ var LocalExecutorImpl = class {
|
|
|
47997
48269
|
}
|
|
47998
48270
|
}
|
|
47999
48271
|
async spawnWithLifecycle(args, spawnFn, onSubstrateExit) {
|
|
48000
|
-
let role = args.role, lifecycleAudit = args.lifecycleAudit ?? "active_task", lifecycleTaskId = args.taskId ??
|
|
48272
|
+
let role = args.role, lifecycleAudit = args.lifecycleAudit ?? "active_task", lifecycleTaskId = args.taskId ?? null, lifecycleCtx = lifecycleTaskId ? { taskId: lifecycleTaskId, ...this.baseCtx } : null, substrateCleanupError, substrateCleanupPromise, cleanupSubstrate = async () => {
|
|
48001
48273
|
onSubstrateExit && (substrateCleanupPromise || (substrateCleanupPromise = onSubstrateExit().catch((error) => {
|
|
48002
48274
|
throw substrateCleanupError = error, error;
|
|
48003
48275
|
})), await substrateCleanupPromise);
|
|
@@ -48050,11 +48322,15 @@ var LocalExecutorImpl = class {
|
|
|
48050
48322
|
...err.refusal,
|
|
48051
48323
|
detail: safeDetail
|
|
48052
48324
|
});
|
|
48053
|
-
await this.bridge.bridgeAuthorityRefusal(
|
|
48054
|
-
|
|
48055
|
-
|
|
48056
|
-
|
|
48057
|
-
|
|
48325
|
+
await this.bridge.bridgeAuthorityRefusal(
|
|
48326
|
+
safeError,
|
|
48327
|
+
{
|
|
48328
|
+
refusedMessageId: "spawn:" + role + ":" + Date.now(),
|
|
48329
|
+
shellContent: `Refused spawn (${role}): ${safeDetail}`,
|
|
48330
|
+
shellMetadata: { role, argv: safeArgv }
|
|
48331
|
+
},
|
|
48332
|
+
lifecycleCtx
|
|
48333
|
+
), primary = safeError;
|
|
48058
48334
|
}
|
|
48059
48335
|
throw substrateCleanupError !== void 0 ? Object.assign(
|
|
48060
48336
|
new Error("implementor spawn failed and substrate cleanup also failed"),
|
|
@@ -49453,11 +49729,12 @@ var LocalExecutorImpl = class {
|
|
|
49453
49729
|
];
|
|
49454
49730
|
return {
|
|
49455
49731
|
writeScopes,
|
|
49456
|
-
// Reads cover the shared workspace (
|
|
49457
|
-
|
|
49458
|
-
|
|
49459
|
-
|
|
49460
|
-
|
|
49732
|
+
// Reads cover the shared workspace (the seeded read scopes) + own writes.
|
|
49733
|
+
// P46a B2 — derived from the SEEDED scope, never another task's grants.
|
|
49734
|
+
readScopes: [...this.baseScope.readScopes, ...writeScopes],
|
|
49735
|
+
networkAllowed: this.baseScope.networkAllowed,
|
|
49736
|
+
commandAllowlist: [...this.baseScope.commandAllowlist],
|
|
49737
|
+
expiresAt: this.baseScope.expiresAt
|
|
49461
49738
|
};
|
|
49462
49739
|
}
|
|
49463
49740
|
/**
|
|
@@ -49583,13 +49860,18 @@ var LocalExecutorImpl = class {
|
|
|
49583
49860
|
}
|
|
49584
49861
|
}
|
|
49585
49862
|
// --- Test seams ------------------------------------------------------------
|
|
49586
|
-
/**
|
|
49587
|
-
|
|
49588
|
-
|
|
49589
|
-
|
|
49590
|
-
|
|
49863
|
+
/**
|
|
49864
|
+
* Replace the SEEDED authority scope (test seam; production seeds it at
|
|
49865
|
+
* construction via `initialScope`). P46a B2: per-task scopes are set only by
|
|
49866
|
+
* a TaskAuthorized (`advanceAuthorityScope`) — use `setAuthorityScopeForTask`
|
|
49867
|
+
* to simulate one in tests.
|
|
49868
|
+
*/
|
|
49591
49869
|
setAuthorityScope(scope) {
|
|
49592
|
-
this.
|
|
49870
|
+
this.baseScope = scope;
|
|
49871
|
+
}
|
|
49872
|
+
/** P46a B2 test seam — install ONE task's merged scope as a TaskAuthorized would. */
|
|
49873
|
+
setAuthorityScopeForTask(taskId, scope) {
|
|
49874
|
+
this.scopeByTask.set(taskId, mergeAuthorityScope(this.baseScope, scope));
|
|
49593
49875
|
}
|
|
49594
49876
|
/** Expose emitter for §8.5 integration test introspection. */
|
|
49595
49877
|
get emitterForTests() {
|
|
@@ -49607,21 +49889,18 @@ var LocalExecutorImpl = class {
|
|
|
49607
49889
|
get trackHeartbeatCountForTests() {
|
|
49608
49890
|
return this.trackHeartbeats.size;
|
|
49609
49891
|
}
|
|
49610
|
-
contextOrNull() {
|
|
49611
|
-
return this.taskId === null ? null : { taskId: this.taskId, ...this.baseCtx };
|
|
49612
|
-
}
|
|
49613
49892
|
/**
|
|
49614
49893
|
* Context resolver for ClassBConsumer emit paths (refusals + PolicyRejection).
|
|
49615
|
-
* Class B inbound packets
|
|
49616
|
-
*
|
|
49617
|
-
*
|
|
49618
|
-
* refusal-audit emit
|
|
49619
|
-
*
|
|
49620
|
-
*
|
|
49894
|
+
* Class B inbound packets carry no task the executor could attribute a
|
|
49895
|
+
* refusal to before they are verified (signature_invalid / ulid_replay /
|
|
49896
|
+
* policy_rejection / malformed), and P46a B2 removed the singleton task id,
|
|
49897
|
+
* so the refusal-audit emit uses the deterministic `pending` placeholder —
|
|
49898
|
+
* never another task's id. Spawn-attributed emits resolve their task from
|
|
49899
|
+
* the per-spawn `args.taskId` (`lifecycleCtx`).
|
|
49621
49900
|
*/
|
|
49622
49901
|
contextOrFallback() {
|
|
49623
49902
|
return {
|
|
49624
|
-
taskId:
|
|
49903
|
+
taskId: "pending",
|
|
49625
49904
|
...this.baseCtx
|
|
49626
49905
|
};
|
|
49627
49906
|
}
|
|
@@ -52023,6 +52302,97 @@ function resolveTeamExecution(_override, _env = process.env) {
|
|
|
52023
52302
|
// src/orchestration-shell/quorum-loop.ts
|
|
52024
52303
|
init_env_scrub();
|
|
52025
52304
|
|
|
52305
|
+
// src/credential-broker/scrubber.ts
|
|
52306
|
+
var REDACTION = "[REDACTED-CP7]", KEY_REDACTION = "[REDACTED-CP7-KEY]", SECRET_PATTERNS = [
|
|
52307
|
+
{
|
|
52308
|
+
// PEM private-key block (RSA / EC / OPENSSH / generic PRIVATE KEY).
|
|
52309
|
+
patternClass: "private_key_pem",
|
|
52310
|
+
regex: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |ENCRYPTED )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH |DSA |ENCRYPTED )?PRIVATE KEY-----/g
|
|
52311
|
+
},
|
|
52312
|
+
{
|
|
52313
|
+
// Anthropic API key: sk-ant-... .
|
|
52314
|
+
patternClass: "anthropic_api_key",
|
|
52315
|
+
regex: /sk-ant-[A-Za-z0-9_-]{20,}/g
|
|
52316
|
+
},
|
|
52317
|
+
{
|
|
52318
|
+
// OpenAI API key: sk-... or sk-proj-... (>= 20 trailing chars).
|
|
52319
|
+
patternClass: "openai_api_key",
|
|
52320
|
+
regex: /sk-(?:proj-)?[A-Za-z0-9_-]{20,}/g
|
|
52321
|
+
},
|
|
52322
|
+
{
|
|
52323
|
+
// AWS access key id (AKIA / ASIA + 16 uppercase alphanumerics).
|
|
52324
|
+
patternClass: "aws_access_key_id",
|
|
52325
|
+
regex: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g
|
|
52326
|
+
},
|
|
52327
|
+
{
|
|
52328
|
+
// AWS secret access key VALUE assignment — a 40-char base64-ish secret
|
|
52329
|
+
// bound to an `aws_secret_access_key`/`AWS_SECRET_ACCESS_KEY` key. Only
|
|
52330
|
+
// the secret value is redacted, not the assignment label.
|
|
52331
|
+
patternClass: "aws_secret_access_key",
|
|
52332
|
+
regex: /(aws_secret_access_key|AWS_SECRET_ACCESS_KEY)(\s*[=:]\s*["']?)([A-Za-z0-9/+]{40})(["']?)/g
|
|
52333
|
+
},
|
|
52334
|
+
{
|
|
52335
|
+
// GitHub tokens (ghp_/gho_/ghu_/ghs_/ghr_ + 36 chars).
|
|
52336
|
+
patternClass: "github_token",
|
|
52337
|
+
regex: /\bgh[pousr]_[A-Za-z0-9]{36}\b/g
|
|
52338
|
+
},
|
|
52339
|
+
{
|
|
52340
|
+
// Authorization: Bearer <token> embedded in content (a bearer token
|
|
52341
|
+
// riding in a model-bound field). Redacts the token, keeps the scheme.
|
|
52342
|
+
patternClass: "bearer_token",
|
|
52343
|
+
regex: /(Bearer\s+)([A-Za-z0-9._~+/=-]{16,})/g
|
|
52344
|
+
}
|
|
52345
|
+
];
|
|
52346
|
+
function scrubString(value) {
|
|
52347
|
+
return redactSecretShapes(value, REDACTION);
|
|
52348
|
+
}
|
|
52349
|
+
function redactSecretShapes(value, placeholder) {
|
|
52350
|
+
let redacted = value, classes = [];
|
|
52351
|
+
for (let pattern of SECRET_PATTERNS)
|
|
52352
|
+
pattern.regex.lastIndex = 0, pattern.regex.test(redacted) && (pattern.regex.lastIndex = 0, pattern.patternClass === "aws_secret_access_key" ? redacted = redacted.replace(pattern.regex, `$1$2${placeholder}$4`) : pattern.patternClass === "bearer_token" ? redacted = redacted.replace(pattern.regex, `$1${placeholder}`) : redacted = redacted.replace(pattern.regex, placeholder), classes.push(pattern.patternClass));
|
|
52353
|
+
return { redacted, classes };
|
|
52354
|
+
}
|
|
52355
|
+
function redactSecretShapesInText(value, placeholder = "[redacted secret]") {
|
|
52356
|
+
return redactSecretShapes(value, placeholder).redacted;
|
|
52357
|
+
}
|
|
52358
|
+
function keyIsSecret(key) {
|
|
52359
|
+
for (let pattern of SECRET_PATTERNS) {
|
|
52360
|
+
pattern.regex.lastIndex = 0;
|
|
52361
|
+
let hit = pattern.regex.test(key);
|
|
52362
|
+
if (pattern.regex.lastIndex = 0, hit) return !0;
|
|
52363
|
+
}
|
|
52364
|
+
return !1;
|
|
52365
|
+
}
|
|
52366
|
+
function joinPath(base, key) {
|
|
52367
|
+
return typeof key == "number" ? `${base}[${key}]` : /^[A-Za-z_][A-Za-z0-9_]*$/.test(key) ? `${base}.${key}` : `${base}['${key.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}']`;
|
|
52368
|
+
}
|
|
52369
|
+
function scrubValue(value, path75, findings) {
|
|
52370
|
+
if (typeof value == "string") {
|
|
52371
|
+
let { redacted, classes } = scrubString(value);
|
|
52372
|
+
for (let patternClass of classes)
|
|
52373
|
+
findings.push({ field: path75, patternClass });
|
|
52374
|
+
return redacted;
|
|
52375
|
+
}
|
|
52376
|
+
if (Array.isArray(value))
|
|
52377
|
+
return value.map((item, i) => scrubValue(item, joinPath(path75, i), findings));
|
|
52378
|
+
if (value !== null && typeof value == "object") {
|
|
52379
|
+
let out = /* @__PURE__ */ Object.create(null), redactedKeyCount = 0;
|
|
52380
|
+
for (let [k, v] of Object.entries(value))
|
|
52381
|
+
if (keyIsSecret(k)) {
|
|
52382
|
+
redactedKeyCount += 1;
|
|
52383
|
+
let placeholder = `${KEY_REDACTION}-${redactedKeyCount}`, placeholderPath = joinPath(path75, placeholder);
|
|
52384
|
+
findings.push({ field: placeholderPath, patternClass: "secret_object_key" }), out[placeholder] = scrubValue(v, placeholderPath, findings);
|
|
52385
|
+
} else
|
|
52386
|
+
out[k] = scrubValue(v, joinPath(path75, k), findings);
|
|
52387
|
+
return out;
|
|
52388
|
+
}
|
|
52389
|
+
return value;
|
|
52390
|
+
}
|
|
52391
|
+
function scrubRequestBody(body) {
|
|
52392
|
+
let findings = [];
|
|
52393
|
+
return { scrubbed: scrubValue(body, "$", findings), findings };
|
|
52394
|
+
}
|
|
52395
|
+
|
|
52026
52396
|
// src/orchestration-shell/context-items.ts
|
|
52027
52397
|
var fs39 = __toESM(require("fs/promises")), import_fs2 = require("fs"), path57 = __toESM(require("path")), os32 = __toESM(require("os")), crypto27 = __toESM(require("crypto")), import_ulid3 = require("ulid"), import_uuid7 = require("uuid");
|
|
52028
52398
|
init_logger2();
|
|
@@ -53898,97 +54268,6 @@ function renderRepoSliceCompact(repos, maxChars) {
|
|
|
53898
54268
|
var fs40 = __toESM(require("fs/promises")), path58 = __toESM(require("path"));
|
|
53899
54269
|
init_logger2();
|
|
53900
54270
|
|
|
53901
|
-
// src/credential-broker/scrubber.ts
|
|
53902
|
-
var REDACTION = "[REDACTED-CP7]", KEY_REDACTION = "[REDACTED-CP7-KEY]", SECRET_PATTERNS = [
|
|
53903
|
-
{
|
|
53904
|
-
// PEM private-key block (RSA / EC / OPENSSH / generic PRIVATE KEY).
|
|
53905
|
-
patternClass: "private_key_pem",
|
|
53906
|
-
regex: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |ENCRYPTED )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH |DSA |ENCRYPTED )?PRIVATE KEY-----/g
|
|
53907
|
-
},
|
|
53908
|
-
{
|
|
53909
|
-
// Anthropic API key: sk-ant-... .
|
|
53910
|
-
patternClass: "anthropic_api_key",
|
|
53911
|
-
regex: /sk-ant-[A-Za-z0-9_-]{20,}/g
|
|
53912
|
-
},
|
|
53913
|
-
{
|
|
53914
|
-
// OpenAI API key: sk-... or sk-proj-... (>= 20 trailing chars).
|
|
53915
|
-
patternClass: "openai_api_key",
|
|
53916
|
-
regex: /sk-(?:proj-)?[A-Za-z0-9_-]{20,}/g
|
|
53917
|
-
},
|
|
53918
|
-
{
|
|
53919
|
-
// AWS access key id (AKIA / ASIA + 16 uppercase alphanumerics).
|
|
53920
|
-
patternClass: "aws_access_key_id",
|
|
53921
|
-
regex: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g
|
|
53922
|
-
},
|
|
53923
|
-
{
|
|
53924
|
-
// AWS secret access key VALUE assignment — a 40-char base64-ish secret
|
|
53925
|
-
// bound to an `aws_secret_access_key`/`AWS_SECRET_ACCESS_KEY` key. Only
|
|
53926
|
-
// the secret value is redacted, not the assignment label.
|
|
53927
|
-
patternClass: "aws_secret_access_key",
|
|
53928
|
-
regex: /(aws_secret_access_key|AWS_SECRET_ACCESS_KEY)(\s*[=:]\s*["']?)([A-Za-z0-9/+]{40})(["']?)/g
|
|
53929
|
-
},
|
|
53930
|
-
{
|
|
53931
|
-
// GitHub tokens (ghp_/gho_/ghu_/ghs_/ghr_ + 36 chars).
|
|
53932
|
-
patternClass: "github_token",
|
|
53933
|
-
regex: /\bgh[pousr]_[A-Za-z0-9]{36}\b/g
|
|
53934
|
-
},
|
|
53935
|
-
{
|
|
53936
|
-
// Authorization: Bearer <token> embedded in content (a bearer token
|
|
53937
|
-
// riding in a model-bound field). Redacts the token, keeps the scheme.
|
|
53938
|
-
patternClass: "bearer_token",
|
|
53939
|
-
regex: /(Bearer\s+)([A-Za-z0-9._~+/=-]{16,})/g
|
|
53940
|
-
}
|
|
53941
|
-
];
|
|
53942
|
-
function scrubString(value) {
|
|
53943
|
-
return redactSecretShapes(value, REDACTION);
|
|
53944
|
-
}
|
|
53945
|
-
function redactSecretShapes(value, placeholder) {
|
|
53946
|
-
let redacted = value, classes = [];
|
|
53947
|
-
for (let pattern of SECRET_PATTERNS)
|
|
53948
|
-
pattern.regex.lastIndex = 0, pattern.regex.test(redacted) && (pattern.regex.lastIndex = 0, pattern.patternClass === "aws_secret_access_key" ? redacted = redacted.replace(pattern.regex, `$1$2${placeholder}$4`) : pattern.patternClass === "bearer_token" ? redacted = redacted.replace(pattern.regex, `$1${placeholder}`) : redacted = redacted.replace(pattern.regex, placeholder), classes.push(pattern.patternClass));
|
|
53949
|
-
return { redacted, classes };
|
|
53950
|
-
}
|
|
53951
|
-
function redactSecretShapesInText(value, placeholder = "[redacted secret]") {
|
|
53952
|
-
return redactSecretShapes(value, placeholder).redacted;
|
|
53953
|
-
}
|
|
53954
|
-
function keyIsSecret(key) {
|
|
53955
|
-
for (let pattern of SECRET_PATTERNS) {
|
|
53956
|
-
pattern.regex.lastIndex = 0;
|
|
53957
|
-
let hit = pattern.regex.test(key);
|
|
53958
|
-
if (pattern.regex.lastIndex = 0, hit) return !0;
|
|
53959
|
-
}
|
|
53960
|
-
return !1;
|
|
53961
|
-
}
|
|
53962
|
-
function joinPath(base, key) {
|
|
53963
|
-
return typeof key == "number" ? `${base}[${key}]` : /^[A-Za-z_][A-Za-z0-9_]*$/.test(key) ? `${base}.${key}` : `${base}['${key.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}']`;
|
|
53964
|
-
}
|
|
53965
|
-
function scrubValue(value, path75, findings) {
|
|
53966
|
-
if (typeof value == "string") {
|
|
53967
|
-
let { redacted, classes } = scrubString(value);
|
|
53968
|
-
for (let patternClass of classes)
|
|
53969
|
-
findings.push({ field: path75, patternClass });
|
|
53970
|
-
return redacted;
|
|
53971
|
-
}
|
|
53972
|
-
if (Array.isArray(value))
|
|
53973
|
-
return value.map((item, i) => scrubValue(item, joinPath(path75, i), findings));
|
|
53974
|
-
if (value !== null && typeof value == "object") {
|
|
53975
|
-
let out = /* @__PURE__ */ Object.create(null), redactedKeyCount = 0;
|
|
53976
|
-
for (let [k, v] of Object.entries(value))
|
|
53977
|
-
if (keyIsSecret(k)) {
|
|
53978
|
-
redactedKeyCount += 1;
|
|
53979
|
-
let placeholder = `${KEY_REDACTION}-${redactedKeyCount}`, placeholderPath = joinPath(path75, placeholder);
|
|
53980
|
-
findings.push({ field: placeholderPath, patternClass: "secret_object_key" }), out[placeholder] = scrubValue(v, placeholderPath, findings);
|
|
53981
|
-
} else
|
|
53982
|
-
out[k] = scrubValue(v, joinPath(path75, k), findings);
|
|
53983
|
-
return out;
|
|
53984
|
-
}
|
|
53985
|
-
return value;
|
|
53986
|
-
}
|
|
53987
|
-
function scrubRequestBody(body) {
|
|
53988
|
-
let findings = [];
|
|
53989
|
-
return { scrubbed: scrubValue(body, "$", findings), findings };
|
|
53990
|
-
}
|
|
53991
|
-
|
|
53992
54271
|
// src/orchestration-shell/user-rules.ts
|
|
53993
54272
|
var USER_RULE_ACTION = "user_rule", USER_RULE_RETIRED_ACTION = "user_rule_retired", USER_WORDS_VISIBLE_TO = ["planner", "brainstorm", "implementor"], USER_RULE_MAX_CHARS = 600, WORKFLOW_HANDOFF_SECTION_LABEL = "Workflow handoff context:", HANDOFF_SECTION_START = new RegExp(
|
|
53994
54273
|
`(?:^|\\n)${WORKFLOW_HANDOFF_SECTION_LABEL.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\nSource: `
|
|
@@ -56936,10 +57215,74 @@ function teamRound0GateId(taskId, taskGroupId, trackIndex, dispatchGeneration) {
|
|
|
56936
57215
|
TEAM_ROUND0_GATE_NAMESPACE
|
|
56937
57216
|
);
|
|
56938
57217
|
}
|
|
57218
|
+
var RETRY_BRIEF_SECTION_CAP = 8 * 1024, RETRY_BRIEF_TOTAL_CAP = 64 * 1024, UNIFIED_DIFF_MAX_CELLS = 4e6;
|
|
57219
|
+
function capText2(text2, cap) {
|
|
57220
|
+
if (Buffer.byteLength(text2, "utf8") <= cap) return text2;
|
|
57221
|
+
let marker = `
|
|
57222
|
+
\u2026 [truncated]`, cut = text2;
|
|
57223
|
+
for (; Buffer.byteLength(cut, "utf8") + Buffer.byteLength(marker, "utf8") > cap && cut.length > 0; )
|
|
57224
|
+
cut = cut.slice(0, Math.max(0, Math.floor(cut.length * 0.9) - 1));
|
|
57225
|
+
return cut + marker;
|
|
57226
|
+
}
|
|
57227
|
+
function unifiedDiff(fromLabel, toLabel, fromText, toText) {
|
|
57228
|
+
let a = fromText.length === 0 ? [] : fromText.split(`
|
|
57229
|
+
`), b = toText.length === 0 ? [] : toText.split(`
|
|
57230
|
+
`), ops = [];
|
|
57231
|
+
if (a.length * b.length > UNIFIED_DIFF_MAX_CELLS) {
|
|
57232
|
+
for (let i = 0; i < a.length; i++) ops.push({ kind: "-", line: a[i], ai: i, bi: 0 });
|
|
57233
|
+
for (let j = 0; j < b.length; j++) ops.push({ kind: "+", line: b[j], ai: a.length, bi: j });
|
|
57234
|
+
} else {
|
|
57235
|
+
let rows = a.length + 1, cols = b.length + 1, table = new Uint32Array(rows * cols);
|
|
57236
|
+
for (let i2 = a.length - 1; i2 >= 0; i2--)
|
|
57237
|
+
for (let j2 = b.length - 1; j2 >= 0; j2--)
|
|
57238
|
+
table[i2 * cols + j2] = a[i2] === b[j2] ? table[(i2 + 1) * cols + j2 + 1] + 1 : Math.max(table[(i2 + 1) * cols + j2], table[i2 * cols + j2 + 1]);
|
|
57239
|
+
let i = 0, j = 0;
|
|
57240
|
+
for (; i < a.length && j < b.length; )
|
|
57241
|
+
a[i] === b[j] ? (ops.push({ kind: " ", line: a[i], ai: i, bi: j }), i++, j++) : table[(i + 1) * cols + j] >= table[i * cols + j + 1] ? (ops.push({ kind: "-", line: a[i], ai: i, bi: j }), i++) : (ops.push({ kind: "+", line: b[j], ai: i, bi: j }), j++);
|
|
57242
|
+
for (; i < a.length; ) ops.push({ kind: "-", line: a[i], ai: i, bi: j }), i++;
|
|
57243
|
+
for (; j < b.length; ) ops.push({ kind: "+", line: b[j], ai: i, bi: j }), j++;
|
|
57244
|
+
}
|
|
57245
|
+
let out = [`--- ${fromLabel}`, `+++ ${toLabel}`], CONTEXT = 3, k = 0;
|
|
57246
|
+
for (; k < ops.length; ) {
|
|
57247
|
+
if (ops[k].kind === " ") {
|
|
57248
|
+
k++;
|
|
57249
|
+
continue;
|
|
57250
|
+
}
|
|
57251
|
+
let start = Math.max(0, k - CONTEXT), end = k, lastChange = k;
|
|
57252
|
+
for (; end < ops.length && (ops[end].kind !== " " && (lastChange = end), !(end - lastChange > 2 * CONTEXT)); )
|
|
57253
|
+
end++;
|
|
57254
|
+
end = Math.min(ops.length, lastChange + CONTEXT + 1);
|
|
57255
|
+
let slice = ops.slice(start, end), fromStart = (slice.find((o) => o.kind !== "+")?.ai ?? ops[start].ai) + 1, toStart = (slice.find((o) => o.kind !== "-")?.bi ?? ops[start].bi) + 1, fromCount = slice.filter((o) => o.kind !== "+").length, toCount = slice.filter((o) => o.kind !== "-").length;
|
|
57256
|
+
out.push(`@@ -${fromStart},${fromCount} +${toStart},${toCount} @@`);
|
|
57257
|
+
for (let o of slice) out.push(`${o.kind}${o.line}`);
|
|
57258
|
+
k = end, start = end;
|
|
57259
|
+
}
|
|
57260
|
+
return out.length === 2 && out.push("(no textual difference)"), out.join(`
|
|
57261
|
+
`);
|
|
57262
|
+
}
|
|
56939
57263
|
var QuorumLoop = class _QuorumLoop {
|
|
56940
57264
|
constructor(deps) {
|
|
56941
|
-
/**
|
|
56942
|
-
|
|
57265
|
+
/**
|
|
57266
|
+
* P46a B1 — the FOCUSED single task: the task the UI means when a command
|
|
57267
|
+
* names none (`/continue`, `/review-reset`, the status "starting" row).
|
|
57268
|
+
* Default = the most recently CONFIRMED START; `/task focus <n>` sets it
|
|
57269
|
+
* explicitly; it advances to the newest remaining single task when the
|
|
57270
|
+
* focused task ends; a rejected START never moves it. It replaces the three
|
|
57271
|
+
* P45 singletons (`activeTaskId` / `activeBrief` / `activeImplementorAgent`):
|
|
57272
|
+
* every reader now resolves through the task's OWN record
|
|
57273
|
+
* (`singleTaskContextByTask`), never through "the latest task".
|
|
57274
|
+
*/
|
|
57275
|
+
this.focusedTaskId = null;
|
|
57276
|
+
/** P46a B1 — confirmed single tasks in START-confirmation order (focus fallback). */
|
|
57277
|
+
this.confirmedSingleOrder = [];
|
|
57278
|
+
/**
|
|
57279
|
+
* P46a B1 — the most recent START still in flight (or null). A legacy
|
|
57280
|
+
* GATE_DISPATCH with an EMPTY envelope task id can only belong to it (the P45
|
|
57281
|
+
* rule); with no START in flight such a packet is a no-op left to recovery.
|
|
57282
|
+
*/
|
|
57283
|
+
this.latestInFlightStart = null;
|
|
57284
|
+
/** Stage 1 r1 F13 — every START still in flight, in issue order; `latestInFlightStart` is its last entry. */
|
|
57285
|
+
this.startOrderInFlight = [];
|
|
56943
57286
|
/** Tasks whose next automatic revise round must start a fresh review baseline. */
|
|
56944
57287
|
this.reviewScopeResetTasks = /* @__PURE__ */ new Set();
|
|
56945
57288
|
/** Non-terminal tasks for which `/review-reset` may arm the next revise round. */
|
|
@@ -56963,16 +57306,9 @@ var QuorumLoop = class _QuorumLoop {
|
|
|
56963
57306
|
// only per QuorumLoop life; a `0`/absent entry omits the ` · ↓ <tokens>` segment.
|
|
56964
57307
|
this.tokensByTaskId = /* @__PURE__ */ new Map();
|
|
56965
57308
|
this.tokensCountedGates = /* @__PURE__ */ new Set();
|
|
56966
|
-
/**
|
|
56967
|
-
* The implementor brief for the active task (set at start_task). The
|
|
56968
|
-
* GATE_DISPATCH consumer reads this to drive the round-0 implementor spawn —
|
|
56969
|
-
* the brief is no longer threaded inline through the spawn call (the spawn
|
|
56970
|
-
* moved out of `startTask` into the GATE_DISPATCH consumer, §3.C.20 FIX).
|
|
56971
|
-
*/
|
|
56972
|
-
this.activeBrief = null;
|
|
56973
57309
|
/**
|
|
56974
57310
|
* IMAGE-ATTACHMENT-DESIGN.md §5 — TASK-SCOPED image attachments (parallel to
|
|
56975
|
-
* `
|
|
57311
|
+
* `singleTaskContextByTask`/`activeBriefByTask`). The single-impl path uses `activeAttachments`;
|
|
56976
57312
|
* the team path keys by the child `taskId` in `activeAttachmentsByTask` (armed at
|
|
56977
57313
|
* `registerTeamTaskBrief`, before its buffered GATE_DISPATCH drains). Re-copied
|
|
56978
57314
|
* into EACH round's shadow at `runImplementorRound` (round-0, revise, continuation,
|
|
@@ -57027,19 +57363,44 @@ var QuorumLoop = class _QuorumLoop {
|
|
|
57027
57363
|
this.recoveredBriefsByAuthority = /* @__PURE__ */ new Map();
|
|
57028
57364
|
this.activeBriefByTask = /* @__PURE__ */ new Map();
|
|
57029
57365
|
/**
|
|
57030
|
-
* P45 D1 (M7 E2E step 8, 2026-09-14) — the brief + agent of EVERY
|
|
57031
|
-
* implementor task this loop started, keyed by task id.
|
|
57032
|
-
*
|
|
57033
|
-
*
|
|
57034
|
-
*
|
|
57035
|
-
*
|
|
57036
|
-
*
|
|
57037
|
-
*
|
|
57038
|
-
* continuation context resolve through this map first; the singletons stay
|
|
57039
|
-
* as the legacy fallback (an empty envelope task id, the test seam). Entries
|
|
57040
|
-
* are removed with the task's other per-task state at its terminal cleanup.
|
|
57366
|
+
* P45 D1 (M7 E2E step 8, 2026-09-14) / P46a B1 — the brief + agent of EVERY
|
|
57367
|
+
* LIVE single-implementor task this loop started, keyed by task id. P45 added
|
|
57368
|
+
* it beside the three "latest task" singletons (a second single task's
|
|
57369
|
+
* request had replaced the first task's in its revise round); P46a deletes
|
|
57370
|
+
* the singletons — round-0 spawns, revise rounds, the continuation context and
|
|
57371
|
+
* every other reader resolve ONLY through this map. An entry exists from the
|
|
57372
|
+
* START call (in flight) until the task's terminal cleanup; `retryOf` links a
|
|
57373
|
+
* `#n retry` task to its `apply_failed` original (P46a C2/C3).
|
|
57041
57374
|
*/
|
|
57042
57375
|
this.singleTaskContextByTask = /* @__PURE__ */ new Map();
|
|
57376
|
+
/** Agent web browsing (upstream 2.0.37) — whether a task's ORIGINAL request needs web access; ends with the task (`forgetSingleTask`, team cleanup). */
|
|
57377
|
+
this.webAccessByTask = /* @__PURE__ */ new Map();
|
|
57378
|
+
/**
|
|
57379
|
+
* P46a D2 — append-only per-loop-life record of every single task this loop
|
|
57380
|
+
* started (confirmed or in flight) with its terminal outcome once known, for
|
|
57381
|
+
* `/status` (`describeTasks`). Never deleted (bounded by the session's tasks).
|
|
57382
|
+
*/
|
|
57383
|
+
this.singleTaskHistory = /* @__PURE__ */ new Map();
|
|
57384
|
+
/**
|
|
57385
|
+
* P46a C1 — every single-task promote THIS session landed, by workspace-
|
|
57386
|
+
* relative path: the promoting task + the post-image hash/mode the promote
|
|
57387
|
+
* compare-and-set reads (`readRealMetadata`; `null` hash = a deletion). Used
|
|
57388
|
+
* to attribute a later collision to a `session-task` (the path's current
|
|
57389
|
+
* metadata equals a recorded post-image) or `external` (anything else —
|
|
57390
|
+
* including a session-promoted file the user edited afterwards, and every
|
|
57391
|
+
* promote by another session). In-memory, fail-safe: empty after a desktop
|
|
57392
|
+
* restart, so every collision then classifies as `external`.
|
|
57393
|
+
*/
|
|
57394
|
+
this.promotedByPath = /* @__PURE__ */ new Map();
|
|
57395
|
+
/** P46a C2 — originals whose ONE automatic retry has been issued (never a second). */
|
|
57396
|
+
this.autoRetryIssuedFor = /* @__PURE__ */ new Set();
|
|
57397
|
+
/**
|
|
57398
|
+
* P46a C2 — retry STARTs planned inside the promote critical section and
|
|
57399
|
+
* issued only after it (and the durable `apply_failed` record) completed:
|
|
57400
|
+
* `startTask` runs the round-0 spawn before returning, so it must never run
|
|
57401
|
+
* inside `promoteShadowLocked`.
|
|
57402
|
+
*/
|
|
57403
|
+
this.pendingPromoteRetries = [];
|
|
57043
57404
|
/**
|
|
57044
57405
|
* CP-1.f revise-context fix (dogfood task 2ac68708, 2026-06-11) — the BINDING
|
|
57045
57406
|
* user-requested changes for a task, accumulated across ALL revise rounds.
|
|
@@ -57502,8 +57863,6 @@ var QuorumLoop = class _QuorumLoop {
|
|
|
57502
57863
|
this.lastWorkspaceOutcome = null;
|
|
57503
57864
|
// ─── §3.C.18 — real Ed25519 signature verify (cached key) ───────────────
|
|
57504
57865
|
this.cachedSigningKey = null;
|
|
57505
|
-
/** The implementor agent the loop spawns (set at start_task). */
|
|
57506
|
-
this.activeImplementorAgent = "CLAUDE";
|
|
57507
57866
|
this.deps = deps, this.sleep = deps.sleep ?? realSleep2, this.loadCompletedSummariesFromDisk();
|
|
57508
57867
|
}
|
|
57509
57868
|
getCompletedSummariesPath() {
|
|
@@ -58092,13 +58451,95 @@ var QuorumLoop = class _QuorumLoop {
|
|
|
58092
58451
|
isTaskTerminallyRetired(taskId) {
|
|
58093
58452
|
return this.isWorkspaceTaskTerminallyRetired(taskId);
|
|
58094
58453
|
}
|
|
58095
|
-
/**
|
|
58454
|
+
/**
|
|
58455
|
+
* P46a B1 — the FOCUSED single task (null when no single task is live). The
|
|
58456
|
+
* P45 `activeTask` name is kept for the shell's readers; it now means the
|
|
58457
|
+
* focused task, never "the latest task".
|
|
58458
|
+
*/
|
|
58096
58459
|
get activeTask() {
|
|
58097
|
-
return this.
|
|
58460
|
+
return this.focusedTaskId;
|
|
58461
|
+
}
|
|
58462
|
+
/** P46a B1 — every LIVE single task this loop started (in flight or confirmed). */
|
|
58463
|
+
knownSingleTasks() {
|
|
58464
|
+
return [...this.singleTaskContextByTask.keys()];
|
|
58465
|
+
}
|
|
58466
|
+
/**
|
|
58467
|
+
* P46a B1 — `/task focus <n>`: make `taskId` the focused task. Refused (false)
|
|
58468
|
+
* for a task this loop does not know as a live single task.
|
|
58469
|
+
*/
|
|
58470
|
+
setFocusedTask(taskId) {
|
|
58471
|
+
return this.singleTaskContextByTask.has(taskId) ? (this.focusedTaskId = taskId, !0) : !1;
|
|
58472
|
+
}
|
|
58473
|
+
/** P46a D1 — install the store-owned ordinal sink (the shell, after construction). */
|
|
58474
|
+
setTaskOrdinalSink(sink) {
|
|
58475
|
+
this.taskOrdinalSink = sink;
|
|
58476
|
+
}
|
|
58477
|
+
/** P46a C3 — install the desktop-local apply-conflict menu sink. */
|
|
58478
|
+
setApplyConflictSink(sink) {
|
|
58479
|
+
this.applyConflictSink = sink;
|
|
58480
|
+
}
|
|
58481
|
+
/** P46a C2 — install the retry-started notifier (the shell records the lifecycle row). */
|
|
58482
|
+
setRetryStartedSink(sink) {
|
|
58483
|
+
this.retryStartedSink = sink;
|
|
58098
58484
|
}
|
|
58099
|
-
/**
|
|
58485
|
+
/** P46a D1 — `#n` / `#n retry` / short id for any task id (never throws). */
|
|
58486
|
+
labelFor(taskId) {
|
|
58487
|
+
try {
|
|
58488
|
+
let label = this.taskOrdinalSink?.label(taskId);
|
|
58489
|
+
if (label) return label;
|
|
58490
|
+
} catch {
|
|
58491
|
+
}
|
|
58492
|
+
return taskId.length > 8 ? taskId.slice(0, 8) : taskId;
|
|
58493
|
+
}
|
|
58494
|
+
/**
|
|
58495
|
+
* P46a D2 — every single task this loop started, for `/status`: its label,
|
|
58496
|
+
* agent, request (desktop-local plaintext — the user's own words), link to an
|
|
58497
|
+
* `apply_failed` original, loop state and whether it is the focused task.
|
|
58498
|
+
*/
|
|
58499
|
+
describeTasks() {
|
|
58500
|
+
let rows = [];
|
|
58501
|
+
for (let [taskId, rec] of this.singleTaskHistory)
|
|
58502
|
+
rows.push({
|
|
58503
|
+
taskId,
|
|
58504
|
+
label: this.labelFor(taskId),
|
|
58505
|
+
agent: rec.agent,
|
|
58506
|
+
// Stage 1 r3 F1 — a retry's excerpt is the user's own request, not its composed brief.
|
|
58507
|
+
brief: rec.request ?? rec.brief,
|
|
58508
|
+
...rec.retryOf ? { retryOf: rec.retryOf } : {},
|
|
58509
|
+
outcome: rec.outcome,
|
|
58510
|
+
implementorActive: this.activeImplementorByTask.has(taskId),
|
|
58511
|
+
focused: taskId === this.focusedTaskId
|
|
58512
|
+
});
|
|
58513
|
+
return rows;
|
|
58514
|
+
}
|
|
58515
|
+
/** Stage 1 r1 F13 — a START confirmed or rejected: the newest START still in flight (if any) becomes the empty-envelope target. */
|
|
58516
|
+
dropInFlightStart(taskId) {
|
|
58517
|
+
let at = this.startOrderInFlight.indexOf(taskId);
|
|
58518
|
+
at >= 0 && this.startOrderInFlight.splice(at, 1), this.latestInFlightStart = this.startOrderInFlight.length > 0 ? this.startOrderInFlight[this.startOrderInFlight.length - 1] : null;
|
|
58519
|
+
}
|
|
58520
|
+
/**
|
|
58521
|
+
* P46a B1 — a single task ended (promote, terminal discard, rejected START):
|
|
58522
|
+
* drop its live record and, when it was the focused task, advance the focus
|
|
58523
|
+
* to the newest remaining confirmed single task (or none).
|
|
58524
|
+
*/
|
|
58525
|
+
forgetSingleTask(taskId, outcome) {
|
|
58526
|
+
this.singleTaskContextByTask.delete(taskId), this.webAccessByTask.delete(taskId), this.deps.localExecutor.forgetTaskScope?.(taskId);
|
|
58527
|
+
let history = this.singleTaskHistory.get(taskId);
|
|
58528
|
+
history && history.outcome === "running" && (history.outcome = outcome);
|
|
58529
|
+
let at = this.confirmedSingleOrder.indexOf(taskId);
|
|
58530
|
+
at >= 0 && this.confirmedSingleOrder.splice(at, 1), this.focusedTaskId === taskId && (this.focusedTaskId = this.confirmedSingleOrder.length > 0 ? this.confirmedSingleOrder[this.confirmedSingleOrder.length - 1] : null);
|
|
58531
|
+
}
|
|
58532
|
+
/**
|
|
58533
|
+
* Arm an explicit, one-shot comprehensive baseline for the FOCUSED task
|
|
58534
|
+
* (P46a B1). Refused with the candidate list when the focused task has no
|
|
58535
|
+
* eligible gate (or no single task is focused) — the shell renders the
|
|
58536
|
+
* `#n` candidates so the user can `/task focus <n>` first.
|
|
58537
|
+
*/
|
|
58100
58538
|
requestReviewScopeReset() {
|
|
58101
|
-
|
|
58539
|
+
let focused = this.focusedTaskId;
|
|
58540
|
+
return focused && this.reviewScopeResetEligibleTasks.has(focused) ? (this.reviewScopeResetTasks.add(focused), { armed: !0, taskId: focused }) : { armed: !1, candidates: [...this.singleTaskContextByTask.keys()].filter(
|
|
58541
|
+
(id) => id !== focused && this.reviewScopeResetEligibleTasks.has(id)
|
|
58542
|
+
) };
|
|
58102
58543
|
}
|
|
58103
58544
|
/**
|
|
58104
58545
|
* Audited-path fix (Fix 5) — the host's detected implementor agents
|
|
@@ -58280,8 +58721,7 @@ var QuorumLoop = class _QuorumLoop {
|
|
|
58280
58721
|
return logger.warn("[QuorumLoop] startTask aborted \u2014 no session key", {
|
|
58281
58722
|
sessionId: this.deps.session.sessionId
|
|
58282
58723
|
}), null;
|
|
58283
|
-
|
|
58284
|
-
this.activeTaskId = taskId, this.activeBrief = args.brief, appendContextItem(this.deps.session.sessionId, {
|
|
58724
|
+
appendContextItem(this.deps.session.sessionId, {
|
|
58285
58725
|
kind: "task_spec",
|
|
58286
58726
|
author: { role: "engine" },
|
|
58287
58727
|
sensitivity: "user",
|
|
@@ -58289,7 +58729,14 @@ var QuorumLoop = class _QuorumLoop {
|
|
|
58289
58729
|
task_id: taskId,
|
|
58290
58730
|
body: { text: args.brief }
|
|
58291
58731
|
}).catch(() => {
|
|
58292
|
-
}), this.activeAttachments = args.attachments ?? [], this.activeAttachmentsByTask.set(taskId, this.activeAttachments)
|
|
58732
|
+
}), this.activeAttachments = args.attachments ?? [], this.activeAttachmentsByTask.set(taskId, this.activeAttachments);
|
|
58733
|
+
let implementorAgent = this.resolveForcedImplementorAgent() ?? args.agent, record = {
|
|
58734
|
+
brief: args.brief,
|
|
58735
|
+
agent: implementorAgent,
|
|
58736
|
+
...args.retryOf ? { retryOf: args.retryOf } : {},
|
|
58737
|
+
...args.request !== void 0 ? { request: args.request } : {}
|
|
58738
|
+
};
|
|
58739
|
+
this.singleTaskContextByTask.set(taskId, record), this.singleTaskHistory.set(taskId, { ...record, outcome: "running" }), this.startTasksInFlight.add(taskId), this.startOrderInFlight.push(taskId), this.latestInFlightStart = taskId;
|
|
58293
58740
|
let workflowState;
|
|
58294
58741
|
try {
|
|
58295
58742
|
workflowState = (await this.deps.appsyncClient.startTask({
|
|
@@ -58302,31 +58749,40 @@ var QuorumLoop = class _QuorumLoop {
|
|
|
58302
58749
|
// an empty `[]` (host detected nothing) is sent verbatim →
|
|
58303
58750
|
// Claude-only fail-safe.
|
|
58304
58751
|
availableAgents: this.deps.detectedAgents,
|
|
58305
|
-
implementorAgent:
|
|
58752
|
+
implementorAgent: implementorAgent.toLowerCase()
|
|
58306
58753
|
})).workflowState;
|
|
58307
58754
|
} catch (err) {
|
|
58308
|
-
let
|
|
58309
|
-
|
|
58755
|
+
let wasLatestStart2 = this.latestInFlightStart === taskId;
|
|
58756
|
+
this.forgetSingleTask(taskId, "rejected"), this.startTasksInFlight.delete(taskId), this.dropInFlightStart(taskId);
|
|
58310
58757
|
for (let i = this.pendingGateDispatches.length - 1; i >= 0; i--) {
|
|
58311
58758
|
let pending = this.pendingGateDispatches[i];
|
|
58312
|
-
(pending.envelopeTaskId === taskId || !pending.envelopeTaskId &&
|
|
58759
|
+
(pending.envelopeTaskId === taskId || !pending.envelopeTaskId && wasLatestStart2) && this.pendingGateDispatches.splice(i, 1);
|
|
58313
58760
|
}
|
|
58314
|
-
return
|
|
58761
|
+
return logger.warn("[QuorumLoop] startTask failed \u2014 dropped the task record + discarded its buffered GATE_DISPATCH", {
|
|
58315
58762
|
err: err.message
|
|
58316
58763
|
}), null;
|
|
58317
58764
|
}
|
|
58318
|
-
this.startTasksInFlight.delete(taskId)
|
|
58765
|
+
this.startTasksInFlight.delete(taskId);
|
|
58766
|
+
let wasLatestStart = this.latestInFlightStart === taskId;
|
|
58767
|
+
this.dropInFlightStart(taskId), this.reviewScopeResetEligibleTasks.add(taskId);
|
|
58768
|
+
let ordinal = this.taskOrdinalSink?.assign(taskId, {
|
|
58769
|
+
kind: "single",
|
|
58770
|
+
...args.retryOf ? { retryOf: args.retryOf } : {}
|
|
58771
|
+
});
|
|
58772
|
+
this.confirmedSingleOrder.push(taskId), this.focusedTaskId = taskId, logger.info("[QuorumLoop] startTask succeeded \u2014 draining buffered GATE_DISPATCH (if any) then awaiting more", {
|
|
58319
58773
|
taskId,
|
|
58774
|
+
...ordinal !== void 0 ? { ordinal } : {},
|
|
58775
|
+
...args.retryOf ? { retryOf: args.retryOf } : {},
|
|
58320
58776
|
workflowState,
|
|
58321
58777
|
buffered: this.pendingGateDispatches.length
|
|
58322
58778
|
});
|
|
58323
58779
|
let drained = [];
|
|
58324
58780
|
for (let i = this.pendingGateDispatches.length - 1; i >= 0; i--) {
|
|
58325
58781
|
let entry = this.pendingGateDispatches[i];
|
|
58326
|
-
(!entry.envelopeTaskId || entry.envelopeTaskId === taskId) && drained.unshift(this.pendingGateDispatches.splice(i, 1)[0]);
|
|
58782
|
+
(!entry.envelopeTaskId && wasLatestStart || entry.envelopeTaskId === taskId) && drained.unshift(this.pendingGateDispatches.splice(i, 1)[0]);
|
|
58327
58783
|
}
|
|
58328
58784
|
for (let entry of drained)
|
|
58329
|
-
await this.handleGateDispatch(entry.payload, entry.envelopeTaskId);
|
|
58785
|
+
await this.handleGateDispatch(entry.payload, entry.envelopeTaskId ?? taskId);
|
|
58330
58786
|
return { taskId };
|
|
58331
58787
|
}
|
|
58332
58788
|
// ─── §3.C.20 — GATE_DISPATCH → round-0 implementor spawn ────────────────
|
|
@@ -58349,7 +58805,7 @@ var QuorumLoop = class _QuorumLoop {
|
|
|
58349
58805
|
* never thrown upward.
|
|
58350
58806
|
*/
|
|
58351
58807
|
async handleGateDispatch(payload, envelopeTaskId) {
|
|
58352
|
-
let dispatchTaskId = envelopeTaskId || this.
|
|
58808
|
+
let dispatchTaskId = envelopeTaskId || this.latestInFlightStart;
|
|
58353
58809
|
if (dispatchTaskId && Array.isArray(payload.reviewerSeats) && payload.reviewerSeats.length > 0) {
|
|
58354
58810
|
let roster = this.expectedRosterByTask.get(dispatchTaskId);
|
|
58355
58811
|
roster || (roster = /* @__PURE__ */ new Map(), this.expectedRosterByTask.set(dispatchTaskId, roster));
|
|
@@ -58367,11 +58823,12 @@ var QuorumLoop = class _QuorumLoop {
|
|
|
58367
58823
|
logger.info("[QuorumLoop] GateDispatch dedup no-op", { gateRunId: payload.gateRunId });
|
|
58368
58824
|
return;
|
|
58369
58825
|
}
|
|
58370
|
-
if (typeof envelopeTaskId == "string" && envelopeTaskId.length > 0 &&
|
|
58371
|
-
//
|
|
58826
|
+
if (typeof envelopeTaskId == "string" && envelopeTaskId.length > 0 && // P45 D1 / P46a B1 — a dispatch for ANY single task this loop started (in
|
|
58827
|
+
// flight or confirmed, earlier or later) is a single-implementor dispatch;
|
|
58828
|
+
// only an unknown task id routes to the team handler.
|
|
58372
58829
|
!this.singleTaskContextByTask.has(envelopeTaskId))
|
|
58373
58830
|
return this.bufferTeamPacketDuringSeeding("gateDispatch", payload, envelopeTaskId) ? void 0 : this.handleTeamGateDispatch(payload, envelopeTaskId);
|
|
58374
|
-
let inFlightFor = typeof envelopeTaskId == "string" && envelopeTaskId.length > 0 ? envelopeTaskId : this.
|
|
58831
|
+
let inFlightFor = typeof envelopeTaskId == "string" && envelopeTaskId.length > 0 ? envelopeTaskId : this.latestInFlightStart;
|
|
58375
58832
|
if (inFlightFor !== null && this.startTasksInFlight.has(inFlightFor)) {
|
|
58376
58833
|
logger.info("[QuorumLoop] GateDispatch buffered \u2014 startTask in flight (early-packet race)", {
|
|
58377
58834
|
gateRunId: payload.gateRunId,
|
|
@@ -58379,15 +58836,15 @@ var QuorumLoop = class _QuorumLoop {
|
|
|
58379
58836
|
}), this.pendingGateDispatches.push({ payload, envelopeTaskId });
|
|
58380
58837
|
return;
|
|
58381
58838
|
}
|
|
58382
|
-
let single = typeof envelopeTaskId == "string" && envelopeTaskId.length > 0 ? this.singleTaskContextByTask.get(envelopeTaskId) : void 0
|
|
58383
|
-
if (!
|
|
58384
|
-
logger.warn("[QuorumLoop] GateDispatch
|
|
58839
|
+
let single = typeof envelopeTaskId == "string" && envelopeTaskId.length > 0 ? this.singleTaskContextByTask.get(envelopeTaskId) : void 0;
|
|
58840
|
+
if (!single || !envelopeTaskId) {
|
|
58841
|
+
logger.warn("[QuorumLoop] GateDispatch names no task this loop started \u2014 no-op (recovery will handle)", {
|
|
58385
58842
|
gateRunId: payload.gateRunId,
|
|
58386
|
-
|
|
58387
|
-
hasBrief: !!brief
|
|
58843
|
+
hasEnvelopeTaskId: typeof envelopeTaskId == "string" && envelopeTaskId.length > 0
|
|
58388
58844
|
});
|
|
58389
58845
|
return;
|
|
58390
58846
|
}
|
|
58847
|
+
let taskId = envelopeTaskId, brief = single.brief, agent = single.agent;
|
|
58391
58848
|
this.seenGateDispatchRunIds.add(payload.gateRunId);
|
|
58392
58849
|
let sessionKey = await this.deps.getSessionKey(this.deps.session.sessionId);
|
|
58393
58850
|
if (!sessionKey) {
|
|
@@ -58942,8 +59399,13 @@ ${section}`);
|
|
|
58942
59399
|
settlePending = resolve23;
|
|
58943
59400
|
}),
|
|
58944
59401
|
...args.teamAuthority ? { teamAuthority: args.teamAuthority } : {}
|
|
58945
|
-
};
|
|
58946
|
-
if (
|
|
59402
|
+
}, needsWeb = this.webAccessByTask.get(args.taskId);
|
|
59403
|
+
if (needsWeb === void 0) {
|
|
59404
|
+
let singleRecord = this.singleTaskContextByTask.get(args.taskId), originalPrompt = (singleRecord ? singleRecord.request ?? singleRecord.brief : void 0) ?? this.activeBriefByTask.get(args.taskId)?.brief ?? // P46a B1 — no singleton fallback: a task without a record reads as no request.
|
|
59405
|
+
(args.roundNumber === 0 ? args.brief : "");
|
|
59406
|
+
needsWeb = isWebAccessNeeded(originalPrompt), this.webAccessByTask.set(args.taskId, needsWeb);
|
|
59407
|
+
}
|
|
59408
|
+
if (needsWeb && (args.agent === "CLAUDE" || args.agent === "ANTIGRAVITY"))
|
|
58947
59409
|
try {
|
|
58948
59410
|
agentWebTurn = await startAgentWebTurn({
|
|
58949
59411
|
sessionId: this.deps.session.sessionId,
|
|
@@ -58974,13 +59436,13 @@ ${section}`);
|
|
|
58974
59436
|
workingDirAuthority: snapshotAuthority,
|
|
58975
59437
|
role: "implementor",
|
|
58976
59438
|
agentKind: args.agent,
|
|
58977
|
-
// CP-12 W2.b — thread THIS round's task id as per-spawn
|
|
58978
|
-
// authority. On the TEAM path `args.taskId` is
|
|
58979
|
-
// `task_id
|
|
58980
|
-
//
|
|
58981
|
-
// (
|
|
58982
|
-
// yields byte-identical single-impl substrate engagement.
|
|
59439
|
+
// CP-12 W2.b / P46a B2 — thread THIS round's task id as the per-spawn
|
|
59440
|
+
// substrate/audit/authority identity. On the TEAM path `args.taskId` is
|
|
59441
|
+
// the per-track child `task_id`; on the single-impl path it is the task's
|
|
59442
|
+
// own id — the executor keeps no singleton any more and resolves the
|
|
59443
|
+
// per-task authority scope (seeded until ITS TaskAuthorized) from it.
|
|
58983
59444
|
taskId: args.taskId,
|
|
59445
|
+
webTurnActive: !!agentWebTurn?.mcpConfigPath,
|
|
58984
59446
|
timeoutMs: null,
|
|
58985
59447
|
stdinTty: !0,
|
|
58986
59448
|
// PHASE-CP-10-MIN (#585) — the cooperative abort signal (#C10M-16).
|
|
@@ -60203,7 +60665,7 @@ ${section}`);
|
|
|
60203
60665
|
));
|
|
60204
60666
|
}
|
|
60205
60667
|
for (let taskId of taskIds)
|
|
60206
|
-
this.activeBriefByTask.get(taskId)?.taskGroupId === taskGroupId && (this.activeBriefByTask.delete(taskId), this.activeAttachmentsByTask.delete(taskId));
|
|
60668
|
+
this.activeBriefByTask.get(taskId)?.taskGroupId === taskGroupId && (this.activeBriefByTask.delete(taskId), this.activeAttachmentsByTask.delete(taskId), this.webAccessByTask.delete(taskId));
|
|
60207
60669
|
if (this.teamAttachmentsByGroup.delete(taskGroupId), failures.length > 0) {
|
|
60208
60670
|
try {
|
|
60209
60671
|
this.surfaceHalt(
|
|
@@ -60281,7 +60743,7 @@ ${section}`);
|
|
|
60281
60743
|
});
|
|
60282
60744
|
return;
|
|
60283
60745
|
}
|
|
60284
|
-
if (opts?.preserveReviseContext || (this.userNotesByTask.delete(taskId), this.reviewScopeResetTasks.delete(taskId), this.reviewScopeResetEligibleTasks.delete(taskId), this.roundHistoryByTask.delete(taskId), opts?.taskContinues !== !0 && this.
|
|
60746
|
+
if (opts?.preserveReviseContext || (this.userNotesByTask.delete(taskId), this.reviewScopeResetTasks.delete(taskId), this.reviewScopeResetEligibleTasks.delete(taskId), this.roundHistoryByTask.delete(taskId), opts?.taskContinues !== !0 && this.forgetSingleTask(taskId, "discarded"), this.rationaleByTask.delete(taskId), this.taskDegradedMetadataByTask.delete(taskId), this.expectedRosterByTask.delete(taskId), this.activeGateByTaskId.delete(taskId), this.conflictedSeatsByTask.delete(taskId), this.nonApprovedSeatsByTask.delete(taskId), this.restartFenceByTaskId.delete(taskId), appendContextItem(this.deps.session.sessionId, {
|
|
60285
60747
|
kind: "verdict",
|
|
60286
60748
|
author: { role: "engine" },
|
|
60287
60749
|
sensitivity: "user",
|
|
@@ -60327,7 +60789,123 @@ ${section}`);
|
|
|
60327
60789
|
*/
|
|
60328
60790
|
async promoteShadow(taskId) {
|
|
60329
60791
|
if (!(this.shuttingDown && (this.terminalClassBAdmissionsByTask.get(taskId)?.size ?? 0) === 0))
|
|
60330
|
-
|
|
60792
|
+
try {
|
|
60793
|
+
await this.runExclusiveShadowOp(taskId, () => this.promoteShadowLocked(taskId));
|
|
60794
|
+
} finally {
|
|
60795
|
+
this.pendingPromoteRetries.length > 0 && setImmediate(() => {
|
|
60796
|
+
this.drainPromoteRetries().catch((err) => {
|
|
60797
|
+
logger.warn("[QuorumLoop] automatic retry drain failed", { err: err.message });
|
|
60798
|
+
});
|
|
60799
|
+
});
|
|
60800
|
+
}
|
|
60801
|
+
}
|
|
60802
|
+
/** P46a C2 — start every planned automatic retry as a new linked task. */
|
|
60803
|
+
async drainPromoteRetries() {
|
|
60804
|
+
for (; this.pendingPromoteRetries.length > 0; ) {
|
|
60805
|
+
let plan = this.pendingPromoteRetries.shift();
|
|
60806
|
+
if (this.shuttingDown) {
|
|
60807
|
+
logger.info("[QuorumLoop] automatic retry skipped \u2014 shutting down", { retryOf: plan.retryOf });
|
|
60808
|
+
continue;
|
|
60809
|
+
}
|
|
60810
|
+
try {
|
|
60811
|
+
let started = await this.startTask({
|
|
60812
|
+
decision: { action: "start_task", rationale: "P46a automatic retry after a session-task collision" },
|
|
60813
|
+
brief: plan.brief,
|
|
60814
|
+
agent: plan.agent,
|
|
60815
|
+
retryOf: plan.retryOf,
|
|
60816
|
+
request: plan.request
|
|
60817
|
+
});
|
|
60818
|
+
started ? (this.retryStartedSink?.({ taskId: started.taskId, retryOf: plan.retryOf, agent: plan.agent }), this.surfaceHalt(
|
|
60819
|
+
`Task ${this.labelFor(started.taskId)} started \u2014 the retry of task ${this.labelFor(plan.retryOf)}.`
|
|
60820
|
+
)) : this.surfaceHalt(
|
|
60821
|
+
`Could not start the automatic retry of task ${this.labelFor(plan.retryOf)} (the START was rejected). Re-issue the request to try again.`
|
|
60822
|
+
);
|
|
60823
|
+
} catch (err) {
|
|
60824
|
+
logger.warn("[QuorumLoop] automatic retry START threw (non-fatal)", {
|
|
60825
|
+
retryOf: plan.retryOf,
|
|
60826
|
+
err: err.message
|
|
60827
|
+
}), this.surfaceHalt(
|
|
60828
|
+
`Could not start the automatic retry of task ${this.labelFor(plan.retryOf)}. Re-issue the request to try again.`
|
|
60829
|
+
);
|
|
60830
|
+
}
|
|
60831
|
+
}
|
|
60832
|
+
}
|
|
60833
|
+
/** P46a D2 — correct a task's recorded outcome after its terminal cleanup ran. */
|
|
60834
|
+
markTaskOutcome(taskId, outcome) {
|
|
60835
|
+
let history = this.singleTaskHistory.get(taskId);
|
|
60836
|
+
history && (history.outcome = outcome);
|
|
60837
|
+
}
|
|
60838
|
+
/**
|
|
60839
|
+
* P46a C1 — classify each conflicting path: `session-task` when its current
|
|
60840
|
+
* real-tree metadata equals a post-image a single-task promote of THIS
|
|
60841
|
+
* session recorded (`promotedByPath`), else `external`.
|
|
60842
|
+
*/
|
|
60843
|
+
async attributeConflicts(shadow, paths) {
|
|
60844
|
+
let out = [];
|
|
60845
|
+
for (let rel of paths) {
|
|
60846
|
+
let recorded = this.promotedByPath.get(rel), cause = "external";
|
|
60847
|
+
if (recorded)
|
|
60848
|
+
try {
|
|
60849
|
+
let current = await shadow.readRealMetadata(rel);
|
|
60850
|
+
current.hash === recorded.postHash && current.mode === recorded.postMode && (cause = "session-task");
|
|
60851
|
+
} catch {
|
|
60852
|
+
cause = "external";
|
|
60853
|
+
}
|
|
60854
|
+
out.push({ path: rel, cause, ...cause === "session-task" && recorded ? { byTaskId: recorded.taskId } : {} });
|
|
60855
|
+
}
|
|
60856
|
+
return out;
|
|
60857
|
+
}
|
|
60858
|
+
/**
|
|
60859
|
+
* P46a C2 — the retry brief: the original request, a "Files changed since
|
|
60860
|
+
* your first attempt" section (a unified diff from the failed task's REVIEWED
|
|
60861
|
+
* contents to the CURRENT real contents, per conflicting path, naming the
|
|
60862
|
+
* session task that changed it), and the reviewed contents themselves. Every
|
|
60863
|
+
* section is capped (`RETRY_BRIEF_SECTION_CAP` per path,
|
|
60864
|
+
* `RETRY_BRIEF_TOTAL_CAP` overall — the brief becomes the retry task's
|
|
60865
|
+
* durable `task_spec`, whose write is fire-and-forget) and secret-scrubbed
|
|
60866
|
+
* (`redactSecretShapesInText`) before insertion.
|
|
60867
|
+
*/
|
|
60868
|
+
async buildRetryBrief(shadow, failedTaskId, originalBrief, attributed, reviewed) {
|
|
60869
|
+
let failedLabel = this.labelFor(failedTaskId), reviewedByPath = new Map(reviewed.map((file) => [file.path, file])), changed = [], contents = [];
|
|
60870
|
+
for (let conflict of attributed) {
|
|
60871
|
+
let who = conflict.cause === "session-task" && conflict.byTaskId ? `changed by task ${this.labelFor(conflict.byTaskId)} in this session` : "changed outside CodeVibe", reviewedFile = reviewedByPath.get(conflict.path), reviewedText = reviewedFile && reviewedFile.change_kind !== "deleted" ? reviewedFile.content : "", currentText = null, currentState = "";
|
|
60872
|
+
try {
|
|
60873
|
+
currentText = await shadow.readRealFileText(conflict.path);
|
|
60874
|
+
} catch (err) {
|
|
60875
|
+
currentText = null, currentState = err.code === "ENOENT" ? "; now absent" : "; unreadable now";
|
|
60876
|
+
}
|
|
60877
|
+
let diffText = unifiedDiff(
|
|
60878
|
+
`reviewed (task ${failedLabel})`,
|
|
60879
|
+
"current",
|
|
60880
|
+
reviewedText,
|
|
60881
|
+
currentText ?? ""
|
|
60882
|
+
);
|
|
60883
|
+
changed.push(
|
|
60884
|
+
`### ${conflict.path} (${who}${currentState})`,
|
|
60885
|
+
"```diff",
|
|
60886
|
+
capText2(diffText, RETRY_BRIEF_SECTION_CAP),
|
|
60887
|
+
"```"
|
|
60888
|
+
), reviewedFile && contents.push(
|
|
60889
|
+
`### ${conflict.path}${reviewedFile.change_kind === "deleted" ? " (you deleted this file)" : ""}`,
|
|
60890
|
+
"```",
|
|
60891
|
+
capText2(reviewedText, RETRY_BRIEF_SECTION_CAP),
|
|
60892
|
+
"```"
|
|
60893
|
+
);
|
|
60894
|
+
}
|
|
60895
|
+
let changedSection = redactSecretShapesInText(changed.join(`
|
|
60896
|
+
`)), contentsSection = redactSecretShapesInText(contents.join(`
|
|
60897
|
+
`)), assembled = [
|
|
60898
|
+
originalBrief.trim(),
|
|
60899
|
+
"",
|
|
60900
|
+
"## Files changed since your first attempt",
|
|
60901
|
+
`Your first attempt (task ${failedLabel}) was reviewed and approved, but could not be applied: ${attributed.length} file(s) changed in the workspace after your attempt. Re-do the request against the CURRENT files. Each diff below goes from the contents you produced (your reviewed version) to the contents now in the workspace.`,
|
|
60902
|
+
changedSection,
|
|
60903
|
+
"",
|
|
60904
|
+
"## Your reviewed contents from the first attempt",
|
|
60905
|
+
contentsSection
|
|
60906
|
+
].join(`
|
|
60907
|
+
`);
|
|
60908
|
+
return capText2(assembled, RETRY_BRIEF_TOTAL_CAP);
|
|
60331
60909
|
}
|
|
60332
60910
|
/**
|
|
60333
60911
|
* CP-12 W2.b (§3.C.2b (d) / H3-4) — promote-quiescence BARRIER for `taskId`.
|
|
@@ -60350,6 +60928,12 @@ ${section}`);
|
|
|
60350
60928
|
return this.promoteShadowLockedInner(taskId, originEpoch);
|
|
60351
60929
|
}
|
|
60352
60930
|
async promoteShadowLockedInner(taskId, originEpoch) {
|
|
60931
|
+
let singleContext = this.singleTaskContextByTask.get(taskId), retryContext = singleContext ? {
|
|
60932
|
+
brief: singleContext.brief,
|
|
60933
|
+
agent: singleContext.agent,
|
|
60934
|
+
...singleContext.retryOf ? { retryOf: singleContext.retryOf } : {},
|
|
60935
|
+
...singleContext.request !== void 0 ? { request: singleContext.request } : {}
|
|
60936
|
+
} : void 0;
|
|
60353
60937
|
try {
|
|
60354
60938
|
let teamAuthority = this.teamAuthorityForTask(taskId);
|
|
60355
60939
|
if (this.teamRunIsHalted(teamAuthority)) return;
|
|
@@ -60362,9 +60946,15 @@ ${section}`);
|
|
|
60362
60946
|
error: err?.message
|
|
60363
60947
|
}), historySnapshot = null;
|
|
60364
60948
|
}
|
|
60365
|
-
this.userNotesByTask.delete(taskId), this.reviewScopeResetTasks.delete(taskId), this.reviewScopeResetEligibleTasks.delete(taskId), this.roundHistoryByTask.delete(taskId), this.
|
|
60949
|
+
this.userNotesByTask.delete(taskId), this.reviewScopeResetTasks.delete(taskId), this.reviewScopeResetEligibleTasks.delete(taskId), this.roundHistoryByTask.delete(taskId), this.forgetSingleTask(taskId, "apply_failed"), this.rationaleByTask.delete(taskId), this.taskDegradedMetadataByTask.delete(taskId), this.expectedRosterByTask.delete(taskId), this.activeGateByTaskId.delete(taskId), this.conflictedSeatsByTask.delete(taskId), this.nonApprovedSeatsByTask.delete(taskId), this.restartFenceByTaskId.delete(taskId);
|
|
60366
60950
|
let shadow = this.shadowsByTask.get(taskId);
|
|
60367
60951
|
if (!shadow) {
|
|
60952
|
+
if (this.singleTaskHistory.get(taskId)?.outcome === "discarded") {
|
|
60953
|
+
this.surfaceHalt(
|
|
60954
|
+
`Could not apply task ${this.labelFor(taskId)} \u2014 its workspace copy was discarded after its implementor round failed on this side; nothing was applied. Re-run the request.`
|
|
60955
|
+
), logger.warn("[QuorumLoop] promote on a discarded task \u2014 nothing applied", { taskId });
|
|
60956
|
+
return;
|
|
60957
|
+
}
|
|
60368
60958
|
logger.info("[QuorumLoop] promote no-op \u2014 no in-memory shadow for task", { taskId });
|
|
60369
60959
|
return;
|
|
60370
60960
|
}
|
|
@@ -60614,7 +61204,8 @@ ${section}`);
|
|
|
60614
61204
|
shadow,
|
|
60615
61205
|
diff,
|
|
60616
61206
|
terminalOutcomeReserved,
|
|
60617
|
-
historySnapshot
|
|
61207
|
+
historySnapshot,
|
|
61208
|
+
retryContext
|
|
60618
61209
|
);
|
|
60619
61210
|
} finally {
|
|
60620
61211
|
this.emitProgress({ phase: "progress_cleared" }, originEpoch);
|
|
@@ -60632,7 +61223,7 @@ ${section}`);
|
|
|
60632
61223
|
* no-op/re-delivery/discard returns (which must stay silent). All emits carry the
|
|
60633
61224
|
* caller-resolved `originEpoch`.
|
|
60634
61225
|
*/
|
|
60635
|
-
async promoteApplyLocked(taskId, originEpoch, shadow, diff, terminalOutcomeReserved, historySnapshot) {
|
|
61226
|
+
async promoteApplyLocked(taskId, originEpoch, shadow, diff, terminalOutcomeReserved, historySnapshot, retryContext) {
|
|
60636
61227
|
this.emitProgress({ phase: "promoting", files: diff.length }, originEpoch);
|
|
60637
61228
|
let teamPromoteEntry = this.activeBriefByTask.get(taskId), captureManifest = this.deps.durableStore && teamPromoteEntry && teamPromoteEntry.taskGroupId.length > 0 ? {
|
|
60638
61229
|
trackIndex: teamPromoteEntry.trackIndex,
|
|
@@ -60652,21 +61243,85 @@ ${section}`);
|
|
|
60652
61243
|
}
|
|
60653
61244
|
} : void 0, res = await shadow.promote(diff, captureManifest ? { captureManifest } : void 0);
|
|
60654
61245
|
if (res.conflicts.length > 0 || res.errors.length > 0) {
|
|
60655
|
-
let paths = [...res.conflicts, ...res.errors.map((e) => e.path)];
|
|
60656
|
-
if (
|
|
60657
|
-
|
|
60658
|
-
|
|
60659
|
-
logger.warn("[QuorumLoop] unresolved promotion retained for reconciliation", {
|
|
61246
|
+
let paths = [...res.conflicts, ...res.errors.map((e) => e.path)], label = this.labelFor(taskId);
|
|
61247
|
+
if (res.mutationState === "unresolved") {
|
|
61248
|
+
this.surfaceHalt(
|
|
61249
|
+
`Could not apply task ${label} \u2014 ${paths.length} file(s) at ${paths.join(", ")} changed under the task or failed to write; the snapshot is retained until it is reconciled.`
|
|
61250
|
+
), logger.warn("[QuorumLoop] unresolved promotion retained for reconciliation", {
|
|
60660
61251
|
taskId,
|
|
60661
61252
|
paths
|
|
61253
|
+
}), this.markTaskOutcome(taskId, "unresolved");
|
|
61254
|
+
return;
|
|
61255
|
+
}
|
|
61256
|
+
let isSingleTask = retryContext !== void 0 && !this.activeBriefByTask.has(taskId), attributed = isSingleTask ? await this.attributeConflicts(shadow, res.conflicts) : [], writeErrors = res.errors.length > 0, retryBrief = null, rootRequest = retryContext ? retryContext.request ?? retryContext.brief : "";
|
|
61257
|
+
if (isSingleTask)
|
|
61258
|
+
if (attributed.length > 0)
|
|
61259
|
+
try {
|
|
61260
|
+
retryBrief = await this.buildRetryBrief(
|
|
61261
|
+
shadow,
|
|
61262
|
+
taskId,
|
|
61263
|
+
rootRequest,
|
|
61264
|
+
attributed,
|
|
61265
|
+
this.reviewedSnapshotByTask.get(taskId) ?? []
|
|
61266
|
+
);
|
|
61267
|
+
} catch (err) {
|
|
61268
|
+
logger.warn("[QuorumLoop] retry brief composition failed (offering the original request only)", {
|
|
61269
|
+
taskId,
|
|
61270
|
+
err: err.message
|
|
61271
|
+
}), retryBrief = rootRequest;
|
|
61272
|
+
}
|
|
61273
|
+
else
|
|
61274
|
+
retryBrief = rootRequest;
|
|
61275
|
+
if (await this.discardShadowLocked(taskId, shadow), this.markTaskOutcome(taskId, "apply_failed"), terminalOutcomeReserved && this.terminallyRetiredWorkspaceTasks.has(taskId) && await this.workspaceOutcomeSink.completeTask(taskId, "apply_failed"), !isSingleTask || retryBrief === null) {
|
|
61276
|
+
this.surfaceHalt(
|
|
61277
|
+
`Could not apply task ${label} \u2014 ${paths.length} file(s) at ${paths.join(", ")} changed under the task or failed to write. The approved change was not applied.`
|
|
61278
|
+
);
|
|
61279
|
+
return;
|
|
61280
|
+
}
|
|
61281
|
+
let sessionChanged = attributed.filter((c) => c.cause === "session-task"), byLabels = [...new Set(sessionChanged.map((c) => this.labelFor(c.byTaskId)))], causes = [];
|
|
61282
|
+
sessionChanged.length > 0 && causes.push(`${sessionChanged.length} changed by task ${byLabels.join(", ")}`), attributed.length - sessionChanged.length > 0 && causes.push(`${attributed.length - sessionChanged.length} changed outside CodeVibe`), writeErrors && causes.push(`${res.errors.length} could not be written`);
|
|
61283
|
+
let total = attributed.length + res.errors.length, summary = `${total} ${total === 1 ? "file" : "files"} (${causes.join("; ")})`;
|
|
61284
|
+
if (!writeErrors && attributed.every((c) => c.cause === "session-task") && retryContext.retryOf === void 0 && !this.autoRetryIssuedFor.has(taskId)) {
|
|
61285
|
+
this.autoRetryIssuedFor.add(taskId), this.pendingPromoteRetries.push({ retryOf: taskId, brief: retryBrief, agent: retryContext.agent, request: rootRequest }), this.surfaceHalt(
|
|
61286
|
+
`Could not apply task ${label}: ${summary} after it was reviewed. Retrying automatically as a new task on a fresh snapshot (it will be reviewed and approved again).`
|
|
61287
|
+
), logger.info("[QuorumLoop] promote collided with a session task \u2014 automatic retry planned", {
|
|
61288
|
+
taskId,
|
|
61289
|
+
conflicts: attributed.length
|
|
60662
61290
|
});
|
|
60663
61291
|
return;
|
|
60664
61292
|
}
|
|
60665
|
-
|
|
61293
|
+
let autoRetried = retryContext.retryOf !== void 0 && this.autoRetryIssuedFor.has(retryContext.retryOf);
|
|
61294
|
+
this.surfaceHalt(
|
|
61295
|
+
`Could not apply task ${label}: ${summary}` + (autoRetried ? " \u2014 the automatic retry collided too." : ".")
|
|
61296
|
+
);
|
|
61297
|
+
let menu = {
|
|
61298
|
+
taskId,
|
|
61299
|
+
taskLabel: label,
|
|
61300
|
+
conflicts: [
|
|
61301
|
+
...attributed.map((c) => ({
|
|
61302
|
+
path: c.path,
|
|
61303
|
+
cause: c.cause,
|
|
61304
|
+
...c.byTaskId ? { byTaskLabel: this.labelFor(c.byTaskId) } : {}
|
|
61305
|
+
})),
|
|
61306
|
+
...res.errors.map((e) => ({ path: e.path, cause: "write-error" }))
|
|
61307
|
+
],
|
|
61308
|
+
autoRetried,
|
|
61309
|
+
retryBrief,
|
|
61310
|
+
request: rootRequest,
|
|
61311
|
+
agent: retryContext.agent
|
|
61312
|
+
};
|
|
61313
|
+
try {
|
|
61314
|
+
this.applyConflictSink?.(menu);
|
|
61315
|
+
} catch (err) {
|
|
61316
|
+
logger.warn("[QuorumLoop] apply-conflict menu sink threw (ignored)", {
|
|
61317
|
+
taskId,
|
|
61318
|
+
err: err.message
|
|
61319
|
+
});
|
|
61320
|
+
}
|
|
60666
61321
|
return;
|
|
60667
61322
|
}
|
|
60668
61323
|
if (!res.materialized)
|
|
60669
|
-
if (res.recovered)
|
|
61324
|
+
if (this.markTaskOutcome(taskId, "applied"), res.recovered)
|
|
60670
61325
|
logger.info("[QuorumLoop] recovered fully-applied snapshot promotion", { taskId });
|
|
60671
61326
|
else {
|
|
60672
61327
|
logger.debug("[QuorumLoop] promote no-op \u2014 marker flipped to promoted mid-call", { taskId }), terminalOutcomeReserved && await this.workspaceOutcomeSink.completeTask(taskId, "applied", {
|
|
@@ -60685,6 +61340,19 @@ ${section}`);
|
|
|
60685
61340
|
return;
|
|
60686
61341
|
}
|
|
60687
61342
|
let appliedPaths = res.recovered ? diff.map((file) => file.path) : res.promoted;
|
|
61343
|
+
if (this.markTaskOutcome(taskId, "applied"), retryContext !== void 0 && !this.activeBriefByTask.has(taskId)) {
|
|
61344
|
+
let at = (/* @__PURE__ */ new Date()).toISOString();
|
|
61345
|
+
for (let rel of appliedPaths)
|
|
61346
|
+
try {
|
|
61347
|
+
let meta = await shadow.readRealMetadata(rel);
|
|
61348
|
+
this.promotedByPath.set(rel, { taskId, postHash: meta.hash, postMode: meta.mode, at });
|
|
61349
|
+
} catch (err) {
|
|
61350
|
+
this.promotedByPath.delete(rel), logger.debug("[QuorumLoop] promoted-path post-image read failed (left unattributed)", {
|
|
61351
|
+
taskId,
|
|
61352
|
+
err: err.message
|
|
61353
|
+
});
|
|
61354
|
+
}
|
|
61355
|
+
}
|
|
60688
61356
|
terminalOutcomeReserved && await this.workspaceOutcomeSink.completeTask(taskId, "applied", {
|
|
60689
61357
|
filesApplied: appliedPaths.length
|
|
60690
61358
|
}), logger.info("[QuorumLoop] shadow promoted to real tree", {
|
|
@@ -60937,7 +61605,11 @@ ${section}`);
|
|
|
60937
61605
|
});
|
|
60938
61606
|
return;
|
|
60939
61607
|
}
|
|
60940
|
-
if (
|
|
61608
|
+
if (logger.info("[QuorumLoop] ReviewerDispatch received", {
|
|
61609
|
+
gateId: payload.gateId,
|
|
61610
|
+
seatId: seat,
|
|
61611
|
+
...dispatchTaskId ? { taskId: dispatchTaskId } : {}
|
|
61612
|
+
}), dispatchTaskId) {
|
|
60941
61613
|
let roster = this.expectedRosterByTask.get(dispatchTaskId);
|
|
60942
61614
|
roster || (roster = /* @__PURE__ */ new Map(), this.expectedRosterByTask.set(dispatchTaskId, roster)), roster.set(String(seat), String(payload.role || "reviewer"));
|
|
60943
61615
|
}
|
|
@@ -61222,7 +61894,7 @@ ${section}`);
|
|
|
61222
61894
|
"[QuorumLoop] reviewer-sandboxing DISABLED by operator opt-out (CODEVIBE_SANDBOX_REVIEWERS=0) \u2014 legacy path",
|
|
61223
61895
|
{ key }
|
|
61224
61896
|
), NO_TEARDOWN;
|
|
61225
|
-
let taskId = this.taskByGateId.get(args.gateId)
|
|
61897
|
+
let taskId = this.taskByGateId.get(args.gateId);
|
|
61226
61898
|
if (!taskId)
|
|
61227
61899
|
return this.surfaceReviewerReducedTrust(
|
|
61228
61900
|
args,
|
|
@@ -61787,32 +62459,51 @@ ${section}`);
|
|
|
61787
62459
|
// ─── §3.C.21b — dropped-packet recovery ─────────────────────────────────
|
|
61788
62460
|
/**
|
|
61789
62461
|
* On WS (re)connect/startup, poll for the seats assigned to this desktop for
|
|
61790
|
-
* any in_review gate of
|
|
61791
|
-
*
|
|
61792
|
-
*
|
|
62462
|
+
* any in_review gate of EVERY in-flight task (P46a B3: the live single tasks,
|
|
62463
|
+
* the tasks with an active implementor, and the registered team tracks — one
|
|
62464
|
+
* query each) and spawn any seat NEITHER already running-in-memory NOR already
|
|
62465
|
+
* verdict-submitted (double-guard). Without this, a REVIEWER_DISPATCH dropped
|
|
62466
|
+
* during a WS gap hangs the gate forever. Residual (unchanged): after a full
|
|
62467
|
+
* desktop restart the in-memory maps are empty.
|
|
61793
62468
|
*/
|
|
61794
62469
|
async recoverInReviewAssignments() {
|
|
61795
|
-
if (this.shuttingDown
|
|
61796
|
-
let
|
|
61797
|
-
|
|
61798
|
-
|
|
61799
|
-
|
|
61800
|
-
|
|
61801
|
-
|
|
61802
|
-
});
|
|
61803
|
-
|
|
61804
|
-
|
|
61805
|
-
|
|
61806
|
-
|
|
61807
|
-
|
|
61808
|
-
|
|
61809
|
-
|
|
61810
|
-
|
|
61811
|
-
|
|
61812
|
-
|
|
61813
|
-
|
|
61814
|
-
}
|
|
62470
|
+
if (this.shuttingDown) return;
|
|
62471
|
+
let taskIds = /* @__PURE__ */ new Set([
|
|
62472
|
+
...this.singleTaskContextByTask.keys(),
|
|
62473
|
+
...this.activeImplementorByTask.keys(),
|
|
62474
|
+
...this.activeBriefByTask.keys()
|
|
62475
|
+
]);
|
|
62476
|
+
if (taskIds.size !== 0) {
|
|
62477
|
+
logger.info("[QuorumLoop] recovering in-review assignments", { taskIds: [...taskIds] });
|
|
62478
|
+
for (let taskId of taskIds) {
|
|
62479
|
+
if (this.shuttingDown) return;
|
|
62480
|
+
let assignments;
|
|
62481
|
+
try {
|
|
62482
|
+
assignments = await this.deps.appsyncClient.getInReviewAssignments(taskId);
|
|
62483
|
+
} catch (err) {
|
|
62484
|
+
logger.warn("[QuorumLoop] getInReviewAssignments failed (non-fatal)", {
|
|
62485
|
+
taskId,
|
|
62486
|
+
err: err.message
|
|
62487
|
+
});
|
|
62488
|
+
continue;
|
|
62489
|
+
}
|
|
62490
|
+
if (this.shuttingDown) return;
|
|
62491
|
+
for (let a of assignments) {
|
|
62492
|
+
if (this.shuttingDown) break;
|
|
62493
|
+
let key = seatKey(a.gateId, a.seatId);
|
|
62494
|
+
this.runningSeats.has(key) || this.submittedSeats.has(key) || (logger.info("[QuorumLoop] recovered in-review seat (its REVIEWER_DISPATCH was not consumed)", {
|
|
62495
|
+
taskId,
|
|
62496
|
+
gateId: a.gateId,
|
|
62497
|
+
seatId: a.seatId
|
|
62498
|
+
}), this.spawnOneSeat({
|
|
62499
|
+
gateId: a.gateId,
|
|
62500
|
+
seatId: a.seatId,
|
|
62501
|
+
role: a.role,
|
|
62502
|
+
agentKind: a.agentKind
|
|
62503
|
+
}));
|
|
62504
|
+
}
|
|
61815
62505
|
}
|
|
62506
|
+
}
|
|
61816
62507
|
}
|
|
61817
62508
|
// ─── §4 / #PR-4 / #GC-1 — shadow recovery + GC on reconnect/startup ──────
|
|
61818
62509
|
/**
|
|
@@ -61852,7 +62543,7 @@ ${section}`);
|
|
|
61852
62543
|
]), signal);
|
|
61853
62544
|
if (!this.admittedSessionWorkMayContinue(admission)) return;
|
|
61854
62545
|
let now = Date.now(), ttlMs = env.ttlMs ?? DEFAULT_SHADOW_TTL_MS, keep = /* @__PURE__ */ new Set();
|
|
61855
|
-
this.
|
|
62546
|
+
for (let taskId of this.singleTaskContextByTask.keys()) keep.add(taskId);
|
|
61856
62547
|
for (let taskId of this.shadowsByTask.keys()) keep.add(taskId);
|
|
61857
62548
|
for (let taskId of this.shadowCreateInFlight.keys()) keep.add(taskId);
|
|
61858
62549
|
let failures = [], sameWorkspace = recoverable.filter(
|
|
@@ -62737,7 +63428,7 @@ ${section}`);
|
|
|
62737
63428
|
});
|
|
62738
63429
|
return;
|
|
62739
63430
|
}
|
|
62740
|
-
let taskId = this.taskByGateId.get(payload.gateId) ??
|
|
63431
|
+
let taskId = this.taskByGateId.get(payload.gateId) ?? "", originEpoch = this.originEpochFor(taskId);
|
|
62741
63432
|
if (taskId === "") {
|
|
62742
63433
|
logger.warn(
|
|
62743
63434
|
"[QuorumLoop] ReviseFeedback could not resolve a task (in-memory state lost after restart?) \u2014 failing loud",
|
|
@@ -62747,7 +63438,17 @@ ${section}`);
|
|
|
62747
63438
|
), this.emitProgress({ phase: "progress_cleared" }, originEpoch);
|
|
62748
63439
|
return;
|
|
62749
63440
|
}
|
|
62750
|
-
let trackEntry = this.activeBriefByTask.get(taskId),
|
|
63441
|
+
let trackEntry = this.activeBriefByTask.get(taskId), singleEntry = this.singleTaskContextByTask.get(taskId);
|
|
63442
|
+
if (!trackEntry && !singleEntry) {
|
|
63443
|
+
logger.warn(
|
|
63444
|
+
"[QuorumLoop] ReviseFeedback names a task with no live record (ended, or lost after restart?) \u2014 failing loud",
|
|
63445
|
+
{ taskId, gateId: payload.gateId, nextGateId: payload.nextGateId }
|
|
63446
|
+
), this.surfaceHalt(
|
|
63447
|
+
`Could not resume this revise for task ${this.labelFor(taskId)} \u2014 its context is gone (re-issue the task to continue).`
|
|
63448
|
+
), this.emitProgress({ phase: "progress_cleared" }, originEpoch);
|
|
63449
|
+
return;
|
|
63450
|
+
}
|
|
63451
|
+
let parentTeamAuthority = this.teamAuthorityByGateId.get(payload.gateId), teamAuthority = parentTeamAuthority ?? (trackEntry ? _QuorumLoop.teamAuthorityForEntry(trackEntry) : void 0);
|
|
62751
63452
|
if (parentTeamAuthority && (!trackEntry || !this.teamEntryMayContinue(taskId, trackEntry, parentTeamAuthority))) {
|
|
62752
63453
|
logger.info("[QuorumLoop] stale team ReviseFeedback refused \u2014 generation retired", {
|
|
62753
63454
|
taskId,
|
|
@@ -62782,7 +63483,9 @@ ${section}`);
|
|
|
62782
63483
|
gateId: payload.nextGateId,
|
|
62783
63484
|
roundNumber: payload.nextRound,
|
|
62784
63485
|
brief,
|
|
62785
|
-
|
|
63486
|
+
// P46a B1 — the TRACK's agent (team) or the single task's OWN agent (its
|
|
63487
|
+
// record is guaranteed above); never a session-wide "active agent".
|
|
63488
|
+
agent: trackEntry?.agent ?? singleEntry.agent,
|
|
62786
63489
|
sessionKey,
|
|
62787
63490
|
...teamAuthority ? { teamAuthority } : {},
|
|
62788
63491
|
...priorRationale ? { priorRationale } : {},
|
|
@@ -62798,32 +63501,53 @@ ${section}`);
|
|
|
62798
63501
|
} : {}
|
|
62799
63502
|
});
|
|
62800
63503
|
}
|
|
62801
|
-
|
|
62802
|
-
|
|
63504
|
+
/**
|
|
63505
|
+
* P46a B1 — set a single task's implementor agent on ITS record (the only
|
|
63506
|
+
* caller is `resumeFromContinuation`, after the user chose a target agent).
|
|
63507
|
+
* Replaces the session-wide `setActiveImplementorAgent` singleton.
|
|
63508
|
+
*/
|
|
63509
|
+
setTaskImplementorAgent(taskId, agent) {
|
|
63510
|
+
let current = this.singleTaskContextByTask.get(taskId);
|
|
63511
|
+
current && this.singleTaskContextByTask.set(taskId, { ...current, agent });
|
|
63512
|
+
let history = this.singleTaskHistory.get(taskId);
|
|
63513
|
+
history && (history.agent = agent);
|
|
62803
63514
|
}
|
|
62804
63515
|
// ─── PHASE-CP-10-MIN (#585) — continuation handoff ────────────────────────
|
|
62805
63516
|
/**
|
|
62806
|
-
* #585 #C10M-9 — resolve
|
|
62807
|
-
* request`
|
|
62808
|
-
*
|
|
62809
|
-
*
|
|
62810
|
-
*
|
|
62811
|
-
*
|
|
63517
|
+
* #585 #C10M-9 / P46a B1 (OQ-1, decided) — resolve WHICH single task a
|
|
63518
|
+
* `/continue request` means: the FOCUSED task if it has an active implementor
|
|
63519
|
+
* round; else the single task that has one; else refuse — `ambiguous` lists
|
|
63520
|
+
* the candidates (the shell renders their `#n` so the user can
|
|
63521
|
+
* `/task focus <n>` first), `none` means no task is running a round. The
|
|
63522
|
+
* `gateId`/`roundNumber` come from the ACTIVE implementor registry (the round
|
|
63523
|
+
* currently running); the brief + source agent from the task's own record.
|
|
62812
63524
|
*/
|
|
62813
|
-
|
|
62814
|
-
let
|
|
62815
|
-
|
|
62816
|
-
|
|
62817
|
-
if (
|
|
62818
|
-
|
|
63525
|
+
resolveContinuationRequest() {
|
|
63526
|
+
let withActiveRound = [...this.singleTaskContextByTask.keys()].filter(
|
|
63527
|
+
(id) => this.activeImplementorByTask.has(id)
|
|
63528
|
+
), taskId = null;
|
|
63529
|
+
if (this.focusedTaskId && this.activeImplementorByTask.has(this.focusedTaskId))
|
|
63530
|
+
taskId = this.focusedTaskId;
|
|
63531
|
+
else if (withActiveRound.length === 1)
|
|
63532
|
+
taskId = withActiveRound[0];
|
|
63533
|
+
else return withActiveRound.length === 0 ? { kind: "none" } : { kind: "ambiguous", candidates: withActiveRound };
|
|
63534
|
+
let active = this.activeImplementorByTask.get(taskId), single = this.singleTaskContextByTask.get(taskId);
|
|
62819
63535
|
return {
|
|
62820
|
-
|
|
62821
|
-
|
|
62822
|
-
|
|
62823
|
-
|
|
62824
|
-
|
|
63536
|
+
kind: "ok",
|
|
63537
|
+
ctx: {
|
|
63538
|
+
taskId,
|
|
63539
|
+
gateId: active.gateId,
|
|
63540
|
+
roundNumber: active.roundNumber,
|
|
63541
|
+
brief: this.activeBriefByTask.get(taskId)?.brief ?? single.brief,
|
|
63542
|
+
sourceAgent: single.agent
|
|
63543
|
+
}
|
|
62825
63544
|
};
|
|
62826
63545
|
}
|
|
63546
|
+
/** The `ok` projection of {@link resolveContinuationRequest} (null otherwise). */
|
|
63547
|
+
getActiveContinuationRequestContext() {
|
|
63548
|
+
let resolved = this.resolveContinuationRequest();
|
|
63549
|
+
return resolved.kind === "ok" ? resolved.ctx : null;
|
|
63550
|
+
}
|
|
62827
63551
|
/**
|
|
62828
63552
|
* #585 L-A — mint a fresh `expiresAt = now + TTL` at millisecond precision
|
|
62829
63553
|
* (RFC-3339). Fresh per request so a CANCEL→re-request produces a distinct
|
|
@@ -63108,9 +63832,7 @@ ${section}`);
|
|
|
63108
63832
|
}
|
|
63109
63833
|
if (trackEntry && teamAuthority && !this.teamEntryMayContinue(input.taskId, trackEntry, teamAuthority) || this.shuttingDown) return !1;
|
|
63110
63834
|
let brief = this.buildResumeBrief(packet);
|
|
63111
|
-
this.
|
|
63112
|
-
let resumedSingle = this.singleTaskContextByTask.get(input.taskId);
|
|
63113
|
-
resumedSingle && this.singleTaskContextByTask.set(input.taskId, { brief: resumedSingle.brief, agent: input.targetAgent }), logger.info("[QuorumLoop] resuming from continuation", {
|
|
63835
|
+
this.setTaskImplementorAgent(input.taskId, input.targetAgent), logger.info("[QuorumLoop] resuming from continuation", {
|
|
63114
63836
|
taskId: input.taskId,
|
|
63115
63837
|
sourceAgent: packet.sourceAgent,
|
|
63116
63838
|
targetAgent: input.targetAgent,
|
|
@@ -63235,7 +63957,7 @@ ${section}`);
|
|
|
63235
63957
|
* user requirement.
|
|
63236
63958
|
*/
|
|
63237
63959
|
buildReviseBrief(taskId, payload) {
|
|
63238
|
-
let originalBrief = this.activeBriefByTask.get(taskId)?.brief ?? this.singleTaskContextByTask.get(taskId)?.brief ??
|
|
63960
|
+
let originalBrief = this.activeBriefByTask.get(taskId)?.brief ?? this.singleTaskContextByTask.get(taskId)?.brief ?? "", userNotes = this.userNotesByTask.get(taskId) ?? [], history = (this.roundHistoryByTask.get(taskId) ?? []).filter(
|
|
63239
63961
|
(r) => r.round < payload.nextRound - 1
|
|
63240
63962
|
);
|
|
63241
63963
|
return composeReviseBrief(originalBrief, userNotes, payload, history);
|
|
@@ -63396,9 +64118,16 @@ ${section}`);
|
|
|
63396
64118
|
get _submittedSeatsForTests() {
|
|
63397
64119
|
return this.submittedSeats;
|
|
63398
64120
|
}
|
|
63399
|
-
/**
|
|
63400
|
-
|
|
63401
|
-
|
|
64121
|
+
/**
|
|
64122
|
+
* @internal — test seam to register a single task (its record + focus)
|
|
64123
|
+
* without start_task. P46a B1: the record is what every reader resolves, so
|
|
64124
|
+
* the seam always creates one (an empty brief when none is given).
|
|
64125
|
+
*/
|
|
64126
|
+
_setActiveTaskForTests(taskId, brief, agent = "CLAUDE", gateIds = []) {
|
|
64127
|
+
this.reviewScopeResetEligibleTasks.add(taskId);
|
|
64128
|
+
let existing = this.singleTaskContextByTask.get(taskId), record = { brief: brief ?? existing?.brief ?? "", agent: existing?.agent ?? agent };
|
|
64129
|
+
this.singleTaskContextByTask.set(taskId, record), this.singleTaskHistory.has(taskId) || this.singleTaskHistory.set(taskId, { ...record, outcome: "running" }), this.confirmedSingleOrder.includes(taskId) || this.confirmedSingleOrder.push(taskId), this.focusedTaskId = taskId;
|
|
64130
|
+
for (let gateId of gateIds) this.taskByGateId.set(gateId, taskId);
|
|
63402
64131
|
}
|
|
63403
64132
|
/** @internal — read a task's accumulated binding user notes. */
|
|
63404
64133
|
_userNotesForTests(taskId) {
|
|
@@ -64247,6 +64976,23 @@ function resolveBrowseUrls(modelUrls, prompt) {
|
|
|
64247
64976
|
let usable = modelUrls.filter(isWellFormedHttpUrl);
|
|
64248
64977
|
return usable.length > 0 ? usable : void 0;
|
|
64249
64978
|
}
|
|
64979
|
+
function numberedLabel(state, taskId) {
|
|
64980
|
+
let label = taskLabel(state, taskId);
|
|
64981
|
+
return label.startsWith("#") ? label : null;
|
|
64982
|
+
}
|
|
64983
|
+
function findActiveContinuationOfferEntry(conversation) {
|
|
64984
|
+
for (let entry of conversation)
|
|
64985
|
+
if (entry.kind === "gate-prompt" && entry.final === !1 && entry.envelope.promptKind === CONTINUATION_OFFER_HANDOFF_PROMPT_KIND)
|
|
64986
|
+
return entry;
|
|
64987
|
+
return null;
|
|
64988
|
+
}
|
|
64989
|
+
function mobileGateReplyAmbiguity(state, text2) {
|
|
64990
|
+
if (!/^\s*\d+\s*$/.test(text2)) return null;
|
|
64991
|
+
let open15 = state.conversation.filter(
|
|
64992
|
+
(e) => e.kind === "gate-prompt" && e.final === !1 && (e.uiState.phase === "awaiting-number" || e.uiState.phase === "awaiting-notes")
|
|
64993
|
+
);
|
|
64994
|
+
return open15.length <= 1 ? null : `Several reviews are open on the desktop (${[...new Set(open15.map((e) => taskLabel(state, e.envelope.taskId)))].map((l) => `task ${l}`).join(", ")}); a number from the phone cannot choose between them yet \u2014 answer on the desktop, or wait until one review is left.`;
|
|
64995
|
+
}
|
|
64250
64996
|
var OrchestrationShellStartupError = class extends Error {
|
|
64251
64997
|
constructor(message, cause) {
|
|
64252
64998
|
super(message), this.name = "OrchestrationShellStartupError", this.cause = cause;
|
|
@@ -64349,11 +65095,21 @@ function wireTeamMergeAuditSummary(store, target) {
|
|
|
64349
65095
|
}
|
|
64350
65096
|
async function runOrchestrationShell(args) {
|
|
64351
65097
|
let store = createOrchestrationStore({ session: args.session }), originalStoreDispatch = store.dispatch.bind(store);
|
|
64352
|
-
store.dispatch = ((action) =>
|
|
64353
|
-
action.envelope.
|
|
64354
|
-
|
|
64355
|
-
|
|
64356
|
-
|
|
65098
|
+
store.dispatch = ((action) => {
|
|
65099
|
+
action && action.type === "GATE_PROMPT_RECEIVED" && action.envelope && args.quorumLoop && typeof args.quorumLoop.registerActiveGate == "function" && args.quorumLoop.registerActiveGate(
|
|
65100
|
+
action.envelope.taskId,
|
|
65101
|
+
action.envelope.gateId,
|
|
65102
|
+
action.envelope.currentRound ?? 0
|
|
65103
|
+
);
|
|
65104
|
+
let gateAlreadyShown = action && action.type === "GATE_PROMPT_RECEIVED" && action.envelope ? store.getState().conversation.some(
|
|
65105
|
+
(e) => e.kind === "gate-prompt" && (e.envelope.gateId === action.envelope.gateId || e.queue.some((queued) => queued.gateId === action.envelope.gateId))
|
|
65106
|
+
) : !0, dispatched = originalStoreDispatch(action);
|
|
65107
|
+
if (action && action.type === "GATE_PROMPT_RECEIVED" && action.envelope && !gateAlreadyShown) {
|
|
65108
|
+
let described = typeof args.quorumLoop?.describeTasks == "function" ? args.quorumLoop.describeTasks() : [], advisory = endedTaskAdvisory(described, action.envelope.taskId);
|
|
65109
|
+
advisory && originalStoreDispatch({ type: "SHELL_ADVISORY", source: "shell", text: advisory, localOnly: !0 });
|
|
65110
|
+
}
|
|
65111
|
+
return dispatched;
|
|
65112
|
+
});
|
|
64357
65113
|
let advisoryAttachmentJournal = advisoryAttachmentJournalFor(args);
|
|
64358
65114
|
try {
|
|
64359
65115
|
await advisoryAttachmentJournal.recoverDeadOwners();
|
|
@@ -64371,7 +65127,23 @@ async function runOrchestrationShell(args) {
|
|
|
64371
65127
|
store.dispatch({ type: "SHELL_ADVISORY", source: "shell", text: message });
|
|
64372
65128
|
}
|
|
64373
65129
|
}) : void 0;
|
|
64374
|
-
args.workspaceOutcomeTap && (args.workspaceOutcomeTap.sink = workspaceTerminalCoordinator), args.quorumLoop?.setWorkspaceOutcomeSink?.(workspaceTerminalCoordinator),
|
|
65130
|
+
args.workspaceOutcomeTap && (args.workspaceOutcomeTap.sink = workspaceTerminalCoordinator), args.quorumLoop?.setWorkspaceOutcomeSink?.(workspaceTerminalCoordinator), args.quorumLoop?.setTaskOrdinalSink?.({
|
|
65131
|
+
assign: (taskId, opts) => (store.dispatch({ type: "TASK_ORDINAL_ASSIGNED", taskId, kind: opts.kind, ...opts.retryOf ? { retryOf: opts.retryOf } : {} }), store.getState().taskOrdinals.get(taskId)?.ordinal),
|
|
65132
|
+
label: (taskId) => taskLabel(store.getState(), taskId)
|
|
65133
|
+
}), args.quorumLoop?.setApplyConflictSink?.((menu) => {
|
|
65134
|
+
store.dispatch({ type: "APPLY_CONFLICT_PRESENTED", menu });
|
|
65135
|
+
}), args.quorumLoop?.setRetryStartedSink?.((info) => {
|
|
65136
|
+
store.dispatch({
|
|
65137
|
+
type: "TASK_LIFECYCLE",
|
|
65138
|
+
task: {
|
|
65139
|
+
taskId: info.taskId,
|
|
65140
|
+
agentKind: info.agent,
|
|
65141
|
+
pid: 0,
|
|
65142
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
65143
|
+
status: "running"
|
|
65144
|
+
}
|
|
65145
|
+
});
|
|
65146
|
+
}), plannerOfferE1Ledger.beginRun(
|
|
64375
65147
|
(promptIds) => args.appsyncClient.refreshOpenPromptTtl(promptIds)
|
|
64376
65148
|
);
|
|
64377
65149
|
let unsubscribeWaitingUser = null;
|
|
@@ -64875,8 +65647,13 @@ async function runOrchestrationShell(args) {
|
|
|
64875
65647
|
if (shellSubmissionsFenced) return;
|
|
64876
65648
|
let fromMobile = options?.fromMobile === !0, convLenBefore = store.getState().conversation.length, userTurnTimestamp = (/* @__PURE__ */ new Date()).toISOString(), turnOwnership = { brainstormPanelOwned: !1 }, ownEntries = [];
|
|
64877
65649
|
await turnAuthoringContext.run({ ownEntries }, async () => {
|
|
64878
|
-
let handledByGate = !1;
|
|
64879
|
-
if (
|
|
65650
|
+
let handledByGate = !1, mobileAmbiguity = fromMobile ? mobileGateReplyAmbiguity(store.getState(), text2) : null;
|
|
65651
|
+
if (mobileAmbiguity ? (store.dispatch({
|
|
65652
|
+
type: "SHELL_ADVISORY",
|
|
65653
|
+
source: "shell",
|
|
65654
|
+
text: mobileAmbiguity,
|
|
65655
|
+
handoffExcluded: !0
|
|
65656
|
+
}), handledByGate = !0) : fromMobile && findActiveGatePromptEntry(store.getState().conversation, store.getState().activeGatePromptId) && (mobileGateDecisionDeps === null ? (store.dispatch({
|
|
64880
65657
|
type: "SHELL_ADVISORY",
|
|
64881
65658
|
source: "shell",
|
|
64882
65659
|
text: "The desktop is still preparing the interactive prompt. Please send your choice again.",
|
|
@@ -65208,9 +65985,13 @@ async function runOrchestrationShell(args) {
|
|
|
65208
65985
|
}
|
|
65209
65986
|
}));
|
|
65210
65987
|
}, onTerminalDecision = args.quorumLoop ? async (resolvedTaskId, postAction, expectedShadow, canonicalDecision, gateResolution) => {
|
|
65211
|
-
|
|
65212
|
-
|
|
65213
|
-
|
|
65988
|
+
if (!resolvedTaskId) {
|
|
65989
|
+
logger.warn("[orchestration-shell] terminal decision without a task id \u2014 no shadow effect applied", {
|
|
65990
|
+
kind: postAction.kind
|
|
65991
|
+
});
|
|
65992
|
+
return;
|
|
65993
|
+
}
|
|
65994
|
+
let taskId = resolvedTaskId, finalApprovalCancelled = postAction.kind === "final_approval_resolved" && postAction.decision.trim().toLowerCase() !== "approve", terminalAbort = postAction.kind === "abort_task";
|
|
65214
65995
|
workspaceTerminalCoordinator?.hasTaskResolution(taskId) && (finalApprovalCancelled || terminalAbort) && await workspaceTerminalCoordinator.reserveTask(
|
|
65215
65996
|
taskId,
|
|
65216
65997
|
finalApprovalCancelled ? "CANCEL" : "ABORT"
|
|
@@ -65476,7 +66257,7 @@ async function runOrchestrationShell(args) {
|
|
|
65476
66257
|
// Codex LOW). Only `handled:false` falls through to the planner.
|
|
65477
66258
|
resolveGateInput: (text2) => routeGatePromptInput(
|
|
65478
66259
|
gateDecisionDeps,
|
|
65479
|
-
findActiveGatePromptEntry(store.getState().conversation),
|
|
66260
|
+
findActiveGatePromptEntry(store.getState().conversation, store.getState().activeGatePromptId),
|
|
65480
66261
|
text2
|
|
65481
66262
|
),
|
|
65482
66263
|
signal: nonTtyAbort.signal
|
|
@@ -66013,11 +66794,11 @@ async function routeMobileUserPrompt(event, sessionKeyResolver, submit, updateEv
|
|
|
66013
66794
|
}
|
|
66014
66795
|
}
|
|
66015
66796
|
async function routeMobileGatePromptInput(deps, text2) {
|
|
66016
|
-
let before = findActiveGatePromptEntry(deps.store.getState().conversation);
|
|
66797
|
+
let before = findActiveGatePromptEntry(deps.store.getState().conversation, deps.store.getState().activeGatePromptId);
|
|
66017
66798
|
if (!before) return !1;
|
|
66018
66799
|
let beforePhase = before.uiState.phase;
|
|
66019
66800
|
if (!(await routeGatePromptInput(deps, before, text2)).handled) return !1;
|
|
66020
|
-
let after = findActiveGatePromptEntry(deps.store.getState().conversation);
|
|
66801
|
+
let after = findActiveGatePromptEntry(deps.store.getState().conversation, deps.store.getState().activeGatePromptId);
|
|
66021
66802
|
return beforePhase === "awaiting-number" && after?.id === before.id && after.uiState.phase === "awaiting-notes" && deps.store.dispatch({
|
|
66022
66803
|
type: "SHELL_ADVISORY",
|
|
66023
66804
|
source: "shell",
|
|
@@ -66196,14 +66977,28 @@ function routeTeamShellEventToStore(store, evt) {
|
|
|
66196
66977
|
}
|
|
66197
66978
|
function parseTaskCommand(text2) {
|
|
66198
66979
|
let rest = text2.trim().slice(5).trim();
|
|
66199
|
-
if (rest.length === 0) return { agent: null, brief: "" };
|
|
66980
|
+
if (rest.length === 0) return { agent: null, brief: "", focus: null, conflictChoice: null };
|
|
66981
|
+
let focusMatch = /^focus\s+#?(\d{1,6})$/i.exec(rest);
|
|
66982
|
+
if (focusMatch)
|
|
66983
|
+
return { agent: null, brief: "", focus: Number.parseInt(focusMatch[1], 10), conflictChoice: null };
|
|
66984
|
+
let conflictMatch = /^(retry|discard)\s+#?(\d{1,6})$/i.exec(rest);
|
|
66985
|
+
if (conflictMatch)
|
|
66986
|
+
return {
|
|
66987
|
+
agent: null,
|
|
66988
|
+
brief: "",
|
|
66989
|
+
focus: null,
|
|
66990
|
+
conflictChoice: {
|
|
66991
|
+
choice: conflictMatch[1].toLowerCase() === "retry" ? 1 : 2,
|
|
66992
|
+
ordinal: Number.parseInt(conflictMatch[2], 10)
|
|
66993
|
+
}
|
|
66994
|
+
};
|
|
66200
66995
|
let tokens = rest.split(/\s+/), agent = null, startIdx = 0;
|
|
66201
66996
|
if (tokens[0] === "--agent") {
|
|
66202
66997
|
let value = (tokens[1] ?? "").toUpperCase();
|
|
66203
66998
|
value === "CLAUDE" || value === "CODEX" || value === "ANTIGRAVITY" ? (agent = value, startIdx = 2) : startIdx = 1;
|
|
66204
66999
|
}
|
|
66205
67000
|
let brief = tokens.slice(startIdx).join(" ").trim();
|
|
66206
|
-
return { agent, brief };
|
|
67001
|
+
return { agent, brief, focus: null, conflictChoice: null };
|
|
66207
67002
|
}
|
|
66208
67003
|
function resolveEnvImplementorAgentOverride() {
|
|
66209
67004
|
let raw = (process.env.CODEVIBE_IMPLEMENTOR_AGENT ?? "").trim().toUpperCase();
|
|
@@ -67000,10 +67795,10 @@ async function launchTeamFromWorkItems(deps) {
|
|
|
67000
67795
|
});
|
|
67001
67796
|
if (result.accepted && result.taskGroupId) {
|
|
67002
67797
|
let gid = result.taskGroupId;
|
|
67003
|
-
store.dispatch({ type: "TEAM_STARTED", taskGroupId: gid }), store.dispatch({
|
|
67798
|
+
store.dispatch({ type: "TEAM_STARTED", taskGroupId: gid }), store.dispatch({ type: "TASK_ORDINAL_ASSIGNED", taskId: gid, kind: "group" }), store.dispatch({
|
|
67004
67799
|
type: "SHELL_ADVISORY",
|
|
67005
67800
|
source: "shell",
|
|
67006
|
-
text: `Agent Teams group
|
|
67801
|
+
text: `Task ${taskLabel(store.getState(), gid)} started \u2014 Agent Teams group (${gid}), ${result.dispatchedTracks ?? workItems.length} tracks dispatched.`
|
|
67007
67802
|
});
|
|
67008
67803
|
try {
|
|
67009
67804
|
createShellEventEmitter(appsyncClient)({
|
|
@@ -67123,10 +67918,10 @@ async function dispatchSynthesizedSingleStartTask(deps) {
|
|
|
67123
67918
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
67124
67919
|
status: "running"
|
|
67125
67920
|
}
|
|
67126
|
-
}),
|
|
67921
|
+
}), store.dispatch({
|
|
67127
67922
|
type: "SHELL_ADVISORY",
|
|
67128
67923
|
source: "shell",
|
|
67129
|
-
text: note
|
|
67924
|
+
text: `Task ${taskLabel(store.getState(), result.taskId)} started with ${selectedAgent}.${note ? ` ${note}` : ""}`
|
|
67130
67925
|
})) : store.dispatch({
|
|
67131
67926
|
type: "SHELL_ADVISORY",
|
|
67132
67927
|
source: "shell",
|
|
@@ -67769,6 +68564,12 @@ function buildContinuationActionDeps(args, store) {
|
|
|
67769
68564
|
let loop = args.quorumLoop;
|
|
67770
68565
|
return loop ? {
|
|
67771
68566
|
getActiveRequestContext: () => loop.getActiveContinuationRequestContext(),
|
|
68567
|
+
// P46a B1 (OQ-1) — when no single task resolves, say why: several tasks are
|
|
68568
|
+
// running a round (list their `#n` so the user can `/task focus <n>`), or none.
|
|
68569
|
+
explainNoActiveRequest: () => {
|
|
68570
|
+
let resolved = loop.resolveContinuationRequest();
|
|
68571
|
+
return resolved.kind === "ambiguous" ? "Several tasks are running \u2014 focus one first with `/task focus <n>`: " + resolved.candidates.map((id) => taskLabel(store.getState(), id)).join(", ") + "." : "No active task to hand off.";
|
|
68572
|
+
},
|
|
67772
68573
|
request: async (ctx, targetAgent) => loop.requestContinuation({
|
|
67773
68574
|
taskId: ctx.taskId,
|
|
67774
68575
|
gateId: ctx.gateId,
|
|
@@ -67779,8 +68580,8 @@ function buildContinuationActionDeps(args, store) {
|
|
|
67779
68580
|
...targetAgent ? { targetAgent } : {}
|
|
67780
68581
|
}),
|
|
67781
68582
|
getActiveOfferContext: () => {
|
|
67782
|
-
let entry =
|
|
67783
|
-
if (!entry
|
|
68583
|
+
let entry = findActiveContinuationOfferEntry(store.getState().conversation);
|
|
68584
|
+
if (!entry)
|
|
67784
68585
|
return null;
|
|
67785
68586
|
let offerId = entry.envelope.offerId;
|
|
67786
68587
|
return typeof offerId != "string" || offerId.length === 0 ? null : {
|
|
@@ -68067,6 +68868,42 @@ function dispatchRefusalAdvisory(store, decision) {
|
|
|
68067
68868
|
function isInteractiveTty() {
|
|
68068
68869
|
return !!process.stdout.isTTY && process.env.CODEVIBE_NO_TUI !== "1";
|
|
68069
68870
|
}
|
|
68871
|
+
async function resolveApplyConflict(deps) {
|
|
68872
|
+
let { store, args, pending, choice, emit } = deps;
|
|
68873
|
+
store.dispatch({ type: "CLEAR_PENDING_APPLY_CONFLICT", taskId: pending.taskId });
|
|
68874
|
+
let echo = `\u2192 ${choice}. ${choice === 1 ? "Retry as a new task" : "Discard"}`;
|
|
68875
|
+
emit === "advisory" && store.dispatch({ type: "SHELL_ADVISORY", source: "shell", text: echo, localOnly: !0 });
|
|
68876
|
+
let result = choice === 1 ? await dispatchApplyConflictRetry({ store, quorumLoop: args.quorumLoop, menu: pending }) : `Discarded task ${pending.taskLabel} \u2014 your files are unchanged.`;
|
|
68877
|
+
return emit === "advisory" && store.dispatch({ type: "SHELL_ADVISORY", source: "shell", text: result }), [result];
|
|
68878
|
+
}
|
|
68879
|
+
async function dispatchApplyConflictRetry(deps) {
|
|
68880
|
+
let { store, quorumLoop, menu } = deps;
|
|
68881
|
+
if (!quorumLoop)
|
|
68882
|
+
return "Task start is unavailable in this session.";
|
|
68883
|
+
try {
|
|
68884
|
+
let result = await quorumLoop.startTask({
|
|
68885
|
+
decision: { action: "start_task", rationale: `retry of task ${menu.taskLabel} after an apply conflict` },
|
|
68886
|
+
brief: menu.retryBrief,
|
|
68887
|
+
agent: menu.agent,
|
|
68888
|
+
retryOf: menu.taskId,
|
|
68889
|
+
request: menu.request
|
|
68890
|
+
});
|
|
68891
|
+
return result ? (store.dispatch({
|
|
68892
|
+
type: "TASK_LIFECYCLE",
|
|
68893
|
+
task: {
|
|
68894
|
+
taskId: result.taskId,
|
|
68895
|
+
agentKind: menu.agent,
|
|
68896
|
+
pid: 0,
|
|
68897
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
68898
|
+
status: "running"
|
|
68899
|
+
}
|
|
68900
|
+
}), `Task ${taskLabel(store.getState(), result.taskId)} started with ${menu.agent} \u2014 the retry of task ${menu.taskLabel}.`) : `Couldn't start the retry of task ${menu.taskLabel} (the task START was rejected). Please re-issue the request.`;
|
|
68901
|
+
} catch (err) {
|
|
68902
|
+
return logger.warn("[orchestration-shell] apply-conflict retry dispatch failed (non-fatal)", {
|
|
68903
|
+
error: err.message
|
|
68904
|
+
}), `Couldn't start the retry of task ${menu.taskLabel}: ${err.message}`;
|
|
68905
|
+
}
|
|
68906
|
+
}
|
|
68070
68907
|
async function resolvePlannerOffer(deps) {
|
|
68071
68908
|
let { store, args, emitShellEventBound, generator, ensureFreshContextStoreFn, offer, choice } = deps;
|
|
68072
68909
|
await plannerOfferE1Ledger.resolveOffer(
|
|
@@ -68220,7 +69057,38 @@ async function handleShellUserInput(deps) {
|
|
|
68220
69057
|
browseSignal = deps.browseSignal
|
|
68221
69058
|
} = deps;
|
|
68222
69059
|
if (text2.trim().length === 0) return;
|
|
68223
|
-
let slashCommandText = text2.trimStart(),
|
|
69060
|
+
let slashCommandText = text2.trimStart(), pendingConflicts = [...store.getState().pendingApplyConflicts.values()];
|
|
69061
|
+
if (pendingConflicts.length > 0 && deps.inputOrigin !== "mobile") {
|
|
69062
|
+
let reply = text2.trim(), openLabels = pendingConflicts.map((m) => `task ${m.taskLabel}`).join(", "), conflictHint = () => {
|
|
69063
|
+
store.dispatch({
|
|
69064
|
+
type: "SHELL_ADVISORY",
|
|
69065
|
+
source: "shell",
|
|
69066
|
+
text: pendingConflicts.length === 1 ? `Reply 1 to retry task ${pendingConflicts[0].taskLabel} as a new task or 2 to discard it (or /task retry <n> / /task discard <n>); other text closes the menu.` : `Several apply-conflict menus are open (${openLabels}) \u2014 answer each with /task retry <n> or /task discard <n>; other text closes them.` + // Stage 1 r3 N2 — a pending suggestion's number is taken by the menus until they close.
|
|
69067
|
+
(store.getState().pendingPlannerOffer ? " The pending suggestion can be answered by number once they are closed." : ""),
|
|
69068
|
+
localOnly: !0
|
|
69069
|
+
});
|
|
69070
|
+
};
|
|
69071
|
+
if (/^[0-9]$/.test(reply)) {
|
|
69072
|
+
let choice = Number.parseInt(reply, 10);
|
|
69073
|
+
if (pendingConflicts.length === 1 && (choice === 1 || choice === 2)) {
|
|
69074
|
+
await resolveApplyConflict({ store, args, pending: pendingConflicts[0], choice, emit: "advisory" });
|
|
69075
|
+
return;
|
|
69076
|
+
}
|
|
69077
|
+
conflictHint();
|
|
69078
|
+
return;
|
|
69079
|
+
}
|
|
69080
|
+
if (/^[0-9]/.test(reply)) {
|
|
69081
|
+
conflictHint();
|
|
69082
|
+
return;
|
|
69083
|
+
}
|
|
69084
|
+
reply.startsWith("/") || (store.dispatch({ type: "CLEAR_PENDING_APPLY_CONFLICT" }), store.dispatch({
|
|
69085
|
+
type: "SHELL_ADVISORY",
|
|
69086
|
+
source: "shell",
|
|
69087
|
+
text: pendingConflicts.length === 1 ? `Closed the apply-conflict menu for task ${pendingConflicts[0].taskLabel} \u2014 no retry was started; re-run the request if you still want that change.` : `Closed the apply-conflict menus for ${openLabels} \u2014 no retry was started; re-run the requests if you still want those changes.`,
|
|
69088
|
+
localOnly: !0
|
|
69089
|
+
}));
|
|
69090
|
+
}
|
|
69091
|
+
let pendingOffer = store.getState().pendingPlannerOffer;
|
|
68224
69092
|
if (pendingOffer) {
|
|
68225
69093
|
let interactionAdvisoryFlags = deps.inputOrigin === "mobile" ? { handoffExcluded: !0 } : { localOnly: !0 }, offerReply = text2.trim(), optionCount = pendingOffer.options.length, offerHint = () => {
|
|
68226
69094
|
store.dispatch({
|
|
@@ -68466,7 +69334,12 @@ async function handleShellUserInput(deps) {
|
|
|
68466
69334
|
kind: "gated",
|
|
68467
69335
|
headline: AUDIT_BROWSER_MAX_HEADLINE,
|
|
68468
69336
|
upgradeHint: AUDIT_BROWSER_UPGRADE_HINT
|
|
68469
|
-
}) : renderSessionTaskList(
|
|
69337
|
+
}) : renderSessionTaskList(
|
|
69338
|
+
store.getState(),
|
|
69339
|
+
// P46a B1 — every live single task (not only "the latest") may
|
|
69340
|
+
// still be inside its `startTask` without a lifecycle row.
|
|
69341
|
+
typeof args.quorumLoop?.knownSingleTasks == "function" ? args.quorumLoop.knownSingleTasks() : args.quorumLoop?.activeTask ? [args.quorumLoop.activeTask] : []
|
|
69342
|
+
);
|
|
68470
69343
|
else
|
|
68471
69344
|
try {
|
|
68472
69345
|
let effectiveSessionId = explicitSessionId || args.session.sessionId, result = runAuditBrowserFn ? await runAuditBrowserFn(taskId, {
|
|
@@ -68555,14 +69428,33 @@ async function handleShellUserInput(deps) {
|
|
|
68555
69428
|
}), output.output = "") : output.output = loaded.message;
|
|
68556
69429
|
}
|
|
68557
69430
|
else if (output.command === "/review-reset" && !output.sideEffect && output.output === "PENDING \u2014 entrypoint arms comprehensive review")
|
|
68558
|
-
|
|
69431
|
+
if (!args.quorumLoop)
|
|
69432
|
+
output.output = "/review-reset requires an orchestration session with a running loop (Pro/Max). It is unavailable in this session.";
|
|
69433
|
+
else {
|
|
69434
|
+
let reset = args.quorumLoop.requestReviewScopeReset();
|
|
69435
|
+
reset.armed ? output.output = `Task ${taskLabel(store.getState(), reset.taskId)}: the next revise round will start a fresh comprehensive review baseline.` : reset.candidates.length > 0 ? output.output = "The focused task has no review round to reset. Tasks that do: " + reset.candidates.map((id) => taskLabel(store.getState(), id)).join(", ") + " \u2014 run `/task focus <n>` first." : output.output = "/review-reset requires an active task. Start a task before requesting a reset.";
|
|
69436
|
+
}
|
|
68559
69437
|
else if (output.command === "/task" && !output.sideEffect && output.output === "PENDING \u2014 entrypoint drives startTask")
|
|
68560
69438
|
if (!args.quorumLoop)
|
|
68561
69439
|
output.output = "/task requires an orchestration session with a running loop (Pro/Max). It is unavailable in this session.";
|
|
68562
69440
|
else {
|
|
68563
69441
|
let parsed = parseTaskCommand(slashCommandText);
|
|
68564
|
-
if (
|
|
68565
|
-
|
|
69442
|
+
if (parsed.conflictChoice !== null) {
|
|
69443
|
+
let { choice, ordinal } = parsed.conflictChoice, open15 = store.getState().pendingApplyConflicts, target = taskIdForOrdinal(store.getState(), ordinal), pending2 = target === null ? void 0 : open15.get(target);
|
|
69444
|
+
if (deps.inputOrigin === "mobile")
|
|
69445
|
+
output.output = "The apply-conflict menu is answered on the desktop (it is not mirrored to the phone yet).";
|
|
69446
|
+
else if (pending2)
|
|
69447
|
+
output.output = (await resolveApplyConflict({ store, args, pending: pending2, choice, emit: "collect" })).join(`
|
|
69448
|
+
`);
|
|
69449
|
+
else {
|
|
69450
|
+
let openLabels = [...open15.values()].map((m) => `task ${m.taskLabel}`).join(", ");
|
|
69451
|
+
output.output = `No apply-conflict menu is open for task #${ordinal}.` + (open15.size > 0 ? ` Open: ${openLabels}.` : "");
|
|
69452
|
+
}
|
|
69453
|
+
} else if (parsed.focus !== null) {
|
|
69454
|
+
let target = taskIdForOrdinal(store.getState(), parsed.focus);
|
|
69455
|
+
target === null ? output.output = `No task #${parsed.focus} in this session. Run /status to list the tasks.` : args.quorumLoop.setFocusedTask(target) ? output.output = `Focused task ${taskLabel(store.getState(), target)}.` : output.output = `Task ${taskLabel(store.getState(), target)} is not a running single task (it has ended, or it is a team group).`;
|
|
69456
|
+
} else if (!parsed.brief)
|
|
69457
|
+
output.output = "Usage: /task [--agent claude|codex|antigravity] <implementation-request> | /task focus <n> | /task retry <n> | /task discard <n>. Pro/Max only; runs without planner classification.";
|
|
68566
69458
|
else {
|
|
68567
69459
|
let detected = typeof args.quorumLoop.getDetectedAgents == "function" ? args.quorumLoop.getDetectedAgents() : [], { agent, note } = selectImplementorAgent(detected, parsed.agent);
|
|
68568
69460
|
try {
|
|
@@ -68582,16 +69474,21 @@ async function handleShellUserInput(deps) {
|
|
|
68582
69474
|
agent,
|
|
68583
69475
|
...turnAttachments.length ? { attachments: turnAttachments } : {}
|
|
68584
69476
|
});
|
|
68585
|
-
result
|
|
68586
|
-
|
|
68587
|
-
|
|
68588
|
-
|
|
68589
|
-
|
|
68590
|
-
|
|
68591
|
-
|
|
68592
|
-
|
|
68593
|
-
|
|
68594
|
-
|
|
69477
|
+
if (result) {
|
|
69478
|
+
store.dispatch({
|
|
69479
|
+
type: "TASK_LIFECYCLE",
|
|
69480
|
+
task: {
|
|
69481
|
+
taskId: result.taskId,
|
|
69482
|
+
agentKind: agent,
|
|
69483
|
+
pid: 0,
|
|
69484
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
69485
|
+
status: "running"
|
|
69486
|
+
}
|
|
69487
|
+
});
|
|
69488
|
+
let startedLabel = taskLabel(store.getState(), result.taskId);
|
|
69489
|
+
output.output = note ? `Started task ${startedLabel} with ${agent}. ${note}` : `Started task ${startedLabel} with ${agent}.`;
|
|
69490
|
+
} else
|
|
69491
|
+
output.output = "Could not start the task (no session key / START rejected). Check that you are signed in and try again.";
|
|
68595
69492
|
} catch (err) {
|
|
68596
69493
|
output.output = `Failed to start task: ${err.message ?? String(err)}`, logger.warn("[orchestration-shell] /task dispatch failed", {
|
|
68597
69494
|
error: err.message
|
|
@@ -68776,7 +69673,10 @@ async function handleShellUserInput(deps) {
|
|
|
68776
69673
|
}, decision, showClassifySpinner = store.getState().progress === null;
|
|
68777
69674
|
showClassifySpinner && store.dispatch({ type: "TASK_PROGRESS", event: { phase: "planner_classifying" } });
|
|
68778
69675
|
try {
|
|
68779
|
-
decision = await dispatchClassify(plannerInput), isLocalPlannerRuntime(args) && decision.action === "
|
|
69676
|
+
decision = await dispatchClassify(plannerInput), isLocalPlannerRuntime(args) && decision.action === "browse" && hasFileMutationIntent(plannerInput.prompt) && (decision = {
|
|
69677
|
+
action: "start_task",
|
|
69678
|
+
rationale: decision.rationale ? `${decision.rationale} (promoted to start_task due to file mutation intent)` : "web request with file mutation intent"
|
|
69679
|
+
}), isLocalPlannerRuntime(args) && decision.action === "start_task" && plannerInput.clarifications.length === 0 && isDestructiveFileRequest(plannerInput.prompt) && (logger.warn(
|
|
68780
69680
|
"[orchestration-shell] P43 backstop: a destructive file request was classified start_task without a confirmation this turn; asking first",
|
|
68781
69681
|
{ rationale: decision.rationale }
|
|
68782
69682
|
), decision = {
|
|
@@ -69048,10 +69948,10 @@ async function handleShellUserInput(deps) {
|
|
|
69048
69948
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
69049
69949
|
status: "running"
|
|
69050
69950
|
}
|
|
69051
|
-
}),
|
|
69951
|
+
}), store.dispatch({
|
|
69052
69952
|
type: "SHELL_ADVISORY",
|
|
69053
69953
|
source: "shell",
|
|
69054
|
-
text: note
|
|
69954
|
+
text: `Task ${taskLabel(store.getState(), result.taskId)} started with ${selectedAgent}.${note ? ` ${note}` : ""}`
|
|
69055
69955
|
})) : store.dispatch({
|
|
69056
69956
|
type: "SHELL_ADVISORY",
|
|
69057
69957
|
source: "shell",
|
|
@@ -69589,17 +70489,20 @@ async function emitWithProvenNoWriteDowngrade(emitter, args) {
|
|
|
69589
70489
|
let result = await emitter(args);
|
|
69590
70490
|
!result.emitted && args.disableWriterAttestation !== !0 && (result.reason === "writer_ineligible" || result.reason === "definitive_rejection") && await emitter({ ...args, disableWriterAttestation: !0 });
|
|
69591
70491
|
}
|
|
69592
|
-
function renderSessionTaskList(state,
|
|
70492
|
+
function renderSessionTaskList(state, liveTaskIds = []) {
|
|
69593
70493
|
let tasks = [...state.sessionTasks.values()].sort(
|
|
69594
70494
|
(a, b) => b.startedAt.localeCompare(a.startedAt)
|
|
69595
|
-
),
|
|
69596
|
-
if (tasks.length === 0 &&
|
|
70495
|
+
), unlisted = liveTaskIds.filter((id) => !state.sessionTasks.has(id));
|
|
70496
|
+
if (tasks.length === 0 && unlisted.length === 0)
|
|
69597
70497
|
return "No tasks have run in this session yet. Once a task runs, `/audit <task-id>` opens its full review history.";
|
|
69598
70498
|
let lines = ["Tasks this session (newest first):"];
|
|
69599
|
-
|
|
70499
|
+
for (let id of [...unlisted].reverse()) {
|
|
70500
|
+
let n = numberedLabel(state, id);
|
|
70501
|
+
lines.push(` ${id} \u2014 task${n ? ` ${n}` : ""} \xB7 running \xB7 starting`);
|
|
70502
|
+
}
|
|
69600
70503
|
for (let t of tasks)
|
|
69601
70504
|
lines.push(
|
|
69602
|
-
` ${t.taskId} \u2014 ${t.agentKind ?? "team"} \xB7 ${t.status} \xB7 started ${t.startedAt}`
|
|
70505
|
+
` ${t.taskId} \u2014 ${numberedLabel(state, t.taskId) ? `${numberedLabel(state, t.taskId)} \xB7 ` : ""}${t.agentKind ?? "team"} \xB7 ${t.status} \xB7 started ${t.startedAt}`
|
|
69603
70506
|
);
|
|
69604
70507
|
return lines.push("Run `/audit <task-id>` to open a task\u2019s full review history."), lines.join(`
|
|
69605
70508
|
`);
|
|
@@ -69615,7 +70518,20 @@ function parseIsoTimestamp(iso) {
|
|
|
69615
70518
|
return Number.isFinite(t) ? t : 0;
|
|
69616
70519
|
}
|
|
69617
70520
|
function buildStatusSummary(state, quorumLoop) {
|
|
69618
|
-
let lines = [];
|
|
70521
|
+
let lines = [], described = typeof quorumLoop?.describeTasks == "function" ? quorumLoop.describeTasks() : [];
|
|
70522
|
+
if (described.length > 0) {
|
|
70523
|
+
let awaiting = /* @__PURE__ */ new Set();
|
|
70524
|
+
for (let entry of state.conversation)
|
|
70525
|
+
entry.kind === "gate-prompt" && !entry.final && awaiting.add(entry.envelope.taskId);
|
|
70526
|
+
for (let taskId of state.pendingApplyConflicts.keys()) awaiting.add(taskId);
|
|
70527
|
+
lines.push("Tasks this session:");
|
|
70528
|
+
for (let t of described) {
|
|
70529
|
+
let stateLabel = t.outcome === "running" ? awaiting.has(t.taskId) ? "awaiting you" : t.implementorActive ? "implementing" : "in review" : t.outcome === "applied" ? "done" : t.outcome === "apply_failed" ? awaiting.has(t.taskId) ? "apply failed \u2014 awaiting you" : "apply failed" : t.outcome === "unresolved" ? "apply failed \xB7 retained" : t.outcome === "rejected" ? "not started" : "discarded", retry = t.retryOf ? ` \xB7 retry of ${taskLabel(state, t.retryOf)}` : "";
|
|
70530
|
+
lines.push(
|
|
70531
|
+
` ${t.label} \xB7 ${t.agent} \xB7 ${stateLabel}${retry} \xB7 ${truncate(sanitizeForTerminal(t.brief.replace(/\s+/g, " ").trim()), 60)}` + (t.focused ? " \u25C0 focused" : "")
|
|
70532
|
+
);
|
|
70533
|
+
}
|
|
70534
|
+
}
|
|
69619
70535
|
if (state.team) {
|
|
69620
70536
|
let t = state.team, indices = [...t.tracks.keys()].sort((a, b) => a - b), n = indices.length, trackParts = indices.map((i) => {
|
|
69621
70537
|
let e = t.tracks.get(i), details = [e.agent, e.taskId ? truncate(e.taskId, 12) : null].filter(
|
|
@@ -69630,21 +70546,27 @@ function buildStatusSummary(state, quorumLoop) {
|
|
|
69630
70546
|
let teamLine = `Agent Teams group${groupRef} \u2014 ${n} ${n === 1 ? "track" : "tracks"} (MergeGate: ${MERGE_GATE_STATUS_LABEL[t.mergeGate]}${mergeElapsedStr})`;
|
|
69631
70547
|
n > 0 && (teamLine += `: ${trackParts.join(", ")}`), teamLine += ".", t.groupResolved ? teamLine += t.outcome === "complete" ? " Team complete." : ` Team halted \u2014 ${t.outcome ?? t.haltReason ?? "unknown"}.` : t.haltReason && (teamLine += ` Halted: ${t.haltReason}.`), lines.push(teamLine);
|
|
69632
70548
|
}
|
|
69633
|
-
let hasRunning = hasRunningSingleTask(state),
|
|
70549
|
+
let hasRunning = hasRunningSingleTask(state), startingTasks = (typeof quorumLoop?.knownSingleTasks == "function" ? quorumLoop.knownSingleTasks() : quorumLoop?.activeTask ? [quorumLoop.activeTask] : []).filter((id) => !state.sessionTasks.has(id)), activeTask = startingTasks.length > 0 ? startingTasks[startingTasks.length - 1] : null, teamActive = state.team !== null && !state.team.groupResolved && state.team.haltReason === null, taskStarting = !hasRunning && !teamActive && activeTask !== null;
|
|
69634
70550
|
if (state.progress) {
|
|
69635
70551
|
let startedAt = state.progress.startedAt, elapsed = !startedAt || isNaN(Date.parse(startedAt)) ? null : formatElapsed(Date.now() - Date.parse(startedAt)), tokens = state.progress.tokens, tokenStr = tokens && tokens > 0 ? ` \xB7 \u2193 ${formatTokens(tokens)}` : "";
|
|
69636
70552
|
lines.push(
|
|
69637
70553
|
`Current task: ${state.progress.text}${elapsed ? ` \xB7 ${elapsed}` : ""}${tokenStr}.`
|
|
69638
70554
|
);
|
|
69639
|
-
} else hasRunning ? lines.push("A task is running.") : taskStarting && lines.push(
|
|
70555
|
+
} else hasRunning ? lines.push("A task is running.") : taskStarting && lines.push(
|
|
70556
|
+
`A task is starting (${startingTasks.map((id) => numberedLabel(state, id) ? `${numberedLabel(state, id)} ${id}` : id).join(", ")}).`
|
|
70557
|
+
);
|
|
69640
70558
|
!state.progress && state.lastReviewerProgress && lines.push(`Reviewers: ${state.lastReviewerProgress.text}.`);
|
|
69641
70559
|
for (let entry of state.conversation) {
|
|
69642
70560
|
if (entry.kind !== "gate-prompt" || entry.final) continue;
|
|
69643
70561
|
let label = GATE_PROMPT_KIND_LABEL[entry.envelope.promptKind] ?? entry.envelope.promptKind, queued = entry.queue.length > 0 ? `; ${entry.queue.length} more queued` : "";
|
|
69644
70562
|
lines.push(
|
|
69645
|
-
`Awaiting your decision: ${label} for task ${entry.envelope.taskId} \u2014 reply with an option number (1-${entry.envelope.options.length})${queued}.`
|
|
70563
|
+
`Awaiting your decision: ${label} for task ${numberedLabel(state, entry.envelope.taskId) ? `${numberedLabel(state, entry.envelope.taskId)} (${entry.envelope.taskId})` : entry.envelope.taskId} \u2014 reply with an option number (1-${entry.envelope.options.length})${queued}.`
|
|
69646
70564
|
);
|
|
69647
70565
|
}
|
|
70566
|
+
for (let menu of state.pendingApplyConflicts.values())
|
|
70567
|
+
lines.push(
|
|
70568
|
+
`Awaiting your decision: task ${menu.taskLabel} could not be applied \u2014 ` + (state.pendingApplyConflicts.size === 1 ? "reply 1 to retry as a new task or 2 to discard (or /task retry|discard <n>)." : `/task retry ${menu.taskLabel.replace(/^#/, "").split(" ")[0]} or /task discard ${menu.taskLabel.replace(/^#/, "").split(" ")[0]}.`)
|
|
70569
|
+
);
|
|
69648
70570
|
let rawOutcome = quorumLoop?.getLastWorkspaceOutcome() ?? null, outcome = null;
|
|
69649
70571
|
if (rawOutcome !== null && !teamActive)
|
|
69650
70572
|
if (rawOutcome.taskId) {
|
|
@@ -72365,6 +73287,14 @@ async function assertAuditOutsideWorkdir(auditPath, workdir) {
|
|
|
72365
73287
|
return resolvedAuditPath;
|
|
72366
73288
|
}
|
|
72367
73289
|
async function engageSubstrate(input) {
|
|
73290
|
+
if (input.webBrowsingActive)
|
|
73291
|
+
return {
|
|
73292
|
+
mode: "reduced_trust",
|
|
73293
|
+
reducedTrust: !0,
|
|
73294
|
+
reducedTrustReason: `Agent web browsing active for ${input.agentKind} \u2014 running in reduced-trust mode with shadow-diff capture and guarded web tools`,
|
|
73295
|
+
teardown: async () => {
|
|
73296
|
+
}
|
|
73297
|
+
};
|
|
72368
73298
|
let provider = providerForAgent(input.agentKind);
|
|
72369
73299
|
if (provider === null)
|
|
72370
73300
|
return input.requireConfined ? engageWriteConfinedResolverSandbox(input) : {
|
|
@@ -74831,14 +75761,10 @@ function buildAndArmQuorumLoop(args) {
|
|
|
74831
75761
|
// manifest BEFORE the tree mutates (the C1 capture is already gated on
|
|
74832
75762
|
// `this.deps.durableStore` inside `promoteShadowLocked`).
|
|
74833
75763
|
...args.durableStore ? { durableStore: args.durableStore } : {},
|
|
74834
|
-
//
|
|
74835
|
-
//
|
|
74836
|
-
//
|
|
74837
|
-
//
|
|
74838
|
-
// rejection by the loop. This is the SAME `taskId` state TaskAuthorized later
|
|
74839
|
-
// advances; arming it early closes the round-0 window where the implementor
|
|
74840
|
-
// would otherwise spawn unbadged/unsandboxed.
|
|
74841
|
-
armLeTaskId: (taskId) => localExecutor.setActiveTaskId(taskId),
|
|
75764
|
+
// P46a B2 — the executor keeps no singleton task id any more (the CP-7
|
|
75765
|
+
// `armLeTaskId` arming is gone): every implementor spawn carries its own
|
|
75766
|
+
// `taskId`, which engages the CP-7 substrate and resolves the per-task
|
|
75767
|
+
// authority scope, single tasks included.
|
|
74842
75768
|
// CP-7 §8 (Stage-1-resolved) — wire the REVIEWER substrate engager so each
|
|
74843
75769
|
// Trusted-agent reviewer seat runs INSIDE the CP-7 sandbox + broker (no
|
|
74844
75770
|
// ambient vendor creds; model call via the loopback broker), exactly like
|
|
@@ -74904,6 +75830,7 @@ async function buildQuorumLoopExecutor(args) {
|
|
|
74904
75830
|
userContextFn: async () => ""
|
|
74905
75831
|
}, localExecutor = new LocalExecutorImpl({
|
|
74906
75832
|
sessionId: session.sessionId,
|
|
75833
|
+
logger,
|
|
74907
75834
|
// CP-1.f Hybrid authority split (mirrors the team LE) — the DESKTOP seeds
|
|
74908
75835
|
// the implementor command allowlist so `enforceCommand` permits the spawn;
|
|
74909
75836
|
// write/read/network stay empty (the engine authorizes PATH scopes via a
|
|
@@ -75145,6 +76072,7 @@ async function buildTeamLocalExecutor(args) {
|
|
|
75145
76072
|
userContextFn: async () => ""
|
|
75146
76073
|
}, localExecutor = new LocalExecutorImpl({
|
|
75147
76074
|
sessionId: session.sessionId,
|
|
76075
|
+
logger,
|
|
75148
76076
|
initialScope,
|
|
75149
76077
|
baseCtx: {
|
|
75150
76078
|
sessionId: session.sessionId,
|