opencode-matrixx 2.6.1 → 2.6.2
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/README.md +12 -10
- package/dist/cli.js +40 -0
- package/dist/config/schema/context-mode.d.ts +7 -0
- package/dist/config/schema/hooks.d.ts +1 -0
- package/dist/config/schema/matrixx-config.d.ts +6 -0
- package/dist/config/schema.d.ts +1 -0
- package/dist/create-hooks.d.ts +1 -0
- package/dist/hooks/context-mode-enforcer/constants.d.ts +6 -0
- package/dist/hooks/context-mode-enforcer/hook.d.ts +3 -0
- package/dist/hooks/context-mode-enforcer/index.d.ts +1 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/task-continuation-enforcer/countdown.test.d.ts +1 -0
- package/dist/hooks/task-continuation-enforcer/idle-event.test.d.ts +1 -0
- package/dist/hooks/todo-continuation-enforcer/countdown.test.d.ts +1 -0
- package/dist/hooks/todo-continuation-enforcer/idle-event.test.d.ts +1 -0
- package/dist/index.js +295 -199
- package/dist/matrixx.schema.json +31 -0
- package/dist/plugin/hooks/create-core-hooks.d.ts +1 -0
- package/dist/plugin/hooks/create-tool-guard-hooks.d.ts +2 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4551,7 +4551,7 @@ var require_picomatch2 = __commonJS(function(exports, module) {
|
|
|
4551
4551
|
});
|
|
4552
4552
|
|
|
4553
4553
|
// src/hooks/matrix-loop/constants.ts
|
|
4554
|
-
var
|
|
4554
|
+
var HOOK_NAME6 = "matrix-loop", DEFAULT_STATE_FILE = ".matrixx/matrix-loop.local.md", DEFAULT_MAX_ITERATIONS = 100, DEFAULT_COMPLETION_PROMISE = "DONE", DEFAULT_VERIFICATION_AGENT = "merovingian", DEFAULT_VERIFICATION_TIMEOUT_MS = 30000, DEFAULT_VERIFICATION_MAX_RETRIES = 2;
|
|
4555
4555
|
|
|
4556
4556
|
// src/hooks/matrix-loop/storage.ts
|
|
4557
4557
|
var exports_storage = {};
|
|
@@ -55689,6 +55689,75 @@ function createCompactionTodoPreserverHook(ctx) {
|
|
|
55689
55689
|
};
|
|
55690
55690
|
return { capture, event };
|
|
55691
55691
|
}
|
|
55692
|
+
// src/hooks/context-mode-enforcer/constants.ts
|
|
55693
|
+
var HOOK_NAME3 = "context-mode-enforcer";
|
|
55694
|
+
var WARN_MESSAGE_READ = "Use ctx_search / ctx_batch_execute / ctx_execute for analysis \u2014 raw read/grep/glob bypasses FTS5 sandbox and pollutes context. Use Read only with hashline IDs for Edit.";
|
|
55695
|
+
var BLOCK_MESSAGE_GREP_GLOB = "Blocked: raw grep/glob is forbidden when context-mode is enforced. Use ctx_search (indexed) \u2192 grep/glob fallback only when ctx_* unavailable, or ctx_batch_execute / ctx_execute for multi-file analysis.";
|
|
55696
|
+
var WARN_MESSAGE_BASH_READ = "Use ctx_* / Read tool instead of `cat`/`head`/`tail`/`grep` via bash for analysis. Raw bash file reads bypass sandbox.";
|
|
55697
|
+
|
|
55698
|
+
// src/hooks/context-mode-enforcer/hook.ts
|
|
55699
|
+
var BASH_FILE_READ_RE = /^\s*(cat|head|tail)\s+/;
|
|
55700
|
+
var BASH_GREP_RE = /\bgrep\b/;
|
|
55701
|
+
function resolveContextModeConfig(pluginConfig) {
|
|
55702
|
+
const cfg = pluginConfig.context_mode;
|
|
55703
|
+
return {
|
|
55704
|
+
enabled: cfg?.enabled ?? true,
|
|
55705
|
+
enforce: cfg?.enforce ?? false,
|
|
55706
|
+
blockedTools: new Set((cfg?.blocked_tools ?? ["grep", "glob"]).map((t) => t.toLowerCase()))
|
|
55707
|
+
};
|
|
55708
|
+
}
|
|
55709
|
+
function createContextModeEnforcerHook(pluginConfig) {
|
|
55710
|
+
const { enabled, enforce, blockedTools } = resolveContextModeConfig(pluginConfig);
|
|
55711
|
+
return {
|
|
55712
|
+
"tool.execute.before": async (input, output) => {
|
|
55713
|
+
if (!enabled)
|
|
55714
|
+
return;
|
|
55715
|
+
const tool = input.tool.toLowerCase();
|
|
55716
|
+
const isBlocked = blockedTools.has(tool);
|
|
55717
|
+
const isRead = tool === "read";
|
|
55718
|
+
const isGrepGlob = tool === "grep" || tool === "glob";
|
|
55719
|
+
const isBash = tool === "bash";
|
|
55720
|
+
if (isBash) {
|
|
55721
|
+
const command = output.args.command;
|
|
55722
|
+
if (typeof command !== "string")
|
|
55723
|
+
return;
|
|
55724
|
+
const isFileRead = BASH_FILE_READ_RE.test(command);
|
|
55725
|
+
const isGrep = BASH_GREP_RE.test(command);
|
|
55726
|
+
if (!isFileRead && !isGrep)
|
|
55727
|
+
return;
|
|
55728
|
+
if (enforce && blockedTools.has("bash")) {
|
|
55729
|
+
log(`[${HOOK_NAME3}] BLOCKED bash file read`, {
|
|
55730
|
+
sessionID: input.sessionID,
|
|
55731
|
+
command: command.slice(0, 120)
|
|
55732
|
+
});
|
|
55733
|
+
throw new Error(`${BLOCK_MESSAGE_GREP_GLOB}
|
|
55734
|
+
|
|
55735
|
+
Bash command: ${command.slice(0, 200)}`);
|
|
55736
|
+
}
|
|
55737
|
+
output.message = WARN_MESSAGE_BASH_READ;
|
|
55738
|
+
log(`[${HOOK_NAME3}] warned on bash`, { sessionID: input.sessionID, command: command.slice(0, 120) });
|
|
55739
|
+
return;
|
|
55740
|
+
}
|
|
55741
|
+
if (isRead) {
|
|
55742
|
+
if (!isBlocked)
|
|
55743
|
+
return;
|
|
55744
|
+
output.message = WARN_MESSAGE_READ;
|
|
55745
|
+
log(`[${HOOK_NAME3}] warned on read (soft)`, { sessionID: input.sessionID });
|
|
55746
|
+
return;
|
|
55747
|
+
}
|
|
55748
|
+
if (isGrepGlob) {
|
|
55749
|
+
if (!isBlocked)
|
|
55750
|
+
return;
|
|
55751
|
+
if (enforce) {
|
|
55752
|
+
log(`[${HOOK_NAME3}] BLOCKED ${tool}`, { sessionID: input.sessionID });
|
|
55753
|
+
throw new Error(BLOCK_MESSAGE_GREP_GLOB);
|
|
55754
|
+
}
|
|
55755
|
+
output.message = WARN_MESSAGE_READ;
|
|
55756
|
+
log(`[${HOOK_NAME3}] warned on ${tool} (soft)`, { sessionID: input.sessionID });
|
|
55757
|
+
}
|
|
55758
|
+
}
|
|
55759
|
+
};
|
|
55760
|
+
}
|
|
55692
55761
|
// src/hooks/context-window-monitor.ts
|
|
55693
55762
|
var ANTHROPIC_DISPLAY_LIMIT = 1e6;
|
|
55694
55763
|
var ANTHROPIC_ACTUAL_LIMIT2 = process.env.ANTHROPIC_1M_CONTEXT === "true" || process.env.VERTEX_ANTHROPIC_1M_CONTEXT === "true" ? 1e6 : 200000;
|
|
@@ -55881,7 +55950,7 @@ ${guidance}`;
|
|
|
55881
55950
|
import { join as join35 } from "path";
|
|
55882
55951
|
|
|
55883
55952
|
// src/hooks/design-intent-preserver/constants.ts
|
|
55884
|
-
var
|
|
55953
|
+
var HOOK_NAME4 = "design-intent-preserver";
|
|
55885
55954
|
var PLANS_DIR2 = ".matrixx/plans";
|
|
55886
55955
|
var PHASE_TRANSITION_PATTERN = /\bphase[\s\-:]*(\d+)\b/i;
|
|
55887
55956
|
var CONSTRUCT_INDICATORS = /\b(?:construct|web[\s-]?designer|frontend[\s-]?ui[\s-]?ux)\b|@designer/i;
|
|
@@ -55968,7 +56037,7 @@ function createDesignIntentPreserverHook(_ctx) {
|
|
|
55968
56037
|
---
|
|
55969
56038
|
|
|
55970
56039
|
${originalText}`;
|
|
55971
|
-
log(`[${
|
|
56040
|
+
log(`[${HOOK_NAME4}] injected design intent guardrail`, {
|
|
55972
56041
|
sessionID: input.sessionID,
|
|
55973
56042
|
planPath
|
|
55974
56043
|
});
|
|
@@ -59515,18 +59584,18 @@ function extractPromptText3(parts) {
|
|
|
59515
59584
|
}
|
|
59516
59585
|
// src/features/ultrawork-state/manager.ts
|
|
59517
59586
|
init_logger();
|
|
59518
|
-
var
|
|
59587
|
+
var HOOK_NAME5 = "ultrawork-state";
|
|
59519
59588
|
var sessionStates = new Map;
|
|
59520
59589
|
function getUltraworkState(sessionID) {
|
|
59521
59590
|
return sessionStates.get(sessionID);
|
|
59522
59591
|
}
|
|
59523
59592
|
function enableUltrawork(sessionID) {
|
|
59524
59593
|
sessionStates.set(sessionID, "enabled");
|
|
59525
|
-
log(`[${
|
|
59594
|
+
log(`[${HOOK_NAME5}] Ultrawork enabled for session`, { sessionID });
|
|
59526
59595
|
}
|
|
59527
59596
|
function disableUltrawork(sessionID) {
|
|
59528
59597
|
sessionStates.set(sessionID, "disabled");
|
|
59529
|
-
log(`[${
|
|
59598
|
+
log(`[${HOOK_NAME5}] Ultrawork disabled for session`, { sessionID });
|
|
59530
59599
|
}
|
|
59531
59600
|
// src/hooks/keyword-detector/hook.ts
|
|
59532
59601
|
function createKeywordDetectorHook(ctx, _collector) {
|
|
@@ -59659,7 +59728,7 @@ function createLoopStateController(options) {
|
|
|
59659
59728
|
};
|
|
59660
59729
|
const success2 = writeState(directory, state3, stateDir);
|
|
59661
59730
|
if (success2) {
|
|
59662
|
-
log(`[${
|
|
59731
|
+
log(`[${HOOK_NAME6}] Loop started`, {
|
|
59663
59732
|
sessionID,
|
|
59664
59733
|
maxIterations: state3.max_iterations,
|
|
59665
59734
|
completionPromise: state3.completion_promise
|
|
@@ -59674,7 +59743,7 @@ function createLoopStateController(options) {
|
|
|
59674
59743
|
}
|
|
59675
59744
|
const success2 = clearState(directory, stateDir);
|
|
59676
59745
|
if (success2) {
|
|
59677
|
-
log(`[${
|
|
59746
|
+
log(`[${HOOK_NAME6}] Loop cancelled`, { sessionID, iteration: state3.iteration });
|
|
59678
59747
|
}
|
|
59679
59748
|
return success2;
|
|
59680
59749
|
},
|
|
@@ -59751,7 +59820,7 @@ async function detectCompletionInSessionMessages(ctx, options) {
|
|
|
59751
59820
|
return false;
|
|
59752
59821
|
} catch (err) {
|
|
59753
59822
|
setTimeout(() => {
|
|
59754
|
-
log(`[${
|
|
59823
|
+
log(`[${HOOK_NAME6}] Session messages check failed`, {
|
|
59755
59824
|
sessionID: options.sessionID,
|
|
59756
59825
|
error: String(err)
|
|
59757
59826
|
});
|
|
@@ -59832,7 +59901,7 @@ async function verifyCompletion(ctx, options) {
|
|
|
59832
59901
|
query: { directory: options.directory }
|
|
59833
59902
|
});
|
|
59834
59903
|
if (createResult.error || !createResult.data?.id) {
|
|
59835
|
-
log(`[${
|
|
59904
|
+
log(`[${HOOK_NAME6}] Verification session creation failed`);
|
|
59836
59905
|
return { verified: true };
|
|
59837
59906
|
}
|
|
59838
59907
|
verifySessionID = createResult.data.id;
|
|
@@ -59862,16 +59931,16 @@ async function verifyCompletion(ctx, options) {
|
|
|
59862
59931
|
return parseVerificationResponse(assistantText);
|
|
59863
59932
|
}
|
|
59864
59933
|
} catch {
|
|
59865
|
-
log(`[${
|
|
59934
|
+
log(`[${HOOK_NAME6}] Verification poll error`);
|
|
59866
59935
|
}
|
|
59867
59936
|
await new Promise((resolve5) => {
|
|
59868
59937
|
setTimeout(resolve5, pollIntervalMs);
|
|
59869
59938
|
});
|
|
59870
59939
|
}
|
|
59871
|
-
log(`[${
|
|
59940
|
+
log(`[${HOOK_NAME6}] Verification timed out, assuming pass`);
|
|
59872
59941
|
return { verified: true };
|
|
59873
59942
|
} catch (err) {
|
|
59874
|
-
log(`[${
|
|
59943
|
+
log(`[${HOOK_NAME6}] Verification error: ${String(err)}`);
|
|
59875
59944
|
return { verified: true };
|
|
59876
59945
|
} finally {
|
|
59877
59946
|
if (verifySessionID) {
|
|
@@ -59947,7 +60016,7 @@ async function injectContinuationPrompt(ctx, options) {
|
|
|
59947
60016
|
|
|
59948
60017
|
// src/hooks/matrix-loop/matrix-loop-event-handler.ts
|
|
59949
60018
|
async function handleVerificationFailure(ctx, options, state3, sessionID, reason, failedCount) {
|
|
59950
|
-
log(`[${
|
|
60019
|
+
log(`[${HOOK_NAME6}] Verification failed`, { sessionID, reason });
|
|
59951
60020
|
const newState = options.loopState.incrementIteration();
|
|
59952
60021
|
if (newState) {
|
|
59953
60022
|
newState.verification_failed_count = failedCount + 1;
|
|
@@ -59965,7 +60034,7 @@ ${buildContinuationPrompt(newState ?? state3)}`,
|
|
|
59965
60034
|
apiTimeoutMs: options.apiTimeoutMs
|
|
59966
60035
|
});
|
|
59967
60036
|
} catch (err) {
|
|
59968
|
-
log(`[${
|
|
60037
|
+
log(`[${HOOK_NAME6}] Failed to inject verification feedback`, { sessionID, error: String(err) });
|
|
59969
60038
|
}
|
|
59970
60039
|
}
|
|
59971
60040
|
function createMatrixLoopEventHandler(ctx, options) {
|
|
@@ -59977,13 +60046,13 @@ function createMatrixLoopEventHandler(ctx, options) {
|
|
|
59977
60046
|
if (!sessionID)
|
|
59978
60047
|
return;
|
|
59979
60048
|
if (inFlightSessions.has(sessionID)) {
|
|
59980
|
-
log(`[${
|
|
60049
|
+
log(`[${HOOK_NAME6}] Skipped: handler in flight`, { sessionID });
|
|
59981
60050
|
return;
|
|
59982
60051
|
}
|
|
59983
60052
|
inFlightSessions.add(sessionID);
|
|
59984
60053
|
try {
|
|
59985
60054
|
if (options.sessionRecovery.isRecovering(sessionID)) {
|
|
59986
|
-
log(`[${
|
|
60055
|
+
log(`[${HOOK_NAME6}] Skipped: in recovery`, { sessionID });
|
|
59987
60056
|
return;
|
|
59988
60057
|
}
|
|
59989
60058
|
const state3 = options.loopState.getState();
|
|
@@ -59995,11 +60064,11 @@ function createMatrixLoopEventHandler(ctx, options) {
|
|
|
59995
60064
|
const exists = await options.checkSessionExists(state3.session_id);
|
|
59996
60065
|
if (!exists) {
|
|
59997
60066
|
options.loopState.clear();
|
|
59998
|
-
log(`[${
|
|
60067
|
+
log(`[${HOOK_NAME6}] Cleared orphaned state from deleted session`, { orphanedSessionId: state3.session_id, currentSessionId: sessionID });
|
|
59999
60068
|
return;
|
|
60000
60069
|
}
|
|
60001
60070
|
} catch (err) {
|
|
60002
|
-
log(`[${
|
|
60071
|
+
log(`[${HOOK_NAME6}] Failed to check session existence`, { sessionId: state3.session_id, error: String(err) });
|
|
60003
60072
|
}
|
|
60004
60073
|
}
|
|
60005
60074
|
return;
|
|
@@ -60018,7 +60087,7 @@ function createMatrixLoopEventHandler(ctx, options) {
|
|
|
60018
60087
|
messages = Array.isArray(messagesResponse) ? messagesResponse : Array.isArray(responseData) ? responseData : [];
|
|
60019
60088
|
} catch (err) {
|
|
60020
60089
|
setTimeout(() => {
|
|
60021
|
-
log(`[${
|
|
60090
|
+
log(`[${HOOK_NAME6}] Session messages fetch failed`, { sessionID, error: String(err) });
|
|
60022
60091
|
}, 0);
|
|
60023
60092
|
messages = [];
|
|
60024
60093
|
}
|
|
@@ -60031,7 +60100,7 @@ function createMatrixLoopEventHandler(ctx, options) {
|
|
|
60031
60100
|
preFetchedMessages: messages
|
|
60032
60101
|
});
|
|
60033
60102
|
if (completionViaTranscript || completionViaApi) {
|
|
60034
|
-
log(`[${
|
|
60103
|
+
log(`[${HOOK_NAME6}] Completion detected!`, { sessionID, iteration: state3.iteration, promise: state3.completion_promise, detectedVia: completionViaTranscript ? "transcript_file" : "session_messages_api" });
|
|
60035
60104
|
if (options.verification?.enabled === true) {
|
|
60036
60105
|
const maxRetries = options.verification?.maxRetries ?? DEFAULT_VERIFICATION_MAX_RETRIES;
|
|
60037
60106
|
const failedCount = state3.verification_failed_count ?? 0;
|
|
@@ -60066,7 +60135,7 @@ function createMatrixLoopEventHandler(ctx, options) {
|
|
|
60066
60135
|
return;
|
|
60067
60136
|
}
|
|
60068
60137
|
if (state3.iteration >= state3.max_iterations) {
|
|
60069
|
-
log(`[${
|
|
60138
|
+
log(`[${HOOK_NAME6}] Max iterations reached`, { sessionID, iteration: state3.iteration, max: state3.max_iterations });
|
|
60070
60139
|
options.loopState.clear();
|
|
60071
60140
|
await ctx.client.tui.showToast({ body: { title: "Matrix Loop Stopped", message: `Max iterations (${state3.max_iterations}) reached without completion`, variant: "warning", duration: 5000 } }).catch((err) => {
|
|
60072
60141
|
log("[matrix-loop] Max iterations toast failed:", err);
|
|
@@ -60075,17 +60144,17 @@ function createMatrixLoopEventHandler(ctx, options) {
|
|
|
60075
60144
|
}
|
|
60076
60145
|
const newState = options.loopState.incrementIteration();
|
|
60077
60146
|
if (!newState) {
|
|
60078
|
-
log(`[${
|
|
60147
|
+
log(`[${HOOK_NAME6}] Failed to increment iteration`, { sessionID });
|
|
60079
60148
|
return;
|
|
60080
60149
|
}
|
|
60081
|
-
log(`[${
|
|
60150
|
+
log(`[${HOOK_NAME6}] Continuing loop`, { sessionID, iteration: newState.iteration, max: newState.max_iterations });
|
|
60082
60151
|
await ctx.client.tui.showToast({ body: { title: "Matrix Loop", message: `Iteration ${newState.iteration}/${newState.max_iterations}`, variant: "info", duration: 2000 } }).catch((err) => {
|
|
60083
60152
|
log("[matrix-loop] Iteration toast failed:", err);
|
|
60084
60153
|
});
|
|
60085
60154
|
try {
|
|
60086
60155
|
await injectContinuationPrompt(ctx, { sessionID, prompt: buildContinuationPrompt(newState), directory: options.directory, apiTimeoutMs: options.apiTimeoutMs });
|
|
60087
60156
|
} catch (err) {
|
|
60088
|
-
log(`[${
|
|
60157
|
+
log(`[${HOOK_NAME6}] Failed to inject continuation`, { sessionID, error: String(err) });
|
|
60089
60158
|
}
|
|
60090
60159
|
return;
|
|
60091
60160
|
} finally {
|
|
@@ -60099,7 +60168,7 @@ function createMatrixLoopEventHandler(ctx, options) {
|
|
|
60099
60168
|
const state3 = options.loopState.getState();
|
|
60100
60169
|
if (state3?.session_id === sessionInfo.id) {
|
|
60101
60170
|
options.loopState.clear();
|
|
60102
|
-
log(`[${
|
|
60171
|
+
log(`[${HOOK_NAME6}] Session deleted, loop cleared`, { sessionID: sessionInfo.id });
|
|
60103
60172
|
}
|
|
60104
60173
|
options.sessionRecovery.clear(sessionInfo.id);
|
|
60105
60174
|
return;
|
|
@@ -60112,7 +60181,7 @@ function createMatrixLoopEventHandler(ctx, options) {
|
|
|
60112
60181
|
const state3 = options.loopState.getState();
|
|
60113
60182
|
if (state3?.session_id === sessionID) {
|
|
60114
60183
|
options.loopState.clear();
|
|
60115
|
-
log(`[${
|
|
60184
|
+
log(`[${HOOK_NAME6}] User aborted, loop cleared`, { sessionID });
|
|
60116
60185
|
}
|
|
60117
60186
|
options.sessionRecovery.clear(sessionID);
|
|
60118
60187
|
}
|
|
@@ -60205,7 +60274,7 @@ function createMcpStartupNotificationHook(ctx) {
|
|
|
60205
60274
|
};
|
|
60206
60275
|
}
|
|
60207
60276
|
// src/hooks/mouse-notepad/constants.ts
|
|
60208
|
-
var
|
|
60277
|
+
var HOOK_NAME7 = "mouse-notepad";
|
|
60209
60278
|
var NOTEPAD_DIRECTIVE = `
|
|
60210
60279
|
<Work_Context>
|
|
60211
60280
|
## Notepad Location (for recording learnings)
|
|
@@ -60252,14 +60321,14 @@ function createMouseNotepadHook(ctx) {
|
|
|
60252
60321
|
return;
|
|
60253
60322
|
}
|
|
60254
60323
|
output.args.prompt = NOTEPAD_DIRECTIVE + prompt;
|
|
60255
|
-
log(`[${
|
|
60324
|
+
log(`[${HOOK_NAME7}] Injected notepad directive to task`, {
|
|
60256
60325
|
sessionID: input.sessionID
|
|
60257
60326
|
});
|
|
60258
60327
|
}
|
|
60259
60328
|
};
|
|
60260
60329
|
}
|
|
60261
60330
|
// src/hooks/non-interactive-env/constants.ts
|
|
60262
|
-
var
|
|
60331
|
+
var HOOK_NAME8 = "non-interactive-env";
|
|
60263
60332
|
var NON_INTERACTIVE_ENV = {
|
|
60264
60333
|
CI: "true",
|
|
60265
60334
|
DEBIAN_FRONTEND: "noninteractive",
|
|
@@ -60350,7 +60419,7 @@ function createNonInteractiveEnvHook(_ctx) {
|
|
|
60350
60419
|
}
|
|
60351
60420
|
const envPrefix = buildEnvPrefix(NON_INTERACTIVE_ENV, "unix");
|
|
60352
60421
|
output.args.command = `${envPrefix} ${command}`;
|
|
60353
|
-
log(`[${
|
|
60422
|
+
log(`[${HOOK_NAME8}] Prepended non-interactive env vars to git command`, {
|
|
60354
60423
|
sessionID: input.sessionID,
|
|
60355
60424
|
envPrefix
|
|
60356
60425
|
});
|
|
@@ -60358,7 +60427,7 @@ function createNonInteractiveEnvHook(_ctx) {
|
|
|
60358
60427
|
};
|
|
60359
60428
|
}
|
|
60360
60429
|
// src/hooks/oracle-md-only/constants.ts
|
|
60361
|
-
var
|
|
60430
|
+
var HOOK_NAME9 = "oracle-md-only";
|
|
60362
60431
|
var ORACLE_AGENT = "oracle";
|
|
60363
60432
|
var ALLOWED_EXTENSIONS = [".md"];
|
|
60364
60433
|
var BLOCKED_TOOLS = ["Write", "Edit", "write", "edit"];
|
|
@@ -60491,7 +60560,7 @@ function createOracleMdOnlyHook(ctx) {
|
|
|
60491
60560
|
const prompt = output.args.prompt;
|
|
60492
60561
|
if (prompt && !prompt.includes(SYSTEM_DIRECTIVE_PREFIX)) {
|
|
60493
60562
|
output.args.prompt = PLANNING_CONSULT_WARNING + prompt;
|
|
60494
|
-
log(`[${
|
|
60563
|
+
log(`[${HOOK_NAME9}] Injected read-only planning warning to ${toolName}`, {
|
|
60495
60564
|
sessionID: input.sessionID,
|
|
60496
60565
|
tool: toolName,
|
|
60497
60566
|
agent: agentName
|
|
@@ -60507,17 +60576,17 @@ function createOracleMdOnlyHook(ctx) {
|
|
|
60507
60576
|
return;
|
|
60508
60577
|
}
|
|
60509
60578
|
if (!isAllowedFile(filePath, ctx.directory)) {
|
|
60510
|
-
log(`[${
|
|
60579
|
+
log(`[${HOOK_NAME9}] Blocked: Oracle can only write to .matrixx/*.md`, {
|
|
60511
60580
|
sessionID: input.sessionID,
|
|
60512
60581
|
tool: toolName,
|
|
60513
60582
|
filePath,
|
|
60514
60583
|
agent: agentName
|
|
60515
60584
|
});
|
|
60516
|
-
throw new Error(`[${
|
|
60585
|
+
throw new Error(`[${HOOK_NAME9}] ${getAgentDisplayName("oracle")} can only write/edit .md files inside .matrixx/ directory. ` + `Attempted to modify: ${filePath}. ` + `${getAgentDisplayName("oracle")} is a READ-ONLY planner. Use /start-work to execute the plan. ` + `APOLOGIZE TO THE USER, REMIND OF YOUR PLAN WRITING PROCESSES, TELL USER WHAT YOU WILL GOING TO DO AS THE PROCESS, WRITE THE PLAN`);
|
|
60517
60586
|
}
|
|
60518
60587
|
const normalizedPath = filePath.toLowerCase().replace(/\\/g, "/");
|
|
60519
60588
|
if (normalizedPath.includes(".matrixx/plans/") || normalizedPath.includes(".matrixx\\plans\\")) {
|
|
60520
|
-
log(`[${
|
|
60589
|
+
log(`[${HOOK_NAME9}] Injecting workflow reminder for plan write`, {
|
|
60521
60590
|
sessionID: input.sessionID,
|
|
60522
60591
|
tool: toolName,
|
|
60523
60592
|
filePath,
|
|
@@ -60525,7 +60594,7 @@ function createOracleMdOnlyHook(ctx) {
|
|
|
60525
60594
|
});
|
|
60526
60595
|
output.message = (output.message || "") + ORACLE_WORKFLOW_REMINDER;
|
|
60527
60596
|
}
|
|
60528
|
-
log(`[${
|
|
60597
|
+
log(`[${HOOK_NAME9}] Allowed: .matrixx/*.md write permitted`, {
|
|
60529
60598
|
sessionID: input.sessionID,
|
|
60530
60599
|
tool: toolName,
|
|
60531
60600
|
filePath,
|
|
@@ -60596,7 +60665,7 @@ async function getGitHead(directory) {
|
|
|
60596
60665
|
}
|
|
60597
60666
|
|
|
60598
60667
|
// src/hooks/plan-persister/hook.ts
|
|
60599
|
-
var
|
|
60668
|
+
var HOOK_NAME10 = "plan-persister";
|
|
60600
60669
|
function createPlanPersister(ctx, options) {
|
|
60601
60670
|
const { directory } = options;
|
|
60602
60671
|
const capture = async (sessionID) => {
|
|
@@ -60615,7 +60684,7 @@ function createPlanPersister(ctx, options) {
|
|
|
60615
60684
|
const response = await ctx.client.session.todo({ path: { id: sessionID } });
|
|
60616
60685
|
todos = normalizeTodos(response);
|
|
60617
60686
|
} catch (err) {
|
|
60618
|
-
log(`[${
|
|
60687
|
+
log(`[${HOOK_NAME10}] Failed to fetch todos`, { sessionID, error: String(err) });
|
|
60619
60688
|
return;
|
|
60620
60689
|
}
|
|
60621
60690
|
const content = readPlanFile(planPath);
|
|
@@ -60637,9 +60706,9 @@ function createPlanPersister(ctx, options) {
|
|
|
60637
60706
|
ensurePlanDir(directory);
|
|
60638
60707
|
const ok = atomicWrite(planPath, finalContent);
|
|
60639
60708
|
if (ok) {
|
|
60640
|
-
log(`[${
|
|
60709
|
+
log(`[${HOOK_NAME10}] Plan file updated`, { planPath, completed, total });
|
|
60641
60710
|
} else {
|
|
60642
|
-
log(`[${
|
|
60711
|
+
log(`[${HOOK_NAME10}] Failed to write plan file`, { planPath });
|
|
60643
60712
|
}
|
|
60644
60713
|
};
|
|
60645
60714
|
const event = async ({ event: evt }) => {
|
|
@@ -62640,7 +62709,7 @@ var RETRYABLE_ERROR_PATTERNS = [
|
|
|
62640
62709
|
/(?:^|\s)503(?:\s|$)/,
|
|
62641
62710
|
/(?:^|\s)529(?:\s|$)/
|
|
62642
62711
|
];
|
|
62643
|
-
var
|
|
62712
|
+
var HOOK_NAME11 = "runtime-fallback";
|
|
62644
62713
|
|
|
62645
62714
|
// src/hooks/runtime-fallback/fallback-models.ts
|
|
62646
62715
|
init_logger();
|
|
@@ -62731,7 +62800,7 @@ function getRawFallbackModelsForSession(sessionID, agent, pluginConfig) {
|
|
|
62731
62800
|
if (result)
|
|
62732
62801
|
return result;
|
|
62733
62802
|
}
|
|
62734
|
-
log(`[${
|
|
62803
|
+
log(`[${HOOK_NAME11}] No category/agent fallback models resolved for session`, { sessionID, agent });
|
|
62735
62804
|
return;
|
|
62736
62805
|
}
|
|
62737
62806
|
|
|
@@ -62760,21 +62829,21 @@ function findNextAvailableFallback(state3, fallbackModels, cooldownSeconds) {
|
|
|
62760
62829
|
if (!isModelInCooldown(candidate, state3, cooldownSeconds)) {
|
|
62761
62830
|
return candidate;
|
|
62762
62831
|
}
|
|
62763
|
-
log(`[${
|
|
62832
|
+
log(`[${HOOK_NAME11}] Skipping fallback model in cooldown`, { model: candidate, index: i });
|
|
62764
62833
|
}
|
|
62765
62834
|
return;
|
|
62766
62835
|
}
|
|
62767
62836
|
function prepareFallback(sessionID, state3, fallbackModels, config2) {
|
|
62768
62837
|
if (state3.attemptCount >= config2.max_fallback_attempts) {
|
|
62769
|
-
log(`[${
|
|
62838
|
+
log(`[${HOOK_NAME11}] Max fallback attempts reached`, { sessionID, attempts: state3.attemptCount });
|
|
62770
62839
|
return { success: false, error: "Max fallback attempts reached", maxAttemptsReached: true };
|
|
62771
62840
|
}
|
|
62772
62841
|
const nextModel = findNextAvailableFallback(state3, fallbackModels, config2.cooldown_seconds);
|
|
62773
62842
|
if (!nextModel) {
|
|
62774
|
-
log(`[${
|
|
62843
|
+
log(`[${HOOK_NAME11}] No available fallback models`, { sessionID });
|
|
62775
62844
|
return { success: false, error: "No available fallback models (all in cooldown or exhausted)" };
|
|
62776
62845
|
}
|
|
62777
|
-
log(`[${
|
|
62846
|
+
log(`[${HOOK_NAME11}] Preparing fallback`, {
|
|
62778
62847
|
sessionID,
|
|
62779
62848
|
from: state3.currentModel,
|
|
62780
62849
|
to: nextModel,
|
|
@@ -62860,9 +62929,9 @@ function createAutoRetryHelpers(deps) {
|
|
|
62860
62929
|
const abortSessionRequest = async (sessionID, source) => {
|
|
62861
62930
|
try {
|
|
62862
62931
|
await ctx.client.session.abort({ path: { id: sessionID } });
|
|
62863
|
-
log(`[${
|
|
62932
|
+
log(`[${HOOK_NAME11}] Aborted in-flight session request (${source})`, { sessionID });
|
|
62864
62933
|
} catch (error51) {
|
|
62865
|
-
log(`[${
|
|
62934
|
+
log(`[${HOOK_NAME11}] Failed to abort in-flight session request (${source})`, {
|
|
62866
62935
|
sessionID,
|
|
62867
62936
|
error: String(error51)
|
|
62868
62937
|
});
|
|
@@ -62886,7 +62955,7 @@ function createAutoRetryHelpers(deps) {
|
|
|
62886
62955
|
if (!state3)
|
|
62887
62956
|
return;
|
|
62888
62957
|
if (sessionRetryInFlight.has(sessionID)) {
|
|
62889
|
-
log(`[${
|
|
62958
|
+
log(`[${HOOK_NAME11}] Overriding in-flight retry due to session timeout`, { sessionID });
|
|
62890
62959
|
}
|
|
62891
62960
|
await abortSessionRequest(sessionID, "session.timeout");
|
|
62892
62961
|
sessionRetryInFlight.delete(sessionID);
|
|
@@ -62896,7 +62965,7 @@ function createAutoRetryHelpers(deps) {
|
|
|
62896
62965
|
const fallbackModels = getFallbackModelsForSession(sessionID, resolvedAgent, pluginConfig);
|
|
62897
62966
|
if (fallbackModels.length === 0)
|
|
62898
62967
|
return;
|
|
62899
|
-
log(`[${
|
|
62968
|
+
log(`[${HOOK_NAME11}] Session fallback timeout reached`, {
|
|
62900
62969
|
sessionID,
|
|
62901
62970
|
timeoutSeconds: config2.timeout_seconds,
|
|
62902
62971
|
currentModel: state3.currentModel
|
|
@@ -62910,7 +62979,7 @@ function createAutoRetryHelpers(deps) {
|
|
|
62910
62979
|
};
|
|
62911
62980
|
const autoRetryWithFallback = async (sessionID, newModel, resolvedAgent, source) => {
|
|
62912
62981
|
if (sessionRetryInFlight.has(sessionID)) {
|
|
62913
|
-
log(`[${
|
|
62982
|
+
log(`[${HOOK_NAME11}] Retry already in flight, skipping (${source})`, { sessionID });
|
|
62914
62983
|
return;
|
|
62915
62984
|
}
|
|
62916
62985
|
const agentSettings = resolvedAgent ? pluginConfig?.agents?.[resolvedAgent] : undefined;
|
|
@@ -62919,7 +62988,7 @@ function createAutoRetryHelpers(deps) {
|
|
|
62919
62988
|
reasoningEffort: agentSettings.reasoningEffort
|
|
62920
62989
|
} : undefined);
|
|
62921
62990
|
if (!retryModelPayload) {
|
|
62922
|
-
log(`[${
|
|
62991
|
+
log(`[${HOOK_NAME11}] Invalid model format (missing provider prefix): ${newModel}`);
|
|
62923
62992
|
const state3 = sessionStates2.get(sessionID);
|
|
62924
62993
|
if (state3?.pendingFallbackModel) {
|
|
62925
62994
|
state3.pendingFallbackModel = undefined;
|
|
@@ -62935,7 +63004,7 @@ function createAutoRetryHelpers(deps) {
|
|
|
62935
63004
|
});
|
|
62936
63005
|
const retryParts = getLastUserRetryParts(messagesResp);
|
|
62937
63006
|
if (retryParts.length > 0) {
|
|
62938
|
-
log(`[${
|
|
63007
|
+
log(`[${HOOK_NAME11}] Auto-retrying with fallback model (${source})`, {
|
|
62939
63008
|
sessionID,
|
|
62940
63009
|
model: newModel
|
|
62941
63010
|
});
|
|
@@ -62954,10 +63023,10 @@ function createAutoRetryHelpers(deps) {
|
|
|
62954
63023
|
});
|
|
62955
63024
|
retryDispatched = true;
|
|
62956
63025
|
} else {
|
|
62957
|
-
log(`[${
|
|
63026
|
+
log(`[${HOOK_NAME11}] No user message found for auto-retry (${source})`, { sessionID });
|
|
62958
63027
|
}
|
|
62959
63028
|
} catch (retryError) {
|
|
62960
|
-
log(`[${
|
|
63029
|
+
log(`[${HOOK_NAME11}] Auto-retry failed (${source})`, { sessionID, error: String(retryError) });
|
|
62961
63030
|
} finally {
|
|
62962
63031
|
sessionRetryInFlight.delete(sessionID);
|
|
62963
63032
|
if (!retryDispatched) {
|
|
@@ -63011,7 +63080,7 @@ function createAutoRetryHelpers(deps) {
|
|
|
63011
63080
|
}
|
|
63012
63081
|
}
|
|
63013
63082
|
if (cleanedCount > 0) {
|
|
63014
|
-
log(`[${
|
|
63083
|
+
log(`[${HOOK_NAME11}] Cleaned up ${cleanedCount} stale session states`);
|
|
63015
63084
|
}
|
|
63016
63085
|
};
|
|
63017
63086
|
return {
|
|
@@ -63042,7 +63111,7 @@ function createChatMessageHandler(deps) {
|
|
|
63042
63111
|
state3.pendingFallbackModel = undefined;
|
|
63043
63112
|
return;
|
|
63044
63113
|
}
|
|
63045
|
-
log(`[${
|
|
63114
|
+
log(`[${HOOK_NAME11}] Detected manual model change, resetting fallback state`, {
|
|
63046
63115
|
sessionID,
|
|
63047
63116
|
from: state3.currentModel,
|
|
63048
63117
|
to: requestedModel
|
|
@@ -63054,7 +63123,7 @@ function createChatMessageHandler(deps) {
|
|
|
63054
63123
|
if (state3.currentModel === state3.originalModel)
|
|
63055
63124
|
return;
|
|
63056
63125
|
const activeModel = state3.currentModel;
|
|
63057
|
-
log(`[${
|
|
63126
|
+
log(`[${HOOK_NAME11}] Applying fallback model override`, {
|
|
63058
63127
|
sessionID,
|
|
63059
63128
|
from: input.model,
|
|
63060
63129
|
to: activeModel
|
|
@@ -63241,7 +63310,7 @@ function resolveFallbackBootstrapModel(options) {
|
|
|
63241
63310
|
const agentConfig = options.resolvedAgent && agentConfigs ? agentConfigs[options.resolvedAgent] : undefined;
|
|
63242
63311
|
const agentModel = typeof agentConfig?.model === "string" ? agentConfig.model : undefined;
|
|
63243
63312
|
if (agentModel) {
|
|
63244
|
-
log(`[${
|
|
63313
|
+
log(`[${HOOK_NAME11}] Derived model from agent config for ${options.source}`, {
|
|
63245
63314
|
sessionID: options.sessionID,
|
|
63246
63315
|
agent: options.resolvedAgent,
|
|
63247
63316
|
model: agentModel
|
|
@@ -63252,7 +63321,7 @@ function resolveFallbackBootstrapModel(options) {
|
|
|
63252
63321
|
if (agentCategory) {
|
|
63253
63322
|
const agentCategoryModel = options.pluginConfig?.categories?.[agentCategory]?.model;
|
|
63254
63323
|
if (typeof agentCategoryModel === "string" && agentCategoryModel.length > 0) {
|
|
63255
|
-
log(`[${
|
|
63324
|
+
log(`[${HOOK_NAME11}] Derived model from agent category config for ${options.source}`, {
|
|
63256
63325
|
sessionID: options.sessionID,
|
|
63257
63326
|
agent: options.resolvedAgent,
|
|
63258
63327
|
category: agentCategory,
|
|
@@ -63264,7 +63333,7 @@ function resolveFallbackBootstrapModel(options) {
|
|
|
63264
63333
|
const sessionCategory = SessionCategoryRegistry.get(options.sessionID);
|
|
63265
63334
|
const categoryModel = sessionCategory ? options.pluginConfig?.categories?.[sessionCategory]?.model : undefined;
|
|
63266
63335
|
if (typeof categoryModel === "string" && categoryModel.length > 0) {
|
|
63267
|
-
log(`[${
|
|
63336
|
+
log(`[${HOOK_NAME11}] Derived model from session category config for ${options.source}`, {
|
|
63268
63337
|
sessionID: options.sessionID,
|
|
63269
63338
|
category: sessionCategory,
|
|
63270
63339
|
model: categoryModel
|
|
@@ -63294,7 +63363,7 @@ async function dispatchFallbackRetry(deps, helpers, options) {
|
|
|
63294
63363
|
await helpers.autoRetryWithFallback(options.sessionID, result.newModel, options.resolvedAgent, options.source);
|
|
63295
63364
|
return;
|
|
63296
63365
|
}
|
|
63297
|
-
log(`[${
|
|
63366
|
+
log(`[${HOOK_NAME11}] Fallback preparation failed`, {
|
|
63298
63367
|
sessionID: options.sessionID,
|
|
63299
63368
|
source: options.source,
|
|
63300
63369
|
error: result.error
|
|
@@ -63347,14 +63416,14 @@ function createSessionStatusHandler(deps, helpers, sessionStatusRetryKeys) {
|
|
|
63347
63416
|
sessionStatusRetryKeys.set(sessionID, retryKey);
|
|
63348
63417
|
if (sessionRetryInFlight.has(sessionID)) {
|
|
63349
63418
|
if (timeoutEnabled) {
|
|
63350
|
-
log(`[${
|
|
63419
|
+
log(`[${HOOK_NAME11}] Overriding in-flight retry due to provider auto-retry signal`, {
|
|
63351
63420
|
sessionID,
|
|
63352
63421
|
model
|
|
63353
63422
|
});
|
|
63354
63423
|
await helpers.abortSessionRequest(sessionID, "session.status.retry-signal");
|
|
63355
63424
|
sessionRetryInFlight.delete(sessionID);
|
|
63356
63425
|
} else {
|
|
63357
|
-
log(`[${
|
|
63426
|
+
log(`[${HOOK_NAME11}] session.status retry skipped - retry already in flight`, { sessionID });
|
|
63358
63427
|
return;
|
|
63359
63428
|
}
|
|
63360
63429
|
}
|
|
@@ -63377,7 +63446,7 @@ function createSessionStatusHandler(deps, helpers, sessionStatusRetryKeys) {
|
|
|
63377
63446
|
});
|
|
63378
63447
|
if (!initialModel) {
|
|
63379
63448
|
sessionStatusRetryKeys.delete(sessionID);
|
|
63380
|
-
log(`[${
|
|
63449
|
+
log(`[${HOOK_NAME11}] session.status retry missing model info, cannot fallback`, { sessionID });
|
|
63381
63450
|
return;
|
|
63382
63451
|
}
|
|
63383
63452
|
state3 = createFallbackState(initialModel);
|
|
@@ -63386,20 +63455,20 @@ function createSessionStatusHandler(deps, helpers, sessionStatusRetryKeys) {
|
|
|
63386
63455
|
sessionLastAccess.set(sessionID, Date.now());
|
|
63387
63456
|
if (state3.pendingFallbackModel) {
|
|
63388
63457
|
if (timeoutEnabled) {
|
|
63389
|
-
log(`[${
|
|
63458
|
+
log(`[${HOOK_NAME11}] Clearing pending fallback due to provider auto-retry signal`, {
|
|
63390
63459
|
sessionID,
|
|
63391
63460
|
pendingFallbackModel: state3.pendingFallbackModel
|
|
63392
63461
|
});
|
|
63393
63462
|
state3.pendingFallbackModel = undefined;
|
|
63394
63463
|
} else {
|
|
63395
|
-
log(`[${
|
|
63464
|
+
log(`[${HOOK_NAME11}] session.status retry skipped (pending fallback in progress)`, {
|
|
63396
63465
|
sessionID,
|
|
63397
63466
|
pendingFallbackModel: state3.pendingFallbackModel
|
|
63398
63467
|
});
|
|
63399
63468
|
return;
|
|
63400
63469
|
}
|
|
63401
63470
|
}
|
|
63402
|
-
log(`[${
|
|
63471
|
+
log(`[${HOOK_NAME11}] Detected provider auto-retry signal in session.status`, {
|
|
63403
63472
|
sessionID,
|
|
63404
63473
|
model: state3.currentModel,
|
|
63405
63474
|
retryAttempt: status.attempt
|
|
@@ -63435,7 +63504,7 @@ function createEventHandler(deps, helpers) {
|
|
|
63435
63504
|
const sessionID = sessionInfo?.id;
|
|
63436
63505
|
const model = sessionInfo?.model;
|
|
63437
63506
|
if (sessionID && model) {
|
|
63438
|
-
log(`[${
|
|
63507
|
+
log(`[${HOOK_NAME11}] Session created with model`, { sessionID, model });
|
|
63439
63508
|
sessionStates2.set(sessionID, createFallbackState(model));
|
|
63440
63509
|
sessionLastAccess.set(sessionID, Date.now());
|
|
63441
63510
|
}
|
|
@@ -63444,7 +63513,7 @@ function createEventHandler(deps, helpers) {
|
|
|
63444
63513
|
const sessionInfo = props?.info;
|
|
63445
63514
|
const sessionID = sessionInfo?.id;
|
|
63446
63515
|
if (sessionID) {
|
|
63447
|
-
log(`[${
|
|
63516
|
+
log(`[${HOOK_NAME11}] Cleaning up session state`, { sessionID });
|
|
63448
63517
|
cancelledSessions.delete(sessionID);
|
|
63449
63518
|
sessionStates2.delete(sessionID);
|
|
63450
63519
|
sessionLastAccess.delete(sessionID);
|
|
@@ -63464,7 +63533,7 @@ function createEventHandler(deps, helpers) {
|
|
|
63464
63533
|
}
|
|
63465
63534
|
cancelledSessions.add(sessionID);
|
|
63466
63535
|
resetRetryState(sessionID);
|
|
63467
|
-
log(`[${
|
|
63536
|
+
log(`[${HOOK_NAME11}] Cleared fallback retry state on session.stop`, { sessionID });
|
|
63468
63537
|
};
|
|
63469
63538
|
const handleMessageUpdated = (props) => {
|
|
63470
63539
|
const info = props?.info;
|
|
@@ -63480,11 +63549,11 @@ function createEventHandler(deps, helpers) {
|
|
|
63480
63549
|
return;
|
|
63481
63550
|
if (cancelledSessions.has(sessionID)) {
|
|
63482
63551
|
resetRetryState(sessionID);
|
|
63483
|
-
log(`[${
|
|
63552
|
+
log(`[${HOOK_NAME11}] Cleared fallback retry state for cancelled session on idle`, { sessionID });
|
|
63484
63553
|
return;
|
|
63485
63554
|
}
|
|
63486
63555
|
if (sessionAwaitingFallbackResult.has(sessionID)) {
|
|
63487
|
-
log(`[${
|
|
63556
|
+
log(`[${HOOK_NAME11}] session.idle while awaiting fallback result; keeping timeout armed`, { sessionID });
|
|
63488
63557
|
return;
|
|
63489
63558
|
}
|
|
63490
63559
|
const hadTimeout = sessionFallbackTimeouts.has(sessionID);
|
|
@@ -63496,7 +63565,7 @@ function createEventHandler(deps, helpers) {
|
|
|
63496
63565
|
state3.pendingFallbackModel = undefined;
|
|
63497
63566
|
}
|
|
63498
63567
|
if (hadTimeout) {
|
|
63499
|
-
log(`[${
|
|
63568
|
+
log(`[${HOOK_NAME11}] Cleared fallback timeout after session completion`, { sessionID });
|
|
63500
63569
|
}
|
|
63501
63570
|
};
|
|
63502
63571
|
const handleSessionError = async (props) => {
|
|
@@ -63504,18 +63573,18 @@ function createEventHandler(deps, helpers) {
|
|
|
63504
63573
|
const error51 = props?.error;
|
|
63505
63574
|
const agent = props?.agent;
|
|
63506
63575
|
if (!sessionID) {
|
|
63507
|
-
log(`[${
|
|
63576
|
+
log(`[${HOOK_NAME11}] session.error without sessionID, skipping`);
|
|
63508
63577
|
return;
|
|
63509
63578
|
}
|
|
63510
63579
|
const resolvedAgent = await helpers.resolveAgentForSessionFromContext(sessionID, agent);
|
|
63511
63580
|
if (isAbortError(error51)) {
|
|
63512
63581
|
cancelledSessions.add(sessionID);
|
|
63513
63582
|
resetRetryState(sessionID);
|
|
63514
|
-
log(`[${
|
|
63583
|
+
log(`[${HOOK_NAME11}] session.error matched cancellation; cleared retry state`, { sessionID, resolvedAgent });
|
|
63515
63584
|
return;
|
|
63516
63585
|
}
|
|
63517
63586
|
if (sessionRetryInFlight.has(sessionID)) {
|
|
63518
|
-
log(`[${
|
|
63587
|
+
log(`[${HOOK_NAME11}] session.error skipped - retry in flight`, {
|
|
63519
63588
|
sessionID,
|
|
63520
63589
|
retryInFlight: true
|
|
63521
63590
|
});
|
|
@@ -63523,7 +63592,7 @@ function createEventHandler(deps, helpers) {
|
|
|
63523
63592
|
}
|
|
63524
63593
|
sessionAwaitingFallbackResult.delete(sessionID);
|
|
63525
63594
|
helpers.clearSessionFallbackTimeout(sessionID);
|
|
63526
|
-
log(`[${
|
|
63595
|
+
log(`[${HOOK_NAME11}] session.error received`, {
|
|
63527
63596
|
sessionID,
|
|
63528
63597
|
agent,
|
|
63529
63598
|
resolvedAgent,
|
|
@@ -63532,7 +63601,7 @@ function createEventHandler(deps, helpers) {
|
|
|
63532
63601
|
errorType: classifyErrorType(error51)
|
|
63533
63602
|
});
|
|
63534
63603
|
if (!isRetryableError(error51, config2.retry_on_errors)) {
|
|
63535
|
-
log(`[${
|
|
63604
|
+
log(`[${HOOK_NAME11}] Error not retryable, skipping fallback`, {
|
|
63536
63605
|
sessionID,
|
|
63537
63606
|
retryable: false,
|
|
63538
63607
|
statusCode: extractStatusCode(error51, config2.retry_on_errors),
|
|
@@ -63544,7 +63613,7 @@ function createEventHandler(deps, helpers) {
|
|
|
63544
63613
|
let state3 = sessionStates2.get(sessionID);
|
|
63545
63614
|
const fallbackModels = getFallbackModelsForSession(sessionID, resolvedAgent, pluginConfig);
|
|
63546
63615
|
if (fallbackModels.length === 0) {
|
|
63547
|
-
log(`[${
|
|
63616
|
+
log(`[${HOOK_NAME11}] No fallback models configured`, { sessionID, agent });
|
|
63548
63617
|
return;
|
|
63549
63618
|
}
|
|
63550
63619
|
if (!state3) {
|
|
@@ -63556,7 +63625,7 @@ function createEventHandler(deps, helpers) {
|
|
|
63556
63625
|
pluginConfig
|
|
63557
63626
|
});
|
|
63558
63627
|
if (!initialModel) {
|
|
63559
|
-
log(`[${
|
|
63628
|
+
log(`[${HOOK_NAME11}] No model info available, cannot fallback`, { sessionID });
|
|
63560
63629
|
return;
|
|
63561
63630
|
}
|
|
63562
63631
|
state3 = createFallbackState(initialModel);
|
|
@@ -63696,7 +63765,7 @@ function createMessageUpdateHandler(deps, helpers) {
|
|
|
63696
63765
|
}
|
|
63697
63766
|
const hasVisible = await checkVisibleResponse(ctx, sessionID, info);
|
|
63698
63767
|
if (!hasVisible) {
|
|
63699
|
-
log(`[${
|
|
63768
|
+
log(`[${HOOK_NAME11}] Assistant update observed without visible final response; keeping fallback timeout`, {
|
|
63700
63769
|
sessionID,
|
|
63701
63770
|
model
|
|
63702
63771
|
});
|
|
@@ -63709,17 +63778,17 @@ function createMessageUpdateHandler(deps, helpers) {
|
|
|
63709
63778
|
if (state3?.pendingFallbackModel) {
|
|
63710
63779
|
state3.pendingFallbackModel = undefined;
|
|
63711
63780
|
}
|
|
63712
|
-
log(`[${
|
|
63781
|
+
log(`[${HOOK_NAME11}] Assistant response observed; cleared fallback timeout`, { sessionID, model });
|
|
63713
63782
|
return;
|
|
63714
63783
|
}
|
|
63715
63784
|
if (sessionID && role === "assistant" && error51) {
|
|
63716
63785
|
sessionAwaitingFallbackResult.delete(sessionID);
|
|
63717
63786
|
if (sessionRetryInFlight.has(sessionID) && !retrySignal) {
|
|
63718
|
-
log(`[${
|
|
63787
|
+
log(`[${HOOK_NAME11}] message.updated fallback skipped (retry in flight)`, { sessionID });
|
|
63719
63788
|
return;
|
|
63720
63789
|
}
|
|
63721
63790
|
if (retrySignal && sessionRetryInFlight.has(sessionID) && timeoutEnabled) {
|
|
63722
|
-
log(`[${
|
|
63791
|
+
log(`[${HOOK_NAME11}] Overriding in-flight retry due to provider auto-retry signal`, {
|
|
63723
63792
|
sessionID,
|
|
63724
63793
|
model
|
|
63725
63794
|
});
|
|
@@ -63727,12 +63796,12 @@ function createMessageUpdateHandler(deps, helpers) {
|
|
|
63727
63796
|
sessionRetryInFlight.delete(sessionID);
|
|
63728
63797
|
}
|
|
63729
63798
|
if (retrySignal && timeoutEnabled) {
|
|
63730
|
-
log(`[${
|
|
63799
|
+
log(`[${HOOK_NAME11}] Detected provider auto-retry signal`, { sessionID, model });
|
|
63731
63800
|
}
|
|
63732
63801
|
if (!retrySignal) {
|
|
63733
63802
|
helpers.clearSessionFallbackTimeout(sessionID);
|
|
63734
63803
|
}
|
|
63735
|
-
log(`[${
|
|
63804
|
+
log(`[${HOOK_NAME11}] message.updated with assistant error`, {
|
|
63736
63805
|
sessionID,
|
|
63737
63806
|
model,
|
|
63738
63807
|
statusCode: extractStatusCode(error51, config2.retry_on_errors),
|
|
@@ -63740,7 +63809,7 @@ function createMessageUpdateHandler(deps, helpers) {
|
|
|
63740
63809
|
errorType: classifyErrorType(error51)
|
|
63741
63810
|
});
|
|
63742
63811
|
if (!isRetryableError(error51, config2.retry_on_errors)) {
|
|
63743
|
-
log(`[${
|
|
63812
|
+
log(`[${HOOK_NAME11}] message.updated error not retryable, skipping fallback`, {
|
|
63744
63813
|
sessionID,
|
|
63745
63814
|
statusCode: extractStatusCode(error51, config2.retry_on_errors),
|
|
63746
63815
|
errorName: extractErrorName(error51),
|
|
@@ -63764,7 +63833,7 @@ function createMessageUpdateHandler(deps, helpers) {
|
|
|
63764
63833
|
pluginConfig
|
|
63765
63834
|
});
|
|
63766
63835
|
if (!initialModel) {
|
|
63767
|
-
log(`[${
|
|
63836
|
+
log(`[${HOOK_NAME11}] message.updated missing model info, cannot fallback`, {
|
|
63768
63837
|
sessionID,
|
|
63769
63838
|
errorName: extractErrorName(error51),
|
|
63770
63839
|
errorType: classifyErrorType(error51)
|
|
@@ -63778,13 +63847,13 @@ function createMessageUpdateHandler(deps, helpers) {
|
|
|
63778
63847
|
sessionLastAccess.set(sessionID, Date.now());
|
|
63779
63848
|
if (state3.pendingFallbackModel) {
|
|
63780
63849
|
if (retrySignal && timeoutEnabled) {
|
|
63781
|
-
log(`[${
|
|
63850
|
+
log(`[${HOOK_NAME11}] Clearing pending fallback due to provider auto-retry signal`, {
|
|
63782
63851
|
sessionID,
|
|
63783
63852
|
pendingFallbackModel: state3.pendingFallbackModel
|
|
63784
63853
|
});
|
|
63785
63854
|
state3.pendingFallbackModel = undefined;
|
|
63786
63855
|
} else {
|
|
63787
|
-
log(`[${
|
|
63856
|
+
log(`[${HOOK_NAME11}] message.updated fallback skipped (pending fallback in progress)`, {
|
|
63788
63857
|
sessionID,
|
|
63789
63858
|
pendingFallbackModel: state3.pendingFallbackModel
|
|
63790
63859
|
});
|
|
@@ -64790,7 +64859,7 @@ function createSessionRecoveryHook(ctx, options) {
|
|
|
64790
64859
|
}
|
|
64791
64860
|
// src/hooks/start-work/start-work-hook.ts
|
|
64792
64861
|
init_logger();
|
|
64793
|
-
var
|
|
64862
|
+
var HOOK_NAME12 = "start-work";
|
|
64794
64863
|
var KEYWORD_PATTERN = /\b(ultrawork|ulw)\b/gi;
|
|
64795
64864
|
function extractUserRequestPlanName(promptText) {
|
|
64796
64865
|
const userRequestMatch = promptText.match(/<user-request>\s*([\s\S]*?)\s*<\/user-request>/i);
|
|
@@ -64820,7 +64889,7 @@ function createStartWorkHook(ctx) {
|
|
|
64820
64889
|
if (!isStartWorkCommand) {
|
|
64821
64890
|
return;
|
|
64822
64891
|
}
|
|
64823
|
-
log(`[${
|
|
64892
|
+
log(`[${HOOK_NAME12}] Processing start-work command`, {
|
|
64824
64893
|
sessionID: input.sessionID
|
|
64825
64894
|
});
|
|
64826
64895
|
updateSessionAgent(input.sessionID, "architect");
|
|
@@ -64830,7 +64899,7 @@ function createStartWorkHook(ctx) {
|
|
|
64830
64899
|
let contextInfo = "";
|
|
64831
64900
|
const explicitPlanName = extractUserRequestPlanName(promptText);
|
|
64832
64901
|
if (explicitPlanName) {
|
|
64833
|
-
log(`[${
|
|
64902
|
+
log(`[${HOOK_NAME12}] Explicit plan name requested: ${explicitPlanName}`, {
|
|
64834
64903
|
sessionID: input.sessionID
|
|
64835
64904
|
});
|
|
64836
64905
|
const allPlans = findOraclePlans(ctx.directory);
|
|
@@ -64971,7 +65040,7 @@ Ask the user which plan to work on. Present the options above and wait for their
|
|
|
64971
65040
|
---
|
|
64972
65041
|
${contextInfo}`;
|
|
64973
65042
|
}
|
|
64974
|
-
log(`[${
|
|
65043
|
+
log(`[${HOOK_NAME12}] Context injected`, {
|
|
64975
65044
|
sessionID: input.sessionID,
|
|
64976
65045
|
hasExistingState: !!existingState
|
|
64977
65046
|
});
|
|
@@ -64980,16 +65049,16 @@ ${contextInfo}`;
|
|
|
64980
65049
|
}
|
|
64981
65050
|
// src/hooks/stop-continuation-guard/hook.ts
|
|
64982
65051
|
init_logger();
|
|
64983
|
-
var
|
|
65052
|
+
var HOOK_NAME13 = "stop-continuation-guard";
|
|
64984
65053
|
function createStopContinuationGuardHook(_ctx, options) {
|
|
64985
65054
|
const stoppedSessions = new Set;
|
|
64986
65055
|
const stop = (sessionID) => {
|
|
64987
65056
|
stoppedSessions.add(sessionID);
|
|
64988
|
-
log(`[${
|
|
65057
|
+
log(`[${HOOK_NAME13}] Continuation stopped for session`, { sessionID });
|
|
64989
65058
|
if (options?.backgroundManager) {
|
|
64990
65059
|
const cancelled = options.backgroundManager.cancelAllForSession(sessionID);
|
|
64991
65060
|
if (cancelled > 0) {
|
|
64992
|
-
log(`[${
|
|
65061
|
+
log(`[${HOOK_NAME13}] Cancelled ${cancelled} background task(s) for session`, { sessionID });
|
|
64993
65062
|
}
|
|
64994
65063
|
}
|
|
64995
65064
|
};
|
|
@@ -64998,7 +65067,7 @@ function createStopContinuationGuardHook(_ctx, options) {
|
|
|
64998
65067
|
};
|
|
64999
65068
|
const clear = (sessionID) => {
|
|
65000
65069
|
stoppedSessions.delete(sessionID);
|
|
65001
|
-
log(`[${
|
|
65070
|
+
log(`[${HOOK_NAME13}] Continuation guard cleared for session`, { sessionID });
|
|
65002
65071
|
};
|
|
65003
65072
|
const event = async ({
|
|
65004
65073
|
event: event2
|
|
@@ -65008,7 +65077,7 @@ function createStopContinuationGuardHook(_ctx, options) {
|
|
|
65008
65077
|
const sessionInfo = props?.info;
|
|
65009
65078
|
if (sessionInfo?.id) {
|
|
65010
65079
|
clear(sessionInfo.id);
|
|
65011
|
-
log(`[${
|
|
65080
|
+
log(`[${HOOK_NAME13}] Session deleted: cleaned up`, { sessionID: sessionInfo.id });
|
|
65012
65081
|
}
|
|
65013
65082
|
}
|
|
65014
65083
|
};
|
|
@@ -65017,7 +65086,7 @@ function createStopContinuationGuardHook(_ctx, options) {
|
|
|
65017
65086
|
}) => {
|
|
65018
65087
|
if (sessionID && stoppedSessions.has(sessionID)) {
|
|
65019
65088
|
clear(sessionID);
|
|
65020
|
-
log(`[${
|
|
65089
|
+
log(`[${HOOK_NAME13}] Cleared stop state on new user message`, { sessionID });
|
|
65021
65090
|
}
|
|
65022
65091
|
};
|
|
65023
65092
|
return {
|
|
@@ -65032,7 +65101,7 @@ function createStopContinuationGuardHook(_ctx, options) {
|
|
|
65032
65101
|
init_logger();
|
|
65033
65102
|
|
|
65034
65103
|
// src/hooks/task-continuation-enforcer/constants.ts
|
|
65035
|
-
var
|
|
65104
|
+
var HOOK_NAME14 = "task-continuation-enforcer";
|
|
65036
65105
|
var DEFAULT_SKIP_AGENTS = ["oracle", "compaction"];
|
|
65037
65106
|
var CONTINUATION_PROMPT2 = `${createSystemDirective(SystemDirectiveTypes.TASK_CONTINUATION)}
|
|
65038
65107
|
|
|
@@ -65352,12 +65421,12 @@ async function injectContinuation(args) {
|
|
|
65352
65421
|
} = args;
|
|
65353
65422
|
const state3 = sessionStateStore.getExistingState(sessionID);
|
|
65354
65423
|
if (state3?.isRecovering) {
|
|
65355
|
-
log(`[${
|
|
65424
|
+
log(`[${HOOK_NAME14}] Skipped injection: in recovery`, { sessionID });
|
|
65356
65425
|
return;
|
|
65357
65426
|
}
|
|
65358
65427
|
const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((task) => task.status === "running") : false;
|
|
65359
65428
|
if (hasRunningBgTasks) {
|
|
65360
|
-
log(`[${
|
|
65429
|
+
log(`[${HOOK_NAME14}] Skipped injection: background tasks running`, { sessionID });
|
|
65361
65430
|
return;
|
|
65362
65431
|
}
|
|
65363
65432
|
const tasks = [];
|
|
@@ -65365,7 +65434,7 @@ async function injectContinuation(args) {
|
|
|
65365
65434
|
try {
|
|
65366
65435
|
const taskDir = getTaskDir({}, ctx.directory);
|
|
65367
65436
|
if (!existsSync43(taskDir)) {
|
|
65368
|
-
log(`[${
|
|
65437
|
+
log(`[${HOOK_NAME14}] Skipped injection: no task dir`, { sessionID, taskDir });
|
|
65369
65438
|
return;
|
|
65370
65439
|
}
|
|
65371
65440
|
const files = readdirSync14(taskDir).filter((f) => f.startsWith("T-") && f.endsWith(".json"));
|
|
@@ -65376,16 +65445,16 @@ async function injectContinuation(args) {
|
|
|
65376
65445
|
}
|
|
65377
65446
|
total = tasks.length;
|
|
65378
65447
|
if (total === 0) {
|
|
65379
|
-
log(`[${
|
|
65448
|
+
log(`[${HOOK_NAME14}] Skipped injection: no tasks`, { sessionID });
|
|
65380
65449
|
return;
|
|
65381
65450
|
}
|
|
65382
65451
|
} catch (error51) {
|
|
65383
|
-
log(`[${
|
|
65452
|
+
log(`[${HOOK_NAME14}] Failed to fetch tasks`, { sessionID, error: String(error51) });
|
|
65384
65453
|
return;
|
|
65385
65454
|
}
|
|
65386
65455
|
const freshIncompleteCount = getIncompleteTaskCount(tasks);
|
|
65387
65456
|
if (freshIncompleteCount === 0) {
|
|
65388
|
-
log(`[${
|
|
65457
|
+
log(`[${HOOK_NAME14}] Skipped injection: no incomplete tasks`, { sessionID, total });
|
|
65389
65458
|
return;
|
|
65390
65459
|
}
|
|
65391
65460
|
let agentName = resolvedInfo?.agent;
|
|
@@ -65408,11 +65477,11 @@ async function injectContinuation(args) {
|
|
|
65408
65477
|
tools = tools ?? previousMessage?.tools;
|
|
65409
65478
|
}
|
|
65410
65479
|
if (agentName && skipAgents.some((s) => getAgentConfigKey(s) === getAgentConfigKey(agentName))) {
|
|
65411
|
-
log(`[${
|
|
65480
|
+
log(`[${HOOK_NAME14}] Skipped: agent in skipAgents list`, { sessionID, agent: agentName });
|
|
65412
65481
|
return;
|
|
65413
65482
|
}
|
|
65414
65483
|
if (!hasWritePermission(tools)) {
|
|
65415
|
-
log(`[${
|
|
65484
|
+
log(`[${HOOK_NAME14}] Skipped: agent lacks write permission`, { sessionID, agent: agentName });
|
|
65416
65485
|
return;
|
|
65417
65486
|
}
|
|
65418
65487
|
const byId = new Map(tasks.map((t) => [t.id, t]));
|
|
@@ -65436,7 +65505,7 @@ ${taskList}`;
|
|
|
65436
65505
|
injectionState.inFlight = true;
|
|
65437
65506
|
}
|
|
65438
65507
|
try {
|
|
65439
|
-
log(`[${
|
|
65508
|
+
log(`[${HOOK_NAME14}] Injecting continuation`, {
|
|
65440
65509
|
sessionID,
|
|
65441
65510
|
agent: agentName,
|
|
65442
65511
|
model,
|
|
@@ -65451,14 +65520,14 @@ ${taskList}`;
|
|
|
65451
65520
|
},
|
|
65452
65521
|
query: { directory: ctx.directory }
|
|
65453
65522
|
});
|
|
65454
|
-
log(`[${
|
|
65523
|
+
log(`[${HOOK_NAME14}] Injection successful`, { sessionID });
|
|
65455
65524
|
if (injectionState) {
|
|
65456
65525
|
injectionState.inFlight = false;
|
|
65457
65526
|
injectionState.lastInjectedAt = Date.now();
|
|
65458
65527
|
injectionState.consecutiveFailures = 0;
|
|
65459
65528
|
}
|
|
65460
65529
|
} catch (error51) {
|
|
65461
|
-
log(`[${
|
|
65530
|
+
log(`[${HOOK_NAME14}] Injection failed`, { sessionID, error: String(error51) });
|
|
65462
65531
|
if (injectionState) {
|
|
65463
65532
|
injectionState.inFlight = false;
|
|
65464
65533
|
injectionState.lastInjectedAt = Date.now();
|
|
@@ -65491,7 +65560,10 @@ function startCountdown(args) {
|
|
|
65491
65560
|
sessionStateStore
|
|
65492
65561
|
} = args;
|
|
65493
65562
|
const state3 = sessionStateStore.getState(sessionID);
|
|
65494
|
-
|
|
65563
|
+
if (state3.countdownTimer || state3.countdownInterval) {
|
|
65564
|
+
log(`[${HOOK_NAME14}] Skipped: countdown already active`, { sessionID });
|
|
65565
|
+
return;
|
|
65566
|
+
}
|
|
65495
65567
|
let secondsRemaining = COUNTDOWN_SECONDS;
|
|
65496
65568
|
showCountdownToast(ctx, secondsRemaining, incompleteCount);
|
|
65497
65569
|
state3.countdownStartedAt = Date.now();
|
|
@@ -65512,7 +65584,7 @@ function startCountdown(args) {
|
|
|
65512
65584
|
sessionStateStore
|
|
65513
65585
|
});
|
|
65514
65586
|
}, COUNTDOWN_SECONDS * 1000);
|
|
65515
|
-
log(`[${
|
|
65587
|
+
log(`[${HOOK_NAME14}] Countdown started`, {
|
|
65516
65588
|
sessionID,
|
|
65517
65589
|
seconds: COUNTDOWN_SECONDS,
|
|
65518
65590
|
incompleteCount
|
|
@@ -65529,16 +65601,20 @@ async function handleSessionIdle(args) {
|
|
|
65529
65601
|
skipAgents = DEFAULT_SKIP_AGENTS,
|
|
65530
65602
|
isContinuationStopped
|
|
65531
65603
|
} = args;
|
|
65532
|
-
log(`[${
|
|
65604
|
+
log(`[${HOOK_NAME14}] session.idle`, { sessionID });
|
|
65533
65605
|
const state3 = sessionStateStore.getState(sessionID);
|
|
65606
|
+
if (state3.countdownTimer || state3.countdownInterval) {
|
|
65607
|
+
log(`[${HOOK_NAME14}] Skipped: countdown already active`, { sessionID });
|
|
65608
|
+
return;
|
|
65609
|
+
}
|
|
65534
65610
|
if (state3.isRecovering) {
|
|
65535
|
-
log(`[${
|
|
65611
|
+
log(`[${HOOK_NAME14}] Skipped: in recovery`, { sessionID });
|
|
65536
65612
|
return;
|
|
65537
65613
|
}
|
|
65538
65614
|
if (state3.abortDetectedAt) {
|
|
65539
65615
|
const timeSinceAbort = Date.now() - state3.abortDetectedAt;
|
|
65540
65616
|
if (timeSinceAbort < ABORT_WINDOW_MS) {
|
|
65541
|
-
log(`[${
|
|
65617
|
+
log(`[${HOOK_NAME14}] Skipped: abort detected via event ${timeSinceAbort}ms ago`, { sessionID });
|
|
65542
65618
|
state3.abortDetectedAt = undefined;
|
|
65543
65619
|
return;
|
|
65544
65620
|
}
|
|
@@ -65546,7 +65622,7 @@ async function handleSessionIdle(args) {
|
|
|
65546
65622
|
}
|
|
65547
65623
|
const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((task) => task.status === "running") : false;
|
|
65548
65624
|
if (hasRunningBgTasks) {
|
|
65549
|
-
log(`[${
|
|
65625
|
+
log(`[${HOOK_NAME14}] Skipped: background tasks running`, { sessionID });
|
|
65550
65626
|
return;
|
|
65551
65627
|
}
|
|
65552
65628
|
try {
|
|
@@ -65556,18 +65632,18 @@ async function handleSessionIdle(args) {
|
|
|
65556
65632
|
});
|
|
65557
65633
|
const messages = normalizeSDKResponse(messagesResp, []);
|
|
65558
65634
|
if (isLastAssistantMessageAborted(messages)) {
|
|
65559
|
-
log(`[${
|
|
65635
|
+
log(`[${HOOK_NAME14}] Skipped: last assistant message was aborted (API fallback)`, { sessionID });
|
|
65560
65636
|
return;
|
|
65561
65637
|
}
|
|
65562
65638
|
} catch (error51) {
|
|
65563
|
-
log(`[${
|
|
65639
|
+
log(`[${HOOK_NAME14}] Messages fetch failed, continuing`, { sessionID, error: String(error51) });
|
|
65564
65640
|
}
|
|
65565
65641
|
let incompleteCount = 0;
|
|
65566
65642
|
let total = 0;
|
|
65567
65643
|
try {
|
|
65568
65644
|
const taskDir = getTaskDir({}, ctx.directory);
|
|
65569
65645
|
if (!existsSync44(taskDir)) {
|
|
65570
|
-
log(`[${
|
|
65646
|
+
log(`[${HOOK_NAME14}] No task dir`, { sessionID, taskDir });
|
|
65571
65647
|
return;
|
|
65572
65648
|
}
|
|
65573
65649
|
const files = readdirSync15(taskDir).filter((f) => f.startsWith("T-") && f.endsWith(".json"));
|
|
@@ -65579,31 +65655,31 @@ async function handleSessionIdle(args) {
|
|
|
65579
65655
|
}
|
|
65580
65656
|
total = tasks.length;
|
|
65581
65657
|
if (total === 0) {
|
|
65582
|
-
log(`[${
|
|
65658
|
+
log(`[${HOOK_NAME14}] No tasks`, { sessionID });
|
|
65583
65659
|
return;
|
|
65584
65660
|
}
|
|
65585
65661
|
incompleteCount = getIncompleteTaskCount(tasks);
|
|
65586
65662
|
} catch (error51) {
|
|
65587
|
-
log(`[${
|
|
65663
|
+
log(`[${HOOK_NAME14}] Task fetch failed`, { sessionID, error: String(error51) });
|
|
65588
65664
|
return;
|
|
65589
65665
|
}
|
|
65590
65666
|
if (incompleteCount === 0) {
|
|
65591
|
-
log(`[${
|
|
65667
|
+
log(`[${HOOK_NAME14}] All tasks complete`, { sessionID, total });
|
|
65592
65668
|
return;
|
|
65593
65669
|
}
|
|
65594
65670
|
if (state3.inFlight) {
|
|
65595
|
-
log(`[${
|
|
65671
|
+
log(`[${HOOK_NAME14}] Skipped: injection in flight`, { sessionID });
|
|
65596
65672
|
return;
|
|
65597
65673
|
}
|
|
65598
65674
|
if ((state3.consecutiveFailures ?? 0) >= MAX_CONSECUTIVE_FAILURES && state3.lastInjectedAt && Date.now() - state3.lastInjectedAt >= FAILURE_RESET_WINDOW_MS) {
|
|
65599
65675
|
state3.consecutiveFailures = 0;
|
|
65600
|
-
log(`[${
|
|
65676
|
+
log(`[${HOOK_NAME14}] Reset consecutive failures after recovery window`, {
|
|
65601
65677
|
sessionID,
|
|
65602
65678
|
failureResetWindowMs: FAILURE_RESET_WINDOW_MS
|
|
65603
65679
|
});
|
|
65604
65680
|
}
|
|
65605
65681
|
if ((state3.consecutiveFailures ?? 0) >= MAX_CONSECUTIVE_FAILURES) {
|
|
65606
|
-
log(`[${
|
|
65682
|
+
log(`[${HOOK_NAME14}] Skipped: max consecutive failures reached`, {
|
|
65607
65683
|
sessionID,
|
|
65608
65684
|
consecutiveFailures: state3.consecutiveFailures ?? 0,
|
|
65609
65685
|
maxConsecutiveFailures: MAX_CONSECUTIVE_FAILURES
|
|
@@ -65612,7 +65688,7 @@ async function handleSessionIdle(args) {
|
|
|
65612
65688
|
}
|
|
65613
65689
|
const effectiveCooldown = CONTINUATION_COOLDOWN_MS2 * 2 ** Math.min(state3.consecutiveFailures ?? 0, 5);
|
|
65614
65690
|
if (state3.lastInjectedAt && Date.now() - state3.lastInjectedAt < effectiveCooldown) {
|
|
65615
|
-
log(`[${
|
|
65691
|
+
log(`[${HOOK_NAME14}] Skipped: cooldown active`, {
|
|
65616
65692
|
sessionID,
|
|
65617
65693
|
effectiveCooldown,
|
|
65618
65694
|
consecutiveFailures: state3.consecutiveFailures ?? 0
|
|
@@ -65642,20 +65718,20 @@ async function handleSessionIdle(args) {
|
|
|
65642
65718
|
}
|
|
65643
65719
|
}
|
|
65644
65720
|
} catch (error51) {
|
|
65645
|
-
log(`[${
|
|
65721
|
+
log(`[${HOOK_NAME14}] Failed to fetch messages for agent check`, { sessionID, error: String(error51) });
|
|
65646
65722
|
}
|
|
65647
|
-
log(`[${
|
|
65723
|
+
log(`[${HOOK_NAME14}] Agent check`, { sessionID, agentName: resolvedInfo?.agent, skipAgents, hasCompactionMessage });
|
|
65648
65724
|
const resolvedAgentName = resolvedInfo?.agent;
|
|
65649
65725
|
if (resolvedAgentName && skipAgents.some((s) => getAgentConfigKey(s) === getAgentConfigKey(resolvedAgentName))) {
|
|
65650
|
-
log(`[${
|
|
65726
|
+
log(`[${HOOK_NAME14}] Skipped: agent in skipAgents list`, { sessionID, agent: resolvedAgentName });
|
|
65651
65727
|
return;
|
|
65652
65728
|
}
|
|
65653
65729
|
if (hasCompactionMessage && !resolvedInfo?.agent) {
|
|
65654
|
-
log(`[${
|
|
65730
|
+
log(`[${HOOK_NAME14}] Skipped: compaction occurred but no agent info resolved`, { sessionID });
|
|
65655
65731
|
return;
|
|
65656
65732
|
}
|
|
65657
65733
|
if (isContinuationStopped?.(sessionID)) {
|
|
65658
|
-
log(`[${
|
|
65734
|
+
log(`[${HOOK_NAME14}] Skipped: continuation stopped for session`, { sessionID });
|
|
65659
65735
|
return;
|
|
65660
65736
|
}
|
|
65661
65737
|
startCountdown({
|
|
@@ -65685,7 +65761,7 @@ function handleNonIdleEvent(args) {
|
|
|
65685
65761
|
if (state3?.countdownStartedAt) {
|
|
65686
65762
|
const elapsed = Date.now() - state3.countdownStartedAt;
|
|
65687
65763
|
if (elapsed < COUNTDOWN_GRACE_PERIOD_MS) {
|
|
65688
|
-
log(`[${
|
|
65764
|
+
log(`[${HOOK_NAME14}] Ignoring user message in grace period`, { sessionID, elapsed });
|
|
65689
65765
|
return;
|
|
65690
65766
|
}
|
|
65691
65767
|
}
|
|
@@ -65729,7 +65805,7 @@ function handleNonIdleEvent(args) {
|
|
|
65729
65805
|
const sessionInfo = properties?.info;
|
|
65730
65806
|
if (sessionInfo?.id) {
|
|
65731
65807
|
sessionStateStore.cleanup(sessionInfo.id);
|
|
65732
|
-
log(`[${
|
|
65808
|
+
log(`[${HOOK_NAME14}] Session deleted: cleaned up`, { sessionID: sessionInfo.id });
|
|
65733
65809
|
}
|
|
65734
65810
|
return;
|
|
65735
65811
|
}
|
|
@@ -65754,10 +65830,10 @@ function createTaskContinuationHandler(args) {
|
|
|
65754
65830
|
if (error51?.name === "MessageAbortedError" || error51?.name === "AbortError") {
|
|
65755
65831
|
const state3 = sessionStateStore.getState(sessionID);
|
|
65756
65832
|
state3.abortDetectedAt = Date.now();
|
|
65757
|
-
log(`[${
|
|
65833
|
+
log(`[${HOOK_NAME14}] Abort detected via session.error`, { sessionID, errorName: error51.name });
|
|
65758
65834
|
}
|
|
65759
65835
|
sessionStateStore.cancelCountdown(sessionID);
|
|
65760
|
-
log(`[${
|
|
65836
|
+
log(`[${HOOK_NAME14}] session.error`, { sessionID });
|
|
65761
65837
|
return;
|
|
65762
65838
|
}
|
|
65763
65839
|
if (event.type === "session.idle") {
|
|
@@ -65872,13 +65948,13 @@ function createTaskContinuationEnforcer(ctx, options = {}) {
|
|
|
65872
65948
|
const state3 = sessionStateStore.getState(sessionID);
|
|
65873
65949
|
state3.isRecovering = true;
|
|
65874
65950
|
sessionStateStore.cancelCountdown(sessionID);
|
|
65875
|
-
log(`[${
|
|
65951
|
+
log(`[${HOOK_NAME14}] Session marked as recovering`, { sessionID });
|
|
65876
65952
|
};
|
|
65877
65953
|
const markRecoveryComplete = (sessionID) => {
|
|
65878
65954
|
const state3 = sessionStateStore.getExistingState(sessionID);
|
|
65879
65955
|
if (state3) {
|
|
65880
65956
|
state3.isRecovering = false;
|
|
65881
|
-
log(`[${
|
|
65957
|
+
log(`[${HOOK_NAME14}] Session recovery complete`, { sessionID });
|
|
65882
65958
|
}
|
|
65883
65959
|
};
|
|
65884
65960
|
const handler = createTaskContinuationHandler({
|
|
@@ -65890,7 +65966,7 @@ function createTaskContinuationEnforcer(ctx, options = {}) {
|
|
|
65890
65966
|
});
|
|
65891
65967
|
const cancelAllCountdowns = () => {
|
|
65892
65968
|
sessionStateStore.cancelAllCountdowns();
|
|
65893
|
-
log(`[${
|
|
65969
|
+
log(`[${HOOK_NAME14}] All countdowns cancelled`);
|
|
65894
65970
|
};
|
|
65895
65971
|
return {
|
|
65896
65972
|
handler,
|
|
@@ -65900,7 +65976,7 @@ function createTaskContinuationEnforcer(ctx, options = {}) {
|
|
|
65900
65976
|
};
|
|
65901
65977
|
}
|
|
65902
65978
|
// src/hooks/task-edit-guard/constants.ts
|
|
65903
|
-
var
|
|
65979
|
+
var HOOK_NAME15 = "task-edit-guard";
|
|
65904
65980
|
var BLOCKED_PATTERNS = [
|
|
65905
65981
|
/sed\s+.*\.matrixx\/plans/,
|
|
65906
65982
|
/sed\s+.*\.matrixx\/tasks/,
|
|
@@ -65936,7 +66012,7 @@ function createTaskEditGuardHook(ctx) {
|
|
|
65936
66012
|
if (!hit)
|
|
65937
66013
|
return;
|
|
65938
66014
|
ctx.directory;
|
|
65939
|
-
log(`[${
|
|
66015
|
+
log(`[${HOOK_NAME15}] BLOCKED raw bash edit`, {
|
|
65940
66016
|
sessionID: input.sessionID,
|
|
65941
66017
|
command: cmd.slice(0, 120)
|
|
65942
66018
|
});
|
|
@@ -65945,7 +66021,7 @@ function createTaskEditGuardHook(ctx) {
|
|
|
65945
66021
|
};
|
|
65946
66022
|
}
|
|
65947
66023
|
// src/hooks/task-notepad/constants.ts
|
|
65948
|
-
var
|
|
66024
|
+
var HOOK_NAME16 = "task-notepad";
|
|
65949
66025
|
var PLAN_DIR_NAME = ".matrixx";
|
|
65950
66026
|
var PLANS_SUBDIR = "plans";
|
|
65951
66027
|
var NOTEPAD_DIR_NAME = "notepads";
|
|
@@ -65974,7 +66050,7 @@ function createTaskNotepadHook(ctx) {
|
|
|
65974
66050
|
const plan = resolveActivePlan(ctx.directory);
|
|
65975
66051
|
if (inProgress) {
|
|
65976
66052
|
if (!plan) {
|
|
65977
|
-
log(`[${
|
|
66053
|
+
log(`[${HOOK_NAME16}] No active plan, skipping notepad creation`, {
|
|
65978
66054
|
sessionID: input.sessionID
|
|
65979
66055
|
});
|
|
65980
66056
|
} else {
|
|
@@ -66001,7 +66077,7 @@ async function fetchTodos(ctx, sessionID) {
|
|
|
66001
66077
|
}
|
|
66002
66078
|
return [];
|
|
66003
66079
|
} catch (err) {
|
|
66004
|
-
log(`[${
|
|
66080
|
+
log(`[${HOOK_NAME16}] ${TODO_FETCH_ERROR_FRAGMENT}`, {
|
|
66005
66081
|
sessionID,
|
|
66006
66082
|
error: String(err)
|
|
66007
66083
|
});
|
|
@@ -66046,7 +66122,7 @@ function ensureNotepad(directory, planName, todo) {
|
|
|
66046
66122
|
mkdirSync16(dirname15(notepadPath), { recursive: true });
|
|
66047
66123
|
const content = renderNotepad(todo);
|
|
66048
66124
|
writeFileSync19(notepadPath, content, "utf-8");
|
|
66049
|
-
log(`[${
|
|
66125
|
+
log(`[${HOOK_NAME16}] Created task notepad`, { plan: planName, taskID: todo.id });
|
|
66050
66126
|
}
|
|
66051
66127
|
function appendCompletion(directory, planName, todo) {
|
|
66052
66128
|
const notepadPath = notepadPathFor(directory, planName, todo.id);
|
|
@@ -66062,7 +66138,7 @@ function appendCompletion(directory, planName, todo) {
|
|
|
66062
66138
|
- status: completed
|
|
66063
66139
|
`;
|
|
66064
66140
|
writeFileSync19(notepadPath, updated, "utf-8");
|
|
66065
|
-
log(`[${
|
|
66141
|
+
log(`[${HOOK_NAME16}] Appended completion to notepad`, { plan: planName, taskID: todo.id });
|
|
66066
66142
|
}
|
|
66067
66143
|
function notepadPathFor(directory, planName, taskID) {
|
|
66068
66144
|
return join52(directory, PLAN_DIR_NAME, NOTEPAD_DIR_NAME, planName, TASKS_SUBDIR, `${taskID}${NOTEPAD_EXTENSION}`);
|
|
@@ -66655,7 +66731,7 @@ function createThinkingBlockValidatorHook() {
|
|
|
66655
66731
|
init_logger();
|
|
66656
66732
|
|
|
66657
66733
|
// src/hooks/todo-continuation-enforcer/constants.ts
|
|
66658
|
-
var
|
|
66734
|
+
var HOOK_NAME17 = "todo-continuation-enforcer";
|
|
66659
66735
|
var DEFAULT_SKIP_AGENTS2 = ["oracle", "compaction"];
|
|
66660
66736
|
var CONTINUATION_PROMPT3 = `${createSystemDirective(SystemDirectiveTypes.TODO_CONTINUATION)}
|
|
66661
66737
|
|
|
@@ -66721,12 +66797,12 @@ async function injectContinuation2(args) {
|
|
|
66721
66797
|
} = args;
|
|
66722
66798
|
const state3 = sessionStateStore.getExistingState(sessionID);
|
|
66723
66799
|
if (state3?.isRecovering) {
|
|
66724
|
-
log(`[${
|
|
66800
|
+
log(`[${HOOK_NAME17}] Skipped injection: in recovery`, { sessionID });
|
|
66725
66801
|
return;
|
|
66726
66802
|
}
|
|
66727
66803
|
const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((task) => task.status === "running") : false;
|
|
66728
66804
|
if (hasRunningBgTasks) {
|
|
66729
|
-
log(`[${
|
|
66805
|
+
log(`[${HOOK_NAME17}] Skipped injection: background tasks running`, { sessionID });
|
|
66730
66806
|
return;
|
|
66731
66807
|
}
|
|
66732
66808
|
let todos = [];
|
|
@@ -66734,12 +66810,12 @@ async function injectContinuation2(args) {
|
|
|
66734
66810
|
const response = await ctx.client.session.todo({ path: { id: sessionID } });
|
|
66735
66811
|
todos = normalizeSDKResponse(response, [], { preferResponseOnMissingData: true });
|
|
66736
66812
|
} catch (error51) {
|
|
66737
|
-
log(`[${
|
|
66813
|
+
log(`[${HOOK_NAME17}] Failed to fetch todos`, { sessionID, error: String(error51) });
|
|
66738
66814
|
return;
|
|
66739
66815
|
}
|
|
66740
66816
|
const freshIncompleteCount = getIncompleteCount(todos);
|
|
66741
66817
|
if (freshIncompleteCount === 0) {
|
|
66742
|
-
log(`[${
|
|
66818
|
+
log(`[${HOOK_NAME17}] Skipped injection: no incomplete todos`, { sessionID });
|
|
66743
66819
|
return;
|
|
66744
66820
|
}
|
|
66745
66821
|
let agentName = resolvedInfo?.agent;
|
|
@@ -66762,11 +66838,11 @@ async function injectContinuation2(args) {
|
|
|
66762
66838
|
tools = tools ?? previousMessage?.tools;
|
|
66763
66839
|
}
|
|
66764
66840
|
if (agentName && skipAgents.some((s) => getAgentConfigKey(s) === getAgentConfigKey(agentName))) {
|
|
66765
|
-
log(`[${
|
|
66841
|
+
log(`[${HOOK_NAME17}] Skipped: agent in skipAgents list`, { sessionID, agent: agentName });
|
|
66766
66842
|
return;
|
|
66767
66843
|
}
|
|
66768
66844
|
if (!hasWritePermission2(tools)) {
|
|
66769
|
-
log(`[${
|
|
66845
|
+
log(`[${HOOK_NAME17}] Skipped: agent lacks write permission`, { sessionID, agent: agentName });
|
|
66770
66846
|
return;
|
|
66771
66847
|
}
|
|
66772
66848
|
const incompleteTodos = todos.filter((todo) => todo.status !== "completed" && todo.status !== "cancelled");
|
|
@@ -66783,7 +66859,7 @@ ${todoList}`;
|
|
|
66783
66859
|
injectionState.inFlight = true;
|
|
66784
66860
|
}
|
|
66785
66861
|
try {
|
|
66786
|
-
log(`[${
|
|
66862
|
+
log(`[${HOOK_NAME17}] Injecting continuation`, {
|
|
66787
66863
|
sessionID,
|
|
66788
66864
|
agent: agentName,
|
|
66789
66865
|
model,
|
|
@@ -66798,14 +66874,14 @@ ${todoList}`;
|
|
|
66798
66874
|
},
|
|
66799
66875
|
query: { directory: ctx.directory }
|
|
66800
66876
|
});
|
|
66801
|
-
log(`[${
|
|
66877
|
+
log(`[${HOOK_NAME17}] Injection successful`, { sessionID });
|
|
66802
66878
|
if (injectionState) {
|
|
66803
66879
|
injectionState.inFlight = false;
|
|
66804
66880
|
injectionState.lastInjectedAt = Date.now();
|
|
66805
66881
|
injectionState.consecutiveFailures = 0;
|
|
66806
66882
|
}
|
|
66807
66883
|
} catch (error51) {
|
|
66808
|
-
log(`[${
|
|
66884
|
+
log(`[${HOOK_NAME17}] Injection failed`, { sessionID, error: String(error51) });
|
|
66809
66885
|
if (injectionState) {
|
|
66810
66886
|
injectionState.inFlight = false;
|
|
66811
66887
|
injectionState.lastInjectedAt = Date.now();
|
|
@@ -66838,7 +66914,10 @@ function startCountdown2(args) {
|
|
|
66838
66914
|
sessionStateStore
|
|
66839
66915
|
} = args;
|
|
66840
66916
|
const state3 = sessionStateStore.getState(sessionID);
|
|
66841
|
-
|
|
66917
|
+
if (state3.countdownTimer || state3.countdownInterval) {
|
|
66918
|
+
log(`[${HOOK_NAME17}] Skipped: countdown already active`, { sessionID });
|
|
66919
|
+
return;
|
|
66920
|
+
}
|
|
66842
66921
|
let secondsRemaining = COUNTDOWN_SECONDS2;
|
|
66843
66922
|
showCountdownToast2(ctx, secondsRemaining, incompleteCount);
|
|
66844
66923
|
state3.countdownStartedAt = Date.now();
|
|
@@ -66859,7 +66938,7 @@ function startCountdown2(args) {
|
|
|
66859
66938
|
sessionStateStore
|
|
66860
66939
|
});
|
|
66861
66940
|
}, COUNTDOWN_SECONDS2 * 1000);
|
|
66862
|
-
log(`[${
|
|
66941
|
+
log(`[${HOOK_NAME17}] Countdown started`, {
|
|
66863
66942
|
sessionID,
|
|
66864
66943
|
seconds: COUNTDOWN_SECONDS2,
|
|
66865
66944
|
incompleteCount
|
|
@@ -66876,16 +66955,20 @@ async function handleSessionIdle2(args) {
|
|
|
66876
66955
|
skipAgents = DEFAULT_SKIP_AGENTS2,
|
|
66877
66956
|
isContinuationStopped
|
|
66878
66957
|
} = args;
|
|
66879
|
-
log(`[${
|
|
66958
|
+
log(`[${HOOK_NAME17}] session.idle`, { sessionID });
|
|
66880
66959
|
const state3 = sessionStateStore.getState(sessionID);
|
|
66960
|
+
if (state3.countdownTimer || state3.countdownInterval) {
|
|
66961
|
+
log(`[${HOOK_NAME17}] Skipped: countdown already active`, { sessionID });
|
|
66962
|
+
return;
|
|
66963
|
+
}
|
|
66881
66964
|
if (state3.isRecovering) {
|
|
66882
|
-
log(`[${
|
|
66965
|
+
log(`[${HOOK_NAME17}] Skipped: in recovery`, { sessionID });
|
|
66883
66966
|
return;
|
|
66884
66967
|
}
|
|
66885
66968
|
if (state3.abortDetectedAt) {
|
|
66886
66969
|
const timeSinceAbort = Date.now() - state3.abortDetectedAt;
|
|
66887
66970
|
if (timeSinceAbort < ABORT_WINDOW_MS2) {
|
|
66888
|
-
log(`[${
|
|
66971
|
+
log(`[${HOOK_NAME17}] Skipped: abort detected via event ${timeSinceAbort}ms ago`, { sessionID });
|
|
66889
66972
|
state3.abortDetectedAt = undefined;
|
|
66890
66973
|
return;
|
|
66891
66974
|
}
|
|
@@ -66893,7 +66976,7 @@ async function handleSessionIdle2(args) {
|
|
|
66893
66976
|
}
|
|
66894
66977
|
const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((task) => task.status === "running") : false;
|
|
66895
66978
|
if (hasRunningBgTasks) {
|
|
66896
|
-
log(`[${
|
|
66979
|
+
log(`[${HOOK_NAME17}] Skipped: background tasks running`, { sessionID });
|
|
66897
66980
|
return;
|
|
66898
66981
|
}
|
|
66899
66982
|
try {
|
|
@@ -66903,42 +66986,42 @@ async function handleSessionIdle2(args) {
|
|
|
66903
66986
|
});
|
|
66904
66987
|
const messages = normalizeSDKResponse(messagesResp, []);
|
|
66905
66988
|
if (isLastAssistantMessageAborted2(messages)) {
|
|
66906
|
-
log(`[${
|
|
66989
|
+
log(`[${HOOK_NAME17}] Skipped: last assistant message was aborted (API fallback)`, { sessionID });
|
|
66907
66990
|
return;
|
|
66908
66991
|
}
|
|
66909
66992
|
} catch (error51) {
|
|
66910
|
-
log(`[${
|
|
66993
|
+
log(`[${HOOK_NAME17}] Messages fetch failed, continuing`, { sessionID, error: String(error51) });
|
|
66911
66994
|
}
|
|
66912
66995
|
let todos = [];
|
|
66913
66996
|
try {
|
|
66914
66997
|
const response = await ctx.client.session.todo({ path: { id: sessionID } });
|
|
66915
66998
|
todos = normalizeSDKResponse(response, [], { preferResponseOnMissingData: true });
|
|
66916
66999
|
} catch (error51) {
|
|
66917
|
-
log(`[${
|
|
67000
|
+
log(`[${HOOK_NAME17}] Todo fetch failed`, { sessionID, error: String(error51) });
|
|
66918
67001
|
return;
|
|
66919
67002
|
}
|
|
66920
67003
|
if (!todos || todos.length === 0) {
|
|
66921
|
-
log(`[${
|
|
67004
|
+
log(`[${HOOK_NAME17}] No todos`, { sessionID });
|
|
66922
67005
|
return;
|
|
66923
67006
|
}
|
|
66924
67007
|
const incompleteCount = getIncompleteCount(todos);
|
|
66925
67008
|
if (incompleteCount === 0) {
|
|
66926
|
-
log(`[${
|
|
67009
|
+
log(`[${HOOK_NAME17}] All todos complete`, { sessionID, total: todos.length });
|
|
66927
67010
|
return;
|
|
66928
67011
|
}
|
|
66929
67012
|
if (state3.inFlight) {
|
|
66930
|
-
log(`[${
|
|
67013
|
+
log(`[${HOOK_NAME17}] Skipped: injection in flight`, { sessionID });
|
|
66931
67014
|
return;
|
|
66932
67015
|
}
|
|
66933
67016
|
if (state3.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES2 && state3.lastInjectedAt && Date.now() - state3.lastInjectedAt >= FAILURE_RESET_WINDOW_MS2) {
|
|
66934
67017
|
state3.consecutiveFailures = 0;
|
|
66935
|
-
log(`[${
|
|
67018
|
+
log(`[${HOOK_NAME17}] Reset consecutive failures after recovery window`, {
|
|
66936
67019
|
sessionID,
|
|
66937
67020
|
failureResetWindowMs: FAILURE_RESET_WINDOW_MS2
|
|
66938
67021
|
});
|
|
66939
67022
|
}
|
|
66940
67023
|
if (state3.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES2) {
|
|
66941
|
-
log(`[${
|
|
67024
|
+
log(`[${HOOK_NAME17}] Skipped: max consecutive failures reached`, {
|
|
66942
67025
|
sessionID,
|
|
66943
67026
|
consecutiveFailures: state3.consecutiveFailures,
|
|
66944
67027
|
maxConsecutiveFailures: MAX_CONSECUTIVE_FAILURES2
|
|
@@ -66947,7 +67030,7 @@ async function handleSessionIdle2(args) {
|
|
|
66947
67030
|
}
|
|
66948
67031
|
const effectiveCooldown = CONTINUATION_COOLDOWN_MS3 * 2 ** Math.min(state3.consecutiveFailures, 5);
|
|
66949
67032
|
if (state3.lastInjectedAt && Date.now() - state3.lastInjectedAt < effectiveCooldown) {
|
|
66950
|
-
log(`[${
|
|
67033
|
+
log(`[${HOOK_NAME17}] Skipped: cooldown active`, {
|
|
66951
67034
|
sessionID,
|
|
66952
67035
|
effectiveCooldown,
|
|
66953
67036
|
consecutiveFailures: state3.consecutiveFailures
|
|
@@ -66977,20 +67060,20 @@ async function handleSessionIdle2(args) {
|
|
|
66977
67060
|
}
|
|
66978
67061
|
}
|
|
66979
67062
|
} catch (error51) {
|
|
66980
|
-
log(`[${
|
|
67063
|
+
log(`[${HOOK_NAME17}] Failed to fetch messages for agent check`, { sessionID, error: String(error51) });
|
|
66981
67064
|
}
|
|
66982
|
-
log(`[${
|
|
67065
|
+
log(`[${HOOK_NAME17}] Agent check`, { sessionID, agentName: resolvedInfo?.agent, skipAgents, hasCompactionMessage });
|
|
66983
67066
|
const resolvedAgentName = resolvedInfo?.agent;
|
|
66984
67067
|
if (resolvedAgentName && skipAgents.some((s) => getAgentConfigKey(s) === getAgentConfigKey(resolvedAgentName))) {
|
|
66985
|
-
log(`[${
|
|
67068
|
+
log(`[${HOOK_NAME17}] Skipped: agent in skipAgents list`, { sessionID, agent: resolvedAgentName });
|
|
66986
67069
|
return;
|
|
66987
67070
|
}
|
|
66988
67071
|
if (hasCompactionMessage && !resolvedInfo?.agent) {
|
|
66989
|
-
log(`[${
|
|
67072
|
+
log(`[${HOOK_NAME17}] Skipped: compaction occurred but no agent info resolved`, { sessionID });
|
|
66990
67073
|
return;
|
|
66991
67074
|
}
|
|
66992
67075
|
if (isContinuationStopped?.(sessionID)) {
|
|
66993
|
-
log(`[${
|
|
67076
|
+
log(`[${HOOK_NAME17}] Skipped: continuation stopped for session`, { sessionID });
|
|
66994
67077
|
return;
|
|
66995
67078
|
}
|
|
66996
67079
|
startCountdown2({
|
|
@@ -67020,7 +67103,7 @@ function handleNonIdleEvent2(args) {
|
|
|
67020
67103
|
if (state3?.countdownStartedAt) {
|
|
67021
67104
|
const elapsed = Date.now() - state3.countdownStartedAt;
|
|
67022
67105
|
if (elapsed < COUNTDOWN_GRACE_PERIOD_MS2) {
|
|
67023
|
-
log(`[${
|
|
67106
|
+
log(`[${HOOK_NAME17}] Ignoring user message in grace period`, { sessionID, elapsed });
|
|
67024
67107
|
return;
|
|
67025
67108
|
}
|
|
67026
67109
|
}
|
|
@@ -67064,7 +67147,7 @@ function handleNonIdleEvent2(args) {
|
|
|
67064
67147
|
const sessionInfo = properties?.info;
|
|
67065
67148
|
if (sessionInfo?.id) {
|
|
67066
67149
|
sessionStateStore.cleanup(sessionInfo.id);
|
|
67067
|
-
log(`[${
|
|
67150
|
+
log(`[${HOOK_NAME17}] Session deleted: cleaned up`, { sessionID: sessionInfo.id });
|
|
67068
67151
|
}
|
|
67069
67152
|
return;
|
|
67070
67153
|
}
|
|
@@ -67089,10 +67172,10 @@ function createTodoContinuationHandler(args) {
|
|
|
67089
67172
|
if (error51?.name === "MessageAbortedError" || error51?.name === "AbortError") {
|
|
67090
67173
|
const state3 = sessionStateStore.getState(sessionID);
|
|
67091
67174
|
state3.abortDetectedAt = Date.now();
|
|
67092
|
-
log(`[${
|
|
67175
|
+
log(`[${HOOK_NAME17}] Abort detected via session.error`, { sessionID, errorName: error51.name });
|
|
67093
67176
|
}
|
|
67094
67177
|
sessionStateStore.cancelCountdown(sessionID);
|
|
67095
|
-
log(`[${
|
|
67178
|
+
log(`[${HOOK_NAME17}] session.error`, { sessionID });
|
|
67096
67179
|
return;
|
|
67097
67180
|
}
|
|
67098
67181
|
if (event.type === "session.idle") {
|
|
@@ -67207,13 +67290,13 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
67207
67290
|
const state3 = sessionStateStore.getState(sessionID);
|
|
67208
67291
|
state3.isRecovering = true;
|
|
67209
67292
|
sessionStateStore.cancelCountdown(sessionID);
|
|
67210
|
-
log(`[${
|
|
67293
|
+
log(`[${HOOK_NAME17}] Session marked as recovering`, { sessionID });
|
|
67211
67294
|
};
|
|
67212
67295
|
const markRecoveryComplete = (sessionID) => {
|
|
67213
67296
|
const state3 = sessionStateStore.getExistingState(sessionID);
|
|
67214
67297
|
if (state3) {
|
|
67215
67298
|
state3.isRecovering = false;
|
|
67216
|
-
log(`[${
|
|
67299
|
+
log(`[${HOOK_NAME17}] Session recovery complete`, { sessionID });
|
|
67217
67300
|
}
|
|
67218
67301
|
};
|
|
67219
67302
|
const handler = createTodoContinuationHandler({
|
|
@@ -67225,7 +67308,7 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
67225
67308
|
});
|
|
67226
67309
|
const cancelAllCountdowns = () => {
|
|
67227
67310
|
sessionStateStore.cancelAllCountdowns();
|
|
67228
|
-
log(`[${
|
|
67311
|
+
log(`[${HOOK_NAME17}] All countdowns cancelled`);
|
|
67229
67312
|
};
|
|
67230
67313
|
return {
|
|
67231
67314
|
handler,
|
|
@@ -67449,7 +67532,7 @@ This is a reminder only. No automatic action was taken.`;
|
|
|
67449
67532
|
}
|
|
67450
67533
|
// src/hooks/unstable-agent-babysitter/unstable-agent-babysitter-hook.ts
|
|
67451
67534
|
init_logger();
|
|
67452
|
-
var
|
|
67535
|
+
var HOOK_NAME18 = "unstable-agent-babysitter";
|
|
67453
67536
|
var DEFAULT_TIMEOUT_MS = 120000;
|
|
67454
67537
|
var COOLDOWN_MS = 5 * 60 * 1000;
|
|
67455
67538
|
async function resolveMainSessionTarget(ctx, sessionID) {
|
|
@@ -67469,7 +67552,7 @@ async function resolveMainSessionTarget(ctx, sessionID) {
|
|
|
67469
67552
|
}
|
|
67470
67553
|
}
|
|
67471
67554
|
} catch (error51) {
|
|
67472
|
-
log(`[${
|
|
67555
|
+
log(`[${HOOK_NAME18}] Failed to resolve main session agent`, { sessionID, error: String(error51) });
|
|
67473
67556
|
}
|
|
67474
67557
|
return { agent, model };
|
|
67475
67558
|
}
|
|
@@ -67502,7 +67585,7 @@ async function getThinkingSummary(ctx, sessionID) {
|
|
|
67502
67585
|
return combined;
|
|
67503
67586
|
return `${combined.slice(0, THINKING_SUMMARY_MAX_CHARS)}...`;
|
|
67504
67587
|
} catch (error51) {
|
|
67505
|
-
log(`[${
|
|
67588
|
+
log(`[${HOOK_NAME18}] Failed to fetch thinking summary`, { sessionID, error: String(error51) });
|
|
67506
67589
|
return null;
|
|
67507
67590
|
}
|
|
67508
67591
|
}
|
|
@@ -67551,9 +67634,9 @@ function createUnstableAgentBabysitterHook(ctx, options) {
|
|
|
67551
67634
|
query: { directory: ctx.directory }
|
|
67552
67635
|
});
|
|
67553
67636
|
reminderCooldowns.set(task.id, now);
|
|
67554
|
-
log(`[${
|
|
67637
|
+
log(`[${HOOK_NAME18}] Reminder injected`, { taskId: task.id, sessionID: mainSessionID });
|
|
67555
67638
|
} catch (error51) {
|
|
67556
|
-
log(`[${
|
|
67639
|
+
log(`[${HOOK_NAME18}] Reminder injection failed`, { taskId: task.id, error: String(error51) });
|
|
67557
67640
|
}
|
|
67558
67641
|
}
|
|
67559
67642
|
};
|
|
@@ -92343,6 +92426,7 @@ function createToolGuardHooks(args) {
|
|
|
92343
92426
|
const envFileWriteGuard = isHookEnabled("env-file-write-guard") ? safeHook("env-file-write-guard", () => createEnvFileWriteGuardHook(pluginConfig.security?.env_file_guard)) : null;
|
|
92344
92427
|
const jsonErrorRecovery = isHookEnabled("json-error-recovery") ? safeHook("json-error-recovery", () => createJsonErrorRecoveryHook(ctx)) : null;
|
|
92345
92428
|
const bashFileReadGuard = isHookEnabled("bash-file-read-guard") ? safeHook("bash-file-read-guard", () => createBashFileReadGuardHook()) : null;
|
|
92429
|
+
const contextModeEnforcer = isHookEnabled("context-mode-enforcer") ? safeHook("context-mode-enforcer", () => createContextModeEnforcerHook(pluginConfig)) : null;
|
|
92346
92430
|
const readImageResizer = isHookEnabled("read-image-resizer") ? safeHook("read-image-resizer", () => createReadImageResizerHook(ctx)) : null;
|
|
92347
92431
|
const webfetchRedirectGuard = isHookEnabled("webfetch-redirect-guard") ? safeHook("webfetch-redirect-guard", () => createWebFetchRedirectGuardHook(ctx)) : null;
|
|
92348
92432
|
const hashlineEditDiffEnhancer = isHookEnabled("hashline-edit-diff-enhancer") ? safeHook("hashline-edit-diff-enhancer", () => createHashlineEditDiffEnhancerHook({ hashline_edit: { enabled: pluginConfig.experimental?.hashline_edit ?? false } })) : null;
|
|
@@ -92364,6 +92448,7 @@ function createToolGuardHooks(args) {
|
|
|
92364
92448
|
envFileWriteGuard,
|
|
92365
92449
|
jsonErrorRecovery,
|
|
92366
92450
|
bashFileReadGuard,
|
|
92451
|
+
contextModeEnforcer,
|
|
92367
92452
|
readImageResizer,
|
|
92368
92453
|
webfetchRedirectGuard,
|
|
92369
92454
|
hashlineEditDiffEnhancer,
|
|
@@ -95397,14 +95482,14 @@ function buildCompactContextDisciplineSection(hasContextMode = false) {
|
|
|
95397
95482
|
| Docs / Web | ctx_fetch_and_index -> ctx_search |
|
|
95398
95483
|
| Compression | compress when ctx_stats > 40% or 10+ tool calls |
|
|
95399
95484
|
|
|
95400
|
-
Edits need prior read for LINE#ID \u2014 read\u2192edit chain exempt.
|
|
95485
|
+
Edits need prior read for LINE#ID \u2014 read\u2192edit chain exempt. MUST use ctx_* when available \u2014 raw grep/read is forbidden for analysis.`;
|
|
95401
95486
|
}
|
|
95402
95487
|
function buildExploreDisciplineSection(hasContextMode = false, hasHeadroom = false) {
|
|
95403
95488
|
if (!hasContextMode && !hasHeadroom)
|
|
95404
95489
|
return "";
|
|
95405
95490
|
const parts = [];
|
|
95406
95491
|
if (hasContextMode) {
|
|
95407
|
-
parts.push("
|
|
95492
|
+
parts.push("MUST use ctx_search for indexed hits \u2192 grep/glob fallback ONLY when ctx_* unavailable; use ctx_batch_execute / ctx_execute for multi-file analysis; use ctx_fetch_and_index for docs/web \u2192 ctx_search. Raw grep/read/glob for analysis is forbidden when ctx_* is available.");
|
|
95408
95493
|
}
|
|
95409
95494
|
if (hasHeadroom) {
|
|
95410
95495
|
parts.push("Use headroom_retrieve / headroom_search for compressed history \u2014 NEVER re-read full history.");
|
|
@@ -102738,6 +102823,12 @@ var BuiltinCommandNameSchema = exports_external.enum([
|
|
|
102738
102823
|
var CommentCheckerConfigSchema = exports_external.object({
|
|
102739
102824
|
custom_prompt: exports_external.string().optional()
|
|
102740
102825
|
});
|
|
102826
|
+
// src/config/schema/context-mode.ts
|
|
102827
|
+
var ContextModeConfigSchema = exports_external.object({
|
|
102828
|
+
enabled: exports_external.boolean().default(true),
|
|
102829
|
+
enforce: exports_external.boolean().default(false),
|
|
102830
|
+
blocked_tools: exports_external.array(exports_external.string()).default(["read", "grep", "glob"])
|
|
102831
|
+
});
|
|
102741
102832
|
// src/config/schema/evolution.ts
|
|
102742
102833
|
var EvolutionWatcherConfigSchema = exports_external.object({
|
|
102743
102834
|
maxArgChars: exports_external.number().int().min(1).default(4000),
|
|
@@ -102887,6 +102978,8 @@ var HookNameSchema = exports_external.enum([
|
|
|
102887
102978
|
"evolution-compressor",
|
|
102888
102979
|
"evolution-quality-gate",
|
|
102889
102980
|
"evolution-hitl",
|
|
102981
|
+
"context-mode-enforcer",
|
|
102982
|
+
"task-edit-guard",
|
|
102890
102983
|
"task-edit-guard"
|
|
102891
102984
|
]);
|
|
102892
102985
|
// src/config/schema/matrix-loop.ts
|
|
@@ -103076,6 +103169,7 @@ var MatrixxConfigSchema = exports_external.object({
|
|
|
103076
103169
|
agent_definitions: AgentDefinitionsConfigSchema.optional(),
|
|
103077
103170
|
matrixx_self_config: MatrixxSelfConfigSkillConfigSchema.optional(),
|
|
103078
103171
|
headroom: HeadroomConfigSchema.optional(),
|
|
103172
|
+
context_mode: ContextModeConfigSchema.optional(),
|
|
103079
103173
|
rtk: RtkConfigSchema.optional(),
|
|
103080
103174
|
evolution: EvolutionConfigSchema.optional(),
|
|
103081
103175
|
_migrations: exports_external.array(exports_external.string()).optional()
|
|
@@ -103638,22 +103732,22 @@ function createToolExecuteAfterHandler2(args) {
|
|
|
103638
103732
|
|
|
103639
103733
|
// src/features/assembly-state/manager.ts
|
|
103640
103734
|
init_logger();
|
|
103641
|
-
var
|
|
103735
|
+
var HOOK_NAME19 = "assembly-state";
|
|
103642
103736
|
var disabledSessions = new Set;
|
|
103643
103737
|
function isAssemblyDisabled(sessionID) {
|
|
103644
103738
|
return disabledSessions.has(sessionID);
|
|
103645
103739
|
}
|
|
103646
103740
|
function disableAssembly(sessionID) {
|
|
103647
103741
|
disabledSessions.add(sessionID);
|
|
103648
|
-
log(`[${
|
|
103742
|
+
log(`[${HOOK_NAME19}] Assembly disabled for session`, { sessionID });
|
|
103649
103743
|
disabledSessions.add(sessionID);
|
|
103650
|
-
log(`[${
|
|
103744
|
+
log(`[${HOOK_NAME19}] Consensus disabled for session`, { sessionID });
|
|
103651
103745
|
}
|
|
103652
103746
|
function enableAssembly(sessionID) {
|
|
103653
103747
|
disabledSessions.delete(sessionID);
|
|
103654
|
-
log(`[${
|
|
103748
|
+
log(`[${HOOK_NAME19}] Assembly enabled for session`, { sessionID });
|
|
103655
103749
|
disabledSessions.delete(sessionID);
|
|
103656
|
-
log(`[${
|
|
103750
|
+
log(`[${HOOK_NAME19}] Consensus enabled for session`, { sessionID });
|
|
103657
103751
|
}
|
|
103658
103752
|
// src/plugin/session-agent-resolver.ts
|
|
103659
103753
|
async function resolveSessionAgent(client2, sessionId, timeoutMs = 5000) {
|
|
@@ -103696,6 +103790,7 @@ function createToolExecuteBeforeHandler2(args) {
|
|
|
103696
103790
|
const writeExistingFileGuardHook = hooks2.writeExistingFileGuard?.["tool.execute.before"];
|
|
103697
103791
|
const tasksTodowriteDisablerHook = hooks2.tasksTodowriteDisabler?.["tool.execute.before"];
|
|
103698
103792
|
const oracleMdOnlyHook = hooks2.oracleMdOnly?.["tool.execute.before"];
|
|
103793
|
+
const contextModeEnforcerHook = hooks2.contextModeEnforcer?.["tool.execute.before"];
|
|
103699
103794
|
const nonInteractiveEnvHook = hooks2.nonInteractiveEnv?.["tool.execute.before"];
|
|
103700
103795
|
const bashFileReadGuardHook = hooks2.bashFileReadGuard?.["tool.execute.before"];
|
|
103701
103796
|
const questionLabelTruncatorHook = hooks2.questionLabelTruncator?.["tool.execute.before"];
|
|
@@ -103715,7 +103810,8 @@ function createToolExecuteBeforeHandler2(args) {
|
|
|
103715
103810
|
envFileWriteGuardHook?.(input, output),
|
|
103716
103811
|
writeExistingFileGuardHook?.(input, output),
|
|
103717
103812
|
tasksTodowriteDisablerHook?.(input, output),
|
|
103718
|
-
oracleMdOnlyHook?.(input, output)
|
|
103813
|
+
oracleMdOnlyHook?.(input, output),
|
|
103814
|
+
contextModeEnforcerHook?.(input, output)
|
|
103719
103815
|
]);
|
|
103720
103816
|
let resolvePromise;
|
|
103721
103817
|
if (input.tool === "task") {
|