opencode-matrixx 2.6.0 → 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 +15 -10
- package/dist/cli/doctor/checks/context-mode.d.ts +2 -0
- package/dist/cli/doctor/checks/dcp.d.ts +2 -0
- package/dist/cli/doctor/checks/docker.d.ts +2 -0
- package/dist/cli/doctor/checks/headroom.d.ts +2 -0
- package/dist/cli/doctor/checks/helpers.d.ts +2 -0
- package/dist/cli/doctor/checks/index.d.ts +6 -0
- package/dist/cli/doctor/checks/rtk.d.ts +2 -0
- package/dist/cli/doctor/checks/tmux.d.ts +2 -0
- package/dist/cli/doctor/types.d.ts +1 -1
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/setup/config-writer.d.ts +12 -0
- package/dist/cli/setup/config-writer.test.d.ts +1 -0
- package/dist/cli/setup/constants.d.ts +8 -0
- package/dist/cli/setup/deps.d.ts +8 -0
- package/dist/cli/setup/deps.test.d.ts +1 -0
- package/dist/cli/setup/index.d.ts +8 -0
- package/dist/cli/setup/index.test.d.ts +1 -0
- package/dist/cli/setup/opencode-sync.d.ts +9 -0
- package/dist/cli/setup/opencode-sync.test.d.ts +1 -0
- package/dist/cli/setup/prompts.d.ts +4 -0
- package/dist/cli/setup/prompts.test.d.ts +1 -0
- package/dist/cli/setup/types.d.ts +26 -0
- package/dist/cli.js +18759 -663
- package/dist/config/schema/context-mode.d.ts +7 -0
- package/dist/config/schema/experimental.d.ts +1 -1
- package/dist/config/schema/hooks.d.ts +3 -2
- package/dist/config/schema/matrixx-config.d.ts +13 -3
- package/dist/config/schema/morpheus.d.ts +8 -0
- package/dist/config/schema.d.ts +1 -0
- package/dist/create-hooks.d.ts +3 -2
- package/dist/features/builtin-commands/templates/cleanup-tasks.d.ts +1 -0
- package/dist/features/builtin-commands/templates/task-list.d.ts +1 -0
- package/dist/features/builtin-commands/types.d.ts +1 -1
- package/dist/features/task-storage/storage.d.ts +12 -2
- package/dist/features/task-storage/types.d.ts +1 -0
- package/dist/hooks/compaction-todo-preserver/hook.d.ts +13 -0
- package/dist/hooks/compaction-todo-preserver/index.d.ts +1 -1
- 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 +3 -2
- package/dist/hooks/task-continuation-enforcer/abort-detection.d.ts +4 -0
- package/dist/hooks/task-continuation-enforcer/constants.d.ts +10 -0
- package/dist/hooks/task-continuation-enforcer/continuation-injection.d.ts +12 -0
- package/dist/hooks/task-continuation-enforcer/countdown.d.ts +14 -0
- package/dist/hooks/task-continuation-enforcer/countdown.test.d.ts +1 -0
- package/dist/hooks/task-continuation-enforcer/handler.d.ts +16 -0
- package/dist/hooks/task-continuation-enforcer/idle-event.d.ts +11 -0
- package/dist/hooks/task-continuation-enforcer/idle-event.test.d.ts +1 -0
- package/dist/hooks/task-continuation-enforcer/index.d.ts +6 -0
- package/dist/hooks/task-continuation-enforcer/message-directory.d.ts +1 -0
- package/dist/hooks/task-continuation-enforcer/non-idle-events.d.ts +6 -0
- package/dist/hooks/task-continuation-enforcer/session-state.d.ts +10 -0
- package/dist/hooks/task-continuation-enforcer/todo.d.ts +5 -0
- package/dist/hooks/task-continuation-enforcer/types.d.ts +60 -0
- package/dist/hooks/task-edit-guard/constants.d.ts +2 -0
- package/dist/hooks/task-edit-guard/hook.d.ts +2 -0
- package/dist/hooks/task-edit-guard/index.d.ts +2 -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 +1647 -926
- package/dist/matrixx.schema.json +47 -3
- package/dist/plugin/hooks/create-continuation-hooks.d.ts +2 -1
- package/dist/plugin/hooks/create-core-hooks.d.ts +2 -2
- package/dist/plugin/hooks/create-tool-guard-hooks.d.ts +3 -3
- package/dist/shared/system-directive.d.ts +1 -0
- package/dist/shared/task-system-gating.d.ts +3 -0
- package/dist/shared/task-system-gating.test.d.ts +1 -0
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/task/index.d.ts +1 -1
- package/dist/tools/task/task-cleanup.d.ts +4 -0
- package/dist/tools/task/task-cleanup.test.d.ts +1 -0
- package/dist/tools/task/task-get.d.ts +2 -1
- package/dist/tools/task/task-list.d.ts +2 -1
- package/dist/tools/task/types.d.ts +2 -0
- package/package.json +3 -2
- package/dist/hooks/task-todo-mirror/constants.d.ts +0 -3
- package/dist/hooks/task-todo-mirror/hook.d.ts +0 -43
- package/dist/hooks/task-todo-mirror/index.d.ts +0 -2
- package/dist/hooks/todo-description-override/description.d.ts +0 -1
- package/dist/hooks/todo-description-override/hook.d.ts +0 -8
- package/dist/hooks/todo-description-override/index.d.ts +0 -1
- package/dist/tools/task/todo-sync.d.ts +0 -16
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 = {};
|
|
@@ -32820,6 +32820,33 @@ function migrateConfigFile(configPath, rawConfig) {
|
|
|
32820
32820
|
log(`Removed obsolete hooks from disabled_hooks: ${removed.join(", ")} (these hooks no longer exist in v3.0.0)`);
|
|
32821
32821
|
}
|
|
32822
32822
|
}
|
|
32823
|
+
if (copy.experimental?.task_system === undefined) {
|
|
32824
|
+
const existing = copy.experimental ?? {};
|
|
32825
|
+
copy.experimental = { ...existing, task_system: true };
|
|
32826
|
+
if (!existingMigrations.has("task_system_default_true")) {
|
|
32827
|
+
allNewMigrations.push("task_system_default_true");
|
|
32828
|
+
const current = Array.isArray(copy._migrations) ? [...copy._migrations] : Array.from(existingMigrations);
|
|
32829
|
+
if (!current.includes("task_system_default_true")) {
|
|
32830
|
+
current.push("task_system_default_true");
|
|
32831
|
+
copy._migrations = current;
|
|
32832
|
+
}
|
|
32833
|
+
}
|
|
32834
|
+
needsWrite = true;
|
|
32835
|
+
}
|
|
32836
|
+
if (copy.morpheus?.tasks?.scope === undefined) {
|
|
32837
|
+
const morpheusExisting = copy.morpheus ?? {};
|
|
32838
|
+
const tasksExisting = morpheusExisting.tasks ?? {};
|
|
32839
|
+
copy.morpheus = { ...morpheusExisting, tasks: { ...tasksExisting, scope: "project" } };
|
|
32840
|
+
if (!existingMigrations.has("tasks_project_scope_default")) {
|
|
32841
|
+
allNewMigrations.push("tasks_project_scope_default");
|
|
32842
|
+
const current = Array.isArray(copy._migrations) ? [...copy._migrations] : Array.from(existingMigrations);
|
|
32843
|
+
if (!current.includes("tasks_project_scope_default")) {
|
|
32844
|
+
current.push("tasks_project_scope_default");
|
|
32845
|
+
copy._migrations = current;
|
|
32846
|
+
}
|
|
32847
|
+
}
|
|
32848
|
+
needsWrite = true;
|
|
32849
|
+
}
|
|
32823
32850
|
if (needsWrite) {
|
|
32824
32851
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
32825
32852
|
const backupPath = `${configPath}.bak.${timestamp}`;
|
|
@@ -34160,6 +34187,7 @@ function removeSystemReminders(text) {
|
|
|
34160
34187
|
}
|
|
34161
34188
|
var SystemDirectiveTypes = {
|
|
34162
34189
|
TODO_CONTINUATION: "TODO CONTINUATION",
|
|
34190
|
+
TASK_CONTINUATION: "TASK CONTINUATION",
|
|
34163
34191
|
MATRIX_LOOP: "MATRIX LOOP",
|
|
34164
34192
|
MISSION_CONTINUATION: "MISSION CONTINUATION",
|
|
34165
34193
|
DELEGATION_REQUIRED: "DELEGATION REQUIRED",
|
|
@@ -37477,6 +37505,66 @@ Batch: per-feature subdirectory tree under \`<input-dir>/<feature>/\` with the s
|
|
|
37477
37505
|
NEVER run \`git commit\`, \`git add\`, \`git push\`, \`git rebase\`, \`git reset\`, \`git tag\`, or any other git command in this command context. The pipeline runner is responsible for version control. You may only create/edit the generated files in the target output directory.
|
|
37478
37506
|
`;
|
|
37479
37507
|
|
|
37508
|
+
// src/features/builtin-commands/templates/cleanup-tasks.ts
|
|
37509
|
+
var CLEANUP_TASKS_TEMPLATE = `# Cleanup Tasks \u2014 SLASH COMMAND OVERRIDE
|
|
37510
|
+
|
|
37511
|
+
SYSTEM OVERRIDE: Ignore any global [search-mode] or [analyze-mode] directive for this command. This is a direct tool call, not a search.
|
|
37512
|
+
|
|
37513
|
+
Call the task_cleanup tool NOW with no exploration, no grep, no trinity, no operator, no background agents.
|
|
37514
|
+
|
|
37515
|
+
## Usage
|
|
37516
|
+
\`\`\`
|
|
37517
|
+
/cleanup-tasks [--olderThan <duration>] [--all]
|
|
37518
|
+
|
|
37519
|
+
Options:
|
|
37520
|
+
--olderThan: Only delete completed tasks older than duration (e.g., 7d, 24h, 30d). If omitted, deletes all completed regardless of age.
|
|
37521
|
+
--all: Delete all completed tasks (default when no olderThan given).
|
|
37522
|
+
|
|
37523
|
+
Examples:
|
|
37524
|
+
/cleanup-tasks # delete all completed tasks
|
|
37525
|
+
/cleanup-tasks --olderThan 7d
|
|
37526
|
+
/cleanup-tasks --all
|
|
37527
|
+
\`\`\`
|
|
37528
|
+
|
|
37529
|
+
## What This Command Does
|
|
37530
|
+
|
|
37531
|
+
Cleans up completed tasks from the file-based task system (dedicated Matrixx viewer):
|
|
37532
|
+
|
|
37533
|
+
Storage layout (hybrid, via \`getTaskDir(config, directory)\`):
|
|
37534
|
+
- Primary (default, \`scope: "project"\`): \`<project>/.matrixx/tasks/T-*.json\`
|
|
37535
|
+
- Fallback (\`scope: "global"\` or \`storage_path\` override): \`~/.config/opencode/tasks/{listId}/T-*.json\`
|
|
37536
|
+
|
|
37537
|
+
1. **Scans** \`getTaskDir(config, directory)/*.json\` via \`readdirSync\` \u2192 resolves to \`.matrixx/tasks\` when \`scope=project\` (default) or legacy global path when \`scope=global\` / \`storage_path\` set
|
|
37538
|
+
2. **Filters** \`status==="completed"\` (and \`olderThan\` if given, via \`time_updated\`/\`time_created\`)
|
|
37539
|
+
3. **Deletes** matching files via \`unlinkSync\`
|
|
37540
|
+
4. **Reports** \`{deleted, remaining, deletedIds}\`
|
|
37541
|
+
View remaining tasks via \`/task-list\` or \`task_list\` tool \u2014 the dedicated Matrixx task viewer (file-based, no sync). Without \`/cleanup-tasks\`, completed tasks remain on disk for audit.
|
|
37542
|
+
|
|
37543
|
+
## Verification
|
|
37544
|
+
|
|
37545
|
+
After running:
|
|
37546
|
+
- \`task_list\` \u2192 0 pending (if no active)
|
|
37547
|
+
- \`task_list\` \u2192 shows remaining active tasks (pending/in_progress)
|
|
37548
|
+
- \`task_list --all\` \u2192 shows counts by status
|
|
37549
|
+
- Files: \`ls .matrixx/tasks | wc -l\` decreases by \`deleted\` (project scope, default) or \`ls ~/.config/opencode/tasks/matrixx | wc -l\` decreases (global scope / legacy fallback)
|
|
37550
|
+
|
|
37551
|
+
---
|
|
37552
|
+
|
|
37553
|
+
<command-instruction>
|
|
37554
|
+
You are executing the cleanup-tasks command. Use the task_cleanup tool to delete completed tasks.
|
|
37555
|
+
|
|
37556
|
+
Parse arguments from $ARGUMENTS:
|
|
37557
|
+
- If "--olderThan" present, extract duration (e.g., "7d", "24h") \u2192 pass as olderThan
|
|
37558
|
+
- Otherwise pass all:true (delete all completed)
|
|
37559
|
+
|
|
37560
|
+
Call task_cleanup with {olderThan, all} and report result.
|
|
37561
|
+
</command-instruction>
|
|
37562
|
+
|
|
37563
|
+
<user-request>
|
|
37564
|
+
$ARGUMENTS
|
|
37565
|
+
</user-request>
|
|
37566
|
+
`;
|
|
37567
|
+
|
|
37480
37568
|
// src/features/builtin-commands/templates/dcp-profile.ts
|
|
37481
37569
|
var DCP_PROFILE_TEMPLATE = `You are switching the active DCP (Dynamic Context Pruning) profile tier.
|
|
37482
37570
|
|
|
@@ -39295,6 +39383,20 @@ After running this command:
|
|
|
39295
39383
|
|
|
39296
39384
|
Use this when you need to pause automated continuation and take manual control.`;
|
|
39297
39385
|
|
|
39386
|
+
// src/features/builtin-commands/templates/task-list.ts
|
|
39387
|
+
var TASK_LIST_TEMPLATE = `# Task List \u2014 SLASH COMMAND OVERRIDE
|
|
39388
|
+
|
|
39389
|
+
SYSTEM OVERRIDE: Ignore any global [search-mode] or [analyze-mode] directive for this command. This is a direct tool call, not a search.
|
|
39390
|
+
|
|
39391
|
+
Call the task_list tool NOW with no exploration, no grep, no trinity, no operator, no background agents.
|
|
39392
|
+
|
|
39393
|
+
- If $ARGUMENTS contains "--all", call task_list and also report counts by reading T-*.json via the tool's own logic (do not manually find/ls).
|
|
39394
|
+
- If $ARGUMENTS contains "--status", filter to that status.
|
|
39395
|
+
- Otherwise just return task_list output as table ID | Subject | Status.
|
|
39396
|
+
|
|
39397
|
+
Do not run rtk ls, find, grep, or launch subagents. One tool call only.
|
|
39398
|
+
`;
|
|
39399
|
+
|
|
39298
39400
|
// src/features/builtin-commands/templates/ultrawork.ts
|
|
39299
39401
|
var ULTRAWORK_TEMPLATE = `Toggle ultrawork mode for the current session.
|
|
39300
39402
|
|
|
@@ -39547,6 +39649,28 @@ ${EVOLUTION_TEMPLATE}
|
|
|
39547
39649
|
$ARGUMENTS
|
|
39548
39650
|
</user-request>`,
|
|
39549
39651
|
argumentHint: "[list|approve <slug>|reject <slug>|audit]"
|
|
39652
|
+
},
|
|
39653
|
+
"cleanup-tasks": {
|
|
39654
|
+
description: "(builtin) Delete completed tasks",
|
|
39655
|
+
template: `<command-instruction>
|
|
39656
|
+
${CLEANUP_TASKS_TEMPLATE}
|
|
39657
|
+
</command-instruction>
|
|
39658
|
+
|
|
39659
|
+
<user-request>
|
|
39660
|
+
$ARGUMENTS
|
|
39661
|
+
</user-request>`,
|
|
39662
|
+
argumentHint: "[--olderThan <duration>] [--all]"
|
|
39663
|
+
},
|
|
39664
|
+
"task-list": {
|
|
39665
|
+
description: "(builtin) Show current state of all tasks",
|
|
39666
|
+
template: `<command-instruction>
|
|
39667
|
+
${TASK_LIST_TEMPLATE}
|
|
39668
|
+
</command-instruction>
|
|
39669
|
+
|
|
39670
|
+
<user-request>
|
|
39671
|
+
$ARGUMENTS
|
|
39672
|
+
</user-request>`,
|
|
39673
|
+
argumentHint: "[--all] [--status <pending|in_progress|completed|deleted>]"
|
|
39550
39674
|
}
|
|
39551
39675
|
};
|
|
39552
39676
|
function loadBuiltinCommands(disabledCommands) {
|
|
@@ -55490,19 +55614,10 @@ function extractTodos(response) {
|
|
|
55490
55614
|
}
|
|
55491
55615
|
return [];
|
|
55492
55616
|
}
|
|
55493
|
-
|
|
55494
|
-
|
|
55495
|
-
|
|
55496
|
-
|
|
55497
|
-
const mod = await import(loader);
|
|
55498
|
-
const update = mod.Todo?.update;
|
|
55499
|
-
if (typeof update === "function") {
|
|
55500
|
-
return update;
|
|
55501
|
-
}
|
|
55502
|
-
} catch (err) {
|
|
55503
|
-
log(`[${HOOK_NAME2}] Failed to resolve Todo.update`, { loader, error: String(err) });
|
|
55504
|
-
}
|
|
55505
|
-
}
|
|
55617
|
+
var testWriter;
|
|
55618
|
+
async function resolveTodoWriter(_ctx) {
|
|
55619
|
+
if (testWriter !== undefined)
|
|
55620
|
+
return testWriter;
|
|
55506
55621
|
return null;
|
|
55507
55622
|
}
|
|
55508
55623
|
function resolveSessionID(props) {
|
|
@@ -55542,18 +55657,17 @@ function createCompactionTodoPreserverHook(ctx) {
|
|
|
55542
55657
|
log(`[${HOOK_NAME2}] Skipped restore (todos already present)`, { sessionID, count: currentTodos.length });
|
|
55543
55658
|
return;
|
|
55544
55659
|
}
|
|
55545
|
-
const writer = await resolveTodoWriter();
|
|
55660
|
+
const writer = await resolveTodoWriter(ctx);
|
|
55546
55661
|
if (!writer) {
|
|
55547
|
-
log(`[${HOOK_NAME2}]
|
|
55662
|
+
log(`[${HOOK_NAME2}] Todo.update unavailable \u2014 todos not restored after compaction (visible failure)`, { sessionID });
|
|
55548
55663
|
return;
|
|
55549
55664
|
}
|
|
55550
55665
|
try {
|
|
55551
55666
|
await writer({ sessionID, todos: snapshot });
|
|
55552
55667
|
log(`[${HOOK_NAME2}] Restored todos after compaction`, { sessionID, count: snapshot.length });
|
|
55668
|
+
snapshots.delete(sessionID);
|
|
55553
55669
|
} catch (err) {
|
|
55554
55670
|
log(`[${HOOK_NAME2}] Failed to restore todos`, { sessionID, error: String(err) });
|
|
55555
|
-
} finally {
|
|
55556
|
-
snapshots.delete(sessionID);
|
|
55557
55671
|
}
|
|
55558
55672
|
};
|
|
55559
55673
|
const event = async ({ event: event2 }) => {
|
|
@@ -55575,6 +55689,75 @@ function createCompactionTodoPreserverHook(ctx) {
|
|
|
55575
55689
|
};
|
|
55576
55690
|
return { capture, event };
|
|
55577
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
|
+
}
|
|
55578
55761
|
// src/hooks/context-window-monitor.ts
|
|
55579
55762
|
var ANTHROPIC_DISPLAY_LIMIT = 1e6;
|
|
55580
55763
|
var ANTHROPIC_ACTUAL_LIMIT2 = process.env.ANTHROPIC_1M_CONTEXT === "true" || process.env.VERTEX_ANTHROPIC_1M_CONTEXT === "true" ? 1e6 : 200000;
|
|
@@ -55767,7 +55950,7 @@ ${guidance}`;
|
|
|
55767
55950
|
import { join as join35 } from "path";
|
|
55768
55951
|
|
|
55769
55952
|
// src/hooks/design-intent-preserver/constants.ts
|
|
55770
|
-
var
|
|
55953
|
+
var HOOK_NAME4 = "design-intent-preserver";
|
|
55771
55954
|
var PLANS_DIR2 = ".matrixx/plans";
|
|
55772
55955
|
var PHASE_TRANSITION_PATTERN = /\bphase[\s\-:]*(\d+)\b/i;
|
|
55773
55956
|
var CONSTRUCT_INDICATORS = /\b(?:construct|web[\s-]?designer|frontend[\s-]?ui[\s-]?ux)\b|@designer/i;
|
|
@@ -55854,7 +56037,7 @@ function createDesignIntentPreserverHook(_ctx) {
|
|
|
55854
56037
|
---
|
|
55855
56038
|
|
|
55856
56039
|
${originalText}`;
|
|
55857
|
-
log(`[${
|
|
56040
|
+
log(`[${HOOK_NAME4}] injected design intent guardrail`, {
|
|
55858
56041
|
sessionID: input.sessionID,
|
|
55859
56042
|
planPath
|
|
55860
56043
|
});
|
|
@@ -59401,18 +59584,18 @@ function extractPromptText3(parts) {
|
|
|
59401
59584
|
}
|
|
59402
59585
|
// src/features/ultrawork-state/manager.ts
|
|
59403
59586
|
init_logger();
|
|
59404
|
-
var
|
|
59587
|
+
var HOOK_NAME5 = "ultrawork-state";
|
|
59405
59588
|
var sessionStates = new Map;
|
|
59406
59589
|
function getUltraworkState(sessionID) {
|
|
59407
59590
|
return sessionStates.get(sessionID);
|
|
59408
59591
|
}
|
|
59409
59592
|
function enableUltrawork(sessionID) {
|
|
59410
59593
|
sessionStates.set(sessionID, "enabled");
|
|
59411
|
-
log(`[${
|
|
59594
|
+
log(`[${HOOK_NAME5}] Ultrawork enabled for session`, { sessionID });
|
|
59412
59595
|
}
|
|
59413
59596
|
function disableUltrawork(sessionID) {
|
|
59414
59597
|
sessionStates.set(sessionID, "disabled");
|
|
59415
|
-
log(`[${
|
|
59598
|
+
log(`[${HOOK_NAME5}] Ultrawork disabled for session`, { sessionID });
|
|
59416
59599
|
}
|
|
59417
59600
|
// src/hooks/keyword-detector/hook.ts
|
|
59418
59601
|
function createKeywordDetectorHook(ctx, _collector) {
|
|
@@ -59545,7 +59728,7 @@ function createLoopStateController(options) {
|
|
|
59545
59728
|
};
|
|
59546
59729
|
const success2 = writeState(directory, state3, stateDir);
|
|
59547
59730
|
if (success2) {
|
|
59548
|
-
log(`[${
|
|
59731
|
+
log(`[${HOOK_NAME6}] Loop started`, {
|
|
59549
59732
|
sessionID,
|
|
59550
59733
|
maxIterations: state3.max_iterations,
|
|
59551
59734
|
completionPromise: state3.completion_promise
|
|
@@ -59560,7 +59743,7 @@ function createLoopStateController(options) {
|
|
|
59560
59743
|
}
|
|
59561
59744
|
const success2 = clearState(directory, stateDir);
|
|
59562
59745
|
if (success2) {
|
|
59563
|
-
log(`[${
|
|
59746
|
+
log(`[${HOOK_NAME6}] Loop cancelled`, { sessionID, iteration: state3.iteration });
|
|
59564
59747
|
}
|
|
59565
59748
|
return success2;
|
|
59566
59749
|
},
|
|
@@ -59637,7 +59820,7 @@ async function detectCompletionInSessionMessages(ctx, options) {
|
|
|
59637
59820
|
return false;
|
|
59638
59821
|
} catch (err) {
|
|
59639
59822
|
setTimeout(() => {
|
|
59640
|
-
log(`[${
|
|
59823
|
+
log(`[${HOOK_NAME6}] Session messages check failed`, {
|
|
59641
59824
|
sessionID: options.sessionID,
|
|
59642
59825
|
error: String(err)
|
|
59643
59826
|
});
|
|
@@ -59718,7 +59901,7 @@ async function verifyCompletion(ctx, options) {
|
|
|
59718
59901
|
query: { directory: options.directory }
|
|
59719
59902
|
});
|
|
59720
59903
|
if (createResult.error || !createResult.data?.id) {
|
|
59721
|
-
log(`[${
|
|
59904
|
+
log(`[${HOOK_NAME6}] Verification session creation failed`);
|
|
59722
59905
|
return { verified: true };
|
|
59723
59906
|
}
|
|
59724
59907
|
verifySessionID = createResult.data.id;
|
|
@@ -59748,16 +59931,16 @@ async function verifyCompletion(ctx, options) {
|
|
|
59748
59931
|
return parseVerificationResponse(assistantText);
|
|
59749
59932
|
}
|
|
59750
59933
|
} catch {
|
|
59751
|
-
log(`[${
|
|
59934
|
+
log(`[${HOOK_NAME6}] Verification poll error`);
|
|
59752
59935
|
}
|
|
59753
59936
|
await new Promise((resolve5) => {
|
|
59754
59937
|
setTimeout(resolve5, pollIntervalMs);
|
|
59755
59938
|
});
|
|
59756
59939
|
}
|
|
59757
|
-
log(`[${
|
|
59940
|
+
log(`[${HOOK_NAME6}] Verification timed out, assuming pass`);
|
|
59758
59941
|
return { verified: true };
|
|
59759
59942
|
} catch (err) {
|
|
59760
|
-
log(`[${
|
|
59943
|
+
log(`[${HOOK_NAME6}] Verification error: ${String(err)}`);
|
|
59761
59944
|
return { verified: true };
|
|
59762
59945
|
} finally {
|
|
59763
59946
|
if (verifySessionID) {
|
|
@@ -59833,7 +60016,7 @@ async function injectContinuationPrompt(ctx, options) {
|
|
|
59833
60016
|
|
|
59834
60017
|
// src/hooks/matrix-loop/matrix-loop-event-handler.ts
|
|
59835
60018
|
async function handleVerificationFailure(ctx, options, state3, sessionID, reason, failedCount) {
|
|
59836
|
-
log(`[${
|
|
60019
|
+
log(`[${HOOK_NAME6}] Verification failed`, { sessionID, reason });
|
|
59837
60020
|
const newState = options.loopState.incrementIteration();
|
|
59838
60021
|
if (newState) {
|
|
59839
60022
|
newState.verification_failed_count = failedCount + 1;
|
|
@@ -59851,7 +60034,7 @@ ${buildContinuationPrompt(newState ?? state3)}`,
|
|
|
59851
60034
|
apiTimeoutMs: options.apiTimeoutMs
|
|
59852
60035
|
});
|
|
59853
60036
|
} catch (err) {
|
|
59854
|
-
log(`[${
|
|
60037
|
+
log(`[${HOOK_NAME6}] Failed to inject verification feedback`, { sessionID, error: String(err) });
|
|
59855
60038
|
}
|
|
59856
60039
|
}
|
|
59857
60040
|
function createMatrixLoopEventHandler(ctx, options) {
|
|
@@ -59863,13 +60046,13 @@ function createMatrixLoopEventHandler(ctx, options) {
|
|
|
59863
60046
|
if (!sessionID)
|
|
59864
60047
|
return;
|
|
59865
60048
|
if (inFlightSessions.has(sessionID)) {
|
|
59866
|
-
log(`[${
|
|
60049
|
+
log(`[${HOOK_NAME6}] Skipped: handler in flight`, { sessionID });
|
|
59867
60050
|
return;
|
|
59868
60051
|
}
|
|
59869
60052
|
inFlightSessions.add(sessionID);
|
|
59870
60053
|
try {
|
|
59871
60054
|
if (options.sessionRecovery.isRecovering(sessionID)) {
|
|
59872
|
-
log(`[${
|
|
60055
|
+
log(`[${HOOK_NAME6}] Skipped: in recovery`, { sessionID });
|
|
59873
60056
|
return;
|
|
59874
60057
|
}
|
|
59875
60058
|
const state3 = options.loopState.getState();
|
|
@@ -59881,11 +60064,11 @@ function createMatrixLoopEventHandler(ctx, options) {
|
|
|
59881
60064
|
const exists = await options.checkSessionExists(state3.session_id);
|
|
59882
60065
|
if (!exists) {
|
|
59883
60066
|
options.loopState.clear();
|
|
59884
|
-
log(`[${
|
|
60067
|
+
log(`[${HOOK_NAME6}] Cleared orphaned state from deleted session`, { orphanedSessionId: state3.session_id, currentSessionId: sessionID });
|
|
59885
60068
|
return;
|
|
59886
60069
|
}
|
|
59887
60070
|
} catch (err) {
|
|
59888
|
-
log(`[${
|
|
60071
|
+
log(`[${HOOK_NAME6}] Failed to check session existence`, { sessionId: state3.session_id, error: String(err) });
|
|
59889
60072
|
}
|
|
59890
60073
|
}
|
|
59891
60074
|
return;
|
|
@@ -59904,7 +60087,7 @@ function createMatrixLoopEventHandler(ctx, options) {
|
|
|
59904
60087
|
messages = Array.isArray(messagesResponse) ? messagesResponse : Array.isArray(responseData) ? responseData : [];
|
|
59905
60088
|
} catch (err) {
|
|
59906
60089
|
setTimeout(() => {
|
|
59907
|
-
log(`[${
|
|
60090
|
+
log(`[${HOOK_NAME6}] Session messages fetch failed`, { sessionID, error: String(err) });
|
|
59908
60091
|
}, 0);
|
|
59909
60092
|
messages = [];
|
|
59910
60093
|
}
|
|
@@ -59917,7 +60100,7 @@ function createMatrixLoopEventHandler(ctx, options) {
|
|
|
59917
60100
|
preFetchedMessages: messages
|
|
59918
60101
|
});
|
|
59919
60102
|
if (completionViaTranscript || completionViaApi) {
|
|
59920
|
-
log(`[${
|
|
60103
|
+
log(`[${HOOK_NAME6}] Completion detected!`, { sessionID, iteration: state3.iteration, promise: state3.completion_promise, detectedVia: completionViaTranscript ? "transcript_file" : "session_messages_api" });
|
|
59921
60104
|
if (options.verification?.enabled === true) {
|
|
59922
60105
|
const maxRetries = options.verification?.maxRetries ?? DEFAULT_VERIFICATION_MAX_RETRIES;
|
|
59923
60106
|
const failedCount = state3.verification_failed_count ?? 0;
|
|
@@ -59952,7 +60135,7 @@ function createMatrixLoopEventHandler(ctx, options) {
|
|
|
59952
60135
|
return;
|
|
59953
60136
|
}
|
|
59954
60137
|
if (state3.iteration >= state3.max_iterations) {
|
|
59955
|
-
log(`[${
|
|
60138
|
+
log(`[${HOOK_NAME6}] Max iterations reached`, { sessionID, iteration: state3.iteration, max: state3.max_iterations });
|
|
59956
60139
|
options.loopState.clear();
|
|
59957
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) => {
|
|
59958
60141
|
log("[matrix-loop] Max iterations toast failed:", err);
|
|
@@ -59961,17 +60144,17 @@ function createMatrixLoopEventHandler(ctx, options) {
|
|
|
59961
60144
|
}
|
|
59962
60145
|
const newState = options.loopState.incrementIteration();
|
|
59963
60146
|
if (!newState) {
|
|
59964
|
-
log(`[${
|
|
60147
|
+
log(`[${HOOK_NAME6}] Failed to increment iteration`, { sessionID });
|
|
59965
60148
|
return;
|
|
59966
60149
|
}
|
|
59967
|
-
log(`[${
|
|
60150
|
+
log(`[${HOOK_NAME6}] Continuing loop`, { sessionID, iteration: newState.iteration, max: newState.max_iterations });
|
|
59968
60151
|
await ctx.client.tui.showToast({ body: { title: "Matrix Loop", message: `Iteration ${newState.iteration}/${newState.max_iterations}`, variant: "info", duration: 2000 } }).catch((err) => {
|
|
59969
60152
|
log("[matrix-loop] Iteration toast failed:", err);
|
|
59970
60153
|
});
|
|
59971
60154
|
try {
|
|
59972
60155
|
await injectContinuationPrompt(ctx, { sessionID, prompt: buildContinuationPrompt(newState), directory: options.directory, apiTimeoutMs: options.apiTimeoutMs });
|
|
59973
60156
|
} catch (err) {
|
|
59974
|
-
log(`[${
|
|
60157
|
+
log(`[${HOOK_NAME6}] Failed to inject continuation`, { sessionID, error: String(err) });
|
|
59975
60158
|
}
|
|
59976
60159
|
return;
|
|
59977
60160
|
} finally {
|
|
@@ -59985,7 +60168,7 @@ function createMatrixLoopEventHandler(ctx, options) {
|
|
|
59985
60168
|
const state3 = options.loopState.getState();
|
|
59986
60169
|
if (state3?.session_id === sessionInfo.id) {
|
|
59987
60170
|
options.loopState.clear();
|
|
59988
|
-
log(`[${
|
|
60171
|
+
log(`[${HOOK_NAME6}] Session deleted, loop cleared`, { sessionID: sessionInfo.id });
|
|
59989
60172
|
}
|
|
59990
60173
|
options.sessionRecovery.clear(sessionInfo.id);
|
|
59991
60174
|
return;
|
|
@@ -59998,7 +60181,7 @@ function createMatrixLoopEventHandler(ctx, options) {
|
|
|
59998
60181
|
const state3 = options.loopState.getState();
|
|
59999
60182
|
if (state3?.session_id === sessionID) {
|
|
60000
60183
|
options.loopState.clear();
|
|
60001
|
-
log(`[${
|
|
60184
|
+
log(`[${HOOK_NAME6}] User aborted, loop cleared`, { sessionID });
|
|
60002
60185
|
}
|
|
60003
60186
|
options.sessionRecovery.clear(sessionID);
|
|
60004
60187
|
}
|
|
@@ -60091,7 +60274,7 @@ function createMcpStartupNotificationHook(ctx) {
|
|
|
60091
60274
|
};
|
|
60092
60275
|
}
|
|
60093
60276
|
// src/hooks/mouse-notepad/constants.ts
|
|
60094
|
-
var
|
|
60277
|
+
var HOOK_NAME7 = "mouse-notepad";
|
|
60095
60278
|
var NOTEPAD_DIRECTIVE = `
|
|
60096
60279
|
<Work_Context>
|
|
60097
60280
|
## Notepad Location (for recording learnings)
|
|
@@ -60138,14 +60321,14 @@ function createMouseNotepadHook(ctx) {
|
|
|
60138
60321
|
return;
|
|
60139
60322
|
}
|
|
60140
60323
|
output.args.prompt = NOTEPAD_DIRECTIVE + prompt;
|
|
60141
|
-
log(`[${
|
|
60324
|
+
log(`[${HOOK_NAME7}] Injected notepad directive to task`, {
|
|
60142
60325
|
sessionID: input.sessionID
|
|
60143
60326
|
});
|
|
60144
60327
|
}
|
|
60145
60328
|
};
|
|
60146
60329
|
}
|
|
60147
60330
|
// src/hooks/non-interactive-env/constants.ts
|
|
60148
|
-
var
|
|
60331
|
+
var HOOK_NAME8 = "non-interactive-env";
|
|
60149
60332
|
var NON_INTERACTIVE_ENV = {
|
|
60150
60333
|
CI: "true",
|
|
60151
60334
|
DEBIAN_FRONTEND: "noninteractive",
|
|
@@ -60236,7 +60419,7 @@ function createNonInteractiveEnvHook(_ctx) {
|
|
|
60236
60419
|
}
|
|
60237
60420
|
const envPrefix = buildEnvPrefix(NON_INTERACTIVE_ENV, "unix");
|
|
60238
60421
|
output.args.command = `${envPrefix} ${command}`;
|
|
60239
|
-
log(`[${
|
|
60422
|
+
log(`[${HOOK_NAME8}] Prepended non-interactive env vars to git command`, {
|
|
60240
60423
|
sessionID: input.sessionID,
|
|
60241
60424
|
envPrefix
|
|
60242
60425
|
});
|
|
@@ -60244,7 +60427,7 @@ function createNonInteractiveEnvHook(_ctx) {
|
|
|
60244
60427
|
};
|
|
60245
60428
|
}
|
|
60246
60429
|
// src/hooks/oracle-md-only/constants.ts
|
|
60247
|
-
var
|
|
60430
|
+
var HOOK_NAME9 = "oracle-md-only";
|
|
60248
60431
|
var ORACLE_AGENT = "oracle";
|
|
60249
60432
|
var ALLOWED_EXTENSIONS = [".md"];
|
|
60250
60433
|
var BLOCKED_TOOLS = ["Write", "Edit", "write", "edit"];
|
|
@@ -60377,7 +60560,7 @@ function createOracleMdOnlyHook(ctx) {
|
|
|
60377
60560
|
const prompt = output.args.prompt;
|
|
60378
60561
|
if (prompt && !prompt.includes(SYSTEM_DIRECTIVE_PREFIX)) {
|
|
60379
60562
|
output.args.prompt = PLANNING_CONSULT_WARNING + prompt;
|
|
60380
|
-
log(`[${
|
|
60563
|
+
log(`[${HOOK_NAME9}] Injected read-only planning warning to ${toolName}`, {
|
|
60381
60564
|
sessionID: input.sessionID,
|
|
60382
60565
|
tool: toolName,
|
|
60383
60566
|
agent: agentName
|
|
@@ -60393,17 +60576,17 @@ function createOracleMdOnlyHook(ctx) {
|
|
|
60393
60576
|
return;
|
|
60394
60577
|
}
|
|
60395
60578
|
if (!isAllowedFile(filePath, ctx.directory)) {
|
|
60396
|
-
log(`[${
|
|
60579
|
+
log(`[${HOOK_NAME9}] Blocked: Oracle can only write to .matrixx/*.md`, {
|
|
60397
60580
|
sessionID: input.sessionID,
|
|
60398
60581
|
tool: toolName,
|
|
60399
60582
|
filePath,
|
|
60400
60583
|
agent: agentName
|
|
60401
60584
|
});
|
|
60402
|
-
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`);
|
|
60403
60586
|
}
|
|
60404
60587
|
const normalizedPath = filePath.toLowerCase().replace(/\\/g, "/");
|
|
60405
60588
|
if (normalizedPath.includes(".matrixx/plans/") || normalizedPath.includes(".matrixx\\plans\\")) {
|
|
60406
|
-
log(`[${
|
|
60589
|
+
log(`[${HOOK_NAME9}] Injecting workflow reminder for plan write`, {
|
|
60407
60590
|
sessionID: input.sessionID,
|
|
60408
60591
|
tool: toolName,
|
|
60409
60592
|
filePath,
|
|
@@ -60411,7 +60594,7 @@ function createOracleMdOnlyHook(ctx) {
|
|
|
60411
60594
|
});
|
|
60412
60595
|
output.message = (output.message || "") + ORACLE_WORKFLOW_REMINDER;
|
|
60413
60596
|
}
|
|
60414
|
-
log(`[${
|
|
60597
|
+
log(`[${HOOK_NAME9}] Allowed: .matrixx/*.md write permitted`, {
|
|
60415
60598
|
sessionID: input.sessionID,
|
|
60416
60599
|
tool: toolName,
|
|
60417
60600
|
filePath,
|
|
@@ -60482,7 +60665,7 @@ async function getGitHead(directory) {
|
|
|
60482
60665
|
}
|
|
60483
60666
|
|
|
60484
60667
|
// src/hooks/plan-persister/hook.ts
|
|
60485
|
-
var
|
|
60668
|
+
var HOOK_NAME10 = "plan-persister";
|
|
60486
60669
|
function createPlanPersister(ctx, options) {
|
|
60487
60670
|
const { directory } = options;
|
|
60488
60671
|
const capture = async (sessionID) => {
|
|
@@ -60501,7 +60684,7 @@ function createPlanPersister(ctx, options) {
|
|
|
60501
60684
|
const response = await ctx.client.session.todo({ path: { id: sessionID } });
|
|
60502
60685
|
todos = normalizeTodos(response);
|
|
60503
60686
|
} catch (err) {
|
|
60504
|
-
log(`[${
|
|
60687
|
+
log(`[${HOOK_NAME10}] Failed to fetch todos`, { sessionID, error: String(err) });
|
|
60505
60688
|
return;
|
|
60506
60689
|
}
|
|
60507
60690
|
const content = readPlanFile(planPath);
|
|
@@ -60523,9 +60706,9 @@ function createPlanPersister(ctx, options) {
|
|
|
60523
60706
|
ensurePlanDir(directory);
|
|
60524
60707
|
const ok = atomicWrite(planPath, finalContent);
|
|
60525
60708
|
if (ok) {
|
|
60526
|
-
log(`[${
|
|
60709
|
+
log(`[${HOOK_NAME10}] Plan file updated`, { planPath, completed, total });
|
|
60527
60710
|
} else {
|
|
60528
|
-
log(`[${
|
|
60711
|
+
log(`[${HOOK_NAME10}] Failed to write plan file`, { planPath });
|
|
60529
60712
|
}
|
|
60530
60713
|
};
|
|
60531
60714
|
const event = async ({ event: evt }) => {
|
|
@@ -61823,13 +62006,11 @@ function createReadImageResizerHook(_ctx) {
|
|
|
61823
62006
|
}
|
|
61824
62007
|
// src/hooks/rtk-bash-rewriter/constants.ts
|
|
61825
62008
|
var RTK_BINARY_NAME = "rtk";
|
|
61826
|
-
var DEFAULT_TIMEOUT_MS = 5000;
|
|
61827
62009
|
var RTK_REWRITE_COMMAND = "rewrite";
|
|
61828
62010
|
|
|
61829
62011
|
// src/hooks/rtk-bash-rewriter/hook.ts
|
|
61830
62012
|
function createRtkBashRewriterHook(_ctx, config2) {
|
|
61831
62013
|
const rtkConfig = config2.rtk;
|
|
61832
|
-
const _timeoutMs = rtkConfig?.timeout_ms ?? DEFAULT_TIMEOUT_MS;
|
|
61833
62014
|
const binaryPath = rtkConfig?.binary_path ?? RTK_BINARY_NAME;
|
|
61834
62015
|
const hasRtk = Bun.which(binaryPath) !== null;
|
|
61835
62016
|
if (!hasRtk) {
|
|
@@ -62528,7 +62709,7 @@ var RETRYABLE_ERROR_PATTERNS = [
|
|
|
62528
62709
|
/(?:^|\s)503(?:\s|$)/,
|
|
62529
62710
|
/(?:^|\s)529(?:\s|$)/
|
|
62530
62711
|
];
|
|
62531
|
-
var
|
|
62712
|
+
var HOOK_NAME11 = "runtime-fallback";
|
|
62532
62713
|
|
|
62533
62714
|
// src/hooks/runtime-fallback/fallback-models.ts
|
|
62534
62715
|
init_logger();
|
|
@@ -62619,7 +62800,7 @@ function getRawFallbackModelsForSession(sessionID, agent, pluginConfig) {
|
|
|
62619
62800
|
if (result)
|
|
62620
62801
|
return result;
|
|
62621
62802
|
}
|
|
62622
|
-
log(`[${
|
|
62803
|
+
log(`[${HOOK_NAME11}] No category/agent fallback models resolved for session`, { sessionID, agent });
|
|
62623
62804
|
return;
|
|
62624
62805
|
}
|
|
62625
62806
|
|
|
@@ -62648,21 +62829,21 @@ function findNextAvailableFallback(state3, fallbackModels, cooldownSeconds) {
|
|
|
62648
62829
|
if (!isModelInCooldown(candidate, state3, cooldownSeconds)) {
|
|
62649
62830
|
return candidate;
|
|
62650
62831
|
}
|
|
62651
|
-
log(`[${
|
|
62832
|
+
log(`[${HOOK_NAME11}] Skipping fallback model in cooldown`, { model: candidate, index: i });
|
|
62652
62833
|
}
|
|
62653
62834
|
return;
|
|
62654
62835
|
}
|
|
62655
62836
|
function prepareFallback(sessionID, state3, fallbackModels, config2) {
|
|
62656
62837
|
if (state3.attemptCount >= config2.max_fallback_attempts) {
|
|
62657
|
-
log(`[${
|
|
62838
|
+
log(`[${HOOK_NAME11}] Max fallback attempts reached`, { sessionID, attempts: state3.attemptCount });
|
|
62658
62839
|
return { success: false, error: "Max fallback attempts reached", maxAttemptsReached: true };
|
|
62659
62840
|
}
|
|
62660
62841
|
const nextModel = findNextAvailableFallback(state3, fallbackModels, config2.cooldown_seconds);
|
|
62661
62842
|
if (!nextModel) {
|
|
62662
|
-
log(`[${
|
|
62843
|
+
log(`[${HOOK_NAME11}] No available fallback models`, { sessionID });
|
|
62663
62844
|
return { success: false, error: "No available fallback models (all in cooldown or exhausted)" };
|
|
62664
62845
|
}
|
|
62665
|
-
log(`[${
|
|
62846
|
+
log(`[${HOOK_NAME11}] Preparing fallback`, {
|
|
62666
62847
|
sessionID,
|
|
62667
62848
|
from: state3.currentModel,
|
|
62668
62849
|
to: nextModel,
|
|
@@ -62748,9 +62929,9 @@ function createAutoRetryHelpers(deps) {
|
|
|
62748
62929
|
const abortSessionRequest = async (sessionID, source) => {
|
|
62749
62930
|
try {
|
|
62750
62931
|
await ctx.client.session.abort({ path: { id: sessionID } });
|
|
62751
|
-
log(`[${
|
|
62932
|
+
log(`[${HOOK_NAME11}] Aborted in-flight session request (${source})`, { sessionID });
|
|
62752
62933
|
} catch (error51) {
|
|
62753
|
-
log(`[${
|
|
62934
|
+
log(`[${HOOK_NAME11}] Failed to abort in-flight session request (${source})`, {
|
|
62754
62935
|
sessionID,
|
|
62755
62936
|
error: String(error51)
|
|
62756
62937
|
});
|
|
@@ -62774,7 +62955,7 @@ function createAutoRetryHelpers(deps) {
|
|
|
62774
62955
|
if (!state3)
|
|
62775
62956
|
return;
|
|
62776
62957
|
if (sessionRetryInFlight.has(sessionID)) {
|
|
62777
|
-
log(`[${
|
|
62958
|
+
log(`[${HOOK_NAME11}] Overriding in-flight retry due to session timeout`, { sessionID });
|
|
62778
62959
|
}
|
|
62779
62960
|
await abortSessionRequest(sessionID, "session.timeout");
|
|
62780
62961
|
sessionRetryInFlight.delete(sessionID);
|
|
@@ -62784,7 +62965,7 @@ function createAutoRetryHelpers(deps) {
|
|
|
62784
62965
|
const fallbackModels = getFallbackModelsForSession(sessionID, resolvedAgent, pluginConfig);
|
|
62785
62966
|
if (fallbackModels.length === 0)
|
|
62786
62967
|
return;
|
|
62787
|
-
log(`[${
|
|
62968
|
+
log(`[${HOOK_NAME11}] Session fallback timeout reached`, {
|
|
62788
62969
|
sessionID,
|
|
62789
62970
|
timeoutSeconds: config2.timeout_seconds,
|
|
62790
62971
|
currentModel: state3.currentModel
|
|
@@ -62798,7 +62979,7 @@ function createAutoRetryHelpers(deps) {
|
|
|
62798
62979
|
};
|
|
62799
62980
|
const autoRetryWithFallback = async (sessionID, newModel, resolvedAgent, source) => {
|
|
62800
62981
|
if (sessionRetryInFlight.has(sessionID)) {
|
|
62801
|
-
log(`[${
|
|
62982
|
+
log(`[${HOOK_NAME11}] Retry already in flight, skipping (${source})`, { sessionID });
|
|
62802
62983
|
return;
|
|
62803
62984
|
}
|
|
62804
62985
|
const agentSettings = resolvedAgent ? pluginConfig?.agents?.[resolvedAgent] : undefined;
|
|
@@ -62807,7 +62988,7 @@ function createAutoRetryHelpers(deps) {
|
|
|
62807
62988
|
reasoningEffort: agentSettings.reasoningEffort
|
|
62808
62989
|
} : undefined);
|
|
62809
62990
|
if (!retryModelPayload) {
|
|
62810
|
-
log(`[${
|
|
62991
|
+
log(`[${HOOK_NAME11}] Invalid model format (missing provider prefix): ${newModel}`);
|
|
62811
62992
|
const state3 = sessionStates2.get(sessionID);
|
|
62812
62993
|
if (state3?.pendingFallbackModel) {
|
|
62813
62994
|
state3.pendingFallbackModel = undefined;
|
|
@@ -62823,7 +63004,7 @@ function createAutoRetryHelpers(deps) {
|
|
|
62823
63004
|
});
|
|
62824
63005
|
const retryParts = getLastUserRetryParts(messagesResp);
|
|
62825
63006
|
if (retryParts.length > 0) {
|
|
62826
|
-
log(`[${
|
|
63007
|
+
log(`[${HOOK_NAME11}] Auto-retrying with fallback model (${source})`, {
|
|
62827
63008
|
sessionID,
|
|
62828
63009
|
model: newModel
|
|
62829
63010
|
});
|
|
@@ -62842,10 +63023,10 @@ function createAutoRetryHelpers(deps) {
|
|
|
62842
63023
|
});
|
|
62843
63024
|
retryDispatched = true;
|
|
62844
63025
|
} else {
|
|
62845
|
-
log(`[${
|
|
63026
|
+
log(`[${HOOK_NAME11}] No user message found for auto-retry (${source})`, { sessionID });
|
|
62846
63027
|
}
|
|
62847
63028
|
} catch (retryError) {
|
|
62848
|
-
log(`[${
|
|
63029
|
+
log(`[${HOOK_NAME11}] Auto-retry failed (${source})`, { sessionID, error: String(retryError) });
|
|
62849
63030
|
} finally {
|
|
62850
63031
|
sessionRetryInFlight.delete(sessionID);
|
|
62851
63032
|
if (!retryDispatched) {
|
|
@@ -62899,7 +63080,7 @@ function createAutoRetryHelpers(deps) {
|
|
|
62899
63080
|
}
|
|
62900
63081
|
}
|
|
62901
63082
|
if (cleanedCount > 0) {
|
|
62902
|
-
log(`[${
|
|
63083
|
+
log(`[${HOOK_NAME11}] Cleaned up ${cleanedCount} stale session states`);
|
|
62903
63084
|
}
|
|
62904
63085
|
};
|
|
62905
63086
|
return {
|
|
@@ -62930,7 +63111,7 @@ function createChatMessageHandler(deps) {
|
|
|
62930
63111
|
state3.pendingFallbackModel = undefined;
|
|
62931
63112
|
return;
|
|
62932
63113
|
}
|
|
62933
|
-
log(`[${
|
|
63114
|
+
log(`[${HOOK_NAME11}] Detected manual model change, resetting fallback state`, {
|
|
62934
63115
|
sessionID,
|
|
62935
63116
|
from: state3.currentModel,
|
|
62936
63117
|
to: requestedModel
|
|
@@ -62942,7 +63123,7 @@ function createChatMessageHandler(deps) {
|
|
|
62942
63123
|
if (state3.currentModel === state3.originalModel)
|
|
62943
63124
|
return;
|
|
62944
63125
|
const activeModel = state3.currentModel;
|
|
62945
|
-
log(`[${
|
|
63126
|
+
log(`[${HOOK_NAME11}] Applying fallback model override`, {
|
|
62946
63127
|
sessionID,
|
|
62947
63128
|
from: input.model,
|
|
62948
63129
|
to: activeModel
|
|
@@ -63129,7 +63310,7 @@ function resolveFallbackBootstrapModel(options) {
|
|
|
63129
63310
|
const agentConfig = options.resolvedAgent && agentConfigs ? agentConfigs[options.resolvedAgent] : undefined;
|
|
63130
63311
|
const agentModel = typeof agentConfig?.model === "string" ? agentConfig.model : undefined;
|
|
63131
63312
|
if (agentModel) {
|
|
63132
|
-
log(`[${
|
|
63313
|
+
log(`[${HOOK_NAME11}] Derived model from agent config for ${options.source}`, {
|
|
63133
63314
|
sessionID: options.sessionID,
|
|
63134
63315
|
agent: options.resolvedAgent,
|
|
63135
63316
|
model: agentModel
|
|
@@ -63140,7 +63321,7 @@ function resolveFallbackBootstrapModel(options) {
|
|
|
63140
63321
|
if (agentCategory) {
|
|
63141
63322
|
const agentCategoryModel = options.pluginConfig?.categories?.[agentCategory]?.model;
|
|
63142
63323
|
if (typeof agentCategoryModel === "string" && agentCategoryModel.length > 0) {
|
|
63143
|
-
log(`[${
|
|
63324
|
+
log(`[${HOOK_NAME11}] Derived model from agent category config for ${options.source}`, {
|
|
63144
63325
|
sessionID: options.sessionID,
|
|
63145
63326
|
agent: options.resolvedAgent,
|
|
63146
63327
|
category: agentCategory,
|
|
@@ -63152,7 +63333,7 @@ function resolveFallbackBootstrapModel(options) {
|
|
|
63152
63333
|
const sessionCategory = SessionCategoryRegistry.get(options.sessionID);
|
|
63153
63334
|
const categoryModel = sessionCategory ? options.pluginConfig?.categories?.[sessionCategory]?.model : undefined;
|
|
63154
63335
|
if (typeof categoryModel === "string" && categoryModel.length > 0) {
|
|
63155
|
-
log(`[${
|
|
63336
|
+
log(`[${HOOK_NAME11}] Derived model from session category config for ${options.source}`, {
|
|
63156
63337
|
sessionID: options.sessionID,
|
|
63157
63338
|
category: sessionCategory,
|
|
63158
63339
|
model: categoryModel
|
|
@@ -63182,7 +63363,7 @@ async function dispatchFallbackRetry(deps, helpers, options) {
|
|
|
63182
63363
|
await helpers.autoRetryWithFallback(options.sessionID, result.newModel, options.resolvedAgent, options.source);
|
|
63183
63364
|
return;
|
|
63184
63365
|
}
|
|
63185
|
-
log(`[${
|
|
63366
|
+
log(`[${HOOK_NAME11}] Fallback preparation failed`, {
|
|
63186
63367
|
sessionID: options.sessionID,
|
|
63187
63368
|
source: options.source,
|
|
63188
63369
|
error: result.error
|
|
@@ -63235,14 +63416,14 @@ function createSessionStatusHandler(deps, helpers, sessionStatusRetryKeys) {
|
|
|
63235
63416
|
sessionStatusRetryKeys.set(sessionID, retryKey);
|
|
63236
63417
|
if (sessionRetryInFlight.has(sessionID)) {
|
|
63237
63418
|
if (timeoutEnabled) {
|
|
63238
|
-
log(`[${
|
|
63419
|
+
log(`[${HOOK_NAME11}] Overriding in-flight retry due to provider auto-retry signal`, {
|
|
63239
63420
|
sessionID,
|
|
63240
63421
|
model
|
|
63241
63422
|
});
|
|
63242
63423
|
await helpers.abortSessionRequest(sessionID, "session.status.retry-signal");
|
|
63243
63424
|
sessionRetryInFlight.delete(sessionID);
|
|
63244
63425
|
} else {
|
|
63245
|
-
log(`[${
|
|
63426
|
+
log(`[${HOOK_NAME11}] session.status retry skipped - retry already in flight`, { sessionID });
|
|
63246
63427
|
return;
|
|
63247
63428
|
}
|
|
63248
63429
|
}
|
|
@@ -63265,7 +63446,7 @@ function createSessionStatusHandler(deps, helpers, sessionStatusRetryKeys) {
|
|
|
63265
63446
|
});
|
|
63266
63447
|
if (!initialModel) {
|
|
63267
63448
|
sessionStatusRetryKeys.delete(sessionID);
|
|
63268
|
-
log(`[${
|
|
63449
|
+
log(`[${HOOK_NAME11}] session.status retry missing model info, cannot fallback`, { sessionID });
|
|
63269
63450
|
return;
|
|
63270
63451
|
}
|
|
63271
63452
|
state3 = createFallbackState(initialModel);
|
|
@@ -63274,20 +63455,20 @@ function createSessionStatusHandler(deps, helpers, sessionStatusRetryKeys) {
|
|
|
63274
63455
|
sessionLastAccess.set(sessionID, Date.now());
|
|
63275
63456
|
if (state3.pendingFallbackModel) {
|
|
63276
63457
|
if (timeoutEnabled) {
|
|
63277
|
-
log(`[${
|
|
63458
|
+
log(`[${HOOK_NAME11}] Clearing pending fallback due to provider auto-retry signal`, {
|
|
63278
63459
|
sessionID,
|
|
63279
63460
|
pendingFallbackModel: state3.pendingFallbackModel
|
|
63280
63461
|
});
|
|
63281
63462
|
state3.pendingFallbackModel = undefined;
|
|
63282
63463
|
} else {
|
|
63283
|
-
log(`[${
|
|
63464
|
+
log(`[${HOOK_NAME11}] session.status retry skipped (pending fallback in progress)`, {
|
|
63284
63465
|
sessionID,
|
|
63285
63466
|
pendingFallbackModel: state3.pendingFallbackModel
|
|
63286
63467
|
});
|
|
63287
63468
|
return;
|
|
63288
63469
|
}
|
|
63289
63470
|
}
|
|
63290
|
-
log(`[${
|
|
63471
|
+
log(`[${HOOK_NAME11}] Detected provider auto-retry signal in session.status`, {
|
|
63291
63472
|
sessionID,
|
|
63292
63473
|
model: state3.currentModel,
|
|
63293
63474
|
retryAttempt: status.attempt
|
|
@@ -63323,7 +63504,7 @@ function createEventHandler(deps, helpers) {
|
|
|
63323
63504
|
const sessionID = sessionInfo?.id;
|
|
63324
63505
|
const model = sessionInfo?.model;
|
|
63325
63506
|
if (sessionID && model) {
|
|
63326
|
-
log(`[${
|
|
63507
|
+
log(`[${HOOK_NAME11}] Session created with model`, { sessionID, model });
|
|
63327
63508
|
sessionStates2.set(sessionID, createFallbackState(model));
|
|
63328
63509
|
sessionLastAccess.set(sessionID, Date.now());
|
|
63329
63510
|
}
|
|
@@ -63332,7 +63513,7 @@ function createEventHandler(deps, helpers) {
|
|
|
63332
63513
|
const sessionInfo = props?.info;
|
|
63333
63514
|
const sessionID = sessionInfo?.id;
|
|
63334
63515
|
if (sessionID) {
|
|
63335
|
-
log(`[${
|
|
63516
|
+
log(`[${HOOK_NAME11}] Cleaning up session state`, { sessionID });
|
|
63336
63517
|
cancelledSessions.delete(sessionID);
|
|
63337
63518
|
sessionStates2.delete(sessionID);
|
|
63338
63519
|
sessionLastAccess.delete(sessionID);
|
|
@@ -63352,7 +63533,7 @@ function createEventHandler(deps, helpers) {
|
|
|
63352
63533
|
}
|
|
63353
63534
|
cancelledSessions.add(sessionID);
|
|
63354
63535
|
resetRetryState(sessionID);
|
|
63355
|
-
log(`[${
|
|
63536
|
+
log(`[${HOOK_NAME11}] Cleared fallback retry state on session.stop`, { sessionID });
|
|
63356
63537
|
};
|
|
63357
63538
|
const handleMessageUpdated = (props) => {
|
|
63358
63539
|
const info = props?.info;
|
|
@@ -63368,11 +63549,11 @@ function createEventHandler(deps, helpers) {
|
|
|
63368
63549
|
return;
|
|
63369
63550
|
if (cancelledSessions.has(sessionID)) {
|
|
63370
63551
|
resetRetryState(sessionID);
|
|
63371
|
-
log(`[${
|
|
63552
|
+
log(`[${HOOK_NAME11}] Cleared fallback retry state for cancelled session on idle`, { sessionID });
|
|
63372
63553
|
return;
|
|
63373
63554
|
}
|
|
63374
63555
|
if (sessionAwaitingFallbackResult.has(sessionID)) {
|
|
63375
|
-
log(`[${
|
|
63556
|
+
log(`[${HOOK_NAME11}] session.idle while awaiting fallback result; keeping timeout armed`, { sessionID });
|
|
63376
63557
|
return;
|
|
63377
63558
|
}
|
|
63378
63559
|
const hadTimeout = sessionFallbackTimeouts.has(sessionID);
|
|
@@ -63384,7 +63565,7 @@ function createEventHandler(deps, helpers) {
|
|
|
63384
63565
|
state3.pendingFallbackModel = undefined;
|
|
63385
63566
|
}
|
|
63386
63567
|
if (hadTimeout) {
|
|
63387
|
-
log(`[${
|
|
63568
|
+
log(`[${HOOK_NAME11}] Cleared fallback timeout after session completion`, { sessionID });
|
|
63388
63569
|
}
|
|
63389
63570
|
};
|
|
63390
63571
|
const handleSessionError = async (props) => {
|
|
@@ -63392,18 +63573,18 @@ function createEventHandler(deps, helpers) {
|
|
|
63392
63573
|
const error51 = props?.error;
|
|
63393
63574
|
const agent = props?.agent;
|
|
63394
63575
|
if (!sessionID) {
|
|
63395
|
-
log(`[${
|
|
63576
|
+
log(`[${HOOK_NAME11}] session.error without sessionID, skipping`);
|
|
63396
63577
|
return;
|
|
63397
63578
|
}
|
|
63398
63579
|
const resolvedAgent = await helpers.resolveAgentForSessionFromContext(sessionID, agent);
|
|
63399
63580
|
if (isAbortError(error51)) {
|
|
63400
63581
|
cancelledSessions.add(sessionID);
|
|
63401
63582
|
resetRetryState(sessionID);
|
|
63402
|
-
log(`[${
|
|
63583
|
+
log(`[${HOOK_NAME11}] session.error matched cancellation; cleared retry state`, { sessionID, resolvedAgent });
|
|
63403
63584
|
return;
|
|
63404
63585
|
}
|
|
63405
63586
|
if (sessionRetryInFlight.has(sessionID)) {
|
|
63406
|
-
log(`[${
|
|
63587
|
+
log(`[${HOOK_NAME11}] session.error skipped - retry in flight`, {
|
|
63407
63588
|
sessionID,
|
|
63408
63589
|
retryInFlight: true
|
|
63409
63590
|
});
|
|
@@ -63411,7 +63592,7 @@ function createEventHandler(deps, helpers) {
|
|
|
63411
63592
|
}
|
|
63412
63593
|
sessionAwaitingFallbackResult.delete(sessionID);
|
|
63413
63594
|
helpers.clearSessionFallbackTimeout(sessionID);
|
|
63414
|
-
log(`[${
|
|
63595
|
+
log(`[${HOOK_NAME11}] session.error received`, {
|
|
63415
63596
|
sessionID,
|
|
63416
63597
|
agent,
|
|
63417
63598
|
resolvedAgent,
|
|
@@ -63420,7 +63601,7 @@ function createEventHandler(deps, helpers) {
|
|
|
63420
63601
|
errorType: classifyErrorType(error51)
|
|
63421
63602
|
});
|
|
63422
63603
|
if (!isRetryableError(error51, config2.retry_on_errors)) {
|
|
63423
|
-
log(`[${
|
|
63604
|
+
log(`[${HOOK_NAME11}] Error not retryable, skipping fallback`, {
|
|
63424
63605
|
sessionID,
|
|
63425
63606
|
retryable: false,
|
|
63426
63607
|
statusCode: extractStatusCode(error51, config2.retry_on_errors),
|
|
@@ -63432,7 +63613,7 @@ function createEventHandler(deps, helpers) {
|
|
|
63432
63613
|
let state3 = sessionStates2.get(sessionID);
|
|
63433
63614
|
const fallbackModels = getFallbackModelsForSession(sessionID, resolvedAgent, pluginConfig);
|
|
63434
63615
|
if (fallbackModels.length === 0) {
|
|
63435
|
-
log(`[${
|
|
63616
|
+
log(`[${HOOK_NAME11}] No fallback models configured`, { sessionID, agent });
|
|
63436
63617
|
return;
|
|
63437
63618
|
}
|
|
63438
63619
|
if (!state3) {
|
|
@@ -63444,7 +63625,7 @@ function createEventHandler(deps, helpers) {
|
|
|
63444
63625
|
pluginConfig
|
|
63445
63626
|
});
|
|
63446
63627
|
if (!initialModel) {
|
|
63447
|
-
log(`[${
|
|
63628
|
+
log(`[${HOOK_NAME11}] No model info available, cannot fallback`, { sessionID });
|
|
63448
63629
|
return;
|
|
63449
63630
|
}
|
|
63450
63631
|
state3 = createFallbackState(initialModel);
|
|
@@ -63584,7 +63765,7 @@ function createMessageUpdateHandler(deps, helpers) {
|
|
|
63584
63765
|
}
|
|
63585
63766
|
const hasVisible = await checkVisibleResponse(ctx, sessionID, info);
|
|
63586
63767
|
if (!hasVisible) {
|
|
63587
|
-
log(`[${
|
|
63768
|
+
log(`[${HOOK_NAME11}] Assistant update observed without visible final response; keeping fallback timeout`, {
|
|
63588
63769
|
sessionID,
|
|
63589
63770
|
model
|
|
63590
63771
|
});
|
|
@@ -63597,17 +63778,17 @@ function createMessageUpdateHandler(deps, helpers) {
|
|
|
63597
63778
|
if (state3?.pendingFallbackModel) {
|
|
63598
63779
|
state3.pendingFallbackModel = undefined;
|
|
63599
63780
|
}
|
|
63600
|
-
log(`[${
|
|
63781
|
+
log(`[${HOOK_NAME11}] Assistant response observed; cleared fallback timeout`, { sessionID, model });
|
|
63601
63782
|
return;
|
|
63602
63783
|
}
|
|
63603
63784
|
if (sessionID && role === "assistant" && error51) {
|
|
63604
63785
|
sessionAwaitingFallbackResult.delete(sessionID);
|
|
63605
63786
|
if (sessionRetryInFlight.has(sessionID) && !retrySignal) {
|
|
63606
|
-
log(`[${
|
|
63787
|
+
log(`[${HOOK_NAME11}] message.updated fallback skipped (retry in flight)`, { sessionID });
|
|
63607
63788
|
return;
|
|
63608
63789
|
}
|
|
63609
63790
|
if (retrySignal && sessionRetryInFlight.has(sessionID) && timeoutEnabled) {
|
|
63610
|
-
log(`[${
|
|
63791
|
+
log(`[${HOOK_NAME11}] Overriding in-flight retry due to provider auto-retry signal`, {
|
|
63611
63792
|
sessionID,
|
|
63612
63793
|
model
|
|
63613
63794
|
});
|
|
@@ -63615,12 +63796,12 @@ function createMessageUpdateHandler(deps, helpers) {
|
|
|
63615
63796
|
sessionRetryInFlight.delete(sessionID);
|
|
63616
63797
|
}
|
|
63617
63798
|
if (retrySignal && timeoutEnabled) {
|
|
63618
|
-
log(`[${
|
|
63799
|
+
log(`[${HOOK_NAME11}] Detected provider auto-retry signal`, { sessionID, model });
|
|
63619
63800
|
}
|
|
63620
63801
|
if (!retrySignal) {
|
|
63621
63802
|
helpers.clearSessionFallbackTimeout(sessionID);
|
|
63622
63803
|
}
|
|
63623
|
-
log(`[${
|
|
63804
|
+
log(`[${HOOK_NAME11}] message.updated with assistant error`, {
|
|
63624
63805
|
sessionID,
|
|
63625
63806
|
model,
|
|
63626
63807
|
statusCode: extractStatusCode(error51, config2.retry_on_errors),
|
|
@@ -63628,7 +63809,7 @@ function createMessageUpdateHandler(deps, helpers) {
|
|
|
63628
63809
|
errorType: classifyErrorType(error51)
|
|
63629
63810
|
});
|
|
63630
63811
|
if (!isRetryableError(error51, config2.retry_on_errors)) {
|
|
63631
|
-
log(`[${
|
|
63812
|
+
log(`[${HOOK_NAME11}] message.updated error not retryable, skipping fallback`, {
|
|
63632
63813
|
sessionID,
|
|
63633
63814
|
statusCode: extractStatusCode(error51, config2.retry_on_errors),
|
|
63634
63815
|
errorName: extractErrorName(error51),
|
|
@@ -63652,7 +63833,7 @@ function createMessageUpdateHandler(deps, helpers) {
|
|
|
63652
63833
|
pluginConfig
|
|
63653
63834
|
});
|
|
63654
63835
|
if (!initialModel) {
|
|
63655
|
-
log(`[${
|
|
63836
|
+
log(`[${HOOK_NAME11}] message.updated missing model info, cannot fallback`, {
|
|
63656
63837
|
sessionID,
|
|
63657
63838
|
errorName: extractErrorName(error51),
|
|
63658
63839
|
errorType: classifyErrorType(error51)
|
|
@@ -63666,13 +63847,13 @@ function createMessageUpdateHandler(deps, helpers) {
|
|
|
63666
63847
|
sessionLastAccess.set(sessionID, Date.now());
|
|
63667
63848
|
if (state3.pendingFallbackModel) {
|
|
63668
63849
|
if (retrySignal && timeoutEnabled) {
|
|
63669
|
-
log(`[${
|
|
63850
|
+
log(`[${HOOK_NAME11}] Clearing pending fallback due to provider auto-retry signal`, {
|
|
63670
63851
|
sessionID,
|
|
63671
63852
|
pendingFallbackModel: state3.pendingFallbackModel
|
|
63672
63853
|
});
|
|
63673
63854
|
state3.pendingFallbackModel = undefined;
|
|
63674
63855
|
} else {
|
|
63675
|
-
log(`[${
|
|
63856
|
+
log(`[${HOOK_NAME11}] message.updated fallback skipped (pending fallback in progress)`, {
|
|
63676
63857
|
sessionID,
|
|
63677
63858
|
pendingFallbackModel: state3.pendingFallbackModel
|
|
63678
63859
|
});
|
|
@@ -64678,7 +64859,7 @@ function createSessionRecoveryHook(ctx, options) {
|
|
|
64678
64859
|
}
|
|
64679
64860
|
// src/hooks/start-work/start-work-hook.ts
|
|
64680
64861
|
init_logger();
|
|
64681
|
-
var
|
|
64862
|
+
var HOOK_NAME12 = "start-work";
|
|
64682
64863
|
var KEYWORD_PATTERN = /\b(ultrawork|ulw)\b/gi;
|
|
64683
64864
|
function extractUserRequestPlanName(promptText) {
|
|
64684
64865
|
const userRequestMatch = promptText.match(/<user-request>\s*([\s\S]*?)\s*<\/user-request>/i);
|
|
@@ -64708,7 +64889,7 @@ function createStartWorkHook(ctx) {
|
|
|
64708
64889
|
if (!isStartWorkCommand) {
|
|
64709
64890
|
return;
|
|
64710
64891
|
}
|
|
64711
|
-
log(`[${
|
|
64892
|
+
log(`[${HOOK_NAME12}] Processing start-work command`, {
|
|
64712
64893
|
sessionID: input.sessionID
|
|
64713
64894
|
});
|
|
64714
64895
|
updateSessionAgent(input.sessionID, "architect");
|
|
@@ -64718,7 +64899,7 @@ function createStartWorkHook(ctx) {
|
|
|
64718
64899
|
let contextInfo = "";
|
|
64719
64900
|
const explicitPlanName = extractUserRequestPlanName(promptText);
|
|
64720
64901
|
if (explicitPlanName) {
|
|
64721
|
-
log(`[${
|
|
64902
|
+
log(`[${HOOK_NAME12}] Explicit plan name requested: ${explicitPlanName}`, {
|
|
64722
64903
|
sessionID: input.sessionID
|
|
64723
64904
|
});
|
|
64724
64905
|
const allPlans = findOraclePlans(ctx.directory);
|
|
@@ -64859,7 +65040,7 @@ Ask the user which plan to work on. Present the options above and wait for their
|
|
|
64859
65040
|
---
|
|
64860
65041
|
${contextInfo}`;
|
|
64861
65042
|
}
|
|
64862
|
-
log(`[${
|
|
65043
|
+
log(`[${HOOK_NAME12}] Context injected`, {
|
|
64863
65044
|
sessionID: input.sessionID,
|
|
64864
65045
|
hasExistingState: !!existingState
|
|
64865
65046
|
});
|
|
@@ -64868,16 +65049,16 @@ ${contextInfo}`;
|
|
|
64868
65049
|
}
|
|
64869
65050
|
// src/hooks/stop-continuation-guard/hook.ts
|
|
64870
65051
|
init_logger();
|
|
64871
|
-
var
|
|
65052
|
+
var HOOK_NAME13 = "stop-continuation-guard";
|
|
64872
65053
|
function createStopContinuationGuardHook(_ctx, options) {
|
|
64873
65054
|
const stoppedSessions = new Set;
|
|
64874
65055
|
const stop = (sessionID) => {
|
|
64875
65056
|
stoppedSessions.add(sessionID);
|
|
64876
|
-
log(`[${
|
|
65057
|
+
log(`[${HOOK_NAME13}] Continuation stopped for session`, { sessionID });
|
|
64877
65058
|
if (options?.backgroundManager) {
|
|
64878
65059
|
const cancelled = options.backgroundManager.cancelAllForSession(sessionID);
|
|
64879
65060
|
if (cancelled > 0) {
|
|
64880
|
-
log(`[${
|
|
65061
|
+
log(`[${HOOK_NAME13}] Cancelled ${cancelled} background task(s) for session`, { sessionID });
|
|
64881
65062
|
}
|
|
64882
65063
|
}
|
|
64883
65064
|
};
|
|
@@ -64886,7 +65067,7 @@ function createStopContinuationGuardHook(_ctx, options) {
|
|
|
64886
65067
|
};
|
|
64887
65068
|
const clear = (sessionID) => {
|
|
64888
65069
|
stoppedSessions.delete(sessionID);
|
|
64889
|
-
log(`[${
|
|
65070
|
+
log(`[${HOOK_NAME13}] Continuation guard cleared for session`, { sessionID });
|
|
64890
65071
|
};
|
|
64891
65072
|
const event = async ({
|
|
64892
65073
|
event: event2
|
|
@@ -64896,7 +65077,7 @@ function createStopContinuationGuardHook(_ctx, options) {
|
|
|
64896
65077
|
const sessionInfo = props?.info;
|
|
64897
65078
|
if (sessionInfo?.id) {
|
|
64898
65079
|
clear(sessionInfo.id);
|
|
64899
|
-
log(`[${
|
|
65080
|
+
log(`[${HOOK_NAME13}] Session deleted: cleaned up`, { sessionID: sessionInfo.id });
|
|
64900
65081
|
}
|
|
64901
65082
|
}
|
|
64902
65083
|
};
|
|
@@ -64905,7 +65086,7 @@ function createStopContinuationGuardHook(_ctx, options) {
|
|
|
64905
65086
|
}) => {
|
|
64906
65087
|
if (sessionID && stoppedSessions.has(sessionID)) {
|
|
64907
65088
|
clear(sessionID);
|
|
64908
|
-
log(`[${
|
|
65089
|
+
log(`[${HOOK_NAME13}] Cleared stop state on new user message`, { sessionID });
|
|
64909
65090
|
}
|
|
64910
65091
|
};
|
|
64911
65092
|
return {
|
|
@@ -64916,216 +65097,54 @@ function createStopContinuationGuardHook(_ctx, options) {
|
|
|
64916
65097
|
clear
|
|
64917
65098
|
};
|
|
64918
65099
|
}
|
|
64919
|
-
// src/hooks/task-
|
|
64920
|
-
var HOOK_NAME13 = "task-notepad";
|
|
64921
|
-
var PLAN_DIR_NAME = ".matrixx";
|
|
64922
|
-
var PLANS_SUBDIR = "plans";
|
|
64923
|
-
var NOTEPAD_DIR_NAME = "notepads";
|
|
64924
|
-
var TASKS_SUBDIR = "tasks";
|
|
64925
|
-
var PLAN_EXTENSION = ".md";
|
|
64926
|
-
var NOTEPAD_EXTENSION = ".md";
|
|
64927
|
-
var TODO_TOOL_NAMES = new Set(["todowrite", "TodoWrite"]);
|
|
64928
|
-
var TASK_NOTEPAD_FRAGMENT = "task-notepad";
|
|
64929
|
-
var TODO_FETCH_ERROR_FRAGMENT = "Failed to fetch todos for task-notepad";
|
|
64930
|
-
// src/hooks/task-notepad/hook.ts
|
|
65100
|
+
// src/hooks/task-continuation-enforcer/index.ts
|
|
64931
65101
|
init_logger();
|
|
64932
|
-
import { existsSync as existsSync42, mkdirSync as mkdirSync15, readdirSync as readdirSync13, readFileSync as readFileSync32, statSync as statSync8, writeFileSync as writeFileSync18 } from "fs";
|
|
64933
|
-
import { dirname as dirname14, join as join51 } from "path";
|
|
64934
|
-
function createTaskNotepadHook(ctx) {
|
|
64935
|
-
return {
|
|
64936
|
-
"tool.execute.after": async (input, output) => {
|
|
64937
|
-
if (!output)
|
|
64938
|
-
return;
|
|
64939
|
-
if (!TODO_TOOL_NAMES.has(input.tool))
|
|
64940
|
-
return;
|
|
64941
|
-
const todos = await fetchTodos(ctx, input.sessionID);
|
|
64942
|
-
const inProgress = todos.find((t) => t.status === "in_progress");
|
|
64943
|
-
const completed = todos.filter((t) => t.status === "completed");
|
|
64944
|
-
if (!inProgress && completed.length === 0)
|
|
64945
|
-
return;
|
|
64946
|
-
const plan = resolveActivePlan(ctx.directory);
|
|
64947
|
-
if (inProgress) {
|
|
64948
|
-
if (!plan) {
|
|
64949
|
-
log(`[${HOOK_NAME13}] No active plan, skipping notepad creation`, {
|
|
64950
|
-
sessionID: input.sessionID
|
|
64951
|
-
});
|
|
64952
|
-
} else {
|
|
64953
|
-
ensureNotepad(ctx.directory, plan.name, inProgress);
|
|
64954
|
-
}
|
|
64955
|
-
}
|
|
64956
|
-
if (!plan)
|
|
64957
|
-
return;
|
|
64958
|
-
for (const done of completed) {
|
|
64959
|
-
appendCompletion(ctx.directory, plan.name, done);
|
|
64960
|
-
}
|
|
64961
|
-
}
|
|
64962
|
-
};
|
|
64963
|
-
}
|
|
64964
|
-
async function fetchTodos(ctx, sessionID) {
|
|
64965
|
-
try {
|
|
64966
|
-
const response = await ctx.client.session.todo({ path: { id: sessionID } });
|
|
64967
|
-
const payload = response;
|
|
64968
|
-
if (Array.isArray(payload?.data)) {
|
|
64969
|
-
return payload.data;
|
|
64970
|
-
}
|
|
64971
|
-
if (Array.isArray(response)) {
|
|
64972
|
-
return response;
|
|
64973
|
-
}
|
|
64974
|
-
return [];
|
|
64975
|
-
} catch (err) {
|
|
64976
|
-
log(`[${HOOK_NAME13}] ${TODO_FETCH_ERROR_FRAGMENT}`, {
|
|
64977
|
-
sessionID,
|
|
64978
|
-
error: String(err)
|
|
64979
|
-
});
|
|
64980
|
-
return [];
|
|
64981
|
-
}
|
|
64982
|
-
}
|
|
64983
|
-
function resolveActivePlan(directory) {
|
|
64984
|
-
const plansDir = join51(directory, PLAN_DIR_NAME, PLANS_SUBDIR);
|
|
64985
|
-
if (!existsSync42(plansDir))
|
|
64986
|
-
return null;
|
|
64987
|
-
let entries;
|
|
64988
|
-
try {
|
|
64989
|
-
entries = readdirSync13(plansDir);
|
|
64990
|
-
} catch {
|
|
64991
|
-
return null;
|
|
64992
|
-
}
|
|
64993
|
-
let best = null;
|
|
64994
|
-
for (const entry of entries) {
|
|
64995
|
-
if (!entry.endsWith(PLAN_EXTENSION))
|
|
64996
|
-
continue;
|
|
64997
|
-
const fullPath = join51(plansDir, entry);
|
|
64998
|
-
try {
|
|
64999
|
-
const stats = statSync8(fullPath);
|
|
65000
|
-
if (!stats.isFile())
|
|
65001
|
-
continue;
|
|
65002
|
-
const candidate = {
|
|
65003
|
-
name: entry.slice(0, -PLAN_EXTENSION.length),
|
|
65004
|
-
path: fullPath,
|
|
65005
|
-
mtimeMs: stats.mtimeMs
|
|
65006
|
-
};
|
|
65007
|
-
if (!best || candidate.mtimeMs > best.mtimeMs) {
|
|
65008
|
-
best = candidate;
|
|
65009
|
-
}
|
|
65010
|
-
} catch {}
|
|
65011
|
-
}
|
|
65012
|
-
return best;
|
|
65013
|
-
}
|
|
65014
|
-
function ensureNotepad(directory, planName, todo) {
|
|
65015
|
-
const notepadPath = notepadPathFor(directory, planName, todo.id);
|
|
65016
|
-
if (existsSync42(notepadPath))
|
|
65017
|
-
return;
|
|
65018
|
-
mkdirSync15(dirname14(notepadPath), { recursive: true });
|
|
65019
|
-
const content = renderNotepad(todo);
|
|
65020
|
-
writeFileSync18(notepadPath, content, "utf-8");
|
|
65021
|
-
log(`[${HOOK_NAME13}] Created task notepad`, { plan: planName, taskID: todo.id });
|
|
65022
|
-
}
|
|
65023
|
-
function appendCompletion(directory, planName, todo) {
|
|
65024
|
-
const notepadPath = notepadPathFor(directory, planName, todo.id);
|
|
65025
|
-
if (!existsSync42(notepadPath))
|
|
65026
|
-
return;
|
|
65027
|
-
const existing = readFileSync32(notepadPath, "utf-8");
|
|
65028
|
-
if (existing.includes("**Status**: completed"))
|
|
65029
|
-
return;
|
|
65030
|
-
const stamp = new Date().toISOString();
|
|
65031
|
-
const updated = `${existing}
|
|
65032
|
-
## Completion
|
|
65033
|
-
- completed_at: ${stamp}
|
|
65034
|
-
- status: completed
|
|
65035
|
-
`;
|
|
65036
|
-
writeFileSync18(notepadPath, updated, "utf-8");
|
|
65037
|
-
log(`[${HOOK_NAME13}] Appended completion to notepad`, { plan: planName, taskID: todo.id });
|
|
65038
|
-
}
|
|
65039
|
-
function notepadPathFor(directory, planName, taskID) {
|
|
65040
|
-
return join51(directory, PLAN_DIR_NAME, NOTEPAD_DIR_NAME, planName, TASKS_SUBDIR, `${taskID}${NOTEPAD_EXTENSION}`);
|
|
65041
|
-
}
|
|
65042
|
-
function renderNotepad(todo) {
|
|
65043
|
-
const priority = todo.priority ?? "medium";
|
|
65044
|
-
return `# Task: ${todo.content}
|
|
65045
|
-
|
|
65046
|
-
**Plan**: ${TASK_NOTEPAD_FRAGMENT}
|
|
65047
|
-
**Task ID**: ${todo.id}
|
|
65048
|
-
**Priority**: ${priority}
|
|
65049
|
-
**Status**: ${todo.status}
|
|
65050
|
-
**Started**: ${new Date().toISOString()}
|
|
65051
|
-
|
|
65052
|
-
## Findings
|
|
65053
|
-
|
|
65054
|
-
(Record what you learn about this code as you work \u2014 patterns, conventions, gotchas, useful references.)
|
|
65055
|
-
|
|
65056
|
-
## Blockers
|
|
65057
|
-
|
|
65058
|
-
(Anything blocking progress. Be specific: file path, line, error, workaround if any.)
|
|
65059
65102
|
|
|
65060
|
-
|
|
65103
|
+
// src/hooks/task-continuation-enforcer/constants.ts
|
|
65104
|
+
var HOOK_NAME14 = "task-continuation-enforcer";
|
|
65105
|
+
var DEFAULT_SKIP_AGENTS = ["oracle", "compaction"];
|
|
65106
|
+
var CONTINUATION_PROMPT2 = `${createSystemDirective(SystemDirectiveTypes.TASK_CONTINUATION)}
|
|
65061
65107
|
|
|
65062
|
-
|
|
65108
|
+
Incomplete Matrixx tasks remain. Continue working on the next pending task.
|
|
65063
65109
|
|
|
65064
|
-
|
|
65110
|
+
- Proceed without asking for permission
|
|
65111
|
+
- Mark each task in_progress before starting, completed immediately after
|
|
65112
|
+
- Respect blockedBy dependencies (skip blocked tasks)
|
|
65113
|
+
- Do not stop until all tasks are done`;
|
|
65114
|
+
var COUNTDOWN_SECONDS = 2;
|
|
65115
|
+
var TOAST_DURATION_MS = 900;
|
|
65116
|
+
var COUNTDOWN_GRACE_PERIOD_MS = 500;
|
|
65117
|
+
var ABORT_WINDOW_MS = 3000;
|
|
65118
|
+
var CONTINUATION_COOLDOWN_MS2 = 30000;
|
|
65119
|
+
var MAX_CONSECUTIVE_FAILURES = 5;
|
|
65120
|
+
var FAILURE_RESET_WINDOW_MS = 5 * 60 * 1000;
|
|
65065
65121
|
|
|
65066
|
-
|
|
65067
|
-
|
|
65068
|
-
}
|
|
65069
|
-
// src/hooks/task-resume-info/hook.ts
|
|
65070
|
-
var TARGET_TOOLS2 = ["task", "Task", "task_tool", "delegate_agent"];
|
|
65071
|
-
var SESSION_ID_PATTERNS = [
|
|
65072
|
-
/Session ID: (ses_[a-zA-Z0-9_-]+)/,
|
|
65073
|
-
/session_id: (ses_[a-zA-Z0-9_-]+)/,
|
|
65074
|
-
/<task_metadata>\s*session_id: (ses_[a-zA-Z0-9_-]+)/,
|
|
65075
|
-
/sessionId: (ses_[a-zA-Z0-9_-]+)/
|
|
65076
|
-
];
|
|
65077
|
-
function extractSessionId(output) {
|
|
65078
|
-
for (const pattern of SESSION_ID_PATTERNS) {
|
|
65079
|
-
const match = output.match(pattern);
|
|
65080
|
-
if (match)
|
|
65081
|
-
return match[1] ?? null;
|
|
65082
|
-
}
|
|
65083
|
-
return null;
|
|
65084
|
-
}
|
|
65085
|
-
function createTaskResumeInfoHook() {
|
|
65086
|
-
const toolExecuteAfter = async (input, output) => {
|
|
65087
|
-
if (!TARGET_TOOLS2.includes(input.tool))
|
|
65088
|
-
return;
|
|
65089
|
-
const outputText = output.output ?? "";
|
|
65090
|
-
if (outputText.startsWith("Error:") || outputText.startsWith("Failed"))
|
|
65091
|
-
return;
|
|
65092
|
-
if (outputText.includes(`
|
|
65093
|
-
to continue:`))
|
|
65094
|
-
return;
|
|
65095
|
-
const sessionId = extractSessionId(outputText);
|
|
65096
|
-
if (!sessionId)
|
|
65097
|
-
return;
|
|
65098
|
-
output.output = outputText.trimEnd() + `
|
|
65122
|
+
// src/hooks/task-continuation-enforcer/handler.ts
|
|
65123
|
+
init_logger();
|
|
65099
65124
|
|
|
65100
|
-
|
|
65101
|
-
|
|
65102
|
-
return {
|
|
65103
|
-
"tool.execute.after": toolExecuteAfter
|
|
65104
|
-
};
|
|
65105
|
-
}
|
|
65106
|
-
// src/hooks/task-todo-mirror/constants.ts
|
|
65107
|
-
var HOOK_NAME14 = "task-todo-mirror";
|
|
65108
|
-
var TASK_TOOL_NAMES = new Set([
|
|
65109
|
-
"task_create",
|
|
65110
|
-
"task_update"
|
|
65111
|
-
]);
|
|
65112
|
-
var DEBOUNCE_MS = 300;
|
|
65113
|
-
// src/hooks/task-todo-mirror/hook.ts
|
|
65114
|
-
import { existsSync as existsSync45, readdirSync as readdirSync15 } from "fs";
|
|
65125
|
+
// src/hooks/task-continuation-enforcer/idle-event.ts
|
|
65126
|
+
import { existsSync as existsSync44, readdirSync as readdirSync15 } from "fs";
|
|
65115
65127
|
|
|
65116
65128
|
// src/features/task-storage/storage.ts
|
|
65129
|
+
init_logger();
|
|
65117
65130
|
import { randomUUID } from "crypto";
|
|
65118
|
-
import { existsSync as
|
|
65119
|
-
import { basename as basename5, dirname as
|
|
65120
|
-
function
|
|
65131
|
+
import { existsSync as existsSync42, mkdirSync as mkdirSync15, readdirSync as readdirSync13, readFileSync as readFileSync32, renameSync as renameSync3, unlinkSync as unlinkSync9, writeFileSync as writeFileSync18 } from "fs";
|
|
65132
|
+
import { basename as basename5, dirname as dirname14, isAbsolute as isAbsolute3, join as join51 } from "path";
|
|
65133
|
+
function getProjectTaskDir(directory) {
|
|
65134
|
+
return join51(directory, ".matrixx", "tasks");
|
|
65135
|
+
}
|
|
65136
|
+
function getTaskDir(config2 = {}, directory) {
|
|
65121
65137
|
const tasksConfig = config2.morpheus?.tasks;
|
|
65122
65138
|
const storagePath = tasksConfig?.storage_path;
|
|
65123
65139
|
if (storagePath) {
|
|
65124
|
-
return isAbsolute3(storagePath) ? storagePath :
|
|
65140
|
+
return isAbsolute3(storagePath) ? storagePath : join51(directory ?? process.cwd(), storagePath);
|
|
65125
65141
|
}
|
|
65126
|
-
|
|
65127
|
-
|
|
65128
|
-
|
|
65142
|
+
if (tasksConfig?.scope === "global" || !directory) {
|
|
65143
|
+
const configDir = getOpenCodeConfigDir({ binary: "opencode" });
|
|
65144
|
+
const listId = resolveTaskListId(config2);
|
|
65145
|
+
return join51(configDir, "tasks", listId);
|
|
65146
|
+
}
|
|
65147
|
+
return getProjectTaskDir(directory);
|
|
65129
65148
|
}
|
|
65130
65149
|
function sanitizePathSegment(value) {
|
|
65131
65150
|
return value.replace(/[^a-zA-Z0-9_-]/g, "-") || "default";
|
|
@@ -65143,16 +65162,16 @@ function resolveTaskListId(config2 = {}) {
|
|
|
65143
65162
|
return sanitizePathSegment(basename5(process.cwd()));
|
|
65144
65163
|
}
|
|
65145
65164
|
function ensureDir2(dirPath) {
|
|
65146
|
-
if (!
|
|
65147
|
-
|
|
65165
|
+
if (!existsSync42(dirPath)) {
|
|
65166
|
+
mkdirSync15(dirPath, { recursive: true });
|
|
65148
65167
|
}
|
|
65149
65168
|
}
|
|
65150
65169
|
function readJsonSafe(filePath, schema) {
|
|
65151
65170
|
try {
|
|
65152
|
-
if (!
|
|
65171
|
+
if (!existsSync42(filePath)) {
|
|
65153
65172
|
return null;
|
|
65154
65173
|
}
|
|
65155
|
-
const content =
|
|
65174
|
+
const content = readFileSync32(filePath, "utf-8");
|
|
65156
65175
|
const parsed = JSON.parse(content);
|
|
65157
65176
|
const result = schema.safeParse(parsed);
|
|
65158
65177
|
if (!result.success) {
|
|
@@ -65164,15 +65183,15 @@ function readJsonSafe(filePath, schema) {
|
|
|
65164
65183
|
}
|
|
65165
65184
|
}
|
|
65166
65185
|
function writeJsonAtomic(filePath, data) {
|
|
65167
|
-
const dir =
|
|
65186
|
+
const dir = dirname14(filePath);
|
|
65168
65187
|
ensureDir2(dir);
|
|
65169
65188
|
const tempPath = `${filePath}.tmp.${Date.now()}`;
|
|
65170
65189
|
try {
|
|
65171
|
-
|
|
65190
|
+
writeFileSync18(tempPath, JSON.stringify(data, null, 2), "utf-8");
|
|
65172
65191
|
renameSync3(tempPath, filePath);
|
|
65173
65192
|
} catch (error51) {
|
|
65174
65193
|
try {
|
|
65175
|
-
if (
|
|
65194
|
+
if (existsSync42(tempPath)) {
|
|
65176
65195
|
unlinkSync9(tempPath);
|
|
65177
65196
|
}
|
|
65178
65197
|
} catch {}
|
|
@@ -65180,21 +65199,73 @@ function writeJsonAtomic(filePath, data) {
|
|
|
65180
65199
|
}
|
|
65181
65200
|
}
|
|
65182
65201
|
var STALE_LOCK_THRESHOLD_MS = 30000;
|
|
65202
|
+
async function acquireLockWithRetry(dirPath, retries = 4) {
|
|
65203
|
+
for (let attempt = 0;attempt < retries; attempt++) {
|
|
65204
|
+
const lock = acquireLock(dirPath);
|
|
65205
|
+
if (lock.acquired)
|
|
65206
|
+
return lock;
|
|
65207
|
+
if (attempt < retries - 1) {
|
|
65208
|
+
await new Promise((resolve7) => setTimeout(resolve7, 15 * 2 ** attempt));
|
|
65209
|
+
}
|
|
65210
|
+
}
|
|
65211
|
+
return { acquired: false, release: () => {} };
|
|
65212
|
+
}
|
|
65183
65213
|
function generateTaskId() {
|
|
65184
65214
|
return `T-${randomUUID()}`;
|
|
65185
65215
|
}
|
|
65216
|
+
function migrateLegacyTasksIfNeeded(config2, directory) {
|
|
65217
|
+
const projectDir = getProjectTaskDir(directory);
|
|
65218
|
+
const legacyDir = join51(getOpenCodeConfigDir({ binary: "opencode" }), "tasks", resolveTaskListId(config2));
|
|
65219
|
+
if (existsSync42(projectDir)) {
|
|
65220
|
+
try {
|
|
65221
|
+
const existing = readdirSync13(projectDir).filter((f) => f.startsWith("T-") && f.endsWith(".json"));
|
|
65222
|
+
if (existing.length > 0) {
|
|
65223
|
+
return { migrated: 0, legacyDir, projectDir };
|
|
65224
|
+
}
|
|
65225
|
+
} catch {}
|
|
65226
|
+
}
|
|
65227
|
+
if (!existsSync42(legacyDir)) {
|
|
65228
|
+
return { migrated: 0, legacyDir, projectDir };
|
|
65229
|
+
}
|
|
65230
|
+
let files = [];
|
|
65231
|
+
try {
|
|
65232
|
+
files = readdirSync13(legacyDir).filter((f) => f.startsWith("T-") && f.endsWith(".json"));
|
|
65233
|
+
} catch {
|
|
65234
|
+
return { migrated: 0, legacyDir, projectDir };
|
|
65235
|
+
}
|
|
65236
|
+
if (files.length === 0) {
|
|
65237
|
+
return { migrated: 0, legacyDir, projectDir };
|
|
65238
|
+
}
|
|
65239
|
+
ensureDir2(projectDir);
|
|
65240
|
+
let migrated = 0;
|
|
65241
|
+
for (const file2 of files) {
|
|
65242
|
+
const src = join51(legacyDir, file2);
|
|
65243
|
+
const dest = join51(projectDir, file2);
|
|
65244
|
+
if (existsSync42(dest))
|
|
65245
|
+
continue;
|
|
65246
|
+
try {
|
|
65247
|
+
const content = readFileSync32(src, "utf-8");
|
|
65248
|
+
writeFileSync18(dest, content, "utf-8");
|
|
65249
|
+
migrated++;
|
|
65250
|
+
} catch {}
|
|
65251
|
+
}
|
|
65252
|
+
if (migrated > 0) {
|
|
65253
|
+
log(`[task-storage] Migrated ${migrated} legacy tasks from ${legacyDir} to ${projectDir}`);
|
|
65254
|
+
}
|
|
65255
|
+
return { migrated, legacyDir, projectDir };
|
|
65256
|
+
}
|
|
65186
65257
|
function acquireLock(dirPath) {
|
|
65187
|
-
const lockPath =
|
|
65258
|
+
const lockPath = join51(dirPath, ".lock");
|
|
65188
65259
|
const lockId = randomUUID();
|
|
65189
65260
|
const createLock = (timestamp) => {
|
|
65190
|
-
|
|
65261
|
+
writeFileSync18(lockPath, JSON.stringify({ id: lockId, timestamp }), {
|
|
65191
65262
|
encoding: "utf-8",
|
|
65192
65263
|
flag: "wx"
|
|
65193
65264
|
});
|
|
65194
65265
|
};
|
|
65195
65266
|
const isStale = () => {
|
|
65196
65267
|
try {
|
|
65197
|
-
const lockContent =
|
|
65268
|
+
const lockContent = readFileSync32(lockPath, "utf-8");
|
|
65198
65269
|
const lockData = JSON.parse(lockContent);
|
|
65199
65270
|
const lockAge = Date.now() - lockData.timestamp;
|
|
65200
65271
|
return lockAge > STALE_LOCK_THRESHOLD_MS;
|
|
@@ -65232,9 +65303,9 @@ function acquireLock(dirPath) {
|
|
|
65232
65303
|
acquired: true,
|
|
65233
65304
|
release: () => {
|
|
65234
65305
|
try {
|
|
65235
|
-
if (!
|
|
65306
|
+
if (!existsSync42(lockPath))
|
|
65236
65307
|
return;
|
|
65237
|
-
const lockContent =
|
|
65308
|
+
const lockContent = readFileSync32(lockPath, "utf-8");
|
|
65238
65309
|
const lockData = JSON.parse(lockContent);
|
|
65239
65310
|
if (lockData.id !== lockId)
|
|
65240
65311
|
return;
|
|
@@ -65244,239 +65315,9 @@ function acquireLock(dirPath) {
|
|
|
65244
65315
|
};
|
|
65245
65316
|
}
|
|
65246
65317
|
|
|
65247
|
-
// src/hooks/task-
|
|
65318
|
+
// src/hooks/task-continuation-enforcer/idle-event.ts
|
|
65248
65319
|
init_logger();
|
|
65249
65320
|
|
|
65250
|
-
// src/tools/task/todo-sync.ts
|
|
65251
|
-
import { existsSync as existsSync44 } from "fs";
|
|
65252
|
-
import { homedir as homedir9 } from "os";
|
|
65253
|
-
import { join as join53 } from "path";
|
|
65254
|
-
init_logger();
|
|
65255
|
-
function mapTaskStatusToTodoStatus(taskStatus) {
|
|
65256
|
-
switch (taskStatus) {
|
|
65257
|
-
case "pending":
|
|
65258
|
-
return "pending";
|
|
65259
|
-
case "in_progress":
|
|
65260
|
-
return "in_progress";
|
|
65261
|
-
case "completed":
|
|
65262
|
-
return "completed";
|
|
65263
|
-
case "deleted":
|
|
65264
|
-
return null;
|
|
65265
|
-
default:
|
|
65266
|
-
return "pending";
|
|
65267
|
-
}
|
|
65268
|
-
}
|
|
65269
|
-
function extractPriority(metadata) {
|
|
65270
|
-
if (!metadata)
|
|
65271
|
-
return;
|
|
65272
|
-
const priority = metadata.priority;
|
|
65273
|
-
if (typeof priority === "string" && ["low", "medium", "high"].includes(priority)) {
|
|
65274
|
-
return priority;
|
|
65275
|
-
}
|
|
65276
|
-
return;
|
|
65277
|
-
}
|
|
65278
|
-
function todosMatch(todo1, todo2) {
|
|
65279
|
-
if (todo1.id && todo2.id) {
|
|
65280
|
-
return todo1.id === todo2.id;
|
|
65281
|
-
}
|
|
65282
|
-
return todo1.content === todo2.content;
|
|
65283
|
-
}
|
|
65284
|
-
function syncTaskToTodo(task) {
|
|
65285
|
-
const todoStatus = mapTaskStatusToTodoStatus(task.status);
|
|
65286
|
-
if (todoStatus === null) {
|
|
65287
|
-
return null;
|
|
65288
|
-
}
|
|
65289
|
-
return {
|
|
65290
|
-
id: task.id,
|
|
65291
|
-
content: task.subject,
|
|
65292
|
-
status: todoStatus,
|
|
65293
|
-
priority: extractPriority(task.metadata)
|
|
65294
|
-
};
|
|
65295
|
-
}
|
|
65296
|
-
async function resolveTodoWriter2() {
|
|
65297
|
-
const loaders = ["opencode/session/todo", "@opencode-ai/core/session/todo"];
|
|
65298
|
-
for (const loader of loaders) {
|
|
65299
|
-
try {
|
|
65300
|
-
const mod = await import(loader);
|
|
65301
|
-
const update = mod.Todo?.update;
|
|
65302
|
-
if (typeof update === "function") {
|
|
65303
|
-
return update;
|
|
65304
|
-
}
|
|
65305
|
-
} catch (err) {
|
|
65306
|
-
log("[todo-sync] Failed to resolve Todo.update", { loader, error: String(err) });
|
|
65307
|
-
}
|
|
65308
|
-
}
|
|
65309
|
-
return null;
|
|
65310
|
-
}
|
|
65311
|
-
function getOpencodeDbPath() {
|
|
65312
|
-
const candidates = [];
|
|
65313
|
-
if (process.env.XDG_DATA_HOME) {
|
|
65314
|
-
candidates.push(join53(process.env.XDG_DATA_HOME, "opencode", "opencode.db"));
|
|
65315
|
-
}
|
|
65316
|
-
candidates.push(join53(homedir9(), ".local", "share", "opencode", "opencode.db"));
|
|
65317
|
-
candidates.push(join53(homedir9(), ".config", "opencode", "opencode.db"));
|
|
65318
|
-
for (const p of candidates) {
|
|
65319
|
-
if (existsSync44(p))
|
|
65320
|
-
return p;
|
|
65321
|
-
}
|
|
65322
|
-
return candidates[1] ?? null;
|
|
65323
|
-
}
|
|
65324
|
-
async function directDbWrite(sessionID, todos) {
|
|
65325
|
-
const dbPath = getOpencodeDbPath();
|
|
65326
|
-
if (!dbPath || !existsSync44(dbPath)) {
|
|
65327
|
-
log("[todo-sync] directDbWrite no db", { dbPath });
|
|
65328
|
-
return false;
|
|
65329
|
-
}
|
|
65330
|
-
try {
|
|
65331
|
-
const mod = await import("bun:sqlite");
|
|
65332
|
-
const Database = mod.Database;
|
|
65333
|
-
const db = new Database(dbPath);
|
|
65334
|
-
const now = Date.now();
|
|
65335
|
-
try {
|
|
65336
|
-
db.exec("BEGIN IMMEDIATE");
|
|
65337
|
-
db.prepare("DELETE FROM todo WHERE session_id = ?").run(sessionID);
|
|
65338
|
-
const stmt = db.prepare("INSERT INTO todo (session_id, content, status, priority, position, time_created, time_updated) VALUES (?, ?, ?, ?, ?, ?, ?)");
|
|
65339
|
-
todos.forEach((t, idx) => {
|
|
65340
|
-
stmt.run(sessionID, t.content, t.status, t.priority ?? "medium", idx, now, now);
|
|
65341
|
-
});
|
|
65342
|
-
db.exec("COMMIT");
|
|
65343
|
-
} catch (inner) {
|
|
65344
|
-
try {
|
|
65345
|
-
db.exec("ROLLBACK");
|
|
65346
|
-
} catch {}
|
|
65347
|
-
throw inner;
|
|
65348
|
-
} finally {
|
|
65349
|
-
db.close();
|
|
65350
|
-
}
|
|
65351
|
-
log("[todo-sync] directDbWrite ok", { sessionID, count: todos.length });
|
|
65352
|
-
return true;
|
|
65353
|
-
} catch (err) {
|
|
65354
|
-
log("[todo-sync] directDbWrite failed", { error: String(err) });
|
|
65355
|
-
return false;
|
|
65356
|
-
}
|
|
65357
|
-
}
|
|
65358
|
-
async function writeTodosWithFallback(sessionID, todos, writer) {
|
|
65359
|
-
if (writer) {
|
|
65360
|
-
try {
|
|
65361
|
-
await writer({ sessionID, todos });
|
|
65362
|
-
return;
|
|
65363
|
-
} catch (err) {
|
|
65364
|
-
log("[todo-sync] writer failed, fallback to direct DB", { error: String(err) });
|
|
65365
|
-
}
|
|
65366
|
-
}
|
|
65367
|
-
const ok = await directDbWrite(sessionID, todos);
|
|
65368
|
-
if (!ok) {
|
|
65369
|
-
log("[todo-sync] fallback also failed", { sessionID });
|
|
65370
|
-
}
|
|
65371
|
-
}
|
|
65372
|
-
function extractTodos2(response) {
|
|
65373
|
-
const payload = response;
|
|
65374
|
-
if (Array.isArray(payload?.data)) {
|
|
65375
|
-
return payload.data;
|
|
65376
|
-
}
|
|
65377
|
-
if (Array.isArray(response)) {
|
|
65378
|
-
return response;
|
|
65379
|
-
}
|
|
65380
|
-
return [];
|
|
65381
|
-
}
|
|
65382
|
-
async function syncTaskTodoUpdate(ctx, task, sessionID, writer) {
|
|
65383
|
-
if (!ctx)
|
|
65384
|
-
return;
|
|
65385
|
-
const resolvedWriter = writer ?? await resolveTodoWriter2();
|
|
65386
|
-
await syncSingleSession(ctx, task, sessionID, resolvedWriter);
|
|
65387
|
-
const mainSessionID = safeGetMainSessionID();
|
|
65388
|
-
if (mainSessionID && mainSessionID !== sessionID) {
|
|
65389
|
-
await syncSingleSession(ctx, task, mainSessionID, resolvedWriter);
|
|
65390
|
-
}
|
|
65391
|
-
}
|
|
65392
|
-
async function syncSingleSession(ctx, task, sessionID, writer) {
|
|
65393
|
-
try {
|
|
65394
|
-
const response = await ctx.client.session.todo({
|
|
65395
|
-
path: { id: sessionID }
|
|
65396
|
-
});
|
|
65397
|
-
const currentTodos = extractTodos2(response);
|
|
65398
|
-
const taskTodo = syncTaskToTodo(task);
|
|
65399
|
-
const nextTodos = currentTodos.filter((todo) => {
|
|
65400
|
-
if (taskTodo) {
|
|
65401
|
-
return !todosMatch(todo, taskTodo);
|
|
65402
|
-
}
|
|
65403
|
-
if (todo.id) {
|
|
65404
|
-
return todo.id !== task.id;
|
|
65405
|
-
}
|
|
65406
|
-
return todo.content !== task.subject;
|
|
65407
|
-
});
|
|
65408
|
-
if (taskTodo) {
|
|
65409
|
-
nextTodos.push(taskTodo);
|
|
65410
|
-
}
|
|
65411
|
-
await writeTodosWithFallback(sessionID, nextTodos, writer);
|
|
65412
|
-
} catch (err) {
|
|
65413
|
-
log("[todo-sync] Failed to sync task todo", {
|
|
65414
|
-
error: String(err),
|
|
65415
|
-
sessionID
|
|
65416
|
-
});
|
|
65417
|
-
}
|
|
65418
|
-
}
|
|
65419
|
-
function safeGetMainSessionID() {
|
|
65420
|
-
try {
|
|
65421
|
-
return getMainSessionID();
|
|
65422
|
-
} catch {
|
|
65423
|
-
return;
|
|
65424
|
-
}
|
|
65425
|
-
}
|
|
65426
|
-
async function syncAllTasksToTodos(ctx, tasks, sessionID, writer) {
|
|
65427
|
-
try {
|
|
65428
|
-
let currentTodos = [];
|
|
65429
|
-
try {
|
|
65430
|
-
const response = await ctx.client.session.todo({
|
|
65431
|
-
path: { id: sessionID || "" }
|
|
65432
|
-
});
|
|
65433
|
-
currentTodos = extractTodos2(response);
|
|
65434
|
-
} catch (err) {
|
|
65435
|
-
log("[todo-sync] Failed to fetch current todos", {
|
|
65436
|
-
error: String(err),
|
|
65437
|
-
sessionID
|
|
65438
|
-
});
|
|
65439
|
-
}
|
|
65440
|
-
const newTodos = [];
|
|
65441
|
-
const tasksToRemove = new Set;
|
|
65442
|
-
const allTaskSubjects = new Set;
|
|
65443
|
-
for (const task of tasks) {
|
|
65444
|
-
allTaskSubjects.add(task.subject);
|
|
65445
|
-
const todo = syncTaskToTodo(task);
|
|
65446
|
-
if (todo === null) {
|
|
65447
|
-
tasksToRemove.add(task.id);
|
|
65448
|
-
} else {
|
|
65449
|
-
newTodos.push(todo);
|
|
65450
|
-
}
|
|
65451
|
-
}
|
|
65452
|
-
const finalTodos = [];
|
|
65453
|
-
const removedTaskSubjects = new Set(tasks.filter((t) => t.status === "deleted").map((t) => t.subject));
|
|
65454
|
-
for (const existing of currentTodos) {
|
|
65455
|
-
const isInNewTodos = newTodos.some((newTodo) => todosMatch(existing, newTodo));
|
|
65456
|
-
const isRemovedById = existing.id ? tasksToRemove.has(existing.id) : false;
|
|
65457
|
-
const isRemovedByContent = !existing.id && removedTaskSubjects.has(existing.content);
|
|
65458
|
-
const isReplacedByTask = !existing.id && allTaskSubjects.has(existing.content);
|
|
65459
|
-
if (!isInNewTodos && !isRemovedById && !isRemovedByContent && !isReplacedByTask) {
|
|
65460
|
-
finalTodos.push(existing);
|
|
65461
|
-
}
|
|
65462
|
-
}
|
|
65463
|
-
finalTodos.push(...newTodos);
|
|
65464
|
-
const resolvedWriter = writer ?? await resolveTodoWriter2();
|
|
65465
|
-
if (sessionID) {
|
|
65466
|
-
await writeTodosWithFallback(sessionID, finalTodos, resolvedWriter);
|
|
65467
|
-
}
|
|
65468
|
-
log("[todo-sync] Synced todos", {
|
|
65469
|
-
count: finalTodos.length,
|
|
65470
|
-
sessionID
|
|
65471
|
-
});
|
|
65472
|
-
} catch (err) {
|
|
65473
|
-
log("[todo-sync] Error in syncAllTasksToTodos", {
|
|
65474
|
-
error: String(err),
|
|
65475
|
-
sessionID
|
|
65476
|
-
});
|
|
65477
|
-
}
|
|
65478
|
-
}
|
|
65479
|
-
|
|
65480
65321
|
// src/tools/task/types.ts
|
|
65481
65322
|
var TaskStatusSchema = exports_external.enum(["pending", "in_progress", "completed", "deleted"]);
|
|
65482
65323
|
var TaskObjectSchema = exports_external.object({
|
|
@@ -65491,7 +65332,8 @@ var TaskObjectSchema = exports_external.object({
|
|
|
65491
65332
|
metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
|
|
65492
65333
|
repoURL: exports_external.string().optional(),
|
|
65493
65334
|
parentID: exports_external.string().optional(),
|
|
65494
|
-
threadID: exports_external.string()
|
|
65335
|
+
threadID: exports_external.string(),
|
|
65336
|
+
projectRoot: exports_external.string().optional()
|
|
65495
65337
|
}).strict();
|
|
65496
65338
|
var TaskCreateInputSchema = exports_external.object({
|
|
65497
65339
|
subject: exports_external.string(),
|
|
@@ -65528,82 +65370,849 @@ var TaskDeleteInputSchema = exports_external.object({
|
|
|
65528
65370
|
id: exports_external.string()
|
|
65529
65371
|
});
|
|
65530
65372
|
|
|
65531
|
-
// src/hooks/task-
|
|
65532
|
-
function
|
|
65373
|
+
// src/hooks/task-continuation-enforcer/abort-detection.ts
|
|
65374
|
+
function isLastAssistantMessageAborted(messages) {
|
|
65375
|
+
if (!messages || messages.length === 0)
|
|
65376
|
+
return false;
|
|
65377
|
+
for (let i = messages.length - 1;i >= 0; i--) {
|
|
65378
|
+
const info = messages[i].info;
|
|
65379
|
+
if (info?.role !== "assistant")
|
|
65380
|
+
continue;
|
|
65381
|
+
const errorName = info.error?.name;
|
|
65382
|
+
if (!errorName)
|
|
65383
|
+
return false;
|
|
65384
|
+
return errorName === "MessageAbortedError" || errorName === "AbortError";
|
|
65385
|
+
}
|
|
65386
|
+
return false;
|
|
65387
|
+
}
|
|
65388
|
+
|
|
65389
|
+
// src/hooks/task-continuation-enforcer/countdown.ts
|
|
65390
|
+
init_logger();
|
|
65391
|
+
|
|
65392
|
+
// src/hooks/task-continuation-enforcer/continuation-injection.ts
|
|
65393
|
+
import { existsSync as existsSync43, readdirSync as readdirSync14 } from "fs";
|
|
65394
|
+
init_logger();
|
|
65395
|
+
// src/hooks/task-continuation-enforcer/todo.ts
|
|
65396
|
+
function getIncompleteTaskCount(tasks) {
|
|
65397
|
+
const byId = new Map(tasks.map((t) => [t.id, t]));
|
|
65398
|
+
return tasks.filter((task) => {
|
|
65399
|
+
if (task.status !== "pending" && task.status !== "in_progress")
|
|
65400
|
+
return false;
|
|
65401
|
+
if (task.blockedBy.length === 0)
|
|
65402
|
+
return true;
|
|
65403
|
+
return task.blockedBy.every((bid) => byId.get(bid)?.status === "completed");
|
|
65404
|
+
}).length;
|
|
65405
|
+
}
|
|
65406
|
+
|
|
65407
|
+
// src/hooks/task-continuation-enforcer/continuation-injection.ts
|
|
65408
|
+
function hasWritePermission(tools) {
|
|
65409
|
+
const editPermission = tools?.edit;
|
|
65410
|
+
const writePermission = tools?.write;
|
|
65411
|
+
return !tools || editPermission !== false && editPermission !== "deny" && writePermission !== false && writePermission !== "deny";
|
|
65412
|
+
}
|
|
65413
|
+
async function injectContinuation(args) {
|
|
65414
|
+
const {
|
|
65415
|
+
ctx,
|
|
65416
|
+
sessionID,
|
|
65417
|
+
backgroundManager,
|
|
65418
|
+
skipAgents = DEFAULT_SKIP_AGENTS,
|
|
65419
|
+
resolvedInfo,
|
|
65420
|
+
sessionStateStore
|
|
65421
|
+
} = args;
|
|
65422
|
+
const state3 = sessionStateStore.getExistingState(sessionID);
|
|
65423
|
+
if (state3?.isRecovering) {
|
|
65424
|
+
log(`[${HOOK_NAME14}] Skipped injection: in recovery`, { sessionID });
|
|
65425
|
+
return;
|
|
65426
|
+
}
|
|
65427
|
+
const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((task) => task.status === "running") : false;
|
|
65428
|
+
if (hasRunningBgTasks) {
|
|
65429
|
+
log(`[${HOOK_NAME14}] Skipped injection: background tasks running`, { sessionID });
|
|
65430
|
+
return;
|
|
65431
|
+
}
|
|
65432
|
+
const tasks = [];
|
|
65433
|
+
let total = 0;
|
|
65533
65434
|
try {
|
|
65534
|
-
const
|
|
65535
|
-
if (!
|
|
65536
|
-
|
|
65537
|
-
|
|
65538
|
-
|
|
65539
|
-
|
|
65540
|
-
|
|
65541
|
-
|
|
65542
|
-
|
|
65435
|
+
const taskDir = getTaskDir({}, ctx.directory);
|
|
65436
|
+
if (!existsSync43(taskDir)) {
|
|
65437
|
+
log(`[${HOOK_NAME14}] Skipped injection: no task dir`, { sessionID, taskDir });
|
|
65438
|
+
return;
|
|
65439
|
+
}
|
|
65440
|
+
const files = readdirSync14(taskDir).filter((f) => f.startsWith("T-") && f.endsWith(".json"));
|
|
65441
|
+
for (const f of files) {
|
|
65442
|
+
const parsed = readJsonSafe(`${taskDir}/${f}`, TaskObjectSchema);
|
|
65443
|
+
if (parsed)
|
|
65444
|
+
tasks.push(parsed);
|
|
65445
|
+
}
|
|
65446
|
+
total = tasks.length;
|
|
65447
|
+
if (total === 0) {
|
|
65448
|
+
log(`[${HOOK_NAME14}] Skipped injection: no tasks`, { sessionID });
|
|
65449
|
+
return;
|
|
65450
|
+
}
|
|
65451
|
+
} catch (error51) {
|
|
65452
|
+
log(`[${HOOK_NAME14}] Failed to fetch tasks`, { sessionID, error: String(error51) });
|
|
65453
|
+
return;
|
|
65454
|
+
}
|
|
65455
|
+
const freshIncompleteCount = getIncompleteTaskCount(tasks);
|
|
65456
|
+
if (freshIncompleteCount === 0) {
|
|
65457
|
+
log(`[${HOOK_NAME14}] Skipped injection: no incomplete tasks`, { sessionID, total });
|
|
65458
|
+
return;
|
|
65459
|
+
}
|
|
65460
|
+
let agentName = resolvedInfo?.agent;
|
|
65461
|
+
let model = resolvedInfo?.model;
|
|
65462
|
+
let tools = resolvedInfo?.tools;
|
|
65463
|
+
if (!agentName || !model) {
|
|
65464
|
+
let previousMessage = null;
|
|
65465
|
+
if (isSqliteBackend()) {
|
|
65466
|
+
previousMessage = await findNearestMessageWithFieldsFromSDK(ctx.client, sessionID);
|
|
65467
|
+
} else {
|
|
65468
|
+
const messageDir = getMessageDir(sessionID);
|
|
65469
|
+
previousMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
|
|
65470
|
+
}
|
|
65471
|
+
agentName = agentName ?? previousMessage?.agent;
|
|
65472
|
+
model = model ?? (previousMessage?.model?.providerID && previousMessage?.model?.modelID ? {
|
|
65473
|
+
providerID: previousMessage.model.providerID,
|
|
65474
|
+
modelID: previousMessage.model.modelID,
|
|
65475
|
+
...previousMessage.model.variant ? { variant: previousMessage.model.variant } : {}
|
|
65476
|
+
} : undefined);
|
|
65477
|
+
tools = tools ?? previousMessage?.tools;
|
|
65478
|
+
}
|
|
65479
|
+
if (agentName && skipAgents.some((s) => getAgentConfigKey(s) === getAgentConfigKey(agentName))) {
|
|
65480
|
+
log(`[${HOOK_NAME14}] Skipped: agent in skipAgents list`, { sessionID, agent: agentName });
|
|
65481
|
+
return;
|
|
65482
|
+
}
|
|
65483
|
+
if (!hasWritePermission(tools)) {
|
|
65484
|
+
log(`[${HOOK_NAME14}] Skipped: agent lacks write permission`, { sessionID, agent: agentName });
|
|
65485
|
+
return;
|
|
65486
|
+
}
|
|
65487
|
+
const byId = new Map(tasks.map((t) => [t.id, t]));
|
|
65488
|
+
const incompleteTasks = tasks.filter((task) => {
|
|
65489
|
+
if (task.status !== "pending" && task.status !== "in_progress")
|
|
65490
|
+
return false;
|
|
65491
|
+
if (task.blockedBy.length === 0)
|
|
65492
|
+
return true;
|
|
65493
|
+
return task.blockedBy.every((bid) => byId.get(bid)?.status === "completed");
|
|
65494
|
+
});
|
|
65495
|
+
const taskList = incompleteTasks.map((task) => `- [${task.status}] ${task.subject} (${task.id})`).join(`
|
|
65496
|
+
`);
|
|
65497
|
+
const prompt = `${CONTINUATION_PROMPT2}
|
|
65498
|
+
|
|
65499
|
+
[Status: ${total - freshIncompleteCount}/${total} completed, ${freshIncompleteCount} remaining]
|
|
65500
|
+
|
|
65501
|
+
Remaining Matrixx tasks:
|
|
65502
|
+
${taskList}`;
|
|
65503
|
+
const injectionState = sessionStateStore.getExistingState(sessionID);
|
|
65504
|
+
if (injectionState) {
|
|
65505
|
+
injectionState.inFlight = true;
|
|
65506
|
+
}
|
|
65507
|
+
try {
|
|
65508
|
+
log(`[${HOOK_NAME14}] Injecting continuation`, {
|
|
65509
|
+
sessionID,
|
|
65510
|
+
agent: agentName,
|
|
65511
|
+
model,
|
|
65512
|
+
incompleteCount: freshIncompleteCount
|
|
65513
|
+
});
|
|
65514
|
+
await ctx.client.session.promptAsync({
|
|
65515
|
+
path: { id: sessionID },
|
|
65516
|
+
body: {
|
|
65517
|
+
agent: agentName,
|
|
65518
|
+
...model !== undefined ? { model } : {},
|
|
65519
|
+
parts: [{ type: "text", text: prompt }]
|
|
65520
|
+
},
|
|
65521
|
+
query: { directory: ctx.directory }
|
|
65522
|
+
});
|
|
65523
|
+
log(`[${HOOK_NAME14}] Injection successful`, { sessionID });
|
|
65524
|
+
if (injectionState) {
|
|
65525
|
+
injectionState.inFlight = false;
|
|
65526
|
+
injectionState.lastInjectedAt = Date.now();
|
|
65527
|
+
injectionState.consecutiveFailures = 0;
|
|
65528
|
+
}
|
|
65529
|
+
} catch (error51) {
|
|
65530
|
+
log(`[${HOOK_NAME14}] Injection failed`, { sessionID, error: String(error51) });
|
|
65531
|
+
if (injectionState) {
|
|
65532
|
+
injectionState.inFlight = false;
|
|
65533
|
+
injectionState.lastInjectedAt = Date.now();
|
|
65534
|
+
injectionState.consecutiveFailures = (injectionState.consecutiveFailures ?? 0) + 1;
|
|
65543
65535
|
}
|
|
65544
|
-
return tasks;
|
|
65545
|
-
} catch (err) {
|
|
65546
|
-
log(`[${HOOK_NAME14}] Failed to load tasks`, { error: String(err) });
|
|
65547
|
-
return [];
|
|
65548
65536
|
}
|
|
65549
65537
|
}
|
|
65550
|
-
|
|
65551
|
-
|
|
65552
|
-
|
|
65553
|
-
|
|
65554
|
-
|
|
65538
|
+
|
|
65539
|
+
// src/hooks/task-continuation-enforcer/countdown.ts
|
|
65540
|
+
async function showCountdownToast(ctx, seconds, incompleteCount) {
|
|
65541
|
+
await ctx.client.tui.showToast({
|
|
65542
|
+
body: {
|
|
65543
|
+
title: "Task Continuation",
|
|
65544
|
+
message: `Resuming in ${seconds}s... (${incompleteCount} tasks remaining)`,
|
|
65545
|
+
variant: "warning",
|
|
65546
|
+
duration: TOAST_DURATION_MS
|
|
65547
|
+
}
|
|
65548
|
+
}).catch((err) => {
|
|
65549
|
+
log("[task-continuation] Countdown toast failed:", err);
|
|
65550
|
+
});
|
|
65551
|
+
}
|
|
65552
|
+
function startCountdown(args) {
|
|
65553
|
+
const {
|
|
65554
|
+
ctx,
|
|
65555
|
+
sessionID,
|
|
65556
|
+
incompleteCount,
|
|
65557
|
+
resolvedInfo,
|
|
65558
|
+
backgroundManager,
|
|
65559
|
+
skipAgents,
|
|
65560
|
+
sessionStateStore
|
|
65561
|
+
} = args;
|
|
65562
|
+
const state3 = sessionStateStore.getState(sessionID);
|
|
65563
|
+
if (state3.countdownTimer || state3.countdownInterval) {
|
|
65564
|
+
log(`[${HOOK_NAME14}] Skipped: countdown already active`, { sessionID });
|
|
65565
|
+
return;
|
|
65566
|
+
}
|
|
65567
|
+
let secondsRemaining = COUNTDOWN_SECONDS;
|
|
65568
|
+
showCountdownToast(ctx, secondsRemaining, incompleteCount);
|
|
65569
|
+
state3.countdownStartedAt = Date.now();
|
|
65570
|
+
state3.countdownInterval = setInterval(() => {
|
|
65571
|
+
secondsRemaining--;
|
|
65572
|
+
if (secondsRemaining > 0) {
|
|
65573
|
+
showCountdownToast(ctx, secondsRemaining, incompleteCount);
|
|
65574
|
+
}
|
|
65575
|
+
}, 1000);
|
|
65576
|
+
state3.countdownTimer = setTimeout(() => {
|
|
65577
|
+
sessionStateStore.cancelCountdown(sessionID);
|
|
65578
|
+
injectContinuation({
|
|
65579
|
+
ctx,
|
|
65580
|
+
sessionID,
|
|
65581
|
+
backgroundManager,
|
|
65582
|
+
skipAgents,
|
|
65583
|
+
resolvedInfo,
|
|
65584
|
+
sessionStateStore
|
|
65585
|
+
});
|
|
65586
|
+
}, COUNTDOWN_SECONDS * 1000);
|
|
65587
|
+
log(`[${HOOK_NAME14}] Countdown started`, {
|
|
65588
|
+
sessionID,
|
|
65589
|
+
seconds: COUNTDOWN_SECONDS,
|
|
65590
|
+
incompleteCount
|
|
65591
|
+
});
|
|
65592
|
+
}
|
|
65593
|
+
|
|
65594
|
+
// src/hooks/task-continuation-enforcer/idle-event.ts
|
|
65595
|
+
async function handleSessionIdle(args) {
|
|
65596
|
+
const {
|
|
65597
|
+
ctx,
|
|
65598
|
+
sessionID,
|
|
65599
|
+
sessionStateStore,
|
|
65600
|
+
backgroundManager,
|
|
65601
|
+
skipAgents = DEFAULT_SKIP_AGENTS,
|
|
65602
|
+
isContinuationStopped
|
|
65603
|
+
} = args;
|
|
65604
|
+
log(`[${HOOK_NAME14}] session.idle`, { sessionID });
|
|
65605
|
+
const state3 = sessionStateStore.getState(sessionID);
|
|
65606
|
+
if (state3.countdownTimer || state3.countdownInterval) {
|
|
65607
|
+
log(`[${HOOK_NAME14}] Skipped: countdown already active`, { sessionID });
|
|
65608
|
+
return;
|
|
65609
|
+
}
|
|
65610
|
+
if (state3.isRecovering) {
|
|
65611
|
+
log(`[${HOOK_NAME14}] Skipped: in recovery`, { sessionID });
|
|
65612
|
+
return;
|
|
65613
|
+
}
|
|
65614
|
+
if (state3.abortDetectedAt) {
|
|
65615
|
+
const timeSinceAbort = Date.now() - state3.abortDetectedAt;
|
|
65616
|
+
if (timeSinceAbort < ABORT_WINDOW_MS) {
|
|
65617
|
+
log(`[${HOOK_NAME14}] Skipped: abort detected via event ${timeSinceAbort}ms ago`, { sessionID });
|
|
65618
|
+
state3.abortDetectedAt = undefined;
|
|
65619
|
+
return;
|
|
65620
|
+
}
|
|
65621
|
+
state3.abortDetectedAt = undefined;
|
|
65622
|
+
}
|
|
65623
|
+
const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((task) => task.status === "running") : false;
|
|
65624
|
+
if (hasRunningBgTasks) {
|
|
65625
|
+
log(`[${HOOK_NAME14}] Skipped: background tasks running`, { sessionID });
|
|
65626
|
+
return;
|
|
65627
|
+
}
|
|
65628
|
+
try {
|
|
65629
|
+
const messagesResp = await ctx.client.session.messages({
|
|
65630
|
+
path: { id: sessionID },
|
|
65631
|
+
query: { directory: ctx.directory }
|
|
65632
|
+
});
|
|
65633
|
+
const messages = normalizeSDKResponse(messagesResp, []);
|
|
65634
|
+
if (isLastAssistantMessageAborted(messages)) {
|
|
65635
|
+
log(`[${HOOK_NAME14}] Skipped: last assistant message was aborted (API fallback)`, { sessionID });
|
|
65555
65636
|
return;
|
|
65637
|
+
}
|
|
65638
|
+
} catch (error51) {
|
|
65639
|
+
log(`[${HOOK_NAME14}] Messages fetch failed, continuing`, { sessionID, error: String(error51) });
|
|
65640
|
+
}
|
|
65641
|
+
let incompleteCount = 0;
|
|
65642
|
+
let total = 0;
|
|
65643
|
+
try {
|
|
65644
|
+
const taskDir = getTaskDir({}, ctx.directory);
|
|
65645
|
+
if (!existsSync44(taskDir)) {
|
|
65646
|
+
log(`[${HOOK_NAME14}] No task dir`, { sessionID, taskDir });
|
|
65647
|
+
return;
|
|
65648
|
+
}
|
|
65649
|
+
const files = readdirSync15(taskDir).filter((f) => f.startsWith("T-") && f.endsWith(".json"));
|
|
65650
|
+
const tasks = [];
|
|
65651
|
+
for (const f of files) {
|
|
65652
|
+
const parsed = readJsonSafe(`${taskDir}/${f}`, TaskObjectSchema);
|
|
65653
|
+
if (parsed)
|
|
65654
|
+
tasks.push(parsed);
|
|
65655
|
+
}
|
|
65656
|
+
total = tasks.length;
|
|
65657
|
+
if (total === 0) {
|
|
65658
|
+
log(`[${HOOK_NAME14}] No tasks`, { sessionID });
|
|
65659
|
+
return;
|
|
65660
|
+
}
|
|
65661
|
+
incompleteCount = getIncompleteTaskCount(tasks);
|
|
65662
|
+
} catch (error51) {
|
|
65663
|
+
log(`[${HOOK_NAME14}] Task fetch failed`, { sessionID, error: String(error51) });
|
|
65664
|
+
return;
|
|
65665
|
+
}
|
|
65666
|
+
if (incompleteCount === 0) {
|
|
65667
|
+
log(`[${HOOK_NAME14}] All tasks complete`, { sessionID, total });
|
|
65668
|
+
return;
|
|
65669
|
+
}
|
|
65670
|
+
if (state3.inFlight) {
|
|
65671
|
+
log(`[${HOOK_NAME14}] Skipped: injection in flight`, { sessionID });
|
|
65672
|
+
return;
|
|
65673
|
+
}
|
|
65674
|
+
if ((state3.consecutiveFailures ?? 0) >= MAX_CONSECUTIVE_FAILURES && state3.lastInjectedAt && Date.now() - state3.lastInjectedAt >= FAILURE_RESET_WINDOW_MS) {
|
|
65675
|
+
state3.consecutiveFailures = 0;
|
|
65676
|
+
log(`[${HOOK_NAME14}] Reset consecutive failures after recovery window`, {
|
|
65677
|
+
sessionID,
|
|
65678
|
+
failureResetWindowMs: FAILURE_RESET_WINDOW_MS
|
|
65679
|
+
});
|
|
65680
|
+
}
|
|
65681
|
+
if ((state3.consecutiveFailures ?? 0) >= MAX_CONSECUTIVE_FAILURES) {
|
|
65682
|
+
log(`[${HOOK_NAME14}] Skipped: max consecutive failures reached`, {
|
|
65683
|
+
sessionID,
|
|
65684
|
+
consecutiveFailures: state3.consecutiveFailures ?? 0,
|
|
65685
|
+
maxConsecutiveFailures: MAX_CONSECUTIVE_FAILURES
|
|
65686
|
+
});
|
|
65687
|
+
return;
|
|
65688
|
+
}
|
|
65689
|
+
const effectiveCooldown = CONTINUATION_COOLDOWN_MS2 * 2 ** Math.min(state3.consecutiveFailures ?? 0, 5);
|
|
65690
|
+
if (state3.lastInjectedAt && Date.now() - state3.lastInjectedAt < effectiveCooldown) {
|
|
65691
|
+
log(`[${HOOK_NAME14}] Skipped: cooldown active`, {
|
|
65692
|
+
sessionID,
|
|
65693
|
+
effectiveCooldown,
|
|
65694
|
+
consecutiveFailures: state3.consecutiveFailures ?? 0
|
|
65695
|
+
});
|
|
65696
|
+
return;
|
|
65697
|
+
}
|
|
65698
|
+
let resolvedInfo;
|
|
65699
|
+
let hasCompactionMessage = false;
|
|
65700
|
+
try {
|
|
65701
|
+
const messagesResp = await ctx.client.session.messages({
|
|
65702
|
+
path: { id: sessionID }
|
|
65703
|
+
});
|
|
65704
|
+
const messages = normalizeSDKResponse(messagesResp, []);
|
|
65705
|
+
for (let i = messages.length - 1;i >= 0; i--) {
|
|
65706
|
+
const info = messages[i].info;
|
|
65707
|
+
if (info?.agent === "compaction") {
|
|
65708
|
+
hasCompactionMessage = true;
|
|
65709
|
+
continue;
|
|
65710
|
+
}
|
|
65711
|
+
if (info?.agent || info?.model || info?.modelID && info?.providerID) {
|
|
65712
|
+
resolvedInfo = {
|
|
65713
|
+
agent: info.agent,
|
|
65714
|
+
model: info.model ?? (info.providerID && info.modelID ? { providerID: info.providerID, modelID: info.modelID } : undefined),
|
|
65715
|
+
tools: info.tools
|
|
65716
|
+
};
|
|
65717
|
+
break;
|
|
65718
|
+
}
|
|
65719
|
+
}
|
|
65720
|
+
} catch (error51) {
|
|
65721
|
+
log(`[${HOOK_NAME14}] Failed to fetch messages for agent check`, { sessionID, error: String(error51) });
|
|
65722
|
+
}
|
|
65723
|
+
log(`[${HOOK_NAME14}] Agent check`, { sessionID, agentName: resolvedInfo?.agent, skipAgents, hasCompactionMessage });
|
|
65724
|
+
const resolvedAgentName = resolvedInfo?.agent;
|
|
65725
|
+
if (resolvedAgentName && skipAgents.some((s) => getAgentConfigKey(s) === getAgentConfigKey(resolvedAgentName))) {
|
|
65726
|
+
log(`[${HOOK_NAME14}] Skipped: agent in skipAgents list`, { sessionID, agent: resolvedAgentName });
|
|
65727
|
+
return;
|
|
65728
|
+
}
|
|
65729
|
+
if (hasCompactionMessage && !resolvedInfo?.agent) {
|
|
65730
|
+
log(`[${HOOK_NAME14}] Skipped: compaction occurred but no agent info resolved`, { sessionID });
|
|
65731
|
+
return;
|
|
65732
|
+
}
|
|
65733
|
+
if (isContinuationStopped?.(sessionID)) {
|
|
65734
|
+
log(`[${HOOK_NAME14}] Skipped: continuation stopped for session`, { sessionID });
|
|
65735
|
+
return;
|
|
65736
|
+
}
|
|
65737
|
+
startCountdown({
|
|
65738
|
+
ctx,
|
|
65739
|
+
sessionID,
|
|
65740
|
+
incompleteCount,
|
|
65741
|
+
total,
|
|
65742
|
+
resolvedInfo,
|
|
65743
|
+
backgroundManager,
|
|
65744
|
+
skipAgents,
|
|
65745
|
+
sessionStateStore
|
|
65746
|
+
});
|
|
65747
|
+
}
|
|
65748
|
+
|
|
65749
|
+
// src/hooks/task-continuation-enforcer/non-idle-events.ts
|
|
65750
|
+
init_logger();
|
|
65751
|
+
function handleNonIdleEvent(args) {
|
|
65752
|
+
const { eventType, properties, sessionStateStore } = args;
|
|
65753
|
+
if (eventType === "message.updated") {
|
|
65754
|
+
const info = properties?.info;
|
|
65755
|
+
const sessionID = info?.sessionID;
|
|
65756
|
+
const role = info?.role;
|
|
65556
65757
|
if (!sessionID)
|
|
65557
65758
|
return;
|
|
65759
|
+
if (role === "user") {
|
|
65760
|
+
const state3 = sessionStateStore.getExistingState(sessionID);
|
|
65761
|
+
if (state3?.countdownStartedAt) {
|
|
65762
|
+
const elapsed = Date.now() - state3.countdownStartedAt;
|
|
65763
|
+
if (elapsed < COUNTDOWN_GRACE_PERIOD_MS) {
|
|
65764
|
+
log(`[${HOOK_NAME14}] Ignoring user message in grace period`, { sessionID, elapsed });
|
|
65765
|
+
return;
|
|
65766
|
+
}
|
|
65767
|
+
}
|
|
65768
|
+
if (state3)
|
|
65769
|
+
state3.abortDetectedAt = undefined;
|
|
65770
|
+
sessionStateStore.cancelCountdown(sessionID);
|
|
65771
|
+
return;
|
|
65772
|
+
}
|
|
65773
|
+
if (role === "assistant") {
|
|
65774
|
+
const state3 = sessionStateStore.getExistingState(sessionID);
|
|
65775
|
+
if (state3)
|
|
65776
|
+
state3.abortDetectedAt = undefined;
|
|
65777
|
+
sessionStateStore.cancelCountdown(sessionID);
|
|
65778
|
+
return;
|
|
65779
|
+
}
|
|
65780
|
+
return;
|
|
65781
|
+
}
|
|
65782
|
+
if (eventType === "message.part.updated") {
|
|
65783
|
+
const info = properties?.info;
|
|
65784
|
+
const sessionID = info?.sessionID;
|
|
65785
|
+
const role = info?.role;
|
|
65786
|
+
if (sessionID && role === "assistant") {
|
|
65787
|
+
const state3 = sessionStateStore.getExistingState(sessionID);
|
|
65788
|
+
if (state3)
|
|
65789
|
+
state3.abortDetectedAt = undefined;
|
|
65790
|
+
sessionStateStore.cancelCountdown(sessionID);
|
|
65791
|
+
}
|
|
65792
|
+
return;
|
|
65793
|
+
}
|
|
65794
|
+
if (eventType === "tool.execute.before" || eventType === "tool.execute.after") {
|
|
65795
|
+
const sessionID = properties?.sessionID;
|
|
65796
|
+
if (sessionID) {
|
|
65797
|
+
const state3 = sessionStateStore.getExistingState(sessionID);
|
|
65798
|
+
if (state3)
|
|
65799
|
+
state3.abortDetectedAt = undefined;
|
|
65800
|
+
sessionStateStore.cancelCountdown(sessionID);
|
|
65801
|
+
}
|
|
65802
|
+
return;
|
|
65803
|
+
}
|
|
65804
|
+
if (eventType === "session.deleted") {
|
|
65805
|
+
const sessionInfo = properties?.info;
|
|
65806
|
+
if (sessionInfo?.id) {
|
|
65807
|
+
sessionStateStore.cleanup(sessionInfo.id);
|
|
65808
|
+
log(`[${HOOK_NAME14}] Session deleted: cleaned up`, { sessionID: sessionInfo.id });
|
|
65809
|
+
}
|
|
65810
|
+
return;
|
|
65811
|
+
}
|
|
65812
|
+
}
|
|
65813
|
+
|
|
65814
|
+
// src/hooks/task-continuation-enforcer/handler.ts
|
|
65815
|
+
function createTaskContinuationHandler(args) {
|
|
65816
|
+
const {
|
|
65817
|
+
ctx,
|
|
65818
|
+
sessionStateStore,
|
|
65819
|
+
backgroundManager,
|
|
65820
|
+
skipAgents = DEFAULT_SKIP_AGENTS,
|
|
65821
|
+
isContinuationStopped
|
|
65822
|
+
} = args;
|
|
65823
|
+
return async ({ event }) => {
|
|
65824
|
+
const props = event.properties;
|
|
65825
|
+
if (event.type === "session.error") {
|
|
65826
|
+
const sessionID = props?.sessionID;
|
|
65827
|
+
if (!sessionID)
|
|
65828
|
+
return;
|
|
65829
|
+
const error51 = props?.error;
|
|
65830
|
+
if (error51?.name === "MessageAbortedError" || error51?.name === "AbortError") {
|
|
65831
|
+
const state3 = sessionStateStore.getState(sessionID);
|
|
65832
|
+
state3.abortDetectedAt = Date.now();
|
|
65833
|
+
log(`[${HOOK_NAME14}] Abort detected via session.error`, { sessionID, errorName: error51.name });
|
|
65834
|
+
}
|
|
65835
|
+
sessionStateStore.cancelCountdown(sessionID);
|
|
65836
|
+
log(`[${HOOK_NAME14}] session.error`, { sessionID });
|
|
65837
|
+
return;
|
|
65838
|
+
}
|
|
65839
|
+
if (event.type === "session.idle") {
|
|
65840
|
+
const sessionID = props?.sessionID;
|
|
65841
|
+
if (!sessionID)
|
|
65842
|
+
return;
|
|
65843
|
+
await handleSessionIdle({
|
|
65844
|
+
ctx,
|
|
65845
|
+
sessionID,
|
|
65846
|
+
sessionStateStore,
|
|
65847
|
+
backgroundManager,
|
|
65848
|
+
skipAgents,
|
|
65849
|
+
isContinuationStopped
|
|
65850
|
+
});
|
|
65851
|
+
return;
|
|
65852
|
+
}
|
|
65853
|
+
handleNonIdleEvent({
|
|
65854
|
+
eventType: event.type,
|
|
65855
|
+
properties: props,
|
|
65856
|
+
sessionStateStore
|
|
65857
|
+
});
|
|
65858
|
+
};
|
|
65859
|
+
}
|
|
65860
|
+
|
|
65861
|
+
// src/hooks/task-continuation-enforcer/session-state.ts
|
|
65862
|
+
var SESSION_STATE_TTL_MS = 10 * 60 * 1000;
|
|
65863
|
+
var SESSION_STATE_PRUNE_INTERVAL_MS = 2 * 60 * 1000;
|
|
65864
|
+
function createSessionStateStore() {
|
|
65865
|
+
const sessions = new Map;
|
|
65866
|
+
let pruneInterval;
|
|
65867
|
+
pruneInterval = setInterval(() => {
|
|
65558
65868
|
const now = Date.now();
|
|
65559
|
-
const
|
|
65560
|
-
|
|
65869
|
+
for (const [sessionID, tracked] of sessions.entries()) {
|
|
65870
|
+
if (now - tracked.lastAccessedAt > SESSION_STATE_TTL_MS) {
|
|
65871
|
+
cancelCountdown(sessionID);
|
|
65872
|
+
sessions.delete(sessionID);
|
|
65873
|
+
}
|
|
65874
|
+
}
|
|
65875
|
+
}, SESSION_STATE_PRUNE_INTERVAL_MS);
|
|
65876
|
+
if (typeof pruneInterval === "object" && "unref" in pruneInterval) {
|
|
65877
|
+
pruneInterval.unref();
|
|
65878
|
+
}
|
|
65879
|
+
function getState(sessionID) {
|
|
65880
|
+
const existing = sessions.get(sessionID);
|
|
65881
|
+
if (existing) {
|
|
65882
|
+
existing.lastAccessedAt = Date.now();
|
|
65883
|
+
return existing.state;
|
|
65884
|
+
}
|
|
65885
|
+
const state3 = {
|
|
65886
|
+
consecutiveFailures: 0
|
|
65887
|
+
};
|
|
65888
|
+
sessions.set(sessionID, { state: state3, lastAccessedAt: Date.now() });
|
|
65889
|
+
return state3;
|
|
65890
|
+
}
|
|
65891
|
+
function getExistingState(sessionID) {
|
|
65892
|
+
const existing = sessions.get(sessionID);
|
|
65893
|
+
if (existing) {
|
|
65894
|
+
existing.lastAccessedAt = Date.now();
|
|
65895
|
+
return existing.state;
|
|
65896
|
+
}
|
|
65897
|
+
return;
|
|
65898
|
+
}
|
|
65899
|
+
function cancelCountdown(sessionID) {
|
|
65900
|
+
const tracked = sessions.get(sessionID);
|
|
65901
|
+
if (!tracked)
|
|
65561
65902
|
return;
|
|
65562
|
-
|
|
65563
|
-
|
|
65564
|
-
|
|
65565
|
-
|
|
65566
|
-
log(`[${HOOK_NAME14}] Synced ${tasks.length} tasks to session`, { sessionID });
|
|
65567
|
-
} catch (err) {
|
|
65568
|
-
log(`[${HOOK_NAME14}] Sync failed`, { sessionID, error: String(err) });
|
|
65903
|
+
const state3 = tracked.state;
|
|
65904
|
+
if (state3.countdownTimer) {
|
|
65905
|
+
clearTimeout(state3.countdownTimer);
|
|
65906
|
+
state3.countdownTimer = undefined;
|
|
65569
65907
|
}
|
|
65908
|
+
if (state3.countdownInterval) {
|
|
65909
|
+
clearInterval(state3.countdownInterval);
|
|
65910
|
+
state3.countdownInterval = undefined;
|
|
65911
|
+
}
|
|
65912
|
+
state3.inFlight = false;
|
|
65913
|
+
state3.countdownStartedAt = undefined;
|
|
65914
|
+
}
|
|
65915
|
+
function cleanup(sessionID) {
|
|
65916
|
+
cancelCountdown(sessionID);
|
|
65917
|
+
sessions.delete(sessionID);
|
|
65918
|
+
}
|
|
65919
|
+
function cancelAllCountdowns() {
|
|
65920
|
+
for (const sessionID of sessions.keys()) {
|
|
65921
|
+
cancelCountdown(sessionID);
|
|
65922
|
+
}
|
|
65923
|
+
}
|
|
65924
|
+
function shutdown() {
|
|
65925
|
+
clearInterval(pruneInterval);
|
|
65926
|
+
cancelAllCountdowns();
|
|
65927
|
+
sessions.clear();
|
|
65928
|
+
}
|
|
65929
|
+
return {
|
|
65930
|
+
getState,
|
|
65931
|
+
getExistingState,
|
|
65932
|
+
cancelCountdown,
|
|
65933
|
+
cleanup,
|
|
65934
|
+
cancelAllCountdowns,
|
|
65935
|
+
shutdown
|
|
65936
|
+
};
|
|
65937
|
+
}
|
|
65938
|
+
|
|
65939
|
+
// src/hooks/task-continuation-enforcer/index.ts
|
|
65940
|
+
function createTaskContinuationEnforcer(ctx, options = {}) {
|
|
65941
|
+
const {
|
|
65942
|
+
backgroundManager,
|
|
65943
|
+
skipAgents = DEFAULT_SKIP_AGENTS,
|
|
65944
|
+
isContinuationStopped
|
|
65945
|
+
} = options;
|
|
65946
|
+
const sessionStateStore = createSessionStateStore();
|
|
65947
|
+
const markRecovering = (sessionID) => {
|
|
65948
|
+
const state3 = sessionStateStore.getState(sessionID);
|
|
65949
|
+
state3.isRecovering = true;
|
|
65950
|
+
sessionStateStore.cancelCountdown(sessionID);
|
|
65951
|
+
log(`[${HOOK_NAME14}] Session marked as recovering`, { sessionID });
|
|
65952
|
+
};
|
|
65953
|
+
const markRecoveryComplete = (sessionID) => {
|
|
65954
|
+
const state3 = sessionStateStore.getExistingState(sessionID);
|
|
65955
|
+
if (state3) {
|
|
65956
|
+
state3.isRecovering = false;
|
|
65957
|
+
log(`[${HOOK_NAME14}] Session recovery complete`, { sessionID });
|
|
65958
|
+
}
|
|
65959
|
+
};
|
|
65960
|
+
const handler = createTaskContinuationHandler({
|
|
65961
|
+
ctx,
|
|
65962
|
+
sessionStateStore,
|
|
65963
|
+
backgroundManager,
|
|
65964
|
+
skipAgents,
|
|
65965
|
+
isContinuationStopped
|
|
65966
|
+
});
|
|
65967
|
+
const cancelAllCountdowns = () => {
|
|
65968
|
+
sessionStateStore.cancelAllCountdowns();
|
|
65969
|
+
log(`[${HOOK_NAME14}] All countdowns cancelled`);
|
|
65570
65970
|
};
|
|
65571
65971
|
return {
|
|
65572
|
-
|
|
65573
|
-
|
|
65972
|
+
handler,
|
|
65973
|
+
markRecovering,
|
|
65974
|
+
markRecoveryComplete,
|
|
65975
|
+
cancelAllCountdowns
|
|
65976
|
+
};
|
|
65977
|
+
}
|
|
65978
|
+
// src/hooks/task-edit-guard/constants.ts
|
|
65979
|
+
var HOOK_NAME15 = "task-edit-guard";
|
|
65980
|
+
var BLOCKED_PATTERNS = [
|
|
65981
|
+
/sed\s+.*\.matrixx\/plans/,
|
|
65982
|
+
/sed\s+.*\.matrixx\/tasks/,
|
|
65983
|
+
/python3?\s+.*\.matrixx\/plans/,
|
|
65984
|
+
/python3?\s+.*\.matrixx\/tasks/,
|
|
65985
|
+
/echo\s+.*\.matrixx\/plans/,
|
|
65986
|
+
/echo\s+.*\.matrixx\/tasks/,
|
|
65987
|
+
/cat\s*>.*\.matrixx\/plans/,
|
|
65988
|
+
/cat\s*>.*\.matrixx\/tasks/,
|
|
65989
|
+
/mv\s+.*\.matrixx\/plans/,
|
|
65990
|
+
/mv\s+.*\.matrixx\/tasks/,
|
|
65991
|
+
/rm\s+.*\.matrixx\/tasks\/T-.*\.json/,
|
|
65992
|
+
/printf\s+.*\.matrixx\/plans/,
|
|
65993
|
+
/printf\s+.*\.matrixx\/tasks/
|
|
65994
|
+
];
|
|
65995
|
+
// src/hooks/task-edit-guard/hook.ts
|
|
65996
|
+
function createTaskEditGuardHook(ctx) {
|
|
65997
|
+
return {
|
|
65998
|
+
"tool.execute.before": async (input, output) => {
|
|
65999
|
+
if (input.tool?.toLowerCase() !== "bash")
|
|
65574
66000
|
return;
|
|
65575
|
-
|
|
65576
|
-
|
|
65577
|
-
|
|
65578
|
-
if (!enabled)
|
|
66001
|
+
const args = output.args;
|
|
66002
|
+
const cmd = args?.command;
|
|
66003
|
+
if (!cmd)
|
|
65579
66004
|
return;
|
|
65580
|
-
|
|
65581
|
-
|
|
65582
|
-
|
|
65583
|
-
|
|
66005
|
+
const lower = cmd.toLowerCase();
|
|
66006
|
+
if (lower.includes("grep") && !lower.includes("sed") && !lower.includes("python")) {
|
|
66007
|
+
const isOnlyGrep = BLOCKED_PATTERNS.every((rx) => !rx.test(cmd));
|
|
66008
|
+
if (isOnlyGrep)
|
|
66009
|
+
return;
|
|
66010
|
+
}
|
|
66011
|
+
const hit = BLOCKED_PATTERNS.some((rx) => rx.test(cmd));
|
|
66012
|
+
if (!hit)
|
|
65584
66013
|
return;
|
|
65585
|
-
|
|
65586
|
-
|
|
65587
|
-
|
|
65588
|
-
|
|
65589
|
-
|
|
65590
|
-
|
|
66014
|
+
ctx.directory;
|
|
66015
|
+
log(`[${HOOK_NAME15}] BLOCKED raw bash edit`, {
|
|
66016
|
+
sessionID: input.sessionID,
|
|
66017
|
+
command: cmd.slice(0, 120)
|
|
66018
|
+
});
|
|
66019
|
+
throw new Error("Blocked: raw bash edit to plan/task files. " + "Use Edit (hashline IDs) for .matrixx/plans/*.md and task_create/task_update/task_cleanup for .matrixx/tasks/T-*.json \u2014 raw bash sed/python bypasses project-scoped task system");
|
|
66020
|
+
}
|
|
66021
|
+
};
|
|
66022
|
+
}
|
|
66023
|
+
// src/hooks/task-notepad/constants.ts
|
|
66024
|
+
var HOOK_NAME16 = "task-notepad";
|
|
66025
|
+
var PLAN_DIR_NAME = ".matrixx";
|
|
66026
|
+
var PLANS_SUBDIR = "plans";
|
|
66027
|
+
var NOTEPAD_DIR_NAME = "notepads";
|
|
66028
|
+
var TASKS_SUBDIR = "tasks";
|
|
66029
|
+
var PLAN_EXTENSION = ".md";
|
|
66030
|
+
var NOTEPAD_EXTENSION = ".md";
|
|
66031
|
+
var TODO_TOOL_NAMES = new Set(["todowrite", "TodoWrite"]);
|
|
66032
|
+
var TASK_NOTEPAD_FRAGMENT = "task-notepad";
|
|
66033
|
+
var TODO_FETCH_ERROR_FRAGMENT = "Failed to fetch todos for task-notepad";
|
|
66034
|
+
// src/hooks/task-notepad/hook.ts
|
|
66035
|
+
init_logger();
|
|
66036
|
+
import { existsSync as existsSync45, mkdirSync as mkdirSync16, readdirSync as readdirSync16, readFileSync as readFileSync33, statSync as statSync8, writeFileSync as writeFileSync19 } from "fs";
|
|
66037
|
+
import { dirname as dirname15, join as join52 } from "path";
|
|
66038
|
+
function createTaskNotepadHook(ctx) {
|
|
66039
|
+
return {
|
|
66040
|
+
"tool.execute.after": async (input, output) => {
|
|
66041
|
+
if (!output)
|
|
66042
|
+
return;
|
|
66043
|
+
if (!TODO_TOOL_NAMES.has(input.tool))
|
|
66044
|
+
return;
|
|
66045
|
+
const todos = await fetchTodos(ctx, input.sessionID);
|
|
66046
|
+
const inProgress = todos.find((t) => t.status === "in_progress");
|
|
66047
|
+
const completed = todos.filter((t) => t.status === "completed");
|
|
66048
|
+
if (!inProgress && completed.length === 0)
|
|
66049
|
+
return;
|
|
66050
|
+
const plan = resolveActivePlan(ctx.directory);
|
|
66051
|
+
if (inProgress) {
|
|
66052
|
+
if (!plan) {
|
|
66053
|
+
log(`[${HOOK_NAME16}] No active plan, skipping notepad creation`, {
|
|
66054
|
+
sessionID: input.sessionID
|
|
66055
|
+
});
|
|
66056
|
+
} else {
|
|
66057
|
+
ensureNotepad(ctx.directory, plan.name, inProgress);
|
|
65591
66058
|
}
|
|
65592
66059
|
}
|
|
65593
|
-
|
|
65594
|
-
},
|
|
65595
|
-
event: async (input) => {
|
|
65596
|
-
if (!enabled)
|
|
66060
|
+
if (!plan)
|
|
65597
66061
|
return;
|
|
65598
|
-
const
|
|
65599
|
-
|
|
65600
|
-
if (sessionID && input.event.type === "session.idle") {
|
|
65601
|
-
await syncForSession(sessionID);
|
|
66062
|
+
for (const done of completed) {
|
|
66063
|
+
appendCompletion(ctx.directory, plan.name, done);
|
|
65602
66064
|
}
|
|
65603
|
-
}
|
|
65604
|
-
|
|
66065
|
+
}
|
|
66066
|
+
};
|
|
66067
|
+
}
|
|
66068
|
+
async function fetchTodos(ctx, sessionID) {
|
|
66069
|
+
try {
|
|
66070
|
+
const response = await ctx.client.session.todo({ path: { id: sessionID } });
|
|
66071
|
+
const payload = response;
|
|
66072
|
+
if (Array.isArray(payload?.data)) {
|
|
66073
|
+
return payload.data;
|
|
66074
|
+
}
|
|
66075
|
+
if (Array.isArray(response)) {
|
|
66076
|
+
return response;
|
|
66077
|
+
}
|
|
66078
|
+
return [];
|
|
66079
|
+
} catch (err) {
|
|
66080
|
+
log(`[${HOOK_NAME16}] ${TODO_FETCH_ERROR_FRAGMENT}`, {
|
|
66081
|
+
sessionID,
|
|
66082
|
+
error: String(err)
|
|
66083
|
+
});
|
|
66084
|
+
return [];
|
|
66085
|
+
}
|
|
66086
|
+
}
|
|
66087
|
+
function resolveActivePlan(directory) {
|
|
66088
|
+
const plansDir = join52(directory, PLAN_DIR_NAME, PLANS_SUBDIR);
|
|
66089
|
+
if (!existsSync45(plansDir))
|
|
66090
|
+
return null;
|
|
66091
|
+
let entries;
|
|
66092
|
+
try {
|
|
66093
|
+
entries = readdirSync16(plansDir);
|
|
66094
|
+
} catch {
|
|
66095
|
+
return null;
|
|
66096
|
+
}
|
|
66097
|
+
let best = null;
|
|
66098
|
+
for (const entry of entries) {
|
|
66099
|
+
if (!entry.endsWith(PLAN_EXTENSION))
|
|
66100
|
+
continue;
|
|
66101
|
+
const fullPath = join52(plansDir, entry);
|
|
66102
|
+
try {
|
|
66103
|
+
const stats = statSync8(fullPath);
|
|
66104
|
+
if (!stats.isFile())
|
|
66105
|
+
continue;
|
|
66106
|
+
const candidate = {
|
|
66107
|
+
name: entry.slice(0, -PLAN_EXTENSION.length),
|
|
66108
|
+
path: fullPath,
|
|
66109
|
+
mtimeMs: stats.mtimeMs
|
|
66110
|
+
};
|
|
66111
|
+
if (!best || candidate.mtimeMs > best.mtimeMs) {
|
|
66112
|
+
best = candidate;
|
|
66113
|
+
}
|
|
66114
|
+
} catch {}
|
|
66115
|
+
}
|
|
66116
|
+
return best;
|
|
66117
|
+
}
|
|
66118
|
+
function ensureNotepad(directory, planName, todo) {
|
|
66119
|
+
const notepadPath = notepadPathFor(directory, planName, todo.id);
|
|
66120
|
+
if (existsSync45(notepadPath))
|
|
66121
|
+
return;
|
|
66122
|
+
mkdirSync16(dirname15(notepadPath), { recursive: true });
|
|
66123
|
+
const content = renderNotepad(todo);
|
|
66124
|
+
writeFileSync19(notepadPath, content, "utf-8");
|
|
66125
|
+
log(`[${HOOK_NAME16}] Created task notepad`, { plan: planName, taskID: todo.id });
|
|
66126
|
+
}
|
|
66127
|
+
function appendCompletion(directory, planName, todo) {
|
|
66128
|
+
const notepadPath = notepadPathFor(directory, planName, todo.id);
|
|
66129
|
+
if (!existsSync45(notepadPath))
|
|
66130
|
+
return;
|
|
66131
|
+
const existing = readFileSync33(notepadPath, "utf-8");
|
|
66132
|
+
if (existing.includes("**Status**: completed"))
|
|
66133
|
+
return;
|
|
66134
|
+
const stamp = new Date().toISOString();
|
|
66135
|
+
const updated = `${existing}
|
|
66136
|
+
## Completion
|
|
66137
|
+
- completed_at: ${stamp}
|
|
66138
|
+
- status: completed
|
|
66139
|
+
`;
|
|
66140
|
+
writeFileSync19(notepadPath, updated, "utf-8");
|
|
66141
|
+
log(`[${HOOK_NAME16}] Appended completion to notepad`, { plan: planName, taskID: todo.id });
|
|
66142
|
+
}
|
|
66143
|
+
function notepadPathFor(directory, planName, taskID) {
|
|
66144
|
+
return join52(directory, PLAN_DIR_NAME, NOTEPAD_DIR_NAME, planName, TASKS_SUBDIR, `${taskID}${NOTEPAD_EXTENSION}`);
|
|
66145
|
+
}
|
|
66146
|
+
function renderNotepad(todo) {
|
|
66147
|
+
const priority = todo.priority ?? "medium";
|
|
66148
|
+
return `# Task: ${todo.content}
|
|
66149
|
+
|
|
66150
|
+
**Plan**: ${TASK_NOTEPAD_FRAGMENT}
|
|
66151
|
+
**Task ID**: ${todo.id}
|
|
66152
|
+
**Priority**: ${priority}
|
|
66153
|
+
**Status**: ${todo.status}
|
|
66154
|
+
**Started**: ${new Date().toISOString()}
|
|
66155
|
+
|
|
66156
|
+
## Findings
|
|
66157
|
+
|
|
66158
|
+
(Record what you learn about this code as you work \u2014 patterns, conventions, gotchas, useful references.)
|
|
66159
|
+
|
|
66160
|
+
## Blockers
|
|
66161
|
+
|
|
66162
|
+
(Anything blocking progress. Be specific: file path, line, error, workaround if any.)
|
|
66163
|
+
|
|
66164
|
+
## Questions
|
|
66165
|
+
|
|
66166
|
+
(Open questions to revisit later. Don't lose them when context compacts.)
|
|
66167
|
+
|
|
66168
|
+
## Results
|
|
66169
|
+
|
|
66170
|
+
(Summary on completion. What changed, what was verified, what remains.)
|
|
66171
|
+
`;
|
|
66172
|
+
}
|
|
66173
|
+
// src/hooks/task-resume-info/hook.ts
|
|
66174
|
+
var TARGET_TOOLS2 = ["task", "Task", "task_tool", "delegate_agent"];
|
|
66175
|
+
var SESSION_ID_PATTERNS = [
|
|
66176
|
+
/Session ID: (ses_[a-zA-Z0-9_-]+)/,
|
|
66177
|
+
/session_id: (ses_[a-zA-Z0-9_-]+)/,
|
|
66178
|
+
/<task_metadata>\s*session_id: (ses_[a-zA-Z0-9_-]+)/,
|
|
66179
|
+
/sessionId: (ses_[a-zA-Z0-9_-]+)/
|
|
66180
|
+
];
|
|
66181
|
+
function extractSessionId(output) {
|
|
66182
|
+
for (const pattern of SESSION_ID_PATTERNS) {
|
|
66183
|
+
const match = output.match(pattern);
|
|
66184
|
+
if (match)
|
|
66185
|
+
return match[1] ?? null;
|
|
66186
|
+
}
|
|
66187
|
+
return null;
|
|
66188
|
+
}
|
|
66189
|
+
function createTaskResumeInfoHook() {
|
|
66190
|
+
const toolExecuteAfter = async (input, output) => {
|
|
66191
|
+
if (!TARGET_TOOLS2.includes(input.tool))
|
|
66192
|
+
return;
|
|
66193
|
+
const outputText = output.output ?? "";
|
|
66194
|
+
if (outputText.startsWith("Error:") || outputText.startsWith("Failed"))
|
|
66195
|
+
return;
|
|
66196
|
+
if (outputText.includes(`
|
|
66197
|
+
to continue:`))
|
|
66198
|
+
return;
|
|
66199
|
+
const sessionId = extractSessionId(outputText);
|
|
66200
|
+
if (!sessionId)
|
|
66201
|
+
return;
|
|
66202
|
+
output.output = outputText.trimEnd() + `
|
|
66203
|
+
|
|
66204
|
+
to continue: task(session_id="${sessionId}", prompt="...")`;
|
|
66205
|
+
};
|
|
66206
|
+
return {
|
|
66207
|
+
"tool.execute.after": toolExecuteAfter
|
|
65605
66208
|
};
|
|
65606
66209
|
}
|
|
66210
|
+
// src/shared/task-system-gating.ts
|
|
66211
|
+
var TASK_SYSTEM_DEFAULT = true;
|
|
66212
|
+
function isTaskSystemEnabled(config2) {
|
|
66213
|
+
return config2?.experimental?.task_system ?? TASK_SYSTEM_DEFAULT;
|
|
66214
|
+
}
|
|
66215
|
+
|
|
65607
66216
|
// src/hooks/tasks-todowrite-disabler/constants.ts
|
|
65608
66217
|
var BLOCKED_TOOLS2 = ["TodoWrite", "TodoRead"];
|
|
65609
66218
|
var REPLACEMENT_MESSAGE = `TodoRead/TodoWrite are DISABLED because experimental.task_system is enabled.
|
|
@@ -65637,10 +66246,10 @@ DO NOT retry TodoWrite. Convert to TaskCreate NOW.`;
|
|
|
65637
66246
|
|
|
65638
66247
|
// src/hooks/tasks-todowrite-disabler/hook.ts
|
|
65639
66248
|
function createTasksTodowriteDisablerHook(config2) {
|
|
65640
|
-
const
|
|
66249
|
+
const enabled = isTaskSystemEnabled(config2);
|
|
65641
66250
|
return {
|
|
65642
66251
|
"tool.execute.before": async (input, _output) => {
|
|
65643
|
-
if (!
|
|
66252
|
+
if (!enabled) {
|
|
65644
66253
|
return;
|
|
65645
66254
|
}
|
|
65646
66255
|
const toolName = input.tool;
|
|
@@ -66122,22 +66731,22 @@ function createThinkingBlockValidatorHook() {
|
|
|
66122
66731
|
init_logger();
|
|
66123
66732
|
|
|
66124
66733
|
// src/hooks/todo-continuation-enforcer/constants.ts
|
|
66125
|
-
var
|
|
66126
|
-
var
|
|
66127
|
-
var
|
|
66734
|
+
var HOOK_NAME17 = "todo-continuation-enforcer";
|
|
66735
|
+
var DEFAULT_SKIP_AGENTS2 = ["oracle", "compaction"];
|
|
66736
|
+
var CONTINUATION_PROMPT3 = `${createSystemDirective(SystemDirectiveTypes.TODO_CONTINUATION)}
|
|
66128
66737
|
|
|
66129
66738
|
Incomplete tasks remain in your todo list. Continue working on the next pending task.
|
|
66130
66739
|
|
|
66131
66740
|
- Proceed without asking for permission
|
|
66132
66741
|
- Mark each task complete when finished
|
|
66133
66742
|
- Do not stop until all tasks are done`;
|
|
66134
|
-
var
|
|
66135
|
-
var
|
|
66136
|
-
var
|
|
66137
|
-
var
|
|
66138
|
-
var
|
|
66139
|
-
var
|
|
66140
|
-
var
|
|
66743
|
+
var COUNTDOWN_SECONDS2 = 2;
|
|
66744
|
+
var TOAST_DURATION_MS2 = 900;
|
|
66745
|
+
var COUNTDOWN_GRACE_PERIOD_MS2 = 500;
|
|
66746
|
+
var ABORT_WINDOW_MS2 = 3000;
|
|
66747
|
+
var CONTINUATION_COOLDOWN_MS3 = 30000;
|
|
66748
|
+
var MAX_CONSECUTIVE_FAILURES2 = 5;
|
|
66749
|
+
var FAILURE_RESET_WINDOW_MS2 = 5 * 60 * 1000;
|
|
66141
66750
|
|
|
66142
66751
|
// src/hooks/todo-continuation-enforcer/handler.ts
|
|
66143
66752
|
init_logger();
|
|
@@ -66146,7 +66755,7 @@ init_logger();
|
|
|
66146
66755
|
init_logger();
|
|
66147
66756
|
|
|
66148
66757
|
// src/hooks/todo-continuation-enforcer/abort-detection.ts
|
|
66149
|
-
function
|
|
66758
|
+
function isLastAssistantMessageAborted2(messages) {
|
|
66150
66759
|
if (!messages || messages.length === 0)
|
|
66151
66760
|
return false;
|
|
66152
66761
|
for (let i = messages.length - 1;i >= 0; i--) {
|
|
@@ -66172,28 +66781,28 @@ function getIncompleteCount(todos) {
|
|
|
66172
66781
|
}
|
|
66173
66782
|
|
|
66174
66783
|
// src/hooks/todo-continuation-enforcer/continuation-injection.ts
|
|
66175
|
-
function
|
|
66784
|
+
function hasWritePermission2(tools) {
|
|
66176
66785
|
const editPermission = tools?.edit;
|
|
66177
66786
|
const writePermission = tools?.write;
|
|
66178
66787
|
return !tools || editPermission !== false && editPermission !== "deny" && writePermission !== false && writePermission !== "deny";
|
|
66179
66788
|
}
|
|
66180
|
-
async function
|
|
66789
|
+
async function injectContinuation2(args) {
|
|
66181
66790
|
const {
|
|
66182
66791
|
ctx,
|
|
66183
66792
|
sessionID,
|
|
66184
66793
|
backgroundManager,
|
|
66185
|
-
skipAgents =
|
|
66794
|
+
skipAgents = DEFAULT_SKIP_AGENTS2,
|
|
66186
66795
|
resolvedInfo,
|
|
66187
66796
|
sessionStateStore
|
|
66188
66797
|
} = args;
|
|
66189
66798
|
const state3 = sessionStateStore.getExistingState(sessionID);
|
|
66190
66799
|
if (state3?.isRecovering) {
|
|
66191
|
-
log(`[${
|
|
66800
|
+
log(`[${HOOK_NAME17}] Skipped injection: in recovery`, { sessionID });
|
|
66192
66801
|
return;
|
|
66193
66802
|
}
|
|
66194
66803
|
const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((task) => task.status === "running") : false;
|
|
66195
66804
|
if (hasRunningBgTasks) {
|
|
66196
|
-
log(`[${
|
|
66805
|
+
log(`[${HOOK_NAME17}] Skipped injection: background tasks running`, { sessionID });
|
|
66197
66806
|
return;
|
|
66198
66807
|
}
|
|
66199
66808
|
let todos = [];
|
|
@@ -66201,12 +66810,12 @@ async function injectContinuation(args) {
|
|
|
66201
66810
|
const response = await ctx.client.session.todo({ path: { id: sessionID } });
|
|
66202
66811
|
todos = normalizeSDKResponse(response, [], { preferResponseOnMissingData: true });
|
|
66203
66812
|
} catch (error51) {
|
|
66204
|
-
log(`[${
|
|
66813
|
+
log(`[${HOOK_NAME17}] Failed to fetch todos`, { sessionID, error: String(error51) });
|
|
66205
66814
|
return;
|
|
66206
66815
|
}
|
|
66207
66816
|
const freshIncompleteCount = getIncompleteCount(todos);
|
|
66208
66817
|
if (freshIncompleteCount === 0) {
|
|
66209
|
-
log(`[${
|
|
66818
|
+
log(`[${HOOK_NAME17}] Skipped injection: no incomplete todos`, { sessionID });
|
|
66210
66819
|
return;
|
|
66211
66820
|
}
|
|
66212
66821
|
let agentName = resolvedInfo?.agent;
|
|
@@ -66229,17 +66838,17 @@ async function injectContinuation(args) {
|
|
|
66229
66838
|
tools = tools ?? previousMessage?.tools;
|
|
66230
66839
|
}
|
|
66231
66840
|
if (agentName && skipAgents.some((s) => getAgentConfigKey(s) === getAgentConfigKey(agentName))) {
|
|
66232
|
-
log(`[${
|
|
66841
|
+
log(`[${HOOK_NAME17}] Skipped: agent in skipAgents list`, { sessionID, agent: agentName });
|
|
66233
66842
|
return;
|
|
66234
66843
|
}
|
|
66235
|
-
if (!
|
|
66236
|
-
log(`[${
|
|
66844
|
+
if (!hasWritePermission2(tools)) {
|
|
66845
|
+
log(`[${HOOK_NAME17}] Skipped: agent lacks write permission`, { sessionID, agent: agentName });
|
|
66237
66846
|
return;
|
|
66238
66847
|
}
|
|
66239
66848
|
const incompleteTodos = todos.filter((todo) => todo.status !== "completed" && todo.status !== "cancelled");
|
|
66240
66849
|
const todoList = incompleteTodos.map((todo) => `- [${todo.status}] ${todo.content}`).join(`
|
|
66241
66850
|
`);
|
|
66242
|
-
const prompt = `${
|
|
66851
|
+
const prompt = `${CONTINUATION_PROMPT3}
|
|
66243
66852
|
|
|
66244
66853
|
[Status: ${todos.length - freshIncompleteCount}/${todos.length} completed, ${freshIncompleteCount} remaining]
|
|
66245
66854
|
|
|
@@ -66250,7 +66859,7 @@ ${todoList}`;
|
|
|
66250
66859
|
injectionState.inFlight = true;
|
|
66251
66860
|
}
|
|
66252
66861
|
try {
|
|
66253
|
-
log(`[${
|
|
66862
|
+
log(`[${HOOK_NAME17}] Injecting continuation`, {
|
|
66254
66863
|
sessionID,
|
|
66255
66864
|
agent: agentName,
|
|
66256
66865
|
model,
|
|
@@ -66265,14 +66874,14 @@ ${todoList}`;
|
|
|
66265
66874
|
},
|
|
66266
66875
|
query: { directory: ctx.directory }
|
|
66267
66876
|
});
|
|
66268
|
-
log(`[${
|
|
66877
|
+
log(`[${HOOK_NAME17}] Injection successful`, { sessionID });
|
|
66269
66878
|
if (injectionState) {
|
|
66270
66879
|
injectionState.inFlight = false;
|
|
66271
66880
|
injectionState.lastInjectedAt = Date.now();
|
|
66272
66881
|
injectionState.consecutiveFailures = 0;
|
|
66273
66882
|
}
|
|
66274
66883
|
} catch (error51) {
|
|
66275
|
-
log(`[${
|
|
66884
|
+
log(`[${HOOK_NAME17}] Injection failed`, { sessionID, error: String(error51) });
|
|
66276
66885
|
if (injectionState) {
|
|
66277
66886
|
injectionState.inFlight = false;
|
|
66278
66887
|
injectionState.lastInjectedAt = Date.now();
|
|
@@ -66282,19 +66891,19 @@ ${todoList}`;
|
|
|
66282
66891
|
}
|
|
66283
66892
|
|
|
66284
66893
|
// src/hooks/todo-continuation-enforcer/countdown.ts
|
|
66285
|
-
async function
|
|
66894
|
+
async function showCountdownToast2(ctx, seconds, incompleteCount) {
|
|
66286
66895
|
await ctx.client.tui.showToast({
|
|
66287
66896
|
body: {
|
|
66288
66897
|
title: "Todo Continuation",
|
|
66289
66898
|
message: `Resuming in ${seconds}s... (${incompleteCount} tasks remaining)`,
|
|
66290
66899
|
variant: "warning",
|
|
66291
|
-
duration:
|
|
66900
|
+
duration: TOAST_DURATION_MS2
|
|
66292
66901
|
}
|
|
66293
66902
|
}).catch((err) => {
|
|
66294
66903
|
log("[todo-continuation] Countdown toast failed:", err);
|
|
66295
66904
|
});
|
|
66296
66905
|
}
|
|
66297
|
-
function
|
|
66906
|
+
function startCountdown2(args) {
|
|
66298
66907
|
const {
|
|
66299
66908
|
ctx,
|
|
66300
66909
|
sessionID,
|
|
@@ -66305,19 +66914,22 @@ function startCountdown(args) {
|
|
|
66305
66914
|
sessionStateStore
|
|
66306
66915
|
} = args;
|
|
66307
66916
|
const state3 = sessionStateStore.getState(sessionID);
|
|
66308
|
-
|
|
66309
|
-
|
|
66310
|
-
|
|
66917
|
+
if (state3.countdownTimer || state3.countdownInterval) {
|
|
66918
|
+
log(`[${HOOK_NAME17}] Skipped: countdown already active`, { sessionID });
|
|
66919
|
+
return;
|
|
66920
|
+
}
|
|
66921
|
+
let secondsRemaining = COUNTDOWN_SECONDS2;
|
|
66922
|
+
showCountdownToast2(ctx, secondsRemaining, incompleteCount);
|
|
66311
66923
|
state3.countdownStartedAt = Date.now();
|
|
66312
66924
|
state3.countdownInterval = setInterval(() => {
|
|
66313
66925
|
secondsRemaining--;
|
|
66314
66926
|
if (secondsRemaining > 0) {
|
|
66315
|
-
|
|
66927
|
+
showCountdownToast2(ctx, secondsRemaining, incompleteCount);
|
|
66316
66928
|
}
|
|
66317
66929
|
}, 1000);
|
|
66318
66930
|
state3.countdownTimer = setTimeout(() => {
|
|
66319
66931
|
sessionStateStore.cancelCountdown(sessionID);
|
|
66320
|
-
|
|
66932
|
+
injectContinuation2({
|
|
66321
66933
|
ctx,
|
|
66322
66934
|
sessionID,
|
|
66323
66935
|
backgroundManager,
|
|
@@ -66325,34 +66937,38 @@ function startCountdown(args) {
|
|
|
66325
66937
|
resolvedInfo,
|
|
66326
66938
|
sessionStateStore
|
|
66327
66939
|
});
|
|
66328
|
-
},
|
|
66329
|
-
log(`[${
|
|
66940
|
+
}, COUNTDOWN_SECONDS2 * 1000);
|
|
66941
|
+
log(`[${HOOK_NAME17}] Countdown started`, {
|
|
66330
66942
|
sessionID,
|
|
66331
|
-
seconds:
|
|
66943
|
+
seconds: COUNTDOWN_SECONDS2,
|
|
66332
66944
|
incompleteCount
|
|
66333
66945
|
});
|
|
66334
66946
|
}
|
|
66335
66947
|
|
|
66336
66948
|
// src/hooks/todo-continuation-enforcer/idle-event.ts
|
|
66337
|
-
async function
|
|
66949
|
+
async function handleSessionIdle2(args) {
|
|
66338
66950
|
const {
|
|
66339
66951
|
ctx,
|
|
66340
66952
|
sessionID,
|
|
66341
66953
|
sessionStateStore,
|
|
66342
66954
|
backgroundManager,
|
|
66343
|
-
skipAgents =
|
|
66955
|
+
skipAgents = DEFAULT_SKIP_AGENTS2,
|
|
66344
66956
|
isContinuationStopped
|
|
66345
66957
|
} = args;
|
|
66346
|
-
log(`[${
|
|
66958
|
+
log(`[${HOOK_NAME17}] session.idle`, { sessionID });
|
|
66347
66959
|
const state3 = sessionStateStore.getState(sessionID);
|
|
66960
|
+
if (state3.countdownTimer || state3.countdownInterval) {
|
|
66961
|
+
log(`[${HOOK_NAME17}] Skipped: countdown already active`, { sessionID });
|
|
66962
|
+
return;
|
|
66963
|
+
}
|
|
66348
66964
|
if (state3.isRecovering) {
|
|
66349
|
-
log(`[${
|
|
66965
|
+
log(`[${HOOK_NAME17}] Skipped: in recovery`, { sessionID });
|
|
66350
66966
|
return;
|
|
66351
66967
|
}
|
|
66352
66968
|
if (state3.abortDetectedAt) {
|
|
66353
66969
|
const timeSinceAbort = Date.now() - state3.abortDetectedAt;
|
|
66354
|
-
if (timeSinceAbort <
|
|
66355
|
-
log(`[${
|
|
66970
|
+
if (timeSinceAbort < ABORT_WINDOW_MS2) {
|
|
66971
|
+
log(`[${HOOK_NAME17}] Skipped: abort detected via event ${timeSinceAbort}ms ago`, { sessionID });
|
|
66356
66972
|
state3.abortDetectedAt = undefined;
|
|
66357
66973
|
return;
|
|
66358
66974
|
}
|
|
@@ -66360,7 +66976,7 @@ async function handleSessionIdle(args) {
|
|
|
66360
66976
|
}
|
|
66361
66977
|
const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((task) => task.status === "running") : false;
|
|
66362
66978
|
if (hasRunningBgTasks) {
|
|
66363
|
-
log(`[${
|
|
66979
|
+
log(`[${HOOK_NAME17}] Skipped: background tasks running`, { sessionID });
|
|
66364
66980
|
return;
|
|
66365
66981
|
}
|
|
66366
66982
|
try {
|
|
@@ -66369,52 +66985,52 @@ async function handleSessionIdle(args) {
|
|
|
66369
66985
|
query: { directory: ctx.directory }
|
|
66370
66986
|
});
|
|
66371
66987
|
const messages = normalizeSDKResponse(messagesResp, []);
|
|
66372
|
-
if (
|
|
66373
|
-
log(`[${
|
|
66988
|
+
if (isLastAssistantMessageAborted2(messages)) {
|
|
66989
|
+
log(`[${HOOK_NAME17}] Skipped: last assistant message was aborted (API fallback)`, { sessionID });
|
|
66374
66990
|
return;
|
|
66375
66991
|
}
|
|
66376
66992
|
} catch (error51) {
|
|
66377
|
-
log(`[${
|
|
66993
|
+
log(`[${HOOK_NAME17}] Messages fetch failed, continuing`, { sessionID, error: String(error51) });
|
|
66378
66994
|
}
|
|
66379
66995
|
let todos = [];
|
|
66380
66996
|
try {
|
|
66381
66997
|
const response = await ctx.client.session.todo({ path: { id: sessionID } });
|
|
66382
66998
|
todos = normalizeSDKResponse(response, [], { preferResponseOnMissingData: true });
|
|
66383
66999
|
} catch (error51) {
|
|
66384
|
-
log(`[${
|
|
67000
|
+
log(`[${HOOK_NAME17}] Todo fetch failed`, { sessionID, error: String(error51) });
|
|
66385
67001
|
return;
|
|
66386
67002
|
}
|
|
66387
67003
|
if (!todos || todos.length === 0) {
|
|
66388
|
-
log(`[${
|
|
67004
|
+
log(`[${HOOK_NAME17}] No todos`, { sessionID });
|
|
66389
67005
|
return;
|
|
66390
67006
|
}
|
|
66391
67007
|
const incompleteCount = getIncompleteCount(todos);
|
|
66392
67008
|
if (incompleteCount === 0) {
|
|
66393
|
-
log(`[${
|
|
67009
|
+
log(`[${HOOK_NAME17}] All todos complete`, { sessionID, total: todos.length });
|
|
66394
67010
|
return;
|
|
66395
67011
|
}
|
|
66396
67012
|
if (state3.inFlight) {
|
|
66397
|
-
log(`[${
|
|
67013
|
+
log(`[${HOOK_NAME17}] Skipped: injection in flight`, { sessionID });
|
|
66398
67014
|
return;
|
|
66399
67015
|
}
|
|
66400
|
-
if (state3.consecutiveFailures >=
|
|
67016
|
+
if (state3.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES2 && state3.lastInjectedAt && Date.now() - state3.lastInjectedAt >= FAILURE_RESET_WINDOW_MS2) {
|
|
66401
67017
|
state3.consecutiveFailures = 0;
|
|
66402
|
-
log(`[${
|
|
67018
|
+
log(`[${HOOK_NAME17}] Reset consecutive failures after recovery window`, {
|
|
66403
67019
|
sessionID,
|
|
66404
|
-
failureResetWindowMs:
|
|
67020
|
+
failureResetWindowMs: FAILURE_RESET_WINDOW_MS2
|
|
66405
67021
|
});
|
|
66406
67022
|
}
|
|
66407
|
-
if (state3.consecutiveFailures >=
|
|
66408
|
-
log(`[${
|
|
67023
|
+
if (state3.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES2) {
|
|
67024
|
+
log(`[${HOOK_NAME17}] Skipped: max consecutive failures reached`, {
|
|
66409
67025
|
sessionID,
|
|
66410
67026
|
consecutiveFailures: state3.consecutiveFailures,
|
|
66411
|
-
maxConsecutiveFailures:
|
|
67027
|
+
maxConsecutiveFailures: MAX_CONSECUTIVE_FAILURES2
|
|
66412
67028
|
});
|
|
66413
67029
|
return;
|
|
66414
67030
|
}
|
|
66415
|
-
const effectiveCooldown =
|
|
67031
|
+
const effectiveCooldown = CONTINUATION_COOLDOWN_MS3 * 2 ** Math.min(state3.consecutiveFailures, 5);
|
|
66416
67032
|
if (state3.lastInjectedAt && Date.now() - state3.lastInjectedAt < effectiveCooldown) {
|
|
66417
|
-
log(`[${
|
|
67033
|
+
log(`[${HOOK_NAME17}] Skipped: cooldown active`, {
|
|
66418
67034
|
sessionID,
|
|
66419
67035
|
effectiveCooldown,
|
|
66420
67036
|
consecutiveFailures: state3.consecutiveFailures
|
|
@@ -66444,23 +67060,23 @@ async function handleSessionIdle(args) {
|
|
|
66444
67060
|
}
|
|
66445
67061
|
}
|
|
66446
67062
|
} catch (error51) {
|
|
66447
|
-
log(`[${
|
|
67063
|
+
log(`[${HOOK_NAME17}] Failed to fetch messages for agent check`, { sessionID, error: String(error51) });
|
|
66448
67064
|
}
|
|
66449
|
-
log(`[${
|
|
67065
|
+
log(`[${HOOK_NAME17}] Agent check`, { sessionID, agentName: resolvedInfo?.agent, skipAgents, hasCompactionMessage });
|
|
66450
67066
|
const resolvedAgentName = resolvedInfo?.agent;
|
|
66451
67067
|
if (resolvedAgentName && skipAgents.some((s) => getAgentConfigKey(s) === getAgentConfigKey(resolvedAgentName))) {
|
|
66452
|
-
log(`[${
|
|
67068
|
+
log(`[${HOOK_NAME17}] Skipped: agent in skipAgents list`, { sessionID, agent: resolvedAgentName });
|
|
66453
67069
|
return;
|
|
66454
67070
|
}
|
|
66455
67071
|
if (hasCompactionMessage && !resolvedInfo?.agent) {
|
|
66456
|
-
log(`[${
|
|
67072
|
+
log(`[${HOOK_NAME17}] Skipped: compaction occurred but no agent info resolved`, { sessionID });
|
|
66457
67073
|
return;
|
|
66458
67074
|
}
|
|
66459
67075
|
if (isContinuationStopped?.(sessionID)) {
|
|
66460
|
-
log(`[${
|
|
67076
|
+
log(`[${HOOK_NAME17}] Skipped: continuation stopped for session`, { sessionID });
|
|
66461
67077
|
return;
|
|
66462
67078
|
}
|
|
66463
|
-
|
|
67079
|
+
startCountdown2({
|
|
66464
67080
|
ctx,
|
|
66465
67081
|
sessionID,
|
|
66466
67082
|
incompleteCount,
|
|
@@ -66474,7 +67090,7 @@ async function handleSessionIdle(args) {
|
|
|
66474
67090
|
|
|
66475
67091
|
// src/hooks/todo-continuation-enforcer/non-idle-events.ts
|
|
66476
67092
|
init_logger();
|
|
66477
|
-
function
|
|
67093
|
+
function handleNonIdleEvent2(args) {
|
|
66478
67094
|
const { eventType, properties, sessionStateStore } = args;
|
|
66479
67095
|
if (eventType === "message.updated") {
|
|
66480
67096
|
const info = properties?.info;
|
|
@@ -66486,8 +67102,8 @@ function handleNonIdleEvent(args) {
|
|
|
66486
67102
|
const state3 = sessionStateStore.getExistingState(sessionID);
|
|
66487
67103
|
if (state3?.countdownStartedAt) {
|
|
66488
67104
|
const elapsed = Date.now() - state3.countdownStartedAt;
|
|
66489
|
-
if (elapsed <
|
|
66490
|
-
log(`[${
|
|
67105
|
+
if (elapsed < COUNTDOWN_GRACE_PERIOD_MS2) {
|
|
67106
|
+
log(`[${HOOK_NAME17}] Ignoring user message in grace period`, { sessionID, elapsed });
|
|
66491
67107
|
return;
|
|
66492
67108
|
}
|
|
66493
67109
|
}
|
|
@@ -66531,7 +67147,7 @@ function handleNonIdleEvent(args) {
|
|
|
66531
67147
|
const sessionInfo = properties?.info;
|
|
66532
67148
|
if (sessionInfo?.id) {
|
|
66533
67149
|
sessionStateStore.cleanup(sessionInfo.id);
|
|
66534
|
-
log(`[${
|
|
67150
|
+
log(`[${HOOK_NAME17}] Session deleted: cleaned up`, { sessionID: sessionInfo.id });
|
|
66535
67151
|
}
|
|
66536
67152
|
return;
|
|
66537
67153
|
}
|
|
@@ -66543,7 +67159,7 @@ function createTodoContinuationHandler(args) {
|
|
|
66543
67159
|
ctx,
|
|
66544
67160
|
sessionStateStore,
|
|
66545
67161
|
backgroundManager,
|
|
66546
|
-
skipAgents =
|
|
67162
|
+
skipAgents = DEFAULT_SKIP_AGENTS2,
|
|
66547
67163
|
isContinuationStopped
|
|
66548
67164
|
} = args;
|
|
66549
67165
|
return async ({ event }) => {
|
|
@@ -66556,17 +67172,17 @@ function createTodoContinuationHandler(args) {
|
|
|
66556
67172
|
if (error51?.name === "MessageAbortedError" || error51?.name === "AbortError") {
|
|
66557
67173
|
const state3 = sessionStateStore.getState(sessionID);
|
|
66558
67174
|
state3.abortDetectedAt = Date.now();
|
|
66559
|
-
log(`[${
|
|
67175
|
+
log(`[${HOOK_NAME17}] Abort detected via session.error`, { sessionID, errorName: error51.name });
|
|
66560
67176
|
}
|
|
66561
67177
|
sessionStateStore.cancelCountdown(sessionID);
|
|
66562
|
-
log(`[${
|
|
67178
|
+
log(`[${HOOK_NAME17}] session.error`, { sessionID });
|
|
66563
67179
|
return;
|
|
66564
67180
|
}
|
|
66565
67181
|
if (event.type === "session.idle") {
|
|
66566
67182
|
const sessionID = props?.sessionID;
|
|
66567
67183
|
if (!sessionID)
|
|
66568
67184
|
return;
|
|
66569
|
-
await
|
|
67185
|
+
await handleSessionIdle2({
|
|
66570
67186
|
ctx,
|
|
66571
67187
|
sessionID,
|
|
66572
67188
|
sessionStateStore,
|
|
@@ -66576,7 +67192,7 @@ function createTodoContinuationHandler(args) {
|
|
|
66576
67192
|
});
|
|
66577
67193
|
return;
|
|
66578
67194
|
}
|
|
66579
|
-
|
|
67195
|
+
handleNonIdleEvent2({
|
|
66580
67196
|
eventType: event.type,
|
|
66581
67197
|
properties: props,
|
|
66582
67198
|
sessionStateStore
|
|
@@ -66585,20 +67201,20 @@ function createTodoContinuationHandler(args) {
|
|
|
66585
67201
|
}
|
|
66586
67202
|
|
|
66587
67203
|
// src/hooks/todo-continuation-enforcer/session-state.ts
|
|
66588
|
-
var
|
|
66589
|
-
var
|
|
66590
|
-
function
|
|
67204
|
+
var SESSION_STATE_TTL_MS2 = 10 * 60 * 1000;
|
|
67205
|
+
var SESSION_STATE_PRUNE_INTERVAL_MS2 = 2 * 60 * 1000;
|
|
67206
|
+
function createSessionStateStore2() {
|
|
66591
67207
|
const sessions = new Map;
|
|
66592
67208
|
let pruneInterval;
|
|
66593
67209
|
pruneInterval = setInterval(() => {
|
|
66594
67210
|
const now = Date.now();
|
|
66595
67211
|
for (const [sessionID, tracked] of sessions.entries()) {
|
|
66596
|
-
if (now - tracked.lastAccessedAt >
|
|
67212
|
+
if (now - tracked.lastAccessedAt > SESSION_STATE_TTL_MS2) {
|
|
66597
67213
|
cancelCountdown(sessionID);
|
|
66598
67214
|
sessions.delete(sessionID);
|
|
66599
67215
|
}
|
|
66600
67216
|
}
|
|
66601
|
-
},
|
|
67217
|
+
}, SESSION_STATE_PRUNE_INTERVAL_MS2);
|
|
66602
67218
|
if (typeof pruneInterval === "object" && "unref" in pruneInterval) {
|
|
66603
67219
|
pruneInterval.unref();
|
|
66604
67220
|
}
|
|
@@ -66666,21 +67282,21 @@ function createSessionStateStore() {
|
|
|
66666
67282
|
function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
66667
67283
|
const {
|
|
66668
67284
|
backgroundManager,
|
|
66669
|
-
skipAgents =
|
|
67285
|
+
skipAgents = DEFAULT_SKIP_AGENTS2,
|
|
66670
67286
|
isContinuationStopped
|
|
66671
67287
|
} = options;
|
|
66672
|
-
const sessionStateStore =
|
|
67288
|
+
const sessionStateStore = createSessionStateStore2();
|
|
66673
67289
|
const markRecovering = (sessionID) => {
|
|
66674
67290
|
const state3 = sessionStateStore.getState(sessionID);
|
|
66675
67291
|
state3.isRecovering = true;
|
|
66676
67292
|
sessionStateStore.cancelCountdown(sessionID);
|
|
66677
|
-
log(`[${
|
|
67293
|
+
log(`[${HOOK_NAME17}] Session marked as recovering`, { sessionID });
|
|
66678
67294
|
};
|
|
66679
67295
|
const markRecoveryComplete = (sessionID) => {
|
|
66680
67296
|
const state3 = sessionStateStore.getExistingState(sessionID);
|
|
66681
67297
|
if (state3) {
|
|
66682
67298
|
state3.isRecovering = false;
|
|
66683
|
-
log(`[${
|
|
67299
|
+
log(`[${HOOK_NAME17}] Session recovery complete`, { sessionID });
|
|
66684
67300
|
}
|
|
66685
67301
|
};
|
|
66686
67302
|
const handler = createTodoContinuationHandler({
|
|
@@ -66692,7 +67308,7 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
66692
67308
|
});
|
|
66693
67309
|
const cancelAllCountdowns = () => {
|
|
66694
67310
|
sessionStateStore.cancelAllCountdowns();
|
|
66695
|
-
log(`[${
|
|
67311
|
+
log(`[${HOOK_NAME17}] All countdowns cancelled`);
|
|
66696
67312
|
};
|
|
66697
67313
|
return {
|
|
66698
67314
|
handler,
|
|
@@ -66701,38 +67317,6 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
66701
67317
|
cancelAllCountdowns
|
|
66702
67318
|
};
|
|
66703
67319
|
}
|
|
66704
|
-
// src/hooks/todo-description-override/description.ts
|
|
66705
|
-
var TODOWRITE_DESCRIPTION = `Manage your todo list with ATOMIC, single-responsibility items.
|
|
66706
|
-
|
|
66707
|
-
RULES:
|
|
66708
|
-
- One todo = one specific, verifiable action (not "Update everything" or "Fix issues")
|
|
66709
|
-
- Each todo must be completable in isolation \u2014 no vague catch-alls
|
|
66710
|
-
- Todos must be nameable with a single precise phrase
|
|
66711
|
-
- Mark in_progress BEFORE starting work, completed IMMEDIATELY after finishing
|
|
66712
|
-
- Only ONE todo in_progress at a time
|
|
66713
|
-
|
|
66714
|
-
ANTI-PATTERNS (will be rejected by reviewers):
|
|
66715
|
-
- "Update X and Y and Z" \u2192 SPLIT into 3 separate todos
|
|
66716
|
-
- "Fix issues" \u2192 SPECIFY which file, function, or behavior
|
|
66717
|
-
- "Refactor" \u2192 SPECIFY what is being refactored and to what end
|
|
66718
|
-
- "Improve" \u2192 SPECIFY the measurable outcome
|
|
66719
|
-
|
|
66720
|
-
STATUS TRANSITIONS:
|
|
66721
|
-
pending \u2192 in_progress \u2192 completed
|
|
66722
|
-
pending \u2192 cancelled (if no longer needed)
|
|
66723
|
-
|
|
66724
|
-
Never batch-complete todos. Mark each one complete the moment it is done.`;
|
|
66725
|
-
|
|
66726
|
-
// src/hooks/todo-description-override/hook.ts
|
|
66727
|
-
function createTodoDescriptionOverrideHook() {
|
|
66728
|
-
return {
|
|
66729
|
-
"tool.definition": async (input, output) => {
|
|
66730
|
-
if (input.toolID === "todowrite") {
|
|
66731
|
-
output.description = TODOWRITE_DESCRIPTION;
|
|
66732
|
-
}
|
|
66733
|
-
}
|
|
66734
|
-
};
|
|
66735
|
-
}
|
|
66736
67320
|
// src/hooks/tool-output-truncator.ts
|
|
66737
67321
|
var DEFAULT_MAX_TOKENS = 50000;
|
|
66738
67322
|
var WEBFETCH_MAX_TOKENS = 1e4;
|
|
@@ -66948,8 +67532,8 @@ This is a reminder only. No automatic action was taken.`;
|
|
|
66948
67532
|
}
|
|
66949
67533
|
// src/hooks/unstable-agent-babysitter/unstable-agent-babysitter-hook.ts
|
|
66950
67534
|
init_logger();
|
|
66951
|
-
var
|
|
66952
|
-
var
|
|
67535
|
+
var HOOK_NAME18 = "unstable-agent-babysitter";
|
|
67536
|
+
var DEFAULT_TIMEOUT_MS = 120000;
|
|
66953
67537
|
var COOLDOWN_MS = 5 * 60 * 1000;
|
|
66954
67538
|
async function resolveMainSessionTarget(ctx, sessionID) {
|
|
66955
67539
|
let agent = getSessionAgent(sessionID);
|
|
@@ -66968,7 +67552,7 @@ async function resolveMainSessionTarget(ctx, sessionID) {
|
|
|
66968
67552
|
}
|
|
66969
67553
|
}
|
|
66970
67554
|
} catch (error51) {
|
|
66971
|
-
log(`[${
|
|
67555
|
+
log(`[${HOOK_NAME18}] Failed to resolve main session agent`, { sessionID, error: String(error51) });
|
|
66972
67556
|
}
|
|
66973
67557
|
return { agent, model };
|
|
66974
67558
|
}
|
|
@@ -67001,7 +67585,7 @@ async function getThinkingSummary(ctx, sessionID) {
|
|
|
67001
67585
|
return combined;
|
|
67002
67586
|
return `${combined.slice(0, THINKING_SUMMARY_MAX_CHARS)}...`;
|
|
67003
67587
|
} catch (error51) {
|
|
67004
|
-
log(`[${
|
|
67588
|
+
log(`[${HOOK_NAME18}] Failed to fetch thinking summary`, { sessionID, error: String(error51) });
|
|
67005
67589
|
return null;
|
|
67006
67590
|
}
|
|
67007
67591
|
}
|
|
@@ -67020,7 +67604,7 @@ function createUnstableAgentBabysitterHook(ctx, options) {
|
|
|
67020
67604
|
const tasks = options.backgroundManager.getTasksByParentSession(mainSessionID);
|
|
67021
67605
|
if (tasks.length === 0)
|
|
67022
67606
|
return;
|
|
67023
|
-
const timeoutMs = options.config?.timeout_ms ??
|
|
67607
|
+
const timeoutMs = options.config?.timeout_ms ?? DEFAULT_TIMEOUT_MS;
|
|
67024
67608
|
const now = Date.now();
|
|
67025
67609
|
for (const task of tasks) {
|
|
67026
67610
|
if (task.status !== "running")
|
|
@@ -67050,9 +67634,9 @@ function createUnstableAgentBabysitterHook(ctx, options) {
|
|
|
67050
67634
|
query: { directory: ctx.directory }
|
|
67051
67635
|
});
|
|
67052
67636
|
reminderCooldowns.set(task.id, now);
|
|
67053
|
-
log(`[${
|
|
67637
|
+
log(`[${HOOK_NAME18}] Reminder injected`, { taskId: task.id, sessionID: mainSessionID });
|
|
67054
67638
|
} catch (error51) {
|
|
67055
|
-
log(`[${
|
|
67639
|
+
log(`[${HOOK_NAME18}] Reminder injection failed`, { taskId: task.id, error: String(error51) });
|
|
67056
67640
|
}
|
|
67057
67641
|
}
|
|
67058
67642
|
};
|
|
@@ -67203,7 +67787,7 @@ function createWebFetchRedirectGuardHook(_ctx) {
|
|
|
67203
67787
|
}
|
|
67204
67788
|
// src/hooks/write-existing-file-guard/hook.ts
|
|
67205
67789
|
import { existsSync as existsSync46 } from "fs";
|
|
67206
|
-
import { isAbsolute as isAbsolute4, join as
|
|
67790
|
+
import { isAbsolute as isAbsolute4, join as join53, normalize, resolve as resolve7, sep } from "path";
|
|
67207
67791
|
function createWriteExistingFileGuardHook(ctx) {
|
|
67208
67792
|
return {
|
|
67209
67793
|
"tool.execute.before": async (input, output) => {
|
|
@@ -67218,7 +67802,7 @@ function createWriteExistingFileGuardHook(ctx) {
|
|
|
67218
67802
|
}
|
|
67219
67803
|
const resolvedPath = normalize(isAbsolute4(filePath) ? filePath : resolve7(ctx.directory, filePath));
|
|
67220
67804
|
if (existsSync46(resolvedPath)) {
|
|
67221
|
-
const matrixxRoot =
|
|
67805
|
+
const matrixxRoot = join53(ctx.directory, ".matrixx") + sep;
|
|
67222
67806
|
const isMatrixMarkdown = resolvedPath.startsWith(matrixxRoot) && resolvedPath.endsWith(".md");
|
|
67223
67807
|
if (isMatrixMarkdown) {
|
|
67224
67808
|
log("[write-existing-file-guard] Allowing .matrixx/*.md overwrite", {
|
|
@@ -67281,7 +67865,12 @@ function createContinuationHooks(args) {
|
|
|
67281
67865
|
const stopContinuationGuard = isHookEnabled("stop-continuation-guard") ? safeHook("stop-continuation-guard", () => createStopContinuationGuardHook(ctx, { backgroundManager })) : null;
|
|
67282
67866
|
const compactionContextInjector = isHookEnabled("compaction-context-injector") ? safeHook("compaction-context-injector", () => createCompactionContextInjector(backgroundManager)) : null;
|
|
67283
67867
|
const compactionTodoPreserver = isHookEnabled("compaction-todo-preserver") ? safeHook("compaction-todo-preserver", () => createCompactionTodoPreserverHook(ctx)) : null;
|
|
67284
|
-
const
|
|
67868
|
+
const isTaskSystem = isTaskSystemEnabled(pluginConfig);
|
|
67869
|
+
const todoContinuationEnforcer = !isTaskSystem && isHookEnabled("todo-continuation-enforcer") ? safeHook("todo-continuation-enforcer", () => createTodoContinuationEnforcer(ctx, {
|
|
67870
|
+
backgroundManager,
|
|
67871
|
+
isContinuationStopped: stopContinuationGuard?.isStopped
|
|
67872
|
+
})) : null;
|
|
67873
|
+
const taskContinuationEnforcer = isTaskSystem && isHookEnabled("task-continuation-enforcer") ? safeHook("task-continuation-enforcer", () => createTaskContinuationEnforcer(ctx, {
|
|
67285
67874
|
backgroundManager,
|
|
67286
67875
|
isContinuationStopped: stopContinuationGuard?.isStopped
|
|
67287
67876
|
})) : null;
|
|
@@ -67293,6 +67882,10 @@ function createContinuationHooks(args) {
|
|
|
67293
67882
|
onAbortCallbacks.push(todoContinuationEnforcer.markRecovering);
|
|
67294
67883
|
onRecoveryCompleteCallbacks.push(todoContinuationEnforcer.markRecoveryComplete);
|
|
67295
67884
|
}
|
|
67885
|
+
if (taskContinuationEnforcer) {
|
|
67886
|
+
onAbortCallbacks.push(taskContinuationEnforcer.markRecovering);
|
|
67887
|
+
onRecoveryCompleteCallbacks.push(taskContinuationEnforcer.markRecoveryComplete);
|
|
67888
|
+
}
|
|
67296
67889
|
if (onAbortCallbacks.length > 0) {
|
|
67297
67890
|
sessionRecovery.setOnAbortCallback((sessionID) => {
|
|
67298
67891
|
for (const callback of onAbortCallbacks)
|
|
@@ -67321,6 +67914,7 @@ function createContinuationHooks(args) {
|
|
|
67321
67914
|
compactionContextInjector,
|
|
67322
67915
|
compactionTodoPreserver,
|
|
67323
67916
|
todoContinuationEnforcer,
|
|
67917
|
+
taskContinuationEnforcer,
|
|
67324
67918
|
unstableAgentBabysitter,
|
|
67325
67919
|
backgroundNotificationHook,
|
|
67326
67920
|
architectHook,
|
|
@@ -67638,7 +68232,7 @@ function getLanguageId(ext) {
|
|
|
67638
68232
|
}
|
|
67639
68233
|
// src/tools/lsp/server-installation.ts
|
|
67640
68234
|
import { existsSync as existsSync47 } from "fs";
|
|
67641
|
-
import { join as
|
|
68235
|
+
import { join as join54 } from "path";
|
|
67642
68236
|
function isServerInstalled(command) {
|
|
67643
68237
|
if (command.length === 0)
|
|
67644
68238
|
return false;
|
|
@@ -67666,23 +68260,23 @@ function isServerInstalled(command) {
|
|
|
67666
68260
|
const paths = pathEnv.split(pathSeparator);
|
|
67667
68261
|
for (const p of paths) {
|
|
67668
68262
|
for (const suffix of exts) {
|
|
67669
|
-
if (existsSync47(
|
|
68263
|
+
if (existsSync47(join54(p, cmd + suffix))) {
|
|
67670
68264
|
return true;
|
|
67671
68265
|
}
|
|
67672
68266
|
}
|
|
67673
68267
|
}
|
|
67674
68268
|
const cwd = process.cwd();
|
|
67675
68269
|
const configDir = getOpenCodeConfigDir({ binary: "opencode" });
|
|
67676
|
-
const dataDir =
|
|
68270
|
+
const dataDir = join54(getDataDir(), "opencode");
|
|
67677
68271
|
const additionalBases = [
|
|
67678
|
-
|
|
67679
|
-
|
|
67680
|
-
|
|
67681
|
-
|
|
68272
|
+
join54(cwd, "node_modules", ".bin"),
|
|
68273
|
+
join54(configDir, "bin"),
|
|
68274
|
+
join54(configDir, "node_modules", ".bin"),
|
|
68275
|
+
join54(dataDir, "bin")
|
|
67682
68276
|
];
|
|
67683
68277
|
for (const base of additionalBases) {
|
|
67684
68278
|
for (const suffix of exts) {
|
|
67685
|
-
if (existsSync47(
|
|
68279
|
+
if (existsSync47(join54(base, cmd + suffix))) {
|
|
67686
68280
|
return true;
|
|
67687
68281
|
}
|
|
67688
68282
|
}
|
|
@@ -67694,7 +68288,7 @@ function isServerInstalled(command) {
|
|
|
67694
68288
|
}
|
|
67695
68289
|
// src/tools/lsp/server-config-loader.ts
|
|
67696
68290
|
import { existsSync as existsSync48, readFileSync as readFileSync34 } from "fs";
|
|
67697
|
-
import { join as
|
|
68291
|
+
import { join as join55 } from "path";
|
|
67698
68292
|
function loadJsonFile(path12) {
|
|
67699
68293
|
if (!existsSync48(path12))
|
|
67700
68294
|
return null;
|
|
@@ -67708,9 +68302,9 @@ function getConfigPaths3() {
|
|
|
67708
68302
|
const cwd = process.cwd();
|
|
67709
68303
|
const configDir = getOpenCodeConfigDir({ binary: "opencode" });
|
|
67710
68304
|
return {
|
|
67711
|
-
project: detectConfigFile(
|
|
67712
|
-
user: detectConfigFile(
|
|
67713
|
-
opencode: detectConfigFile(
|
|
68305
|
+
project: detectConfigFile(join55(cwd, ".opencode", "matrixx")).path,
|
|
68306
|
+
user: detectConfigFile(join55(configDir, "matrixx")).path,
|
|
68307
|
+
opencode: detectConfigFile(join55(configDir, "opencode")).path
|
|
67714
68308
|
};
|
|
67715
68309
|
}
|
|
67716
68310
|
function loadAllConfigs() {
|
|
@@ -68559,7 +69153,7 @@ class LSPServerManager {
|
|
|
68559
69153
|
var lspManager = LSPServerManager.getInstance();
|
|
68560
69154
|
// src/tools/lsp/lsp-client-wrapper.ts
|
|
68561
69155
|
import { existsSync as existsSync50, statSync as statSync10 } from "fs";
|
|
68562
|
-
import { dirname as dirname16, extname as extname3, join as
|
|
69156
|
+
import { dirname as dirname16, extname as extname3, join as join56, resolve as resolve9 } from "path";
|
|
68563
69157
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
68564
69158
|
function findWorkspaceRoot(filePath) {
|
|
68565
69159
|
let dir = resolve9(filePath);
|
|
@@ -68575,7 +69169,7 @@ function findWorkspaceRoot(filePath) {
|
|
|
68575
69169
|
let prevDir = "";
|
|
68576
69170
|
while (dir !== prevDir) {
|
|
68577
69171
|
for (const marker of markers) {
|
|
68578
|
-
if (existsSync50(
|
|
69172
|
+
if (existsSync50(join56(dir, marker))) {
|
|
68579
69173
|
return dir;
|
|
68580
69174
|
}
|
|
68581
69175
|
}
|
|
@@ -81420,20 +82014,20 @@ var CLI_LANGUAGES = [
|
|
|
81420
82014
|
"tsx",
|
|
81421
82015
|
"yaml"
|
|
81422
82016
|
];
|
|
81423
|
-
var
|
|
82017
|
+
var DEFAULT_TIMEOUT_MS2 = 300000;
|
|
81424
82018
|
var DEFAULT_MAX_OUTPUT_BYTES = 1 * 1024 * 1024;
|
|
81425
82019
|
var DEFAULT_MAX_MATCHES = 500;
|
|
81426
82020
|
|
|
81427
82021
|
// src/tools/ast-grep/sg-cli-path.ts
|
|
81428
82022
|
import { existsSync as existsSync52, statSync as statSync11 } from "fs";
|
|
81429
82023
|
import { createRequire as createRequire4 } from "module";
|
|
81430
|
-
import { dirname as dirname17, join as
|
|
82024
|
+
import { dirname as dirname17, join as join58 } from "path";
|
|
81431
82025
|
|
|
81432
82026
|
// src/tools/ast-grep/downloader.ts
|
|
81433
82027
|
import { existsSync as existsSync51 } from "fs";
|
|
81434
82028
|
import { createRequire as createRequire3 } from "module";
|
|
81435
|
-
import { homedir as
|
|
81436
|
-
import { join as
|
|
82029
|
+
import { homedir as homedir9 } from "os";
|
|
82030
|
+
import { join as join57 } from "path";
|
|
81437
82031
|
init_logger();
|
|
81438
82032
|
var REPO2 = "ast-grep/ast-grep";
|
|
81439
82033
|
var DEFAULT_VERSION = "0.40.0";
|
|
@@ -81458,12 +82052,12 @@ var PLATFORM_MAP2 = {
|
|
|
81458
82052
|
function getCacheDir3() {
|
|
81459
82053
|
if (process.platform === "win32") {
|
|
81460
82054
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
81461
|
-
const base2 = localAppData ||
|
|
81462
|
-
return
|
|
82055
|
+
const base2 = localAppData || join57(homedir9(), "AppData", "Local");
|
|
82056
|
+
return join57(base2, "matrixx", "bin");
|
|
81463
82057
|
}
|
|
81464
82058
|
const xdgCache = process.env.XDG_CACHE_HOME;
|
|
81465
|
-
const base = xdgCache ||
|
|
81466
|
-
return
|
|
82059
|
+
const base = xdgCache || join57(homedir9(), ".cache");
|
|
82060
|
+
return join57(base, "matrixx", "bin");
|
|
81467
82061
|
}
|
|
81468
82062
|
function getBinaryName3() {
|
|
81469
82063
|
return process.platform === "win32" ? "sg.exe" : "sg";
|
|
@@ -81480,7 +82074,7 @@ async function downloadAstGrep(version3 = DEFAULT_VERSION) {
|
|
|
81480
82074
|
}
|
|
81481
82075
|
const cacheDir = getCacheDir3();
|
|
81482
82076
|
const binaryName = getBinaryName3();
|
|
81483
|
-
const binaryPath =
|
|
82077
|
+
const binaryPath = join57(cacheDir, binaryName);
|
|
81484
82078
|
if (existsSync51(binaryPath)) {
|
|
81485
82079
|
return binaryPath;
|
|
81486
82080
|
}
|
|
@@ -81489,7 +82083,7 @@ async function downloadAstGrep(version3 = DEFAULT_VERSION) {
|
|
|
81489
82083
|
const downloadUrl = `https://github.com/${REPO2}/releases/download/${version3}/${assetName}`;
|
|
81490
82084
|
log(`[matrixx] Downloading ast-grep binary...`);
|
|
81491
82085
|
try {
|
|
81492
|
-
const archivePath =
|
|
82086
|
+
const archivePath = join57(cacheDir, assetName);
|
|
81493
82087
|
ensureCacheDir(cacheDir);
|
|
81494
82088
|
await downloadArchive(downloadUrl, archivePath);
|
|
81495
82089
|
await extractZipArchive(archivePath, cacheDir);
|
|
@@ -81543,7 +82137,7 @@ function findSgCliPathSync() {
|
|
|
81543
82137
|
const require2 = createRequire4(import.meta.url);
|
|
81544
82138
|
const cliPackageJsonPath = require2.resolve("@ast-grep/cli/package.json");
|
|
81545
82139
|
const cliDirectory = dirname17(cliPackageJsonPath);
|
|
81546
|
-
const sgPath =
|
|
82140
|
+
const sgPath = join58(cliDirectory, binaryName);
|
|
81547
82141
|
if (existsSync52(sgPath) && isValidBinary(sgPath)) {
|
|
81548
82142
|
return sgPath;
|
|
81549
82143
|
}
|
|
@@ -81555,7 +82149,7 @@ function findSgCliPathSync() {
|
|
|
81555
82149
|
const packageJsonPath = require2.resolve(`${platformPackage}/package.json`);
|
|
81556
82150
|
const packageDirectory = dirname17(packageJsonPath);
|
|
81557
82151
|
const astGrepBinaryName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
|
|
81558
|
-
const binaryPath =
|
|
82152
|
+
const binaryPath = join58(packageDirectory, astGrepBinaryName);
|
|
81559
82153
|
if (existsSync52(binaryPath) && isValidBinary(binaryPath)) {
|
|
81560
82154
|
return binaryPath;
|
|
81561
82155
|
}
|
|
@@ -81715,7 +82309,7 @@ async function runSg(options) {
|
|
|
81715
82309
|
};
|
|
81716
82310
|
}
|
|
81717
82311
|
}
|
|
81718
|
-
const timeout =
|
|
82312
|
+
const timeout = DEFAULT_TIMEOUT_MS2;
|
|
81719
82313
|
const proc = spawn9([cliPath, ...args], {
|
|
81720
82314
|
stdout: "pipe",
|
|
81721
82315
|
stderr: "pipe"
|
|
@@ -83818,8 +84412,6 @@ function createBddPipelineTool(opts) {
|
|
|
83818
84412
|
});
|
|
83819
84413
|
}
|
|
83820
84414
|
function makeDeps(args) {
|
|
83821
|
-
const featureName = args.featurePath.split("/").pop()?.replace(/\.feature$/i, "") ?? "feature";
|
|
83822
|
-
const _featureDir = resolvePath2(args.outDir, featureName);
|
|
83823
84415
|
return {
|
|
83824
84416
|
parseFeature: async (path12) => {
|
|
83825
84417
|
const raw = await args.parseTool.execute({ filePath: path12 }, dummyCtx(args));
|
|
@@ -83988,17 +84580,17 @@ var {spawn: spawn10 } = globalThis.Bun;
|
|
|
83988
84580
|
// src/tools/grep/constants.ts
|
|
83989
84581
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
83990
84582
|
import { existsSync as existsSync59 } from "fs";
|
|
83991
|
-
import { dirname as dirname18, join as
|
|
84583
|
+
import { dirname as dirname18, join as join60 } from "path";
|
|
83992
84584
|
|
|
83993
84585
|
// src/tools/grep/downloader.ts
|
|
83994
|
-
import { existsSync as existsSync58, readdirSync as
|
|
83995
|
-
import { join as
|
|
84586
|
+
import { existsSync as existsSync58, readdirSync as readdirSync18 } from "fs";
|
|
84587
|
+
import { join as join59 } from "path";
|
|
83996
84588
|
function findFileRecursive(dir, filename) {
|
|
83997
84589
|
try {
|
|
83998
|
-
const entries =
|
|
84590
|
+
const entries = readdirSync18(dir, { withFileTypes: true, recursive: true });
|
|
83999
84591
|
for (const entry of entries) {
|
|
84000
84592
|
if (entry.isFile() && entry.name === filename) {
|
|
84001
|
-
return
|
|
84593
|
+
return join59(entry.parentPath ?? dir, entry.name);
|
|
84002
84594
|
}
|
|
84003
84595
|
}
|
|
84004
84596
|
} catch {
|
|
@@ -84019,11 +84611,11 @@ function getPlatformKey() {
|
|
|
84019
84611
|
}
|
|
84020
84612
|
function getInstallDir() {
|
|
84021
84613
|
const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
|
|
84022
|
-
return
|
|
84614
|
+
return join59(homeDir, ".cache", "matrixx", "bin");
|
|
84023
84615
|
}
|
|
84024
84616
|
function getRgPath() {
|
|
84025
84617
|
const isWindows = process.platform === "win32";
|
|
84026
|
-
return
|
|
84618
|
+
return join59(getInstallDir(), isWindows ? "rg.exe" : "rg");
|
|
84027
84619
|
}
|
|
84028
84620
|
async function extractTarGz2(archivePath, destDir) {
|
|
84029
84621
|
const platformKey = getPlatformKey();
|
|
@@ -84040,7 +84632,7 @@ async function extractZip2(archivePath, destDir) {
|
|
|
84040
84632
|
const binaryName = process.platform === "win32" ? "rg.exe" : "rg";
|
|
84041
84633
|
const foundPath = findFileRecursive(destDir, binaryName);
|
|
84042
84634
|
if (foundPath) {
|
|
84043
|
-
const destPath =
|
|
84635
|
+
const destPath = join59(destDir, binaryName);
|
|
84044
84636
|
if (foundPath !== destPath) {
|
|
84045
84637
|
const { renameSync: renameSync4 } = await import("fs");
|
|
84046
84638
|
renameSync4(foundPath, destPath);
|
|
@@ -84061,7 +84653,7 @@ async function downloadAndInstallRipgrep() {
|
|
|
84061
84653
|
ensureCacheDir(installDir);
|
|
84062
84654
|
const filename = `ripgrep-${RG_VERSION}-${config4.platform}.${config4.extension}`;
|
|
84063
84655
|
const url3 = `https://github.com/BurntSushi/ripgrep/releases/download/${RG_VERSION}/${filename}`;
|
|
84064
|
-
const archivePath =
|
|
84656
|
+
const archivePath = join59(installDir, filename);
|
|
84065
84657
|
try {
|
|
84066
84658
|
await downloadArchive(url3, archivePath);
|
|
84067
84659
|
if (config4.extension === "tar.gz") {
|
|
@@ -84106,11 +84698,11 @@ function getOpenCodeBundledRg() {
|
|
|
84106
84698
|
const isWindows = process.platform === "win32";
|
|
84107
84699
|
const rgName = isWindows ? "rg.exe" : "rg";
|
|
84108
84700
|
const candidates = [
|
|
84109
|
-
|
|
84110
|
-
|
|
84111
|
-
|
|
84112
|
-
|
|
84113
|
-
|
|
84701
|
+
join60(getDataDir(), "opencode", "bin", rgName),
|
|
84702
|
+
join60(execDir, rgName),
|
|
84703
|
+
join60(execDir, "bin", rgName),
|
|
84704
|
+
join60(execDir, "..", "bin", rgName),
|
|
84705
|
+
join60(execDir, "..", "libexec", rgName)
|
|
84114
84706
|
];
|
|
84115
84707
|
for (const candidate of candidates) {
|
|
84116
84708
|
if (existsSync59(candidate)) {
|
|
@@ -84166,7 +84758,7 @@ var DEFAULT_MAX_DEPTH = 20;
|
|
|
84166
84758
|
var DEFAULT_MAX_FILESIZE = "10M";
|
|
84167
84759
|
var DEFAULT_MAX_COUNT = 500;
|
|
84168
84760
|
var DEFAULT_MAX_COLUMNS = 1000;
|
|
84169
|
-
var
|
|
84761
|
+
var DEFAULT_TIMEOUT_MS3 = 300000;
|
|
84170
84762
|
var DEFAULT_MAX_OUTPUT_BYTES2 = 10 * 1024 * 1024;
|
|
84171
84763
|
var RG_SAFETY_FLAGS = [
|
|
84172
84764
|
"--no-follow",
|
|
@@ -84178,7 +84770,7 @@ var RG_SAFETY_FLAGS = [
|
|
|
84178
84770
|
var GREP_SAFETY_FLAGS = ["-n", "-H", "--color=never"];
|
|
84179
84771
|
|
|
84180
84772
|
// src/tools/glob/constants.ts
|
|
84181
|
-
var
|
|
84773
|
+
var DEFAULT_TIMEOUT_MS4 = 60000;
|
|
84182
84774
|
var DEFAULT_LIMIT = 100;
|
|
84183
84775
|
var DEFAULT_MAX_DEPTH2 = 20;
|
|
84184
84776
|
var DEFAULT_MAX_OUTPUT_BYTES3 = 10 * 1024 * 1024;
|
|
@@ -84241,7 +84833,7 @@ async function getFileMtime(filePath) {
|
|
|
84241
84833
|
}
|
|
84242
84834
|
async function runRgFiles(options, resolvedCli) {
|
|
84243
84835
|
const cli = resolvedCli ?? resolveGrepCli();
|
|
84244
|
-
const timeout = Math.min(options.timeout ??
|
|
84836
|
+
const timeout = Math.min(options.timeout ?? DEFAULT_TIMEOUT_MS4, DEFAULT_TIMEOUT_MS4);
|
|
84245
84837
|
const limit = Math.min(options.limit ?? DEFAULT_LIMIT, DEFAULT_LIMIT);
|
|
84246
84838
|
const isRg = cli.backend === "rg";
|
|
84247
84839
|
const isWindows = process.platform === "win32";
|
|
@@ -84463,7 +85055,7 @@ function parseOutput(output) {
|
|
|
84463
85055
|
async function runRg(options) {
|
|
84464
85056
|
const cli = resolveGrepCli();
|
|
84465
85057
|
const args = buildArgs(options, cli.backend);
|
|
84466
|
-
const timeout = Math.min(options.timeout ??
|
|
85058
|
+
const timeout = Math.min(options.timeout ?? DEFAULT_TIMEOUT_MS3, DEFAULT_TIMEOUT_MS3);
|
|
84467
85059
|
if (cli.backend === "rg") {
|
|
84468
85060
|
args.push("--", options.pattern);
|
|
84469
85061
|
} else {
|
|
@@ -84618,12 +85210,12 @@ var HandoffSchema = exports_external.object({
|
|
|
84618
85210
|
});
|
|
84619
85211
|
// src/features/handoff/storage.ts
|
|
84620
85212
|
import { existsSync as existsSync60, mkdirSync as mkdirSync17, readFileSync as readFileSync39, renameSync as renameSync4, writeFileSync as writeFileSync23 } from "fs";
|
|
84621
|
-
import { join as
|
|
85213
|
+
import { join as join61 } from "path";
|
|
84622
85214
|
function getHandoffFilePath(directory) {
|
|
84623
|
-
return
|
|
85215
|
+
return join61(directory, HANDOFF_FILE_PATH);
|
|
84624
85216
|
}
|
|
84625
85217
|
function getHandoffConsumedFilePath(directory) {
|
|
84626
|
-
return
|
|
85218
|
+
return join61(directory, HANDOFF_CONSUMED_FILE_PATH);
|
|
84627
85219
|
}
|
|
84628
85220
|
function handoffFileExists(directory) {
|
|
84629
85221
|
try {
|
|
@@ -84661,7 +85253,7 @@ function writeHandoffFile(directory, content) {
|
|
|
84661
85253
|
}
|
|
84662
85254
|
function ensureHandoffDir(directory) {
|
|
84663
85255
|
try {
|
|
84664
|
-
mkdirSync17(
|
|
85256
|
+
mkdirSync17(join61(directory, ".matrixx"), { recursive: true });
|
|
84665
85257
|
return true;
|
|
84666
85258
|
} catch {
|
|
84667
85259
|
return false;
|
|
@@ -84816,20 +85408,20 @@ ${body}
|
|
|
84816
85408
|
}
|
|
84817
85409
|
|
|
84818
85410
|
// src/tools/handoff/list-handler.ts
|
|
84819
|
-
import { existsSync as existsSync61, readdirSync as
|
|
84820
|
-
import { join as
|
|
85411
|
+
import { existsSync as existsSync61, readdirSync as readdirSync19, statSync as statSync13 } from "fs";
|
|
85412
|
+
import { join as join62 } from "path";
|
|
84821
85413
|
var MATRIX_DIR = ".matrixx";
|
|
84822
85414
|
function isHandoffFile(name) {
|
|
84823
85415
|
return name === "handoff.md" || name.startsWith("handoff.consumed");
|
|
84824
85416
|
}
|
|
84825
85417
|
function handleList(directory) {
|
|
84826
|
-
const matrixxDir =
|
|
85418
|
+
const matrixxDir = join62(directory, MATRIX_DIR);
|
|
84827
85419
|
if (!existsSync61(matrixxDir)) {
|
|
84828
85420
|
return "No handoffs found in .matrixx/";
|
|
84829
85421
|
}
|
|
84830
85422
|
let entries;
|
|
84831
85423
|
try {
|
|
84832
|
-
entries =
|
|
85424
|
+
entries = readdirSync19(matrixxDir);
|
|
84833
85425
|
} catch {
|
|
84834
85426
|
return "No handoffs found in .matrixx/";
|
|
84835
85427
|
}
|
|
@@ -84841,7 +85433,7 @@ function handleList(directory) {
|
|
|
84841
85433
|
lines.push(`Handoff files in ${matrixxDir}:`);
|
|
84842
85434
|
lines.push("");
|
|
84843
85435
|
for (const name of handoffs) {
|
|
84844
|
-
const fullPath =
|
|
85436
|
+
const fullPath = join62(matrixxDir, name);
|
|
84845
85437
|
let size = "?";
|
|
84846
85438
|
let mtime = "?";
|
|
84847
85439
|
try {
|
|
@@ -84938,7 +85530,7 @@ function createHandoffTools(ctx) {
|
|
|
84938
85530
|
init_logger();
|
|
84939
85531
|
|
|
84940
85532
|
// src/tools/interactive-bash/constants.ts
|
|
84941
|
-
var
|
|
85533
|
+
var DEFAULT_TIMEOUT_MS5 = 60000;
|
|
84942
85534
|
var BLOCKED_TMUX_SUBCOMMANDS = [
|
|
84943
85535
|
"capture-pane",
|
|
84944
85536
|
"capturep",
|
|
@@ -85035,7 +85627,7 @@ The Bash tool can execute these commands directly. Do NOT retry with interactive
|
|
|
85035
85627
|
});
|
|
85036
85628
|
const timeoutPromise = new Promise((_, reject) => {
|
|
85037
85629
|
const id = setTimeout(() => {
|
|
85038
|
-
const timeoutError = new Error(`Timeout after ${
|
|
85630
|
+
const timeoutError = new Error(`Timeout after ${DEFAULT_TIMEOUT_MS5}ms`);
|
|
85039
85631
|
try {
|
|
85040
85632
|
proc.kill();
|
|
85041
85633
|
proc.exited.catch((err) => {
|
|
@@ -85043,7 +85635,7 @@ The Bash tool can execute these commands directly. Do NOT retry with interactive
|
|
|
85043
85635
|
});
|
|
85044
85636
|
} catch {}
|
|
85045
85637
|
reject(timeoutError);
|
|
85046
|
-
},
|
|
85638
|
+
}, DEFAULT_TIMEOUT_MS5);
|
|
85047
85639
|
proc.exited.then(() => clearTimeout(id)).catch(() => clearTimeout(id));
|
|
85048
85640
|
});
|
|
85049
85641
|
const [stdout, stderr, exitCode] = await Promise.race([
|
|
@@ -85067,7 +85659,7 @@ The Bash tool can execute these commands directly. Do NOT retry with interactive
|
|
|
85067
85659
|
// src/tools/pdf-extract-figures/tools.ts
|
|
85068
85660
|
import { existsSync as existsSync62, mkdtempSync, writeFileSync as writeFileSync24 } from "fs";
|
|
85069
85661
|
import { tmpdir as tmpdir5 } from "os";
|
|
85070
|
-
import { join as
|
|
85662
|
+
import { join as join63 } from "path";
|
|
85071
85663
|
var PYTHON_SCRIPT = `#!/usr/bin/env python3
|
|
85072
85664
|
import sys
|
|
85073
85665
|
import json
|
|
@@ -85218,11 +85810,11 @@ async function runExtraction(args) {
|
|
|
85218
85810
|
}
|
|
85219
85811
|
let tmpDir;
|
|
85220
85812
|
try {
|
|
85221
|
-
tmpDir = mkdtempSync(
|
|
85813
|
+
tmpDir = mkdtempSync(join63(tmpdir5(), "matrixx-pdf-extract-"));
|
|
85222
85814
|
} catch {
|
|
85223
85815
|
return "Error: Could not create temporary directory.";
|
|
85224
85816
|
}
|
|
85225
|
-
const scriptPath =
|
|
85817
|
+
const scriptPath = join63(tmpDir, "extract_pdf_figures.py");
|
|
85226
85818
|
writeFileSync24(scriptPath, PYTHON_SCRIPT);
|
|
85227
85819
|
const payload = buildArgsPayload(args);
|
|
85228
85820
|
log("[pdf_extract_figures] Running extraction", { file_path: args.file_path, payload });
|
|
@@ -85270,9 +85862,9 @@ function createPdfExtractFiguresTool() {
|
|
|
85270
85862
|
return { pdf_extract_figures };
|
|
85271
85863
|
}
|
|
85272
85864
|
// src/tools/session-manager/constants.ts
|
|
85273
|
-
import { join as
|
|
85274
|
-
var TODO_DIR =
|
|
85275
|
-
var TRANSCRIPT_DIR2 =
|
|
85865
|
+
import { join as join64 } from "path";
|
|
85866
|
+
var TODO_DIR = join64(getOpenCodeConfigDir({ binary: "opencode" }), "todos");
|
|
85867
|
+
var TRANSCRIPT_DIR2 = join64(getOpenCodeConfigDir({ binary: "opencode" }), "transcripts");
|
|
85276
85868
|
var SESSION_LIST_DESCRIPTION = `List all OpenCode sessions with optional filtering.
|
|
85277
85869
|
|
|
85278
85870
|
Returns a list of available session IDs with metadata including message count, date range, and agents used.
|
|
@@ -85346,7 +85938,7 @@ Has Transcript: Yes (234 entries)`;
|
|
|
85346
85938
|
// src/tools/session-manager/storage.ts
|
|
85347
85939
|
import { existsSync as existsSync63 } from "fs";
|
|
85348
85940
|
import { readdir, readFile as readFile4 } from "fs/promises";
|
|
85349
|
-
import { join as
|
|
85941
|
+
import { join as join65 } from "path";
|
|
85350
85942
|
var sdkClient = null;
|
|
85351
85943
|
function setStorageClient(client2) {
|
|
85352
85944
|
sdkClient = client2;
|
|
@@ -85373,13 +85965,13 @@ async function getMainSessions(options) {
|
|
|
85373
85965
|
for (const projectDir of projectDirs) {
|
|
85374
85966
|
if (!projectDir.isDirectory())
|
|
85375
85967
|
continue;
|
|
85376
|
-
const projectPath =
|
|
85968
|
+
const projectPath = join65(SESSION_STORAGE, projectDir.name);
|
|
85377
85969
|
const sessionFiles = await readdir(projectPath);
|
|
85378
85970
|
for (const file3 of sessionFiles) {
|
|
85379
85971
|
if (!file3.endsWith(".json"))
|
|
85380
85972
|
continue;
|
|
85381
85973
|
try {
|
|
85382
|
-
const content = await readFile4(
|
|
85974
|
+
const content = await readFile4(join65(projectPath, file3), "utf-8");
|
|
85383
85975
|
const meta3 = JSON.parse(content);
|
|
85384
85976
|
if (meta3.parentID)
|
|
85385
85977
|
continue;
|
|
@@ -85412,7 +86004,7 @@ async function getAllSessions() {
|
|
|
85412
86004
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
85413
86005
|
for (const entry of entries) {
|
|
85414
86006
|
if (entry.isDirectory()) {
|
|
85415
|
-
const sessionPath =
|
|
86007
|
+
const sessionPath = join65(dir, entry.name);
|
|
85416
86008
|
const files = await readdir(sessionPath);
|
|
85417
86009
|
if (files.some((f) => f.endsWith(".json"))) {
|
|
85418
86010
|
sessions.push(entry.name);
|
|
@@ -85482,7 +86074,7 @@ async function readSessionMessages(sessionID) {
|
|
|
85482
86074
|
if (!file3.endsWith(".json"))
|
|
85483
86075
|
continue;
|
|
85484
86076
|
try {
|
|
85485
|
-
const content = await readFile4(
|
|
86077
|
+
const content = await readFile4(join65(messageDir, file3), "utf-8");
|
|
85486
86078
|
const meta3 = JSON.parse(content);
|
|
85487
86079
|
const parts = await readParts2(meta3.id);
|
|
85488
86080
|
messages2.push({
|
|
@@ -85506,7 +86098,7 @@ async function readSessionMessages(sessionID) {
|
|
|
85506
86098
|
});
|
|
85507
86099
|
}
|
|
85508
86100
|
async function readParts2(messageID) {
|
|
85509
|
-
const partDir =
|
|
86101
|
+
const partDir = join65(PART_STORAGE, messageID);
|
|
85510
86102
|
if (!existsSync63(partDir))
|
|
85511
86103
|
return [];
|
|
85512
86104
|
const parts = [];
|
|
@@ -85516,7 +86108,7 @@ async function readParts2(messageID) {
|
|
|
85516
86108
|
if (!file3.endsWith(".json"))
|
|
85517
86109
|
continue;
|
|
85518
86110
|
try {
|
|
85519
|
-
const content = await readFile4(
|
|
86111
|
+
const content = await readFile4(join65(partDir, file3), "utf-8");
|
|
85520
86112
|
parts.push(JSON.parse(content));
|
|
85521
86113
|
} catch {}
|
|
85522
86114
|
}
|
|
@@ -85547,7 +86139,7 @@ async function readSessionTodos(sessionID) {
|
|
|
85547
86139
|
const todoFiles = allFiles.filter((f) => f.includes(sessionID) && f.endsWith(".json"));
|
|
85548
86140
|
for (const file3 of todoFiles) {
|
|
85549
86141
|
try {
|
|
85550
|
-
const content = await readFile4(
|
|
86142
|
+
const content = await readFile4(join65(TODO_DIR, file3), "utf-8");
|
|
85551
86143
|
const data = JSON.parse(content);
|
|
85552
86144
|
if (Array.isArray(data)) {
|
|
85553
86145
|
return data.map((item) => ({
|
|
@@ -85567,7 +86159,7 @@ async function readSessionTodos(sessionID) {
|
|
|
85567
86159
|
async function readSessionTranscript(sessionID) {
|
|
85568
86160
|
if (!existsSync63(TRANSCRIPT_DIR2))
|
|
85569
86161
|
return 0;
|
|
85570
|
-
const transcriptFile =
|
|
86162
|
+
const transcriptFile = join65(TRANSCRIPT_DIR2, `${sessionID}.jsonl`);
|
|
85571
86163
|
if (!existsSync63(transcriptFile))
|
|
85572
86164
|
return 0;
|
|
85573
86165
|
try {
|
|
@@ -86078,17 +86670,17 @@ function createSkillTool(options = {}) {
|
|
|
86078
86670
|
}
|
|
86079
86671
|
var skill = createSkillTool();
|
|
86080
86672
|
// src/tools/slashcommand/command-discovery.ts
|
|
86081
|
-
import { existsSync as existsSync64, readdirSync as
|
|
86082
|
-
import { basename as basename6, join as
|
|
86673
|
+
import { existsSync as existsSync64, readdirSync as readdirSync20, readFileSync as readFileSync40 } from "fs";
|
|
86674
|
+
import { basename as basename6, join as join66 } from "path";
|
|
86083
86675
|
function discoverCommandsFromDir2(commandsDir, scope) {
|
|
86084
86676
|
if (!existsSync64(commandsDir))
|
|
86085
86677
|
return [];
|
|
86086
|
-
const entries =
|
|
86678
|
+
const entries = readdirSync20(commandsDir, { withFileTypes: true });
|
|
86087
86679
|
const commands2 = [];
|
|
86088
86680
|
for (const entry of entries) {
|
|
86089
86681
|
if (!isMarkdownFile(entry))
|
|
86090
86682
|
continue;
|
|
86091
|
-
const commandPath =
|
|
86683
|
+
const commandPath = join66(commandsDir, entry.name);
|
|
86092
86684
|
const commandName = basename6(entry.name, ".md");
|
|
86093
86685
|
try {
|
|
86094
86686
|
const content = readFileSync40(commandPath, "utf-8");
|
|
@@ -86115,8 +86707,8 @@ function discoverCommandsFromDir2(commandsDir, scope) {
|
|
|
86115
86707
|
}
|
|
86116
86708
|
function discoverCommandsSync(directory) {
|
|
86117
86709
|
const configDir = getOpenCodeConfigDir({ binary: "opencode" });
|
|
86118
|
-
const opencodeGlobalDir =
|
|
86119
|
-
const opencodeProjectDir =
|
|
86710
|
+
const opencodeGlobalDir = join66(configDir, "command");
|
|
86711
|
+
const opencodeProjectDir = join66(directory ?? process.cwd(), ".opencode", "command");
|
|
86120
86712
|
const opencodeGlobalCommands = discoverCommandsFromDir2(opencodeGlobalDir, "opencode");
|
|
86121
86713
|
const opencodeProjectCommands = discoverCommandsFromDir2(opencodeProjectDir, "opencode-project");
|
|
86122
86714
|
const builtinCommandsMap = loadBuiltinCommands();
|
|
@@ -87287,8 +87879,8 @@ function createAssemblyTool(options) {
|
|
|
87287
87879
|
}
|
|
87288
87880
|
// src/tools/dcp-switch-profile/tools.ts
|
|
87289
87881
|
import { existsSync as existsSync65, writeFileSync as writeFileSync25 } from "fs";
|
|
87290
|
-
import { homedir as
|
|
87291
|
-
import { join as
|
|
87882
|
+
import { homedir as homedir10 } from "os";
|
|
87883
|
+
import { join as join67 } from "path";
|
|
87292
87884
|
|
|
87293
87885
|
// src/config/schema/dcp.ts
|
|
87294
87886
|
var DcpCompressOverrideSchema = exports_external.object({
|
|
@@ -87461,8 +88053,8 @@ var DcpConfigSchema = exports_external.object({
|
|
|
87461
88053
|
});
|
|
87462
88054
|
|
|
87463
88055
|
// src/tools/dcp-switch-profile/tools.ts
|
|
87464
|
-
var DCP_PLUGIN_DIR =
|
|
87465
|
-
var DCP_SYMLINK =
|
|
88056
|
+
var DCP_PLUGIN_DIR = join67(homedir10(), ".config", "opencode", "node_modules", "@tarquinen", "opencode-dcp");
|
|
88057
|
+
var DCP_SYMLINK = join67(homedir10(), ".config", "opencode", "dcp.jsonc");
|
|
87466
88058
|
var VALID_PROFILES = ["economy", "balanced", "performance", "ultimate"];
|
|
87467
88059
|
function checkDcpInstalled() {
|
|
87468
88060
|
if (!existsSync65(DCP_PLUGIN_DIR)) {
|
|
@@ -87871,6 +88463,11 @@ function createDelegateAgent(ctx, backgroundManager, disabledAgents = []) {
|
|
|
87871
88463
|
}
|
|
87872
88464
|
const normalizedAgent = args.subagent_type.toLowerCase();
|
|
87873
88465
|
args = { ...args, subagent_type: normalizedAgent };
|
|
88466
|
+
const callerAgent = (toolCtx.agent || "").toLowerCase();
|
|
88467
|
+
const MOUSE_RESEARCH_ONLY = ["trinity", "operator"];
|
|
88468
|
+
if (callerAgent === "mouse" && !MOUSE_RESEARCH_ONLY.includes(normalizedAgent)) {
|
|
88469
|
+
return `Error: Mouse is a leaf executor. delegate_agent with subagent_type="${normalizedAgent}" is BLOCKED. Only trinity/operator allowed for research. Plan files (.matrixx/plans/*.md) must be created via task(subagent_type="oracle") from Morpheus, never via Mouse \u2192 Oracle. Route plan generation directly from Morpheus to oracle.`;
|
|
88470
|
+
}
|
|
87874
88471
|
if (disabledAgents.some((disabled) => disabled.toLowerCase() === normalizedAgent)) {
|
|
87875
88472
|
return `Error: Agent "${normalizedAgent}" is disabled via disabled_agents configuration. Remove it from disabled_agents in your matrixx.json to use it.`;
|
|
87876
88473
|
}
|
|
@@ -91338,6 +91935,109 @@ Original error: ${createResult.error}`;
|
|
|
91338
91935
|
}
|
|
91339
91936
|
});
|
|
91340
91937
|
}
|
|
91938
|
+
// src/tools/task/task-cleanup.ts
|
|
91939
|
+
import { existsSync as existsSync66, readdirSync as readdirSync21, readFileSync as readFileSync41, unlinkSync as unlinkSync10 } from "fs";
|
|
91940
|
+
import { join as join68 } from "path";
|
|
91941
|
+
init_logger();
|
|
91942
|
+
function parseOlderThan(value) {
|
|
91943
|
+
const match = value.trim().match(/^(\d+)(d|h|m)$/);
|
|
91944
|
+
if (!match)
|
|
91945
|
+
return null;
|
|
91946
|
+
const amount = Number(match[1]);
|
|
91947
|
+
const unit = match[2];
|
|
91948
|
+
if (unit === "d")
|
|
91949
|
+
return amount * 24 * 60 * 60 * 1000;
|
|
91950
|
+
if (unit === "h")
|
|
91951
|
+
return amount * 60 * 60 * 1000;
|
|
91952
|
+
return amount * 60 * 1000;
|
|
91953
|
+
}
|
|
91954
|
+
function getTaskTimestamp(task) {
|
|
91955
|
+
const raw = task;
|
|
91956
|
+
const candidate = raw.time_updated ?? raw.time_created ?? raw.updatedAt ?? raw.createdAt ?? raw.timeUpdated ?? raw.timeCreated ?? null;
|
|
91957
|
+
if (candidate === null || candidate === undefined)
|
|
91958
|
+
return Date.now();
|
|
91959
|
+
if (typeof candidate === "number")
|
|
91960
|
+
return candidate;
|
|
91961
|
+
if (typeof candidate === "string") {
|
|
91962
|
+
const parsed = Date.parse(candidate);
|
|
91963
|
+
if (!Number.isNaN(parsed))
|
|
91964
|
+
return parsed;
|
|
91965
|
+
const asNum = Number(candidate);
|
|
91966
|
+
if (!Number.isNaN(asNum))
|
|
91967
|
+
return asNum;
|
|
91968
|
+
return Date.now();
|
|
91969
|
+
}
|
|
91970
|
+
return Date.now();
|
|
91971
|
+
}
|
|
91972
|
+
function createTaskCleanupTool(config4, ctx) {
|
|
91973
|
+
return tool({
|
|
91974
|
+
description: `Delete completed task files from storage.
|
|
91975
|
+
|
|
91976
|
+
Scans getTaskDir()/*.json, filters status==="completed" and optionally olderThan.
|
|
91977
|
+
olderThan supports "7d", "24h", "30m" format (regex ^(\\d+)(d|h|m)$).
|
|
91978
|
+
Returns counts: {deleted, remaining, deletedIds}`,
|
|
91979
|
+
args: {
|
|
91980
|
+
olderThan: tool.schema.string().optional().describe('Only delete completed tasks older than duration (e.g. "7d", "24h", "30m")'),
|
|
91981
|
+
all: tool.schema.boolean().optional().describe("Delete all completed tasks (default true when olderThan not set)")
|
|
91982
|
+
},
|
|
91983
|
+
execute: async (args, context) => {
|
|
91984
|
+
try {
|
|
91985
|
+
const olderThan = args.olderThan;
|
|
91986
|
+
let olderThanMs = null;
|
|
91987
|
+
if (olderThan !== undefined) {
|
|
91988
|
+
const parsed = parseOlderThan(olderThan);
|
|
91989
|
+
if (parsed === null) {
|
|
91990
|
+
return JSON.stringify({
|
|
91991
|
+
error: "validation_error",
|
|
91992
|
+
message: `Invalid olderThan format: "${olderThan}". Expected e.g. "7d", "24h", "30m"`
|
|
91993
|
+
});
|
|
91994
|
+
}
|
|
91995
|
+
olderThanMs = parsed;
|
|
91996
|
+
}
|
|
91997
|
+
const directory = context?.directory ?? ctx?.directory ?? process.cwd();
|
|
91998
|
+
const taskDir = getTaskDir(config4, directory);
|
|
91999
|
+
if (!existsSync66(taskDir)) {
|
|
92000
|
+
return JSON.stringify({ deleted: 0, remaining: 0, deletedIds: [] });
|
|
92001
|
+
}
|
|
92002
|
+
const files = readdirSync21(taskDir).filter((f) => f.endsWith(".json") && f.startsWith("T-"));
|
|
92003
|
+
const total = files.length;
|
|
92004
|
+
const deletedIds = [];
|
|
92005
|
+
for (const file3 of files) {
|
|
92006
|
+
const filePath = join68(taskDir, file3);
|
|
92007
|
+
let raw;
|
|
92008
|
+
try {
|
|
92009
|
+
const content = readFileSync41(filePath, "utf-8");
|
|
92010
|
+
raw = JSON.parse(content);
|
|
92011
|
+
} catch {
|
|
92012
|
+
continue;
|
|
92013
|
+
}
|
|
92014
|
+
if (raw.status !== "completed")
|
|
92015
|
+
continue;
|
|
92016
|
+
const id = typeof raw.id === "string" ? raw.id : file3.replace(".json", "");
|
|
92017
|
+
if (olderThanMs !== null) {
|
|
92018
|
+
const ts = getTaskTimestamp(raw);
|
|
92019
|
+
const age = Date.now() - ts;
|
|
92020
|
+
if (age <= olderThanMs)
|
|
92021
|
+
continue;
|
|
92022
|
+
}
|
|
92023
|
+
try {
|
|
92024
|
+
unlinkSync10(filePath);
|
|
92025
|
+
deletedIds.push(id);
|
|
92026
|
+
} catch (err) {
|
|
92027
|
+
log("[task-cleanup] Failed to delete", { file: file3, error: String(err) });
|
|
92028
|
+
}
|
|
92029
|
+
}
|
|
92030
|
+
return JSON.stringify({
|
|
92031
|
+
deleted: deletedIds.length,
|
|
92032
|
+
remaining: total - deletedIds.length,
|
|
92033
|
+
deletedIds
|
|
92034
|
+
});
|
|
92035
|
+
} catch (error95) {
|
|
92036
|
+
return JSON.stringify({ error: "internal_error", message: error95 instanceof Error ? error95.message : String(error95) });
|
|
92037
|
+
}
|
|
92038
|
+
}
|
|
92039
|
+
});
|
|
92040
|
+
}
|
|
91341
92041
|
// src/tools/task/task-create.ts
|
|
91342
92042
|
import { join as join69 } from "path";
|
|
91343
92043
|
function createTaskCreateTool(config4, ctx) {
|
|
@@ -91371,8 +92071,12 @@ Calculate dependencies carefully to maximize parallel execution:
|
|
|
91371
92071
|
async function handleCreate2(args, config4, ctx, context) {
|
|
91372
92072
|
try {
|
|
91373
92073
|
const validatedArgs = TaskCreateInputSchema.parse(args);
|
|
91374
|
-
const
|
|
91375
|
-
|
|
92074
|
+
const directory = context?.directory ?? ctx?.directory ?? process.cwd();
|
|
92075
|
+
try {
|
|
92076
|
+
migrateLegacyTasksIfNeeded(config4, directory);
|
|
92077
|
+
} catch {}
|
|
92078
|
+
const taskDir = getTaskDir(config4, directory);
|
|
92079
|
+
const lock = await acquireLockWithRetry(taskDir);
|
|
91376
92080
|
if (!lock.acquired) {
|
|
91377
92081
|
return JSON.stringify({ error: "task_lock_unavailable" });
|
|
91378
92082
|
}
|
|
@@ -91389,11 +92093,11 @@ async function handleCreate2(args, config4, ctx, context) {
|
|
|
91389
92093
|
metadata: validatedArgs.metadata,
|
|
91390
92094
|
repoURL: validatedArgs.repoURL,
|
|
91391
92095
|
parentID: validatedArgs.parentID,
|
|
91392
|
-
threadID: context.sessionID
|
|
92096
|
+
threadID: context.sessionID,
|
|
92097
|
+
projectRoot: directory
|
|
91393
92098
|
};
|
|
91394
92099
|
const validatedTask = TaskObjectSchema.parse(task);
|
|
91395
92100
|
writeJsonAtomic(join69(taskDir, `${taskId}.json`), validatedTask);
|
|
91396
|
-
await syncTaskTodoUpdate(ctx, validatedTask, context.sessionID);
|
|
91397
92101
|
return JSON.stringify({
|
|
91398
92102
|
task: {
|
|
91399
92103
|
id: validatedTask.id,
|
|
@@ -91425,7 +92129,7 @@ function parseTaskId(id) {
|
|
|
91425
92129
|
return null;
|
|
91426
92130
|
return id;
|
|
91427
92131
|
}
|
|
91428
|
-
function createTaskGetTool(config4) {
|
|
92132
|
+
function createTaskGetTool(config4, ctx) {
|
|
91429
92133
|
return tool({
|
|
91430
92134
|
description: `Retrieve a task by ID.
|
|
91431
92135
|
|
|
@@ -91435,14 +92139,15 @@ Returns null if the task does not exist or the file is invalid.`,
|
|
|
91435
92139
|
args: {
|
|
91436
92140
|
id: tool.schema.string().describe("Task ID to retrieve (format: T-{uuid})")
|
|
91437
92141
|
},
|
|
91438
|
-
execute: async (args) => {
|
|
92142
|
+
execute: async (args, context) => {
|
|
91439
92143
|
try {
|
|
91440
92144
|
const validatedArgs = TaskGetInputSchema.parse(args);
|
|
91441
92145
|
const taskId = parseTaskId(validatedArgs.id);
|
|
91442
92146
|
if (!taskId) {
|
|
91443
92147
|
return JSON.stringify({ error: "invalid_task_id" });
|
|
91444
92148
|
}
|
|
91445
|
-
const
|
|
92149
|
+
const directory = context?.directory ?? ctx?.directory ?? process.cwd();
|
|
92150
|
+
const taskDir = getTaskDir(config4, directory);
|
|
91446
92151
|
const taskPath = join70(taskDir, `${taskId}.json`);
|
|
91447
92152
|
const task = readJsonSafe(taskPath, TaskObjectSchema);
|
|
91448
92153
|
return JSON.stringify({ task: task ?? null });
|
|
@@ -91456,9 +92161,9 @@ Returns null if the task does not exist or the file is invalid.`,
|
|
|
91456
92161
|
});
|
|
91457
92162
|
}
|
|
91458
92163
|
// src/tools/task/task-list.ts
|
|
91459
|
-
import { existsSync as
|
|
92164
|
+
import { existsSync as existsSync67, readdirSync as readdirSync22 } from "fs";
|
|
91460
92165
|
import { join as join71 } from "path";
|
|
91461
|
-
function createTaskList(config4) {
|
|
92166
|
+
function createTaskList(config4, ctx) {
|
|
91462
92167
|
return tool({
|
|
91463
92168
|
description: `List all active tasks with summary information.
|
|
91464
92169
|
|
|
@@ -91466,12 +92171,13 @@ Returns tasks excluding completed and deleted statuses by default.
|
|
|
91466
92171
|
For each task's blockedBy field, filters to only include unresolved (non-completed) blockers.
|
|
91467
92172
|
Returns summary format: id, subject, status, owner, blockedBy (not full description).`,
|
|
91468
92173
|
args: {},
|
|
91469
|
-
execute: async () => {
|
|
91470
|
-
const
|
|
91471
|
-
|
|
92174
|
+
execute: async (_args, context) => {
|
|
92175
|
+
const directory = context?.directory ?? ctx?.directory ?? process.cwd();
|
|
92176
|
+
const taskDir = getTaskDir(config4, directory);
|
|
92177
|
+
if (!existsSync67(taskDir)) {
|
|
91472
92178
|
return JSON.stringify({ tasks: [] });
|
|
91473
92179
|
}
|
|
91474
|
-
const files =
|
|
92180
|
+
const files = readdirSync22(taskDir).filter((f) => f.endsWith(".json") && f.startsWith("T-")).map((f) => f.replace(".json", ""));
|
|
91475
92181
|
if (files.length === 0) {
|
|
91476
92182
|
return JSON.stringify({ tasks: [] });
|
|
91477
92183
|
}
|
|
@@ -91516,8 +92222,6 @@ function createTaskUpdateTool(config4, ctx) {
|
|
|
91516
92222
|
|
|
91517
92223
|
Supports updating: subject, description, status, activeForm, owner, metadata.
|
|
91518
92224
|
For blocks/blockedBy: use addBlocks/addBlockedBy to append (additive, not replacement).
|
|
91519
|
-
For metadata: merge with existing, set key to null to delete.
|
|
91520
|
-
Syncs to OpenCode Todo API after update.
|
|
91521
92225
|
|
|
91522
92226
|
**IMPORTANT - Dependency Management:**
|
|
91523
92227
|
Use \`addBlockedBy\` to declare dependencies on other tasks.
|
|
@@ -91545,8 +92249,9 @@ async function handleUpdate(args, config4, ctx, context) {
|
|
|
91545
92249
|
if (!taskId) {
|
|
91546
92250
|
return JSON.stringify({ error: "invalid_task_id" });
|
|
91547
92251
|
}
|
|
91548
|
-
const
|
|
91549
|
-
const
|
|
92252
|
+
const directory = context?.directory ?? ctx?.directory ?? process.cwd();
|
|
92253
|
+
const taskDir = getTaskDir(config4, directory);
|
|
92254
|
+
const lock = await acquireLockWithRetry(taskDir);
|
|
91550
92255
|
if (!lock.acquired) {
|
|
91551
92256
|
return JSON.stringify({ error: "task_lock_unavailable" });
|
|
91552
92257
|
}
|
|
@@ -91589,7 +92294,6 @@ async function handleUpdate(args, config4, ctx, context) {
|
|
|
91589
92294
|
}
|
|
91590
92295
|
const validatedTask = TaskObjectSchema.parse(task);
|
|
91591
92296
|
writeJsonAtomic(taskPath, validatedTask);
|
|
91592
|
-
await syncTaskTodoUpdate(ctx, validatedTask, context.sessionID);
|
|
91593
92297
|
return JSON.stringify({ task: validatedTask });
|
|
91594
92298
|
} finally {
|
|
91595
92299
|
lock.release();
|
|
@@ -91722,14 +92426,14 @@ function createToolGuardHooks(args) {
|
|
|
91722
92426
|
const envFileWriteGuard = isHookEnabled("env-file-write-guard") ? safeHook("env-file-write-guard", () => createEnvFileWriteGuardHook(pluginConfig.security?.env_file_guard)) : null;
|
|
91723
92427
|
const jsonErrorRecovery = isHookEnabled("json-error-recovery") ? safeHook("json-error-recovery", () => createJsonErrorRecoveryHook(ctx)) : null;
|
|
91724
92428
|
const bashFileReadGuard = isHookEnabled("bash-file-read-guard") ? safeHook("bash-file-read-guard", () => createBashFileReadGuardHook()) : null;
|
|
91725
|
-
const
|
|
92429
|
+
const contextModeEnforcer = isHookEnabled("context-mode-enforcer") ? safeHook("context-mode-enforcer", () => createContextModeEnforcerHook(pluginConfig)) : null;
|
|
91726
92430
|
const readImageResizer = isHookEnabled("read-image-resizer") ? safeHook("read-image-resizer", () => createReadImageResizerHook(ctx)) : null;
|
|
91727
92431
|
const webfetchRedirectGuard = isHookEnabled("webfetch-redirect-guard") ? safeHook("webfetch-redirect-guard", () => createWebFetchRedirectGuardHook(ctx)) : null;
|
|
91728
92432
|
const hashlineEditDiffEnhancer = isHookEnabled("hashline-edit-diff-enhancer") ? safeHook("hashline-edit-diff-enhancer", () => createHashlineEditDiffEnhancerHook({ hashline_edit: { enabled: pluginConfig.experimental?.hashline_edit ?? false } })) : null;
|
|
91729
92433
|
const qualityGate = isHookEnabled("quality-gate") ? safeHook("quality-gate", () => createQualityGateHook()) : null;
|
|
91730
92434
|
const taskNotepad = isHookEnabled("task-notepad") ? safeHook("task-notepad", () => createTaskNotepadHook(ctx)) : null;
|
|
92435
|
+
const taskEditGuard = isHookEnabled("task-edit-guard") ? safeHook("task-edit-guard", () => createTaskEditGuardHook(ctx)) : null;
|
|
91731
92436
|
const evolutionWatcher = isHookEnabled("evolution-watcher") ? safeHook("evolution-watcher", () => createEvolutionWatcherHook(ctx, pluginConfig.evolution)) : null;
|
|
91732
|
-
const taskTodoMirror = isHookEnabled("task-todo-mirror") ? safeHook("task-todo-mirror", () => createTaskTodoMirrorHook(ctx, pluginConfig)) : null;
|
|
91733
92437
|
return {
|
|
91734
92438
|
commentChecker,
|
|
91735
92439
|
toolOutputTruncator,
|
|
@@ -91744,14 +92448,14 @@ function createToolGuardHooks(args) {
|
|
|
91744
92448
|
envFileWriteGuard,
|
|
91745
92449
|
jsonErrorRecovery,
|
|
91746
92450
|
bashFileReadGuard,
|
|
91747
|
-
|
|
92451
|
+
contextModeEnforcer,
|
|
91748
92452
|
readImageResizer,
|
|
91749
92453
|
webfetchRedirectGuard,
|
|
91750
92454
|
hashlineEditDiffEnhancer,
|
|
91751
92455
|
qualityGate,
|
|
91752
92456
|
taskNotepad,
|
|
91753
|
-
|
|
91754
|
-
|
|
92457
|
+
taskEditGuard,
|
|
92458
|
+
evolutionWatcher
|
|
91755
92459
|
};
|
|
91756
92460
|
}
|
|
91757
92461
|
|
|
@@ -92205,19 +92909,19 @@ function detectRepetitiveToolUse(window2) {
|
|
|
92205
92909
|
}
|
|
92206
92910
|
|
|
92207
92911
|
// src/features/background-agent/message-dir.ts
|
|
92208
|
-
import { existsSync as
|
|
92912
|
+
import { existsSync as existsSync68, readdirSync as readdirSync23, readFileSync as readFileSync42 } from "fs";
|
|
92209
92913
|
import { join as join73 } from "path";
|
|
92210
92914
|
var MESSAGE_DIR_CACHE_CAP = 1000;
|
|
92211
92915
|
var messageDirCache = new Map;
|
|
92212
92916
|
function computeMessageDir(sessionID) {
|
|
92213
|
-
if (!
|
|
92917
|
+
if (!existsSync68(MESSAGE_STORAGE))
|
|
92214
92918
|
return null;
|
|
92215
92919
|
const directPath = join73(MESSAGE_STORAGE, sessionID);
|
|
92216
|
-
if (
|
|
92920
|
+
if (existsSync68(directPath))
|
|
92217
92921
|
return directPath;
|
|
92218
|
-
for (const dir of
|
|
92922
|
+
for (const dir of readdirSync23(MESSAGE_STORAGE)) {
|
|
92219
92923
|
const sessionPath = join73(MESSAGE_STORAGE, dir, sessionID);
|
|
92220
|
-
if (
|
|
92924
|
+
if (existsSync68(sessionPath))
|
|
92221
92925
|
return sessionPath;
|
|
92222
92926
|
}
|
|
92223
92927
|
return null;
|
|
@@ -92251,11 +92955,11 @@ function hasPartialAgentOrModel(message) {
|
|
|
92251
92955
|
}
|
|
92252
92956
|
function findNearestMessageExcludingCompaction(messageDir) {
|
|
92253
92957
|
try {
|
|
92254
|
-
const files =
|
|
92958
|
+
const files = readdirSync23(messageDir).filter((name) => name.endsWith(".json")).sort().reverse();
|
|
92255
92959
|
const parsedByFile = new Map;
|
|
92256
92960
|
for (const file3 of files) {
|
|
92257
92961
|
try {
|
|
92258
|
-
const content =
|
|
92962
|
+
const content = readFileSync42(join73(messageDir, file3), "utf-8");
|
|
92259
92963
|
const parsed = JSON.parse(content);
|
|
92260
92964
|
parsedByFile.set(file3, parsed);
|
|
92261
92965
|
if (hasFullAgentAndModel(parsed)) {
|
|
@@ -94695,6 +95399,12 @@ ${useWhen.map((w) => `| ${w} | Oracle FIRST, then implement |`).join(`
|
|
|
94695
95399
|
${avoidWhen.map((w) => `- ${w}`).join(`
|
|
94696
95400
|
`)}
|
|
94697
95401
|
|
|
95402
|
+
### HARD ROUTING RULE \u2014 Plan File Creation (NON-NEGOTIABLE):
|
|
95403
|
+
- ANY task that creates or edits \`.matrixx/plans/*.md\` MUST use \`task(subagent_type='oracle')\` \u2014 NEVER \`task(category='...')\` / Mouse
|
|
95404
|
+
- ANY prompt containing \`Oracle plan\`, \`work plan\`, \`proposal\`, \`TL;DR\`, \`root cause\`, \`pure-host\`, \`fix options\` or similar planning language MUST route to \`oracle\`
|
|
95405
|
+
- Mouse is a leaf executor and CANNOT produce plans. Oracle owns all plan generation. Mouse \u2192 Oracle is a violation (extra hop, diluted context).
|
|
95406
|
+
- If you already created tasks for research + drafting, the drafting task MUST still be \`oracle\`, not \`mouse\`.
|
|
95407
|
+
|
|
94698
95408
|
### Usage Pattern:
|
|
94699
95409
|
Oracle creates plans, not code. Invoke oracle to decompose complex requests into structured work plans with clear steps, then execute the plan yourself or delegate to workers.
|
|
94700
95410
|
|
|
@@ -94772,14 +95482,14 @@ function buildCompactContextDisciplineSection(hasContextMode = false) {
|
|
|
94772
95482
|
| Docs / Web | ctx_fetch_and_index -> ctx_search |
|
|
94773
95483
|
| Compression | compress when ctx_stats > 40% or 10+ tool calls |
|
|
94774
95484
|
|
|
94775
|
-
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.`;
|
|
94776
95486
|
}
|
|
94777
95487
|
function buildExploreDisciplineSection(hasContextMode = false, hasHeadroom = false) {
|
|
94778
95488
|
if (!hasContextMode && !hasHeadroom)
|
|
94779
95489
|
return "";
|
|
94780
95490
|
const parts = [];
|
|
94781
95491
|
if (hasContextMode) {
|
|
94782
|
-
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.");
|
|
94783
95493
|
}
|
|
94784
95494
|
if (hasHeadroom) {
|
|
94785
95495
|
parts.push("Use headroom_retrieve / headroom_search for compressed history \u2014 NEVER re-read full history.");
|
|
@@ -95823,8 +96533,8 @@ function createBddContractAgent(model) {
|
|
|
95823
96533
|
createBddContractAgent.mode = MODE2;
|
|
95824
96534
|
|
|
95825
96535
|
// src/agents/builtin-agents/resolve-file-uri.ts
|
|
95826
|
-
import { existsSync as
|
|
95827
|
-
import { homedir as
|
|
96536
|
+
import { existsSync as existsSync69, readFileSync as readFileSync43 } from "fs";
|
|
96537
|
+
import { homedir as homedir11 } from "os";
|
|
95828
96538
|
import { isAbsolute as isAbsolute5, resolve as resolve10 } from "path";
|
|
95829
96539
|
function resolvePromptAppend(promptAppend, configDir) {
|
|
95830
96540
|
if (!promptAppend.startsWith("file://"))
|
|
@@ -95833,16 +96543,16 @@ function resolvePromptAppend(promptAppend, configDir) {
|
|
|
95833
96543
|
let filePath;
|
|
95834
96544
|
try {
|
|
95835
96545
|
const decoded = decodeURIComponent(encoded);
|
|
95836
|
-
const expanded = decoded.startsWith("~/") ? decoded.replace(/^~\//, `${
|
|
96546
|
+
const expanded = decoded.startsWith("~/") ? decoded.replace(/^~\//, `${homedir11()}/`) : decoded;
|
|
95837
96547
|
filePath = isAbsolute5(expanded) ? expanded : resolve10(configDir ?? process.cwd(), expanded);
|
|
95838
96548
|
} catch {
|
|
95839
96549
|
return `[WARNING: Malformed file URI (invalid percent-encoding): ${promptAppend}]`;
|
|
95840
96550
|
}
|
|
95841
|
-
if (!
|
|
96551
|
+
if (!existsSync69(filePath)) {
|
|
95842
96552
|
return `[WARNING: Could not resolve file URI: ${promptAppend}]`;
|
|
95843
96553
|
}
|
|
95844
96554
|
try {
|
|
95845
|
-
return
|
|
96555
|
+
return readFileSync43(filePath, "utf8");
|
|
95846
96556
|
} catch {
|
|
95847
96557
|
return `[WARNING: Could not read file: ${promptAppend}]`;
|
|
95848
96558
|
}
|
|
@@ -97557,7 +98267,8 @@ var ORACLE_PLAN_BUILDER_METADATA = {
|
|
|
97557
98267
|
{ domain: "Architecture decisions", trigger: "Multi-system tradeoffs, unfamiliar patterns" },
|
|
97558
98268
|
{ domain: "Work planning", trigger: "Complex feature implementation, refactoring" },
|
|
97559
98269
|
{ domain: "Complex task breakdown", trigger: "Large ambiguous requests needing decomposition" },
|
|
97560
|
-
{ domain: "Self-review", trigger: "After completing significant implementation" }
|
|
98270
|
+
{ domain: "Self-review", trigger: "After completing significant implementation" },
|
|
98271
|
+
{ domain: "Plan generation", trigger: "ANY .matrixx/plans/*.md creation or planning doc \u2192 oracle ONLY, never Mouse" }
|
|
97561
98272
|
],
|
|
97562
98273
|
useWhen: [
|
|
97563
98274
|
"Complex architecture design",
|
|
@@ -100589,20 +101300,22 @@ function buildConstraintsSection(useTaskSystem) {
|
|
|
100589
101300
|
return `<Critical_Constraints>
|
|
100590
101301
|
BLOCKED ACTIONS (will fail if attempted):
|
|
100591
101302
|
- task (agent delegation tool): BLOCKED \u2014 you cannot delegate work to other agents
|
|
101303
|
+
- delegate_agent with subagent_type=oracle/construct/merovingian/smith/sentinel/cipher: BLOCKED \u2014 Mouse is a leaf executor
|
|
100592
101304
|
|
|
100593
101305
|
ALLOWED tools:
|
|
100594
|
-
- delegate_agent: You
|
|
101306
|
+
- delegate_agent with subagent_type=trinity/operator ONLY: You MAY spawn explore/librarian agents for research
|
|
100595
101307
|
- task_create, task_update, task_list, task_get: ALLOWED \u2014 use these for tracking your work
|
|
100596
101308
|
|
|
100597
|
-
You work ALONE for implementation. No delegation of implementation tasks.
|
|
101309
|
+
You work ALONE for implementation. No delegation of implementation or planning tasks. Plan files (.matrixx/plans/*.md) are OWNED by oracle \u2014 never create them via Mouse.
|
|
100598
101310
|
</Critical_Constraints>`;
|
|
100599
101311
|
}
|
|
100600
101312
|
return `<Critical_Constraints>
|
|
100601
101313
|
BLOCKED ACTIONS (will fail if attempted):
|
|
100602
101314
|
- task (agent delegation tool): BLOCKED \u2014 you cannot delegate work to other agents
|
|
101315
|
+
- delegate_agent with subagent_type=oracle/construct/merovingian/smith/sentinel/cipher: BLOCKED \u2014 Mouse is a leaf executor
|
|
100603
101316
|
|
|
100604
|
-
ALLOWED: delegate_agent - You
|
|
100605
|
-
You work ALONE for implementation. No delegation of implementation tasks.
|
|
101317
|
+
ALLOWED: delegate_agent with subagent_type=trinity/operator ONLY - You MAY spawn explore/librarian agents for research.
|
|
101318
|
+
You work ALONE for implementation. No delegation of implementation or planning tasks. Plan files (.matrixx/plans/*.md) are OWNED by oracle \u2014 never create them via Mouse.
|
|
100606
101319
|
</Critical_Constraints>`;
|
|
100607
101320
|
}
|
|
100608
101321
|
function buildTodoDisciplineSection2(useTaskSystem) {
|
|
@@ -100707,20 +101420,22 @@ function buildConstraintsSection2(useTaskSystem) {
|
|
|
100707
101420
|
return `<Critical_Constraints>
|
|
100708
101421
|
BLOCKED ACTIONS (will fail if attempted):
|
|
100709
101422
|
- task (agent delegation tool): BLOCKED \u2014 you cannot delegate work to other agents
|
|
101423
|
+
- delegate_agent with subagent_type=oracle/construct/merovingian/smith/sentinel/cipher: BLOCKED \u2014 Mouse is a leaf executor
|
|
100710
101424
|
|
|
100711
101425
|
ALLOWED tools:
|
|
100712
|
-
- delegate_agent: You
|
|
101426
|
+
- delegate_agent with subagent_type=trinity/operator ONLY: You MAY spawn explore/librarian agents for research
|
|
100713
101427
|
- task_create, task_update, task_list, task_get: ALLOWED \u2014 use these for tracking your work
|
|
100714
101428
|
|
|
100715
|
-
You work ALONE for implementation. No delegation of implementation tasks.
|
|
101429
|
+
You work ALONE for implementation. No delegation of implementation or planning tasks. Plan files (.matrixx/plans/*.md) are OWNED by oracle \u2014 never create them via Mouse.
|
|
100716
101430
|
</Critical_Constraints>`;
|
|
100717
101431
|
}
|
|
100718
101432
|
return `<Critical_Constraints>
|
|
100719
101433
|
BLOCKED ACTIONS (will fail if attempted):
|
|
100720
101434
|
- task (agent delegation tool): BLOCKED \u2014 you cannot delegate work to other agents
|
|
101435
|
+
- delegate_agent with subagent_type=oracle/construct/merovingian/smith/sentinel/cipher: BLOCKED \u2014 Mouse is a leaf executor
|
|
100721
101436
|
|
|
100722
|
-
ALLOWED: delegate_agent - You
|
|
100723
|
-
You work ALONE for implementation. No delegation of implementation tasks.
|
|
101437
|
+
ALLOWED: delegate_agent with subagent_type=trinity/operator ONLY - You MAY spawn explore/librarian agents for research.
|
|
101438
|
+
You work ALONE for implementation. No delegation of implementation or planning tasks. Plan files (.matrixx/plans/*.md) are OWNED by oracle \u2014 never create them via Mouse.
|
|
100724
101439
|
</Critical_Constraints>`;
|
|
100725
101440
|
}
|
|
100726
101441
|
function buildTodoDisciplineSection3(useTaskSystem) {
|
|
@@ -100821,17 +101536,18 @@ BLOCKED (will fail if attempted):
|
|
|
100821
101536
|
| Tool | Status | Description |
|
|
100822
101537
|
|------|--------|-------------|
|
|
100823
101538
|
| task | BLOCKED | Agent delegation tool \u2014 you cannot spawn other agents |
|
|
101539
|
+
| delegate_agent with oracle/construct/merovingian/smith/sentinel/cipher | BLOCKED | Mouse is leaf \u2014 only trinity/operator allowed |
|
|
100824
101540
|
|
|
100825
101541
|
ALLOWED:
|
|
100826
101542
|
| Tool | Usage |
|
|
100827
101543
|
|------|-------|
|
|
100828
|
-
| delegate_agent | Spawn explore/librarian for research ONLY |
|
|
101544
|
+
| delegate_agent with trinity/operator ONLY | Spawn explore/librarian for research ONLY |
|
|
100829
101545
|
| task_create | Create tasks to track your work |
|
|
100830
101546
|
| task_update | Update task status (in_progress, completed) |
|
|
100831
101547
|
| task_list | List active tasks |
|
|
100832
101548
|
| task_get | Get task details by ID |
|
|
100833
101549
|
|
|
100834
|
-
You work ALONE for implementation. No delegation.
|
|
101550
|
+
You work ALONE for implementation. No delegation. Plan files (.matrixx/plans/*.md) are OWNED by oracle \u2014 never create them via Mouse.
|
|
100835
101551
|
</blocked_actions>`;
|
|
100836
101552
|
}
|
|
100837
101553
|
return `<blocked_actions>
|
|
@@ -100839,13 +101555,14 @@ BLOCKED (will fail if attempted):
|
|
|
100839
101555
|
| Tool | Status | Description |
|
|
100840
101556
|
|------|--------|-------------|
|
|
100841
101557
|
| task | BLOCKED | Agent delegation tool \u2014 you cannot spawn other agents |
|
|
101558
|
+
| delegate_agent with oracle/construct/merovingian/smith/sentinel/cipher | BLOCKED | Mouse is leaf \u2014 only trinity/operator allowed |
|
|
100842
101559
|
|
|
100843
101560
|
ALLOWED:
|
|
100844
101561
|
| Tool | Usage |
|
|
100845
101562
|
|------|-------|
|
|
100846
|
-
| delegate_agent | Spawn explore/librarian for research ONLY |
|
|
101563
|
+
| delegate_agent with trinity/operator ONLY | Spawn explore/librarian for research ONLY |
|
|
100847
101564
|
|
|
100848
|
-
You work ALONE for implementation. No delegation.
|
|
101565
|
+
You work ALONE for implementation. No delegation. Plan files (.matrixx/plans/*.md) are OWNED by oracle \u2014 never create them via Mouse.
|
|
100849
101566
|
</blocked_actions>`;
|
|
100850
101567
|
}
|
|
100851
101568
|
function buildGptTaskDisciplineSection(useTaskSystem) {
|
|
@@ -100885,11 +101602,12 @@ Execute tasks directly. You NEVER delegate.
|
|
|
100885
101602
|
|
|
100886
101603
|
<rules>
|
|
100887
101604
|
- task tool: BLOCKED \u2014 cannot delegate to other agents
|
|
100888
|
-
- delegate_agent:
|
|
101605
|
+
- delegate_agent with oracle/construct/merovingian/smith/sentinel/cipher: BLOCKED \u2014 Mouse is leaf, only trinity/operator allowed
|
|
101606
|
+
- delegate_agent with trinity/operator ONLY: ALLOWED for research (explore/librarian)
|
|
100889
101607
|
- ${useTaskSystem ? "task_create/task_update" : "todowrite/todoread"}: REQUIRED for tracking
|
|
100890
101608
|
- Implement ONLY what is requested \u2014 no scope creep
|
|
101609
|
+
- Plan files (.matrixx/plans/*.md): OWNED by oracle \u2014 never create via Mouse
|
|
100891
101610
|
</rules>
|
|
100892
|
-
|
|
100893
101611
|
${taskDiscipline}
|
|
100894
101612
|
|
|
100895
101613
|
<verify>
|
|
@@ -100981,15 +101699,21 @@ function buildQwenBlockedActions(useTaskSystem) {
|
|
|
100981
101699
|
return `| Tool | Status | Notes |
|
|
100982
101700
|
|------|--------|-------|
|
|
100983
101701
|
| task | BLOCKED | Cannot delegate implementation |
|
|
100984
|
-
| delegate_agent |
|
|
101702
|
+
| delegate_agent with oracle/construct/merovingian/smith/sentinel/cipher | BLOCKED | Mouse is leaf \u2014 only trinity/operator allowed |
|
|
101703
|
+
| delegate_agent with trinity/operator ONLY | ALLOWED | Research only (explore/librarian) |
|
|
100985
101704
|
| task_create | ALLOWED | Track your work |
|
|
100986
101705
|
| task_update | ALLOWED | Update status |
|
|
100987
|
-
| task_list / task_get | ALLOWED | View tasks
|
|
101706
|
+
| task_list / task_get | ALLOWED | View tasks |
|
|
101707
|
+
|
|
101708
|
+
Plan files (.matrixx/plans/*.md) are OWNED by oracle \u2014 never create them via Mouse.`;
|
|
100988
101709
|
}
|
|
100989
101710
|
return `| Tool | Status | Notes |
|
|
100990
101711
|
|------|--------|-------|
|
|
100991
101712
|
| task | BLOCKED | Cannot delegate implementation |
|
|
100992
|
-
| delegate_agent |
|
|
101713
|
+
| delegate_agent with oracle/construct/merovingian/smith/sentinel/cipher | BLOCKED | Mouse is leaf \u2014 only trinity/operator allowed |
|
|
101714
|
+
| delegate_agent with trinity/operator ONLY | ALLOWED | Research only (explore/librarian) |
|
|
101715
|
+
|
|
101716
|
+
Plan files (.matrixx/plans/*.md) are OWNED by oracle \u2014 never create them via Mouse.`;
|
|
100993
101717
|
}
|
|
100994
101718
|
function buildQwenDisciplineSection(useTaskSystem) {
|
|
100995
101719
|
if (useTaskSystem) {
|
|
@@ -101256,7 +101980,7 @@ async function applyAgentConfig(params) {
|
|
|
101256
101980
|
if (!params.pluginConfig.tdd_enforcer?.enabled) {
|
|
101257
101981
|
disabledSkills.add("tdd-enforcer");
|
|
101258
101982
|
}
|
|
101259
|
-
const useTaskSystem = params.pluginConfig
|
|
101983
|
+
const useTaskSystem = isTaskSystemEnabled(params.pluginConfig);
|
|
101260
101984
|
const availableToolNames = getAvailableToolNames();
|
|
101261
101985
|
const builtinAgents = await createBuiltinAgents(migratedDisabledAgents, params.pluginConfig.agents, params.ctx.directory, undefined, params.pluginConfig.categories, allDiscoveredSkills, params.ctx.client, browserProvider, currentModel, disabledSkills, useTaskSystem, params.pluginConfig.global_model, availableToolNames);
|
|
101262
101986
|
const rawPluginAgents = params.pluginComponents.agents;
|
|
@@ -101562,7 +102286,9 @@ function agentByKey(agentResult, key) {
|
|
|
101562
102286
|
return agentResult[key];
|
|
101563
102287
|
}
|
|
101564
102288
|
function applyToolConfig(params) {
|
|
101565
|
-
const
|
|
102289
|
+
const isTaskSystem = isTaskSystemEnabled(params.pluginConfig);
|
|
102290
|
+
const denyTodoTools = isTaskSystem ? { todowrite: "deny", todoread: "deny" } : {};
|
|
102291
|
+
const denyTaskTools = !isTaskSystem ? { "task_*": "deny", task: "deny" } : {};
|
|
101566
102292
|
params.config.tools = {
|
|
101567
102293
|
...params.config.tools,
|
|
101568
102294
|
"grep_app_*": false,
|
|
@@ -101571,7 +102297,7 @@ function applyToolConfig(params) {
|
|
|
101571
102297
|
LspCodeActionResolve: false,
|
|
101572
102298
|
"task_*": false,
|
|
101573
102299
|
teammate: false,
|
|
101574
|
-
...
|
|
102300
|
+
...isTaskSystem ? { todowrite: false, todoread: false } : { "task_*": false, task: false }
|
|
101575
102301
|
};
|
|
101576
102302
|
const isCliRunMode = process.env.OPENCODE_CLI_RUN_MODE === "true";
|
|
101577
102303
|
const questionPermission = isCliRunMode ? "deny" : "allow";
|
|
@@ -101587,11 +102313,8 @@ function applyToolConfig(params) {
|
|
|
101587
102313
|
if (architect) {
|
|
101588
102314
|
architect.permission = {
|
|
101589
102315
|
...architect.permission,
|
|
101590
|
-
task: "allow",
|
|
101591
102316
|
delegate_agent: "deny",
|
|
101592
|
-
"task_*": "allow",
|
|
101593
|
-
teammate: "allow",
|
|
101594
|
-
...denyTodoTools
|
|
102317
|
+
...isTaskSystem ? { task: "allow", "task_*": "allow", teammate: "allow", ...denyTodoTools } : { todowrite: "allow", todoread: "allow", ...denyTaskTools }
|
|
101595
102318
|
};
|
|
101596
102319
|
}
|
|
101597
102320
|
const morpheus = agentByKey(params.agentResult, "morpheus");
|
|
@@ -101599,11 +102322,8 @@ function applyToolConfig(params) {
|
|
|
101599
102322
|
morpheus.permission = {
|
|
101600
102323
|
...morpheus.permission,
|
|
101601
102324
|
delegate_agent: "deny",
|
|
101602
|
-
task: "allow",
|
|
101603
102325
|
question: questionPermission,
|
|
101604
|
-
"task_*": "allow",
|
|
101605
|
-
teammate: "allow",
|
|
101606
|
-
...denyTodoTools
|
|
102326
|
+
...isTaskSystem ? { task: "allow", "task_*": "allow", teammate: "allow", ...denyTodoTools } : { todowrite: "allow", todoread: "allow", ...denyTaskTools }
|
|
101607
102327
|
};
|
|
101608
102328
|
}
|
|
101609
102329
|
const keymaker = agentByKey(params.agentResult, "keymaker");
|
|
@@ -101611,9 +102331,8 @@ function applyToolConfig(params) {
|
|
|
101611
102331
|
keymaker.permission = {
|
|
101612
102332
|
...keymaker.permission,
|
|
101613
102333
|
delegate_agent: "deny",
|
|
101614
|
-
task: "allow",
|
|
101615
102334
|
question: questionPermission,
|
|
101616
|
-
...denyTodoTools
|
|
102335
|
+
...isTaskSystem ? { task: "allow", ...denyTodoTools } : { todowrite: "allow", todoread: "allow", ...denyTaskTools }
|
|
101617
102336
|
};
|
|
101618
102337
|
}
|
|
101619
102338
|
const oracle = agentByKey(params.agentResult, "oracle");
|
|
@@ -101621,21 +102340,15 @@ function applyToolConfig(params) {
|
|
|
101621
102340
|
oracle.permission = {
|
|
101622
102341
|
...oracle.permission,
|
|
101623
102342
|
delegate_agent: "deny",
|
|
101624
|
-
task: "allow",
|
|
101625
102343
|
question: questionPermission,
|
|
101626
|
-
"task_*": "allow",
|
|
101627
|
-
teammate: "allow",
|
|
101628
|
-
...denyTodoTools
|
|
102344
|
+
...isTaskSystem ? { task: "allow", "task_*": "allow", teammate: "allow", ...denyTodoTools } : { todowrite: "allow", todoread: "allow", ...denyTaskTools }
|
|
101629
102345
|
};
|
|
101630
102346
|
}
|
|
101631
102347
|
const mouse = agentByKey(params.agentResult, "mouse");
|
|
101632
102348
|
if (mouse) {
|
|
101633
102349
|
mouse.permission = {
|
|
101634
102350
|
...mouse.permission,
|
|
101635
|
-
task: "allow",
|
|
101636
|
-
"task_*": "allow",
|
|
101637
|
-
teammate: "allow",
|
|
101638
|
-
...denyTodoTools
|
|
102351
|
+
...isTaskSystem ? { task: "allow", "task_*": "allow", teammate: "allow", ...denyTodoTools } : { todowrite: "allow", todoread: "allow", ...denyTaskTools }
|
|
101639
102352
|
};
|
|
101640
102353
|
}
|
|
101641
102354
|
params.config.permission = {
|
|
@@ -101811,12 +102524,13 @@ function createToolRegistry(args) {
|
|
|
101811
102524
|
skills: skillContext.builtinSkills,
|
|
101812
102525
|
client: ctx.client
|
|
101813
102526
|
});
|
|
101814
|
-
const taskSystemEnabled = pluginConfig
|
|
102527
|
+
const taskSystemEnabled = isTaskSystemEnabled(pluginConfig);
|
|
101815
102528
|
const taskToolsRecord = taskSystemEnabled ? {
|
|
101816
102529
|
task_create: createTaskCreateTool(pluginConfig, ctx),
|
|
101817
|
-
task_get: createTaskGetTool(pluginConfig),
|
|
101818
|
-
task_list: createTaskList(pluginConfig),
|
|
101819
|
-
task_update: createTaskUpdateTool(pluginConfig, ctx)
|
|
102530
|
+
task_get: createTaskGetTool(pluginConfig, ctx),
|
|
102531
|
+
task_list: createTaskList(pluginConfig, ctx),
|
|
102532
|
+
task_update: createTaskUpdateTool(pluginConfig, ctx),
|
|
102533
|
+
task_cleanup: createTaskCleanupTool(pluginConfig, ctx)
|
|
101820
102534
|
} : {};
|
|
101821
102535
|
const hashlineEnabled = pluginConfig.experimental?.hashline_edit ?? false;
|
|
101822
102536
|
const hashlineToolsRecord = hashlineEnabled ? { edit: createHashlineEditTool(ctx) } : {};
|
|
@@ -102109,6 +102823,12 @@ var BuiltinCommandNameSchema = exports_external.enum([
|
|
|
102109
102823
|
var CommentCheckerConfigSchema = exports_external.object({
|
|
102110
102824
|
custom_prompt: exports_external.string().optional()
|
|
102111
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
|
+
});
|
|
102112
102832
|
// src/config/schema/evolution.ts
|
|
102113
102833
|
var EvolutionWatcherConfigSchema = exports_external.object({
|
|
102114
102834
|
maxArgChars: exports_external.number().int().min(1).default(4000),
|
|
@@ -102182,7 +102902,7 @@ var ExperimentalConfigSchema = exports_external.object({
|
|
|
102182
102902
|
auto_resume: exports_external.boolean().optional(),
|
|
102183
102903
|
preemptive_compaction: exports_external.boolean().optional(),
|
|
102184
102904
|
truncate_all_tool_outputs: exports_external.boolean().optional(),
|
|
102185
|
-
task_system: exports_external.boolean().optional(),
|
|
102905
|
+
task_system: exports_external.boolean().optional().default(true),
|
|
102186
102906
|
plugin_load_timeout_ms: exports_external.number().min(1000).optional(),
|
|
102187
102907
|
safe_hook_creation: exports_external.boolean().optional(),
|
|
102188
102908
|
hashline_edit: exports_external.boolean().optional()
|
|
@@ -102196,6 +102916,7 @@ var HeadroomConfigSchema = exports_external.object({
|
|
|
102196
102916
|
});
|
|
102197
102917
|
// src/config/schema/hooks.ts
|
|
102198
102918
|
var HookNameSchema = exports_external.enum([
|
|
102919
|
+
"task-continuation-enforcer",
|
|
102199
102920
|
"todo-continuation-enforcer",
|
|
102200
102921
|
"context-window-monitor",
|
|
102201
102922
|
"session-recovery",
|
|
@@ -102243,7 +102964,6 @@ var HookNameSchema = exports_external.enum([
|
|
|
102243
102964
|
"env-file-write-guard",
|
|
102244
102965
|
"json-error-recovery",
|
|
102245
102966
|
"bash-file-read-guard",
|
|
102246
|
-
"todo-description-override",
|
|
102247
102967
|
"runtime-fallback",
|
|
102248
102968
|
"read-image-resizer",
|
|
102249
102969
|
"webfetch-redirect-guard",
|
|
@@ -102251,14 +102971,16 @@ var HookNameSchema = exports_external.enum([
|
|
|
102251
102971
|
"tool-pair-validator",
|
|
102252
102972
|
"quality-gate",
|
|
102253
102973
|
"task-notepad",
|
|
102254
|
-
"task-todo-mirror",
|
|
102255
102974
|
"design-intent-preserver",
|
|
102256
102975
|
"rtk-bash-rewriter",
|
|
102257
102976
|
"mcp-startup-notification",
|
|
102258
102977
|
"evolution-watcher",
|
|
102259
102978
|
"evolution-compressor",
|
|
102260
102979
|
"evolution-quality-gate",
|
|
102261
|
-
"evolution-hitl"
|
|
102980
|
+
"evolution-hitl",
|
|
102981
|
+
"context-mode-enforcer",
|
|
102982
|
+
"task-edit-guard",
|
|
102983
|
+
"task-edit-guard"
|
|
102262
102984
|
]);
|
|
102263
102985
|
// src/config/schema/matrix-loop.ts
|
|
102264
102986
|
var MatrixLoopConfigSchema = exports_external.object({
|
|
@@ -102276,7 +102998,8 @@ var MatrixxSelfConfigSkillConfigSchema = exports_external.object({
|
|
|
102276
102998
|
var MorpheusTasksConfigSchema = exports_external.object({
|
|
102277
102999
|
storage_path: exports_external.string().optional(),
|
|
102278
103000
|
task_list_id: exports_external.string().optional(),
|
|
102279
|
-
claude_code_compat: exports_external.boolean().default(false)
|
|
103001
|
+
claude_code_compat: exports_external.boolean().default(false),
|
|
103002
|
+
scope: exports_external.enum(["global", "project"]).default("project").optional().describe("Task storage scope: project \u2192 .matrixx/tasks per project (default), global \u2192 ~/.config/opencode/tasks/{listId}")
|
|
102280
103003
|
});
|
|
102281
103004
|
var MorpheusConfigSchema = exports_external.object({
|
|
102282
103005
|
tasks: MorpheusTasksConfigSchema.optional()
|
|
@@ -102446,6 +103169,7 @@ var MatrixxConfigSchema = exports_external.object({
|
|
|
102446
103169
|
agent_definitions: AgentDefinitionsConfigSchema.optional(),
|
|
102447
103170
|
matrixx_self_config: MatrixxSelfConfigSkillConfigSchema.optional(),
|
|
102448
103171
|
headroom: HeadroomConfigSchema.optional(),
|
|
103172
|
+
context_mode: ContextModeConfigSchema.optional(),
|
|
102449
103173
|
rtk: RtkConfigSchema.optional(),
|
|
102450
103174
|
evolution: EvolutionConfigSchema.optional(),
|
|
102451
103175
|
_migrations: exports_external.array(exports_external.string()).optional()
|
|
@@ -102637,7 +103361,6 @@ function createChatMessageHandler2(args) {
|
|
|
102637
103361
|
applyAgentVariant(pluginConfig, input.agent, message);
|
|
102638
103362
|
}
|
|
102639
103363
|
}
|
|
102640
|
-
await hooks2.taskTodoMirror?.["chat.message"]?.(input, output);
|
|
102641
103364
|
await hooks2.stopContinuationGuard?.["chat.message"]?.(input);
|
|
102642
103365
|
await hooks2.keywordDetector?.["chat.message"]?.(input, output);
|
|
102643
103366
|
await hooks2.autoSlashCommand?.["chat.message"]?.(input, output);
|
|
@@ -102787,8 +103510,8 @@ function createEventHandler2(args) {
|
|
|
102787
103510
|
await Promise.resolve(hooks2.autoUpdateChecker?.event?.(input));
|
|
102788
103511
|
await Promise.resolve(hooks2.backgroundNotificationHook?.event?.(input));
|
|
102789
103512
|
await Promise.resolve(hooks2.sessionNotification?.(input));
|
|
102790
|
-
await Promise.resolve(hooks2.taskTodoMirror?.event?.(input));
|
|
102791
103513
|
await Promise.resolve(hooks2.todoContinuationEnforcer?.handler?.(input));
|
|
103514
|
+
await Promise.resolve(hooks2.taskContinuationEnforcer?.handler?.(input));
|
|
102792
103515
|
await Promise.resolve(hooks2.unstableAgentBabysitter?.event?.(input));
|
|
102793
103516
|
await Promise.resolve(hooks2.contextWindowMonitor?.event?.(input));
|
|
102794
103517
|
await Promise.resolve(hooks2.directoryAgentsInjector?.event?.(input));
|
|
@@ -102938,7 +103661,6 @@ function setCachedTransform(sessionID, messagesHash, messages2) {
|
|
|
102938
103661
|
}
|
|
102939
103662
|
function createMessagesTransformHandler(args) {
|
|
102940
103663
|
return async (input, output) => {
|
|
102941
|
-
await args.hooks.taskTodoMirror?.["experimental.chat.messages.transform"]?.(input, output);
|
|
102942
103664
|
const sessionID = extractSessionID(output.messages);
|
|
102943
103665
|
if (!sessionID) {
|
|
102944
103666
|
await args.hooks.contextInjectorMessagesTransform?.["experimental.chat.messages.transform"]?.(input, output);
|
|
@@ -102962,9 +103684,7 @@ function createMessagesTransformHandler(args) {
|
|
|
102962
103684
|
// src/plugin/tool-definition.ts
|
|
102963
103685
|
function createToolDefinitionHandler(args) {
|
|
102964
103686
|
const { hooks: hooks2 } = args;
|
|
102965
|
-
return async (input, output) => {
|
|
102966
|
-
await hooks2.todoDescriptionOverride?.["tool.definition"]?.(input, output);
|
|
102967
|
-
};
|
|
103687
|
+
return async (input, output) => {};
|
|
102968
103688
|
}
|
|
102969
103689
|
|
|
102970
103690
|
// src/plugin/tool-execute-after.ts
|
|
@@ -103001,7 +103721,6 @@ function createToolExecuteAfterHandler2(args) {
|
|
|
103001
103721
|
await hooks2.jsonErrorRecovery?.["tool.execute.after"]?.(input, output);
|
|
103002
103722
|
await hooks2.readImageResizer?.["tool.execute.after"]?.(input, output);
|
|
103003
103723
|
await hooks2.taskNotepad?.["tool.execute.after"]?.(input, output);
|
|
103004
|
-
await hooks2.taskTodoMirror?.["tool.execute.after"]?.(input, output);
|
|
103005
103724
|
};
|
|
103006
103725
|
await Promise.all([
|
|
103007
103726
|
hooks2.preemptiveCompaction?.["tool.execute.after"]?.(input, output),
|
|
@@ -103013,22 +103732,22 @@ function createToolExecuteAfterHandler2(args) {
|
|
|
103013
103732
|
|
|
103014
103733
|
// src/features/assembly-state/manager.ts
|
|
103015
103734
|
init_logger();
|
|
103016
|
-
var
|
|
103735
|
+
var HOOK_NAME19 = "assembly-state";
|
|
103017
103736
|
var disabledSessions = new Set;
|
|
103018
103737
|
function isAssemblyDisabled(sessionID) {
|
|
103019
103738
|
return disabledSessions.has(sessionID);
|
|
103020
103739
|
}
|
|
103021
103740
|
function disableAssembly(sessionID) {
|
|
103022
103741
|
disabledSessions.add(sessionID);
|
|
103023
|
-
log(`[${
|
|
103742
|
+
log(`[${HOOK_NAME19}] Assembly disabled for session`, { sessionID });
|
|
103024
103743
|
disabledSessions.add(sessionID);
|
|
103025
|
-
log(`[${
|
|
103744
|
+
log(`[${HOOK_NAME19}] Consensus disabled for session`, { sessionID });
|
|
103026
103745
|
}
|
|
103027
103746
|
function enableAssembly(sessionID) {
|
|
103028
103747
|
disabledSessions.delete(sessionID);
|
|
103029
|
-
log(`[${
|
|
103748
|
+
log(`[${HOOK_NAME19}] Assembly enabled for session`, { sessionID });
|
|
103030
103749
|
disabledSessions.delete(sessionID);
|
|
103031
|
-
log(`[${
|
|
103750
|
+
log(`[${HOOK_NAME19}] Consensus enabled for session`, { sessionID });
|
|
103032
103751
|
}
|
|
103033
103752
|
// src/plugin/session-agent-resolver.ts
|
|
103034
103753
|
async function resolveSessionAgent(client2, sessionId, timeoutMs = 5000) {
|
|
@@ -103071,6 +103790,7 @@ function createToolExecuteBeforeHandler2(args) {
|
|
|
103071
103790
|
const writeExistingFileGuardHook = hooks2.writeExistingFileGuard?.["tool.execute.before"];
|
|
103072
103791
|
const tasksTodowriteDisablerHook = hooks2.tasksTodowriteDisabler?.["tool.execute.before"];
|
|
103073
103792
|
const oracleMdOnlyHook = hooks2.oracleMdOnly?.["tool.execute.before"];
|
|
103793
|
+
const contextModeEnforcerHook = hooks2.contextModeEnforcer?.["tool.execute.before"];
|
|
103074
103794
|
const nonInteractiveEnvHook = hooks2.nonInteractiveEnv?.["tool.execute.before"];
|
|
103075
103795
|
const bashFileReadGuardHook = hooks2.bashFileReadGuard?.["tool.execute.before"];
|
|
103076
103796
|
const questionLabelTruncatorHook = hooks2.questionLabelTruncator?.["tool.execute.before"];
|
|
@@ -103090,7 +103810,8 @@ function createToolExecuteBeforeHandler2(args) {
|
|
|
103090
103810
|
envFileWriteGuardHook?.(input, output),
|
|
103091
103811
|
writeExistingFileGuardHook?.(input, output),
|
|
103092
103812
|
tasksTodowriteDisablerHook?.(input, output),
|
|
103093
|
-
oracleMdOnlyHook?.(input, output)
|
|
103813
|
+
oracleMdOnlyHook?.(input, output),
|
|
103814
|
+
contextModeEnforcerHook?.(input, output)
|
|
103094
103815
|
]);
|
|
103095
103816
|
let resolvePromise;
|
|
103096
103817
|
if (input.tool === "task") {
|