opencode-matrixx 2.6.0 → 2.6.1
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 +3 -0
- 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 +18719 -663
- package/dist/config/schema/experimental.d.ts +1 -1
- package/dist/config/schema/hooks.d.ts +2 -2
- package/dist/config/schema/matrixx-config.d.ts +7 -3
- package/dist/config/schema/morpheus.d.ts +8 -0
- package/dist/create-hooks.d.ts +2 -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/index.d.ts +2 -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/handler.d.ts +16 -0
- package/dist/hooks/task-continuation-enforcer/idle-event.d.ts +11 -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/index.js +1445 -820
- package/dist/matrixx.schema.json +16 -3
- package/dist/plugin/hooks/create-continuation-hooks.d.ts +2 -1
- package/dist/plugin/hooks/create-core-hooks.d.ts +1 -2
- package/dist/plugin/hooks/create-tool-guard-hooks.d.ts +2 -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
|
@@ -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 }) => {
|
|
@@ -61823,13 +61937,11 @@ function createReadImageResizerHook(_ctx) {
|
|
|
61823
61937
|
}
|
|
61824
61938
|
// src/hooks/rtk-bash-rewriter/constants.ts
|
|
61825
61939
|
var RTK_BINARY_NAME = "rtk";
|
|
61826
|
-
var DEFAULT_TIMEOUT_MS = 5000;
|
|
61827
61940
|
var RTK_REWRITE_COMMAND = "rewrite";
|
|
61828
61941
|
|
|
61829
61942
|
// src/hooks/rtk-bash-rewriter/hook.ts
|
|
61830
61943
|
function createRtkBashRewriterHook(_ctx, config2) {
|
|
61831
61944
|
const rtkConfig = config2.rtk;
|
|
61832
|
-
const _timeoutMs = rtkConfig?.timeout_ms ?? DEFAULT_TIMEOUT_MS;
|
|
61833
61945
|
const binaryPath = rtkConfig?.binary_path ?? RTK_BINARY_NAME;
|
|
61834
61946
|
const hasRtk = Bun.which(binaryPath) !== null;
|
|
61835
61947
|
if (!hasRtk) {
|
|
@@ -64916,216 +65028,54 @@ function createStopContinuationGuardHook(_ctx, options) {
|
|
|
64916
65028
|
clear
|
|
64917
65029
|
};
|
|
64918
65030
|
}
|
|
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
|
|
65031
|
+
// src/hooks/task-continuation-enforcer/index.ts
|
|
64931
65032
|
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
65033
|
|
|
65046
|
-
|
|
65047
|
-
|
|
65048
|
-
|
|
65049
|
-
|
|
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
|
-
|
|
65060
|
-
## Questions
|
|
65034
|
+
// src/hooks/task-continuation-enforcer/constants.ts
|
|
65035
|
+
var HOOK_NAME13 = "task-continuation-enforcer";
|
|
65036
|
+
var DEFAULT_SKIP_AGENTS = ["oracle", "compaction"];
|
|
65037
|
+
var CONTINUATION_PROMPT2 = `${createSystemDirective(SystemDirectiveTypes.TASK_CONTINUATION)}
|
|
65061
65038
|
|
|
65062
|
-
|
|
65039
|
+
Incomplete Matrixx tasks remain. Continue working on the next pending task.
|
|
65063
65040
|
|
|
65064
|
-
|
|
65041
|
+
- Proceed without asking for permission
|
|
65042
|
+
- Mark each task in_progress before starting, completed immediately after
|
|
65043
|
+
- Respect blockedBy dependencies (skip blocked tasks)
|
|
65044
|
+
- Do not stop until all tasks are done`;
|
|
65045
|
+
var COUNTDOWN_SECONDS = 2;
|
|
65046
|
+
var TOAST_DURATION_MS = 900;
|
|
65047
|
+
var COUNTDOWN_GRACE_PERIOD_MS = 500;
|
|
65048
|
+
var ABORT_WINDOW_MS = 3000;
|
|
65049
|
+
var CONTINUATION_COOLDOWN_MS2 = 30000;
|
|
65050
|
+
var MAX_CONSECUTIVE_FAILURES = 5;
|
|
65051
|
+
var FAILURE_RESET_WINDOW_MS = 5 * 60 * 1000;
|
|
65065
65052
|
|
|
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() + `
|
|
65053
|
+
// src/hooks/task-continuation-enforcer/handler.ts
|
|
65054
|
+
init_logger();
|
|
65099
65055
|
|
|
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";
|
|
65056
|
+
// src/hooks/task-continuation-enforcer/idle-event.ts
|
|
65057
|
+
import { existsSync as existsSync44, readdirSync as readdirSync15 } from "fs";
|
|
65115
65058
|
|
|
65116
65059
|
// src/features/task-storage/storage.ts
|
|
65060
|
+
init_logger();
|
|
65117
65061
|
import { randomUUID } from "crypto";
|
|
65118
|
-
import { existsSync as
|
|
65119
|
-
import { basename as basename5, dirname as
|
|
65120
|
-
function
|
|
65062
|
+
import { existsSync as existsSync42, mkdirSync as mkdirSync15, readdirSync as readdirSync13, readFileSync as readFileSync32, renameSync as renameSync3, unlinkSync as unlinkSync9, writeFileSync as writeFileSync18 } from "fs";
|
|
65063
|
+
import { basename as basename5, dirname as dirname14, isAbsolute as isAbsolute3, join as join51 } from "path";
|
|
65064
|
+
function getProjectTaskDir(directory) {
|
|
65065
|
+
return join51(directory, ".matrixx", "tasks");
|
|
65066
|
+
}
|
|
65067
|
+
function getTaskDir(config2 = {}, directory) {
|
|
65121
65068
|
const tasksConfig = config2.morpheus?.tasks;
|
|
65122
65069
|
const storagePath = tasksConfig?.storage_path;
|
|
65123
65070
|
if (storagePath) {
|
|
65124
|
-
return isAbsolute3(storagePath) ? storagePath :
|
|
65071
|
+
return isAbsolute3(storagePath) ? storagePath : join51(directory ?? process.cwd(), storagePath);
|
|
65125
65072
|
}
|
|
65126
|
-
|
|
65127
|
-
|
|
65128
|
-
|
|
65073
|
+
if (tasksConfig?.scope === "global" || !directory) {
|
|
65074
|
+
const configDir = getOpenCodeConfigDir({ binary: "opencode" });
|
|
65075
|
+
const listId = resolveTaskListId(config2);
|
|
65076
|
+
return join51(configDir, "tasks", listId);
|
|
65077
|
+
}
|
|
65078
|
+
return getProjectTaskDir(directory);
|
|
65129
65079
|
}
|
|
65130
65080
|
function sanitizePathSegment(value) {
|
|
65131
65081
|
return value.replace(/[^a-zA-Z0-9_-]/g, "-") || "default";
|
|
@@ -65143,16 +65093,16 @@ function resolveTaskListId(config2 = {}) {
|
|
|
65143
65093
|
return sanitizePathSegment(basename5(process.cwd()));
|
|
65144
65094
|
}
|
|
65145
65095
|
function ensureDir2(dirPath) {
|
|
65146
|
-
if (!
|
|
65147
|
-
|
|
65096
|
+
if (!existsSync42(dirPath)) {
|
|
65097
|
+
mkdirSync15(dirPath, { recursive: true });
|
|
65148
65098
|
}
|
|
65149
65099
|
}
|
|
65150
65100
|
function readJsonSafe(filePath, schema) {
|
|
65151
65101
|
try {
|
|
65152
|
-
if (!
|
|
65102
|
+
if (!existsSync42(filePath)) {
|
|
65153
65103
|
return null;
|
|
65154
65104
|
}
|
|
65155
|
-
const content =
|
|
65105
|
+
const content = readFileSync32(filePath, "utf-8");
|
|
65156
65106
|
const parsed = JSON.parse(content);
|
|
65157
65107
|
const result = schema.safeParse(parsed);
|
|
65158
65108
|
if (!result.success) {
|
|
@@ -65164,15 +65114,15 @@ function readJsonSafe(filePath, schema) {
|
|
|
65164
65114
|
}
|
|
65165
65115
|
}
|
|
65166
65116
|
function writeJsonAtomic(filePath, data) {
|
|
65167
|
-
const dir =
|
|
65117
|
+
const dir = dirname14(filePath);
|
|
65168
65118
|
ensureDir2(dir);
|
|
65169
65119
|
const tempPath = `${filePath}.tmp.${Date.now()}`;
|
|
65170
65120
|
try {
|
|
65171
|
-
|
|
65121
|
+
writeFileSync18(tempPath, JSON.stringify(data, null, 2), "utf-8");
|
|
65172
65122
|
renameSync3(tempPath, filePath);
|
|
65173
65123
|
} catch (error51) {
|
|
65174
65124
|
try {
|
|
65175
|
-
if (
|
|
65125
|
+
if (existsSync42(tempPath)) {
|
|
65176
65126
|
unlinkSync9(tempPath);
|
|
65177
65127
|
}
|
|
65178
65128
|
} catch {}
|
|
@@ -65180,21 +65130,73 @@ function writeJsonAtomic(filePath, data) {
|
|
|
65180
65130
|
}
|
|
65181
65131
|
}
|
|
65182
65132
|
var STALE_LOCK_THRESHOLD_MS = 30000;
|
|
65133
|
+
async function acquireLockWithRetry(dirPath, retries = 4) {
|
|
65134
|
+
for (let attempt = 0;attempt < retries; attempt++) {
|
|
65135
|
+
const lock = acquireLock(dirPath);
|
|
65136
|
+
if (lock.acquired)
|
|
65137
|
+
return lock;
|
|
65138
|
+
if (attempt < retries - 1) {
|
|
65139
|
+
await new Promise((resolve7) => setTimeout(resolve7, 15 * 2 ** attempt));
|
|
65140
|
+
}
|
|
65141
|
+
}
|
|
65142
|
+
return { acquired: false, release: () => {} };
|
|
65143
|
+
}
|
|
65183
65144
|
function generateTaskId() {
|
|
65184
65145
|
return `T-${randomUUID()}`;
|
|
65185
65146
|
}
|
|
65147
|
+
function migrateLegacyTasksIfNeeded(config2, directory) {
|
|
65148
|
+
const projectDir = getProjectTaskDir(directory);
|
|
65149
|
+
const legacyDir = join51(getOpenCodeConfigDir({ binary: "opencode" }), "tasks", resolveTaskListId(config2));
|
|
65150
|
+
if (existsSync42(projectDir)) {
|
|
65151
|
+
try {
|
|
65152
|
+
const existing = readdirSync13(projectDir).filter((f) => f.startsWith("T-") && f.endsWith(".json"));
|
|
65153
|
+
if (existing.length > 0) {
|
|
65154
|
+
return { migrated: 0, legacyDir, projectDir };
|
|
65155
|
+
}
|
|
65156
|
+
} catch {}
|
|
65157
|
+
}
|
|
65158
|
+
if (!existsSync42(legacyDir)) {
|
|
65159
|
+
return { migrated: 0, legacyDir, projectDir };
|
|
65160
|
+
}
|
|
65161
|
+
let files = [];
|
|
65162
|
+
try {
|
|
65163
|
+
files = readdirSync13(legacyDir).filter((f) => f.startsWith("T-") && f.endsWith(".json"));
|
|
65164
|
+
} catch {
|
|
65165
|
+
return { migrated: 0, legacyDir, projectDir };
|
|
65166
|
+
}
|
|
65167
|
+
if (files.length === 0) {
|
|
65168
|
+
return { migrated: 0, legacyDir, projectDir };
|
|
65169
|
+
}
|
|
65170
|
+
ensureDir2(projectDir);
|
|
65171
|
+
let migrated = 0;
|
|
65172
|
+
for (const file2 of files) {
|
|
65173
|
+
const src = join51(legacyDir, file2);
|
|
65174
|
+
const dest = join51(projectDir, file2);
|
|
65175
|
+
if (existsSync42(dest))
|
|
65176
|
+
continue;
|
|
65177
|
+
try {
|
|
65178
|
+
const content = readFileSync32(src, "utf-8");
|
|
65179
|
+
writeFileSync18(dest, content, "utf-8");
|
|
65180
|
+
migrated++;
|
|
65181
|
+
} catch {}
|
|
65182
|
+
}
|
|
65183
|
+
if (migrated > 0) {
|
|
65184
|
+
log(`[task-storage] Migrated ${migrated} legacy tasks from ${legacyDir} to ${projectDir}`);
|
|
65185
|
+
}
|
|
65186
|
+
return { migrated, legacyDir, projectDir };
|
|
65187
|
+
}
|
|
65186
65188
|
function acquireLock(dirPath) {
|
|
65187
|
-
const lockPath =
|
|
65189
|
+
const lockPath = join51(dirPath, ".lock");
|
|
65188
65190
|
const lockId = randomUUID();
|
|
65189
65191
|
const createLock = (timestamp) => {
|
|
65190
|
-
|
|
65192
|
+
writeFileSync18(lockPath, JSON.stringify({ id: lockId, timestamp }), {
|
|
65191
65193
|
encoding: "utf-8",
|
|
65192
65194
|
flag: "wx"
|
|
65193
65195
|
});
|
|
65194
65196
|
};
|
|
65195
65197
|
const isStale = () => {
|
|
65196
65198
|
try {
|
|
65197
|
-
const lockContent =
|
|
65199
|
+
const lockContent = readFileSync32(lockPath, "utf-8");
|
|
65198
65200
|
const lockData = JSON.parse(lockContent);
|
|
65199
65201
|
const lockAge = Date.now() - lockData.timestamp;
|
|
65200
65202
|
return lockAge > STALE_LOCK_THRESHOLD_MS;
|
|
@@ -65232,9 +65234,9 @@ function acquireLock(dirPath) {
|
|
|
65232
65234
|
acquired: true,
|
|
65233
65235
|
release: () => {
|
|
65234
65236
|
try {
|
|
65235
|
-
if (!
|
|
65237
|
+
if (!existsSync42(lockPath))
|
|
65236
65238
|
return;
|
|
65237
|
-
const lockContent =
|
|
65239
|
+
const lockContent = readFileSync32(lockPath, "utf-8");
|
|
65238
65240
|
const lockData = JSON.parse(lockContent);
|
|
65239
65241
|
if (lockData.id !== lockId)
|
|
65240
65242
|
return;
|
|
@@ -65244,239 +65246,9 @@ function acquireLock(dirPath) {
|
|
|
65244
65246
|
};
|
|
65245
65247
|
}
|
|
65246
65248
|
|
|
65247
|
-
// src/hooks/task-
|
|
65249
|
+
// src/hooks/task-continuation-enforcer/idle-event.ts
|
|
65248
65250
|
init_logger();
|
|
65249
65251
|
|
|
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
65252
|
// src/tools/task/types.ts
|
|
65481
65253
|
var TaskStatusSchema = exports_external.enum(["pending", "in_progress", "completed", "deleted"]);
|
|
65482
65254
|
var TaskObjectSchema = exports_external.object({
|
|
@@ -65491,7 +65263,8 @@ var TaskObjectSchema = exports_external.object({
|
|
|
65491
65263
|
metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
|
|
65492
65264
|
repoURL: exports_external.string().optional(),
|
|
65493
65265
|
parentID: exports_external.string().optional(),
|
|
65494
|
-
threadID: exports_external.string()
|
|
65266
|
+
threadID: exports_external.string(),
|
|
65267
|
+
projectRoot: exports_external.string().optional()
|
|
65495
65268
|
}).strict();
|
|
65496
65269
|
var TaskCreateInputSchema = exports_external.object({
|
|
65497
65270
|
subject: exports_external.string(),
|
|
@@ -65528,82 +65301,842 @@ var TaskDeleteInputSchema = exports_external.object({
|
|
|
65528
65301
|
id: exports_external.string()
|
|
65529
65302
|
});
|
|
65530
65303
|
|
|
65531
|
-
// src/hooks/task-
|
|
65532
|
-
function
|
|
65304
|
+
// src/hooks/task-continuation-enforcer/abort-detection.ts
|
|
65305
|
+
function isLastAssistantMessageAborted(messages) {
|
|
65306
|
+
if (!messages || messages.length === 0)
|
|
65307
|
+
return false;
|
|
65308
|
+
for (let i = messages.length - 1;i >= 0; i--) {
|
|
65309
|
+
const info = messages[i].info;
|
|
65310
|
+
if (info?.role !== "assistant")
|
|
65311
|
+
continue;
|
|
65312
|
+
const errorName = info.error?.name;
|
|
65313
|
+
if (!errorName)
|
|
65314
|
+
return false;
|
|
65315
|
+
return errorName === "MessageAbortedError" || errorName === "AbortError";
|
|
65316
|
+
}
|
|
65317
|
+
return false;
|
|
65318
|
+
}
|
|
65319
|
+
|
|
65320
|
+
// src/hooks/task-continuation-enforcer/countdown.ts
|
|
65321
|
+
init_logger();
|
|
65322
|
+
|
|
65323
|
+
// src/hooks/task-continuation-enforcer/continuation-injection.ts
|
|
65324
|
+
import { existsSync as existsSync43, readdirSync as readdirSync14 } from "fs";
|
|
65325
|
+
init_logger();
|
|
65326
|
+
// src/hooks/task-continuation-enforcer/todo.ts
|
|
65327
|
+
function getIncompleteTaskCount(tasks) {
|
|
65328
|
+
const byId = new Map(tasks.map((t) => [t.id, t]));
|
|
65329
|
+
return tasks.filter((task) => {
|
|
65330
|
+
if (task.status !== "pending" && task.status !== "in_progress")
|
|
65331
|
+
return false;
|
|
65332
|
+
if (task.blockedBy.length === 0)
|
|
65333
|
+
return true;
|
|
65334
|
+
return task.blockedBy.every((bid) => byId.get(bid)?.status === "completed");
|
|
65335
|
+
}).length;
|
|
65336
|
+
}
|
|
65337
|
+
|
|
65338
|
+
// src/hooks/task-continuation-enforcer/continuation-injection.ts
|
|
65339
|
+
function hasWritePermission(tools) {
|
|
65340
|
+
const editPermission = tools?.edit;
|
|
65341
|
+
const writePermission = tools?.write;
|
|
65342
|
+
return !tools || editPermission !== false && editPermission !== "deny" && writePermission !== false && writePermission !== "deny";
|
|
65343
|
+
}
|
|
65344
|
+
async function injectContinuation(args) {
|
|
65345
|
+
const {
|
|
65346
|
+
ctx,
|
|
65347
|
+
sessionID,
|
|
65348
|
+
backgroundManager,
|
|
65349
|
+
skipAgents = DEFAULT_SKIP_AGENTS,
|
|
65350
|
+
resolvedInfo,
|
|
65351
|
+
sessionStateStore
|
|
65352
|
+
} = args;
|
|
65353
|
+
const state3 = sessionStateStore.getExistingState(sessionID);
|
|
65354
|
+
if (state3?.isRecovering) {
|
|
65355
|
+
log(`[${HOOK_NAME13}] Skipped injection: in recovery`, { sessionID });
|
|
65356
|
+
return;
|
|
65357
|
+
}
|
|
65358
|
+
const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((task) => task.status === "running") : false;
|
|
65359
|
+
if (hasRunningBgTasks) {
|
|
65360
|
+
log(`[${HOOK_NAME13}] Skipped injection: background tasks running`, { sessionID });
|
|
65361
|
+
return;
|
|
65362
|
+
}
|
|
65363
|
+
const tasks = [];
|
|
65364
|
+
let total = 0;
|
|
65533
65365
|
try {
|
|
65534
|
-
const
|
|
65535
|
-
if (!
|
|
65536
|
-
|
|
65537
|
-
|
|
65538
|
-
|
|
65539
|
-
|
|
65540
|
-
|
|
65541
|
-
|
|
65542
|
-
|
|
65366
|
+
const taskDir = getTaskDir({}, ctx.directory);
|
|
65367
|
+
if (!existsSync43(taskDir)) {
|
|
65368
|
+
log(`[${HOOK_NAME13}] Skipped injection: no task dir`, { sessionID, taskDir });
|
|
65369
|
+
return;
|
|
65370
|
+
}
|
|
65371
|
+
const files = readdirSync14(taskDir).filter((f) => f.startsWith("T-") && f.endsWith(".json"));
|
|
65372
|
+
for (const f of files) {
|
|
65373
|
+
const parsed = readJsonSafe(`${taskDir}/${f}`, TaskObjectSchema);
|
|
65374
|
+
if (parsed)
|
|
65375
|
+
tasks.push(parsed);
|
|
65376
|
+
}
|
|
65377
|
+
total = tasks.length;
|
|
65378
|
+
if (total === 0) {
|
|
65379
|
+
log(`[${HOOK_NAME13}] Skipped injection: no tasks`, { sessionID });
|
|
65380
|
+
return;
|
|
65381
|
+
}
|
|
65382
|
+
} catch (error51) {
|
|
65383
|
+
log(`[${HOOK_NAME13}] Failed to fetch tasks`, { sessionID, error: String(error51) });
|
|
65384
|
+
return;
|
|
65385
|
+
}
|
|
65386
|
+
const freshIncompleteCount = getIncompleteTaskCount(tasks);
|
|
65387
|
+
if (freshIncompleteCount === 0) {
|
|
65388
|
+
log(`[${HOOK_NAME13}] Skipped injection: no incomplete tasks`, { sessionID, total });
|
|
65389
|
+
return;
|
|
65390
|
+
}
|
|
65391
|
+
let agentName = resolvedInfo?.agent;
|
|
65392
|
+
let model = resolvedInfo?.model;
|
|
65393
|
+
let tools = resolvedInfo?.tools;
|
|
65394
|
+
if (!agentName || !model) {
|
|
65395
|
+
let previousMessage = null;
|
|
65396
|
+
if (isSqliteBackend()) {
|
|
65397
|
+
previousMessage = await findNearestMessageWithFieldsFromSDK(ctx.client, sessionID);
|
|
65398
|
+
} else {
|
|
65399
|
+
const messageDir = getMessageDir(sessionID);
|
|
65400
|
+
previousMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
|
|
65401
|
+
}
|
|
65402
|
+
agentName = agentName ?? previousMessage?.agent;
|
|
65403
|
+
model = model ?? (previousMessage?.model?.providerID && previousMessage?.model?.modelID ? {
|
|
65404
|
+
providerID: previousMessage.model.providerID,
|
|
65405
|
+
modelID: previousMessage.model.modelID,
|
|
65406
|
+
...previousMessage.model.variant ? { variant: previousMessage.model.variant } : {}
|
|
65407
|
+
} : undefined);
|
|
65408
|
+
tools = tools ?? previousMessage?.tools;
|
|
65409
|
+
}
|
|
65410
|
+
if (agentName && skipAgents.some((s) => getAgentConfigKey(s) === getAgentConfigKey(agentName))) {
|
|
65411
|
+
log(`[${HOOK_NAME13}] Skipped: agent in skipAgents list`, { sessionID, agent: agentName });
|
|
65412
|
+
return;
|
|
65413
|
+
}
|
|
65414
|
+
if (!hasWritePermission(tools)) {
|
|
65415
|
+
log(`[${HOOK_NAME13}] Skipped: agent lacks write permission`, { sessionID, agent: agentName });
|
|
65416
|
+
return;
|
|
65417
|
+
}
|
|
65418
|
+
const byId = new Map(tasks.map((t) => [t.id, t]));
|
|
65419
|
+
const incompleteTasks = tasks.filter((task) => {
|
|
65420
|
+
if (task.status !== "pending" && task.status !== "in_progress")
|
|
65421
|
+
return false;
|
|
65422
|
+
if (task.blockedBy.length === 0)
|
|
65423
|
+
return true;
|
|
65424
|
+
return task.blockedBy.every((bid) => byId.get(bid)?.status === "completed");
|
|
65425
|
+
});
|
|
65426
|
+
const taskList = incompleteTasks.map((task) => `- [${task.status}] ${task.subject} (${task.id})`).join(`
|
|
65427
|
+
`);
|
|
65428
|
+
const prompt = `${CONTINUATION_PROMPT2}
|
|
65429
|
+
|
|
65430
|
+
[Status: ${total - freshIncompleteCount}/${total} completed, ${freshIncompleteCount} remaining]
|
|
65431
|
+
|
|
65432
|
+
Remaining Matrixx tasks:
|
|
65433
|
+
${taskList}`;
|
|
65434
|
+
const injectionState = sessionStateStore.getExistingState(sessionID);
|
|
65435
|
+
if (injectionState) {
|
|
65436
|
+
injectionState.inFlight = true;
|
|
65437
|
+
}
|
|
65438
|
+
try {
|
|
65439
|
+
log(`[${HOOK_NAME13}] Injecting continuation`, {
|
|
65440
|
+
sessionID,
|
|
65441
|
+
agent: agentName,
|
|
65442
|
+
model,
|
|
65443
|
+
incompleteCount: freshIncompleteCount
|
|
65444
|
+
});
|
|
65445
|
+
await ctx.client.session.promptAsync({
|
|
65446
|
+
path: { id: sessionID },
|
|
65447
|
+
body: {
|
|
65448
|
+
agent: agentName,
|
|
65449
|
+
...model !== undefined ? { model } : {},
|
|
65450
|
+
parts: [{ type: "text", text: prompt }]
|
|
65451
|
+
},
|
|
65452
|
+
query: { directory: ctx.directory }
|
|
65453
|
+
});
|
|
65454
|
+
log(`[${HOOK_NAME13}] Injection successful`, { sessionID });
|
|
65455
|
+
if (injectionState) {
|
|
65456
|
+
injectionState.inFlight = false;
|
|
65457
|
+
injectionState.lastInjectedAt = Date.now();
|
|
65458
|
+
injectionState.consecutiveFailures = 0;
|
|
65459
|
+
}
|
|
65460
|
+
} catch (error51) {
|
|
65461
|
+
log(`[${HOOK_NAME13}] Injection failed`, { sessionID, error: String(error51) });
|
|
65462
|
+
if (injectionState) {
|
|
65463
|
+
injectionState.inFlight = false;
|
|
65464
|
+
injectionState.lastInjectedAt = Date.now();
|
|
65465
|
+
injectionState.consecutiveFailures = (injectionState.consecutiveFailures ?? 0) + 1;
|
|
65543
65466
|
}
|
|
65544
|
-
return tasks;
|
|
65545
|
-
} catch (err) {
|
|
65546
|
-
log(`[${HOOK_NAME14}] Failed to load tasks`, { error: String(err) });
|
|
65547
|
-
return [];
|
|
65548
65467
|
}
|
|
65549
65468
|
}
|
|
65550
|
-
|
|
65551
|
-
|
|
65552
|
-
|
|
65553
|
-
|
|
65554
|
-
|
|
65469
|
+
|
|
65470
|
+
// src/hooks/task-continuation-enforcer/countdown.ts
|
|
65471
|
+
async function showCountdownToast(ctx, seconds, incompleteCount) {
|
|
65472
|
+
await ctx.client.tui.showToast({
|
|
65473
|
+
body: {
|
|
65474
|
+
title: "Task Continuation",
|
|
65475
|
+
message: `Resuming in ${seconds}s... (${incompleteCount} tasks remaining)`,
|
|
65476
|
+
variant: "warning",
|
|
65477
|
+
duration: TOAST_DURATION_MS
|
|
65478
|
+
}
|
|
65479
|
+
}).catch((err) => {
|
|
65480
|
+
log("[task-continuation] Countdown toast failed:", err);
|
|
65481
|
+
});
|
|
65482
|
+
}
|
|
65483
|
+
function startCountdown(args) {
|
|
65484
|
+
const {
|
|
65485
|
+
ctx,
|
|
65486
|
+
sessionID,
|
|
65487
|
+
incompleteCount,
|
|
65488
|
+
resolvedInfo,
|
|
65489
|
+
backgroundManager,
|
|
65490
|
+
skipAgents,
|
|
65491
|
+
sessionStateStore
|
|
65492
|
+
} = args;
|
|
65493
|
+
const state3 = sessionStateStore.getState(sessionID);
|
|
65494
|
+
sessionStateStore.cancelCountdown(sessionID);
|
|
65495
|
+
let secondsRemaining = COUNTDOWN_SECONDS;
|
|
65496
|
+
showCountdownToast(ctx, secondsRemaining, incompleteCount);
|
|
65497
|
+
state3.countdownStartedAt = Date.now();
|
|
65498
|
+
state3.countdownInterval = setInterval(() => {
|
|
65499
|
+
secondsRemaining--;
|
|
65500
|
+
if (secondsRemaining > 0) {
|
|
65501
|
+
showCountdownToast(ctx, secondsRemaining, incompleteCount);
|
|
65502
|
+
}
|
|
65503
|
+
}, 1000);
|
|
65504
|
+
state3.countdownTimer = setTimeout(() => {
|
|
65505
|
+
sessionStateStore.cancelCountdown(sessionID);
|
|
65506
|
+
injectContinuation({
|
|
65507
|
+
ctx,
|
|
65508
|
+
sessionID,
|
|
65509
|
+
backgroundManager,
|
|
65510
|
+
skipAgents,
|
|
65511
|
+
resolvedInfo,
|
|
65512
|
+
sessionStateStore
|
|
65513
|
+
});
|
|
65514
|
+
}, COUNTDOWN_SECONDS * 1000);
|
|
65515
|
+
log(`[${HOOK_NAME13}] Countdown started`, {
|
|
65516
|
+
sessionID,
|
|
65517
|
+
seconds: COUNTDOWN_SECONDS,
|
|
65518
|
+
incompleteCount
|
|
65519
|
+
});
|
|
65520
|
+
}
|
|
65521
|
+
|
|
65522
|
+
// src/hooks/task-continuation-enforcer/idle-event.ts
|
|
65523
|
+
async function handleSessionIdle(args) {
|
|
65524
|
+
const {
|
|
65525
|
+
ctx,
|
|
65526
|
+
sessionID,
|
|
65527
|
+
sessionStateStore,
|
|
65528
|
+
backgroundManager,
|
|
65529
|
+
skipAgents = DEFAULT_SKIP_AGENTS,
|
|
65530
|
+
isContinuationStopped
|
|
65531
|
+
} = args;
|
|
65532
|
+
log(`[${HOOK_NAME13}] session.idle`, { sessionID });
|
|
65533
|
+
const state3 = sessionStateStore.getState(sessionID);
|
|
65534
|
+
if (state3.isRecovering) {
|
|
65535
|
+
log(`[${HOOK_NAME13}] Skipped: in recovery`, { sessionID });
|
|
65536
|
+
return;
|
|
65537
|
+
}
|
|
65538
|
+
if (state3.abortDetectedAt) {
|
|
65539
|
+
const timeSinceAbort = Date.now() - state3.abortDetectedAt;
|
|
65540
|
+
if (timeSinceAbort < ABORT_WINDOW_MS) {
|
|
65541
|
+
log(`[${HOOK_NAME13}] Skipped: abort detected via event ${timeSinceAbort}ms ago`, { sessionID });
|
|
65542
|
+
state3.abortDetectedAt = undefined;
|
|
65543
|
+
return;
|
|
65544
|
+
}
|
|
65545
|
+
state3.abortDetectedAt = undefined;
|
|
65546
|
+
}
|
|
65547
|
+
const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((task) => task.status === "running") : false;
|
|
65548
|
+
if (hasRunningBgTasks) {
|
|
65549
|
+
log(`[${HOOK_NAME13}] Skipped: background tasks running`, { sessionID });
|
|
65550
|
+
return;
|
|
65551
|
+
}
|
|
65552
|
+
try {
|
|
65553
|
+
const messagesResp = await ctx.client.session.messages({
|
|
65554
|
+
path: { id: sessionID },
|
|
65555
|
+
query: { directory: ctx.directory }
|
|
65556
|
+
});
|
|
65557
|
+
const messages = normalizeSDKResponse(messagesResp, []);
|
|
65558
|
+
if (isLastAssistantMessageAborted(messages)) {
|
|
65559
|
+
log(`[${HOOK_NAME13}] Skipped: last assistant message was aborted (API fallback)`, { sessionID });
|
|
65560
|
+
return;
|
|
65561
|
+
}
|
|
65562
|
+
} catch (error51) {
|
|
65563
|
+
log(`[${HOOK_NAME13}] Messages fetch failed, continuing`, { sessionID, error: String(error51) });
|
|
65564
|
+
}
|
|
65565
|
+
let incompleteCount = 0;
|
|
65566
|
+
let total = 0;
|
|
65567
|
+
try {
|
|
65568
|
+
const taskDir = getTaskDir({}, ctx.directory);
|
|
65569
|
+
if (!existsSync44(taskDir)) {
|
|
65570
|
+
log(`[${HOOK_NAME13}] No task dir`, { sessionID, taskDir });
|
|
65571
|
+
return;
|
|
65572
|
+
}
|
|
65573
|
+
const files = readdirSync15(taskDir).filter((f) => f.startsWith("T-") && f.endsWith(".json"));
|
|
65574
|
+
const tasks = [];
|
|
65575
|
+
for (const f of files) {
|
|
65576
|
+
const parsed = readJsonSafe(`${taskDir}/${f}`, TaskObjectSchema);
|
|
65577
|
+
if (parsed)
|
|
65578
|
+
tasks.push(parsed);
|
|
65579
|
+
}
|
|
65580
|
+
total = tasks.length;
|
|
65581
|
+
if (total === 0) {
|
|
65582
|
+
log(`[${HOOK_NAME13}] No tasks`, { sessionID });
|
|
65555
65583
|
return;
|
|
65584
|
+
}
|
|
65585
|
+
incompleteCount = getIncompleteTaskCount(tasks);
|
|
65586
|
+
} catch (error51) {
|
|
65587
|
+
log(`[${HOOK_NAME13}] Task fetch failed`, { sessionID, error: String(error51) });
|
|
65588
|
+
return;
|
|
65589
|
+
}
|
|
65590
|
+
if (incompleteCount === 0) {
|
|
65591
|
+
log(`[${HOOK_NAME13}] All tasks complete`, { sessionID, total });
|
|
65592
|
+
return;
|
|
65593
|
+
}
|
|
65594
|
+
if (state3.inFlight) {
|
|
65595
|
+
log(`[${HOOK_NAME13}] Skipped: injection in flight`, { sessionID });
|
|
65596
|
+
return;
|
|
65597
|
+
}
|
|
65598
|
+
if ((state3.consecutiveFailures ?? 0) >= MAX_CONSECUTIVE_FAILURES && state3.lastInjectedAt && Date.now() - state3.lastInjectedAt >= FAILURE_RESET_WINDOW_MS) {
|
|
65599
|
+
state3.consecutiveFailures = 0;
|
|
65600
|
+
log(`[${HOOK_NAME13}] Reset consecutive failures after recovery window`, {
|
|
65601
|
+
sessionID,
|
|
65602
|
+
failureResetWindowMs: FAILURE_RESET_WINDOW_MS
|
|
65603
|
+
});
|
|
65604
|
+
}
|
|
65605
|
+
if ((state3.consecutiveFailures ?? 0) >= MAX_CONSECUTIVE_FAILURES) {
|
|
65606
|
+
log(`[${HOOK_NAME13}] Skipped: max consecutive failures reached`, {
|
|
65607
|
+
sessionID,
|
|
65608
|
+
consecutiveFailures: state3.consecutiveFailures ?? 0,
|
|
65609
|
+
maxConsecutiveFailures: MAX_CONSECUTIVE_FAILURES
|
|
65610
|
+
});
|
|
65611
|
+
return;
|
|
65612
|
+
}
|
|
65613
|
+
const effectiveCooldown = CONTINUATION_COOLDOWN_MS2 * 2 ** Math.min(state3.consecutiveFailures ?? 0, 5);
|
|
65614
|
+
if (state3.lastInjectedAt && Date.now() - state3.lastInjectedAt < effectiveCooldown) {
|
|
65615
|
+
log(`[${HOOK_NAME13}] Skipped: cooldown active`, {
|
|
65616
|
+
sessionID,
|
|
65617
|
+
effectiveCooldown,
|
|
65618
|
+
consecutiveFailures: state3.consecutiveFailures ?? 0
|
|
65619
|
+
});
|
|
65620
|
+
return;
|
|
65621
|
+
}
|
|
65622
|
+
let resolvedInfo;
|
|
65623
|
+
let hasCompactionMessage = false;
|
|
65624
|
+
try {
|
|
65625
|
+
const messagesResp = await ctx.client.session.messages({
|
|
65626
|
+
path: { id: sessionID }
|
|
65627
|
+
});
|
|
65628
|
+
const messages = normalizeSDKResponse(messagesResp, []);
|
|
65629
|
+
for (let i = messages.length - 1;i >= 0; i--) {
|
|
65630
|
+
const info = messages[i].info;
|
|
65631
|
+
if (info?.agent === "compaction") {
|
|
65632
|
+
hasCompactionMessage = true;
|
|
65633
|
+
continue;
|
|
65634
|
+
}
|
|
65635
|
+
if (info?.agent || info?.model || info?.modelID && info?.providerID) {
|
|
65636
|
+
resolvedInfo = {
|
|
65637
|
+
agent: info.agent,
|
|
65638
|
+
model: info.model ?? (info.providerID && info.modelID ? { providerID: info.providerID, modelID: info.modelID } : undefined),
|
|
65639
|
+
tools: info.tools
|
|
65640
|
+
};
|
|
65641
|
+
break;
|
|
65642
|
+
}
|
|
65643
|
+
}
|
|
65644
|
+
} catch (error51) {
|
|
65645
|
+
log(`[${HOOK_NAME13}] Failed to fetch messages for agent check`, { sessionID, error: String(error51) });
|
|
65646
|
+
}
|
|
65647
|
+
log(`[${HOOK_NAME13}] Agent check`, { sessionID, agentName: resolvedInfo?.agent, skipAgents, hasCompactionMessage });
|
|
65648
|
+
const resolvedAgentName = resolvedInfo?.agent;
|
|
65649
|
+
if (resolvedAgentName && skipAgents.some((s) => getAgentConfigKey(s) === getAgentConfigKey(resolvedAgentName))) {
|
|
65650
|
+
log(`[${HOOK_NAME13}] Skipped: agent in skipAgents list`, { sessionID, agent: resolvedAgentName });
|
|
65651
|
+
return;
|
|
65652
|
+
}
|
|
65653
|
+
if (hasCompactionMessage && !resolvedInfo?.agent) {
|
|
65654
|
+
log(`[${HOOK_NAME13}] Skipped: compaction occurred but no agent info resolved`, { sessionID });
|
|
65655
|
+
return;
|
|
65656
|
+
}
|
|
65657
|
+
if (isContinuationStopped?.(sessionID)) {
|
|
65658
|
+
log(`[${HOOK_NAME13}] Skipped: continuation stopped for session`, { sessionID });
|
|
65659
|
+
return;
|
|
65660
|
+
}
|
|
65661
|
+
startCountdown({
|
|
65662
|
+
ctx,
|
|
65663
|
+
sessionID,
|
|
65664
|
+
incompleteCount,
|
|
65665
|
+
total,
|
|
65666
|
+
resolvedInfo,
|
|
65667
|
+
backgroundManager,
|
|
65668
|
+
skipAgents,
|
|
65669
|
+
sessionStateStore
|
|
65670
|
+
});
|
|
65671
|
+
}
|
|
65672
|
+
|
|
65673
|
+
// src/hooks/task-continuation-enforcer/non-idle-events.ts
|
|
65674
|
+
init_logger();
|
|
65675
|
+
function handleNonIdleEvent(args) {
|
|
65676
|
+
const { eventType, properties, sessionStateStore } = args;
|
|
65677
|
+
if (eventType === "message.updated") {
|
|
65678
|
+
const info = properties?.info;
|
|
65679
|
+
const sessionID = info?.sessionID;
|
|
65680
|
+
const role = info?.role;
|
|
65556
65681
|
if (!sessionID)
|
|
65557
65682
|
return;
|
|
65683
|
+
if (role === "user") {
|
|
65684
|
+
const state3 = sessionStateStore.getExistingState(sessionID);
|
|
65685
|
+
if (state3?.countdownStartedAt) {
|
|
65686
|
+
const elapsed = Date.now() - state3.countdownStartedAt;
|
|
65687
|
+
if (elapsed < COUNTDOWN_GRACE_PERIOD_MS) {
|
|
65688
|
+
log(`[${HOOK_NAME13}] Ignoring user message in grace period`, { sessionID, elapsed });
|
|
65689
|
+
return;
|
|
65690
|
+
}
|
|
65691
|
+
}
|
|
65692
|
+
if (state3)
|
|
65693
|
+
state3.abortDetectedAt = undefined;
|
|
65694
|
+
sessionStateStore.cancelCountdown(sessionID);
|
|
65695
|
+
return;
|
|
65696
|
+
}
|
|
65697
|
+
if (role === "assistant") {
|
|
65698
|
+
const state3 = sessionStateStore.getExistingState(sessionID);
|
|
65699
|
+
if (state3)
|
|
65700
|
+
state3.abortDetectedAt = undefined;
|
|
65701
|
+
sessionStateStore.cancelCountdown(sessionID);
|
|
65702
|
+
return;
|
|
65703
|
+
}
|
|
65704
|
+
return;
|
|
65705
|
+
}
|
|
65706
|
+
if (eventType === "message.part.updated") {
|
|
65707
|
+
const info = properties?.info;
|
|
65708
|
+
const sessionID = info?.sessionID;
|
|
65709
|
+
const role = info?.role;
|
|
65710
|
+
if (sessionID && role === "assistant") {
|
|
65711
|
+
const state3 = sessionStateStore.getExistingState(sessionID);
|
|
65712
|
+
if (state3)
|
|
65713
|
+
state3.abortDetectedAt = undefined;
|
|
65714
|
+
sessionStateStore.cancelCountdown(sessionID);
|
|
65715
|
+
}
|
|
65716
|
+
return;
|
|
65717
|
+
}
|
|
65718
|
+
if (eventType === "tool.execute.before" || eventType === "tool.execute.after") {
|
|
65719
|
+
const sessionID = properties?.sessionID;
|
|
65720
|
+
if (sessionID) {
|
|
65721
|
+
const state3 = sessionStateStore.getExistingState(sessionID);
|
|
65722
|
+
if (state3)
|
|
65723
|
+
state3.abortDetectedAt = undefined;
|
|
65724
|
+
sessionStateStore.cancelCountdown(sessionID);
|
|
65725
|
+
}
|
|
65726
|
+
return;
|
|
65727
|
+
}
|
|
65728
|
+
if (eventType === "session.deleted") {
|
|
65729
|
+
const sessionInfo = properties?.info;
|
|
65730
|
+
if (sessionInfo?.id) {
|
|
65731
|
+
sessionStateStore.cleanup(sessionInfo.id);
|
|
65732
|
+
log(`[${HOOK_NAME13}] Session deleted: cleaned up`, { sessionID: sessionInfo.id });
|
|
65733
|
+
}
|
|
65734
|
+
return;
|
|
65735
|
+
}
|
|
65736
|
+
}
|
|
65737
|
+
|
|
65738
|
+
// src/hooks/task-continuation-enforcer/handler.ts
|
|
65739
|
+
function createTaskContinuationHandler(args) {
|
|
65740
|
+
const {
|
|
65741
|
+
ctx,
|
|
65742
|
+
sessionStateStore,
|
|
65743
|
+
backgroundManager,
|
|
65744
|
+
skipAgents = DEFAULT_SKIP_AGENTS,
|
|
65745
|
+
isContinuationStopped
|
|
65746
|
+
} = args;
|
|
65747
|
+
return async ({ event }) => {
|
|
65748
|
+
const props = event.properties;
|
|
65749
|
+
if (event.type === "session.error") {
|
|
65750
|
+
const sessionID = props?.sessionID;
|
|
65751
|
+
if (!sessionID)
|
|
65752
|
+
return;
|
|
65753
|
+
const error51 = props?.error;
|
|
65754
|
+
if (error51?.name === "MessageAbortedError" || error51?.name === "AbortError") {
|
|
65755
|
+
const state3 = sessionStateStore.getState(sessionID);
|
|
65756
|
+
state3.abortDetectedAt = Date.now();
|
|
65757
|
+
log(`[${HOOK_NAME13}] Abort detected via session.error`, { sessionID, errorName: error51.name });
|
|
65758
|
+
}
|
|
65759
|
+
sessionStateStore.cancelCountdown(sessionID);
|
|
65760
|
+
log(`[${HOOK_NAME13}] session.error`, { sessionID });
|
|
65761
|
+
return;
|
|
65762
|
+
}
|
|
65763
|
+
if (event.type === "session.idle") {
|
|
65764
|
+
const sessionID = props?.sessionID;
|
|
65765
|
+
if (!sessionID)
|
|
65766
|
+
return;
|
|
65767
|
+
await handleSessionIdle({
|
|
65768
|
+
ctx,
|
|
65769
|
+
sessionID,
|
|
65770
|
+
sessionStateStore,
|
|
65771
|
+
backgroundManager,
|
|
65772
|
+
skipAgents,
|
|
65773
|
+
isContinuationStopped
|
|
65774
|
+
});
|
|
65775
|
+
return;
|
|
65776
|
+
}
|
|
65777
|
+
handleNonIdleEvent({
|
|
65778
|
+
eventType: event.type,
|
|
65779
|
+
properties: props,
|
|
65780
|
+
sessionStateStore
|
|
65781
|
+
});
|
|
65782
|
+
};
|
|
65783
|
+
}
|
|
65784
|
+
|
|
65785
|
+
// src/hooks/task-continuation-enforcer/session-state.ts
|
|
65786
|
+
var SESSION_STATE_TTL_MS = 10 * 60 * 1000;
|
|
65787
|
+
var SESSION_STATE_PRUNE_INTERVAL_MS = 2 * 60 * 1000;
|
|
65788
|
+
function createSessionStateStore() {
|
|
65789
|
+
const sessions = new Map;
|
|
65790
|
+
let pruneInterval;
|
|
65791
|
+
pruneInterval = setInterval(() => {
|
|
65558
65792
|
const now = Date.now();
|
|
65559
|
-
const
|
|
65560
|
-
|
|
65793
|
+
for (const [sessionID, tracked] of sessions.entries()) {
|
|
65794
|
+
if (now - tracked.lastAccessedAt > SESSION_STATE_TTL_MS) {
|
|
65795
|
+
cancelCountdown(sessionID);
|
|
65796
|
+
sessions.delete(sessionID);
|
|
65797
|
+
}
|
|
65798
|
+
}
|
|
65799
|
+
}, SESSION_STATE_PRUNE_INTERVAL_MS);
|
|
65800
|
+
if (typeof pruneInterval === "object" && "unref" in pruneInterval) {
|
|
65801
|
+
pruneInterval.unref();
|
|
65802
|
+
}
|
|
65803
|
+
function getState(sessionID) {
|
|
65804
|
+
const existing = sessions.get(sessionID);
|
|
65805
|
+
if (existing) {
|
|
65806
|
+
existing.lastAccessedAt = Date.now();
|
|
65807
|
+
return existing.state;
|
|
65808
|
+
}
|
|
65809
|
+
const state3 = {
|
|
65810
|
+
consecutiveFailures: 0
|
|
65811
|
+
};
|
|
65812
|
+
sessions.set(sessionID, { state: state3, lastAccessedAt: Date.now() });
|
|
65813
|
+
return state3;
|
|
65814
|
+
}
|
|
65815
|
+
function getExistingState(sessionID) {
|
|
65816
|
+
const existing = sessions.get(sessionID);
|
|
65817
|
+
if (existing) {
|
|
65818
|
+
existing.lastAccessedAt = Date.now();
|
|
65819
|
+
return existing.state;
|
|
65820
|
+
}
|
|
65821
|
+
return;
|
|
65822
|
+
}
|
|
65823
|
+
function cancelCountdown(sessionID) {
|
|
65824
|
+
const tracked = sessions.get(sessionID);
|
|
65825
|
+
if (!tracked)
|
|
65561
65826
|
return;
|
|
65562
|
-
|
|
65563
|
-
|
|
65564
|
-
|
|
65565
|
-
|
|
65566
|
-
|
|
65567
|
-
|
|
65568
|
-
|
|
65827
|
+
const state3 = tracked.state;
|
|
65828
|
+
if (state3.countdownTimer) {
|
|
65829
|
+
clearTimeout(state3.countdownTimer);
|
|
65830
|
+
state3.countdownTimer = undefined;
|
|
65831
|
+
}
|
|
65832
|
+
if (state3.countdownInterval) {
|
|
65833
|
+
clearInterval(state3.countdownInterval);
|
|
65834
|
+
state3.countdownInterval = undefined;
|
|
65835
|
+
}
|
|
65836
|
+
state3.inFlight = false;
|
|
65837
|
+
state3.countdownStartedAt = undefined;
|
|
65838
|
+
}
|
|
65839
|
+
function cleanup(sessionID) {
|
|
65840
|
+
cancelCountdown(sessionID);
|
|
65841
|
+
sessions.delete(sessionID);
|
|
65842
|
+
}
|
|
65843
|
+
function cancelAllCountdowns() {
|
|
65844
|
+
for (const sessionID of sessions.keys()) {
|
|
65845
|
+
cancelCountdown(sessionID);
|
|
65846
|
+
}
|
|
65847
|
+
}
|
|
65848
|
+
function shutdown() {
|
|
65849
|
+
clearInterval(pruneInterval);
|
|
65850
|
+
cancelAllCountdowns();
|
|
65851
|
+
sessions.clear();
|
|
65852
|
+
}
|
|
65853
|
+
return {
|
|
65854
|
+
getState,
|
|
65855
|
+
getExistingState,
|
|
65856
|
+
cancelCountdown,
|
|
65857
|
+
cleanup,
|
|
65858
|
+
cancelAllCountdowns,
|
|
65859
|
+
shutdown
|
|
65860
|
+
};
|
|
65861
|
+
}
|
|
65862
|
+
|
|
65863
|
+
// src/hooks/task-continuation-enforcer/index.ts
|
|
65864
|
+
function createTaskContinuationEnforcer(ctx, options = {}) {
|
|
65865
|
+
const {
|
|
65866
|
+
backgroundManager,
|
|
65867
|
+
skipAgents = DEFAULT_SKIP_AGENTS,
|
|
65868
|
+
isContinuationStopped
|
|
65869
|
+
} = options;
|
|
65870
|
+
const sessionStateStore = createSessionStateStore();
|
|
65871
|
+
const markRecovering = (sessionID) => {
|
|
65872
|
+
const state3 = sessionStateStore.getState(sessionID);
|
|
65873
|
+
state3.isRecovering = true;
|
|
65874
|
+
sessionStateStore.cancelCountdown(sessionID);
|
|
65875
|
+
log(`[${HOOK_NAME13}] Session marked as recovering`, { sessionID });
|
|
65876
|
+
};
|
|
65877
|
+
const markRecoveryComplete = (sessionID) => {
|
|
65878
|
+
const state3 = sessionStateStore.getExistingState(sessionID);
|
|
65879
|
+
if (state3) {
|
|
65880
|
+
state3.isRecovering = false;
|
|
65881
|
+
log(`[${HOOK_NAME13}] Session recovery complete`, { sessionID });
|
|
65569
65882
|
}
|
|
65570
65883
|
};
|
|
65884
|
+
const handler = createTaskContinuationHandler({
|
|
65885
|
+
ctx,
|
|
65886
|
+
sessionStateStore,
|
|
65887
|
+
backgroundManager,
|
|
65888
|
+
skipAgents,
|
|
65889
|
+
isContinuationStopped
|
|
65890
|
+
});
|
|
65891
|
+
const cancelAllCountdowns = () => {
|
|
65892
|
+
sessionStateStore.cancelAllCountdowns();
|
|
65893
|
+
log(`[${HOOK_NAME13}] All countdowns cancelled`);
|
|
65894
|
+
};
|
|
65571
65895
|
return {
|
|
65572
|
-
|
|
65573
|
-
|
|
65896
|
+
handler,
|
|
65897
|
+
markRecovering,
|
|
65898
|
+
markRecoveryComplete,
|
|
65899
|
+
cancelAllCountdowns
|
|
65900
|
+
};
|
|
65901
|
+
}
|
|
65902
|
+
// src/hooks/task-edit-guard/constants.ts
|
|
65903
|
+
var HOOK_NAME14 = "task-edit-guard";
|
|
65904
|
+
var BLOCKED_PATTERNS = [
|
|
65905
|
+
/sed\s+.*\.matrixx\/plans/,
|
|
65906
|
+
/sed\s+.*\.matrixx\/tasks/,
|
|
65907
|
+
/python3?\s+.*\.matrixx\/plans/,
|
|
65908
|
+
/python3?\s+.*\.matrixx\/tasks/,
|
|
65909
|
+
/echo\s+.*\.matrixx\/plans/,
|
|
65910
|
+
/echo\s+.*\.matrixx\/tasks/,
|
|
65911
|
+
/cat\s*>.*\.matrixx\/plans/,
|
|
65912
|
+
/cat\s*>.*\.matrixx\/tasks/,
|
|
65913
|
+
/mv\s+.*\.matrixx\/plans/,
|
|
65914
|
+
/mv\s+.*\.matrixx\/tasks/,
|
|
65915
|
+
/rm\s+.*\.matrixx\/tasks\/T-.*\.json/,
|
|
65916
|
+
/printf\s+.*\.matrixx\/plans/,
|
|
65917
|
+
/printf\s+.*\.matrixx\/tasks/
|
|
65918
|
+
];
|
|
65919
|
+
// src/hooks/task-edit-guard/hook.ts
|
|
65920
|
+
function createTaskEditGuardHook(ctx) {
|
|
65921
|
+
return {
|
|
65922
|
+
"tool.execute.before": async (input, output) => {
|
|
65923
|
+
if (input.tool?.toLowerCase() !== "bash")
|
|
65574
65924
|
return;
|
|
65575
|
-
|
|
65576
|
-
|
|
65577
|
-
|
|
65578
|
-
if (!enabled)
|
|
65925
|
+
const args = output.args;
|
|
65926
|
+
const cmd = args?.command;
|
|
65927
|
+
if (!cmd)
|
|
65579
65928
|
return;
|
|
65580
|
-
|
|
65581
|
-
|
|
65582
|
-
|
|
65583
|
-
|
|
65929
|
+
const lower = cmd.toLowerCase();
|
|
65930
|
+
if (lower.includes("grep") && !lower.includes("sed") && !lower.includes("python")) {
|
|
65931
|
+
const isOnlyGrep = BLOCKED_PATTERNS.every((rx) => !rx.test(cmd));
|
|
65932
|
+
if (isOnlyGrep)
|
|
65933
|
+
return;
|
|
65934
|
+
}
|
|
65935
|
+
const hit = BLOCKED_PATTERNS.some((rx) => rx.test(cmd));
|
|
65936
|
+
if (!hit)
|
|
65584
65937
|
return;
|
|
65585
|
-
|
|
65586
|
-
|
|
65587
|
-
|
|
65588
|
-
|
|
65589
|
-
|
|
65590
|
-
|
|
65938
|
+
ctx.directory;
|
|
65939
|
+
log(`[${HOOK_NAME14}] BLOCKED raw bash edit`, {
|
|
65940
|
+
sessionID: input.sessionID,
|
|
65941
|
+
command: cmd.slice(0, 120)
|
|
65942
|
+
});
|
|
65943
|
+
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");
|
|
65944
|
+
}
|
|
65945
|
+
};
|
|
65946
|
+
}
|
|
65947
|
+
// src/hooks/task-notepad/constants.ts
|
|
65948
|
+
var HOOK_NAME15 = "task-notepad";
|
|
65949
|
+
var PLAN_DIR_NAME = ".matrixx";
|
|
65950
|
+
var PLANS_SUBDIR = "plans";
|
|
65951
|
+
var NOTEPAD_DIR_NAME = "notepads";
|
|
65952
|
+
var TASKS_SUBDIR = "tasks";
|
|
65953
|
+
var PLAN_EXTENSION = ".md";
|
|
65954
|
+
var NOTEPAD_EXTENSION = ".md";
|
|
65955
|
+
var TODO_TOOL_NAMES = new Set(["todowrite", "TodoWrite"]);
|
|
65956
|
+
var TASK_NOTEPAD_FRAGMENT = "task-notepad";
|
|
65957
|
+
var TODO_FETCH_ERROR_FRAGMENT = "Failed to fetch todos for task-notepad";
|
|
65958
|
+
// src/hooks/task-notepad/hook.ts
|
|
65959
|
+
init_logger();
|
|
65960
|
+
import { existsSync as existsSync45, mkdirSync as mkdirSync16, readdirSync as readdirSync16, readFileSync as readFileSync33, statSync as statSync8, writeFileSync as writeFileSync19 } from "fs";
|
|
65961
|
+
import { dirname as dirname15, join as join52 } from "path";
|
|
65962
|
+
function createTaskNotepadHook(ctx) {
|
|
65963
|
+
return {
|
|
65964
|
+
"tool.execute.after": async (input, output) => {
|
|
65965
|
+
if (!output)
|
|
65966
|
+
return;
|
|
65967
|
+
if (!TODO_TOOL_NAMES.has(input.tool))
|
|
65968
|
+
return;
|
|
65969
|
+
const todos = await fetchTodos(ctx, input.sessionID);
|
|
65970
|
+
const inProgress = todos.find((t) => t.status === "in_progress");
|
|
65971
|
+
const completed = todos.filter((t) => t.status === "completed");
|
|
65972
|
+
if (!inProgress && completed.length === 0)
|
|
65973
|
+
return;
|
|
65974
|
+
const plan = resolveActivePlan(ctx.directory);
|
|
65975
|
+
if (inProgress) {
|
|
65976
|
+
if (!plan) {
|
|
65977
|
+
log(`[${HOOK_NAME15}] No active plan, skipping notepad creation`, {
|
|
65978
|
+
sessionID: input.sessionID
|
|
65979
|
+
});
|
|
65980
|
+
} else {
|
|
65981
|
+
ensureNotepad(ctx.directory, plan.name, inProgress);
|
|
65591
65982
|
}
|
|
65592
65983
|
}
|
|
65593
|
-
|
|
65594
|
-
},
|
|
65595
|
-
event: async (input) => {
|
|
65596
|
-
if (!enabled)
|
|
65984
|
+
if (!plan)
|
|
65597
65985
|
return;
|
|
65598
|
-
const
|
|
65599
|
-
|
|
65600
|
-
if (sessionID && input.event.type === "session.idle") {
|
|
65601
|
-
await syncForSession(sessionID);
|
|
65986
|
+
for (const done of completed) {
|
|
65987
|
+
appendCompletion(ctx.directory, plan.name, done);
|
|
65602
65988
|
}
|
|
65603
|
-
}
|
|
65604
|
-
|
|
65989
|
+
}
|
|
65990
|
+
};
|
|
65991
|
+
}
|
|
65992
|
+
async function fetchTodos(ctx, sessionID) {
|
|
65993
|
+
try {
|
|
65994
|
+
const response = await ctx.client.session.todo({ path: { id: sessionID } });
|
|
65995
|
+
const payload = response;
|
|
65996
|
+
if (Array.isArray(payload?.data)) {
|
|
65997
|
+
return payload.data;
|
|
65998
|
+
}
|
|
65999
|
+
if (Array.isArray(response)) {
|
|
66000
|
+
return response;
|
|
66001
|
+
}
|
|
66002
|
+
return [];
|
|
66003
|
+
} catch (err) {
|
|
66004
|
+
log(`[${HOOK_NAME15}] ${TODO_FETCH_ERROR_FRAGMENT}`, {
|
|
66005
|
+
sessionID,
|
|
66006
|
+
error: String(err)
|
|
66007
|
+
});
|
|
66008
|
+
return [];
|
|
66009
|
+
}
|
|
66010
|
+
}
|
|
66011
|
+
function resolveActivePlan(directory) {
|
|
66012
|
+
const plansDir = join52(directory, PLAN_DIR_NAME, PLANS_SUBDIR);
|
|
66013
|
+
if (!existsSync45(plansDir))
|
|
66014
|
+
return null;
|
|
66015
|
+
let entries;
|
|
66016
|
+
try {
|
|
66017
|
+
entries = readdirSync16(plansDir);
|
|
66018
|
+
} catch {
|
|
66019
|
+
return null;
|
|
66020
|
+
}
|
|
66021
|
+
let best = null;
|
|
66022
|
+
for (const entry of entries) {
|
|
66023
|
+
if (!entry.endsWith(PLAN_EXTENSION))
|
|
66024
|
+
continue;
|
|
66025
|
+
const fullPath = join52(plansDir, entry);
|
|
66026
|
+
try {
|
|
66027
|
+
const stats = statSync8(fullPath);
|
|
66028
|
+
if (!stats.isFile())
|
|
66029
|
+
continue;
|
|
66030
|
+
const candidate = {
|
|
66031
|
+
name: entry.slice(0, -PLAN_EXTENSION.length),
|
|
66032
|
+
path: fullPath,
|
|
66033
|
+
mtimeMs: stats.mtimeMs
|
|
66034
|
+
};
|
|
66035
|
+
if (!best || candidate.mtimeMs > best.mtimeMs) {
|
|
66036
|
+
best = candidate;
|
|
66037
|
+
}
|
|
66038
|
+
} catch {}
|
|
66039
|
+
}
|
|
66040
|
+
return best;
|
|
66041
|
+
}
|
|
66042
|
+
function ensureNotepad(directory, planName, todo) {
|
|
66043
|
+
const notepadPath = notepadPathFor(directory, planName, todo.id);
|
|
66044
|
+
if (existsSync45(notepadPath))
|
|
66045
|
+
return;
|
|
66046
|
+
mkdirSync16(dirname15(notepadPath), { recursive: true });
|
|
66047
|
+
const content = renderNotepad(todo);
|
|
66048
|
+
writeFileSync19(notepadPath, content, "utf-8");
|
|
66049
|
+
log(`[${HOOK_NAME15}] Created task notepad`, { plan: planName, taskID: todo.id });
|
|
66050
|
+
}
|
|
66051
|
+
function appendCompletion(directory, planName, todo) {
|
|
66052
|
+
const notepadPath = notepadPathFor(directory, planName, todo.id);
|
|
66053
|
+
if (!existsSync45(notepadPath))
|
|
66054
|
+
return;
|
|
66055
|
+
const existing = readFileSync33(notepadPath, "utf-8");
|
|
66056
|
+
if (existing.includes("**Status**: completed"))
|
|
66057
|
+
return;
|
|
66058
|
+
const stamp = new Date().toISOString();
|
|
66059
|
+
const updated = `${existing}
|
|
66060
|
+
## Completion
|
|
66061
|
+
- completed_at: ${stamp}
|
|
66062
|
+
- status: completed
|
|
66063
|
+
`;
|
|
66064
|
+
writeFileSync19(notepadPath, updated, "utf-8");
|
|
66065
|
+
log(`[${HOOK_NAME15}] Appended completion to notepad`, { plan: planName, taskID: todo.id });
|
|
66066
|
+
}
|
|
66067
|
+
function notepadPathFor(directory, planName, taskID) {
|
|
66068
|
+
return join52(directory, PLAN_DIR_NAME, NOTEPAD_DIR_NAME, planName, TASKS_SUBDIR, `${taskID}${NOTEPAD_EXTENSION}`);
|
|
66069
|
+
}
|
|
66070
|
+
function renderNotepad(todo) {
|
|
66071
|
+
const priority = todo.priority ?? "medium";
|
|
66072
|
+
return `# Task: ${todo.content}
|
|
66073
|
+
|
|
66074
|
+
**Plan**: ${TASK_NOTEPAD_FRAGMENT}
|
|
66075
|
+
**Task ID**: ${todo.id}
|
|
66076
|
+
**Priority**: ${priority}
|
|
66077
|
+
**Status**: ${todo.status}
|
|
66078
|
+
**Started**: ${new Date().toISOString()}
|
|
66079
|
+
|
|
66080
|
+
## Findings
|
|
66081
|
+
|
|
66082
|
+
(Record what you learn about this code as you work \u2014 patterns, conventions, gotchas, useful references.)
|
|
66083
|
+
|
|
66084
|
+
## Blockers
|
|
66085
|
+
|
|
66086
|
+
(Anything blocking progress. Be specific: file path, line, error, workaround if any.)
|
|
66087
|
+
|
|
66088
|
+
## Questions
|
|
66089
|
+
|
|
66090
|
+
(Open questions to revisit later. Don't lose them when context compacts.)
|
|
66091
|
+
|
|
66092
|
+
## Results
|
|
66093
|
+
|
|
66094
|
+
(Summary on completion. What changed, what was verified, what remains.)
|
|
66095
|
+
`;
|
|
66096
|
+
}
|
|
66097
|
+
// src/hooks/task-resume-info/hook.ts
|
|
66098
|
+
var TARGET_TOOLS2 = ["task", "Task", "task_tool", "delegate_agent"];
|
|
66099
|
+
var SESSION_ID_PATTERNS = [
|
|
66100
|
+
/Session ID: (ses_[a-zA-Z0-9_-]+)/,
|
|
66101
|
+
/session_id: (ses_[a-zA-Z0-9_-]+)/,
|
|
66102
|
+
/<task_metadata>\s*session_id: (ses_[a-zA-Z0-9_-]+)/,
|
|
66103
|
+
/sessionId: (ses_[a-zA-Z0-9_-]+)/
|
|
66104
|
+
];
|
|
66105
|
+
function extractSessionId(output) {
|
|
66106
|
+
for (const pattern of SESSION_ID_PATTERNS) {
|
|
66107
|
+
const match = output.match(pattern);
|
|
66108
|
+
if (match)
|
|
66109
|
+
return match[1] ?? null;
|
|
66110
|
+
}
|
|
66111
|
+
return null;
|
|
66112
|
+
}
|
|
66113
|
+
function createTaskResumeInfoHook() {
|
|
66114
|
+
const toolExecuteAfter = async (input, output) => {
|
|
66115
|
+
if (!TARGET_TOOLS2.includes(input.tool))
|
|
66116
|
+
return;
|
|
66117
|
+
const outputText = output.output ?? "";
|
|
66118
|
+
if (outputText.startsWith("Error:") || outputText.startsWith("Failed"))
|
|
66119
|
+
return;
|
|
66120
|
+
if (outputText.includes(`
|
|
66121
|
+
to continue:`))
|
|
66122
|
+
return;
|
|
66123
|
+
const sessionId = extractSessionId(outputText);
|
|
66124
|
+
if (!sessionId)
|
|
66125
|
+
return;
|
|
66126
|
+
output.output = outputText.trimEnd() + `
|
|
66127
|
+
|
|
66128
|
+
to continue: task(session_id="${sessionId}", prompt="...")`;
|
|
65605
66129
|
};
|
|
66130
|
+
return {
|
|
66131
|
+
"tool.execute.after": toolExecuteAfter
|
|
66132
|
+
};
|
|
66133
|
+
}
|
|
66134
|
+
// src/shared/task-system-gating.ts
|
|
66135
|
+
var TASK_SYSTEM_DEFAULT = true;
|
|
66136
|
+
function isTaskSystemEnabled(config2) {
|
|
66137
|
+
return config2?.experimental?.task_system ?? TASK_SYSTEM_DEFAULT;
|
|
65606
66138
|
}
|
|
66139
|
+
|
|
65607
66140
|
// src/hooks/tasks-todowrite-disabler/constants.ts
|
|
65608
66141
|
var BLOCKED_TOOLS2 = ["TodoWrite", "TodoRead"];
|
|
65609
66142
|
var REPLACEMENT_MESSAGE = `TodoRead/TodoWrite are DISABLED because experimental.task_system is enabled.
|
|
@@ -65637,10 +66170,10 @@ DO NOT retry TodoWrite. Convert to TaskCreate NOW.`;
|
|
|
65637
66170
|
|
|
65638
66171
|
// src/hooks/tasks-todowrite-disabler/hook.ts
|
|
65639
66172
|
function createTasksTodowriteDisablerHook(config2) {
|
|
65640
|
-
const
|
|
66173
|
+
const enabled = isTaskSystemEnabled(config2);
|
|
65641
66174
|
return {
|
|
65642
66175
|
"tool.execute.before": async (input, _output) => {
|
|
65643
|
-
if (!
|
|
66176
|
+
if (!enabled) {
|
|
65644
66177
|
return;
|
|
65645
66178
|
}
|
|
65646
66179
|
const toolName = input.tool;
|
|
@@ -66122,22 +66655,22 @@ function createThinkingBlockValidatorHook() {
|
|
|
66122
66655
|
init_logger();
|
|
66123
66656
|
|
|
66124
66657
|
// src/hooks/todo-continuation-enforcer/constants.ts
|
|
66125
|
-
var
|
|
66126
|
-
var
|
|
66127
|
-
var
|
|
66658
|
+
var HOOK_NAME16 = "todo-continuation-enforcer";
|
|
66659
|
+
var DEFAULT_SKIP_AGENTS2 = ["oracle", "compaction"];
|
|
66660
|
+
var CONTINUATION_PROMPT3 = `${createSystemDirective(SystemDirectiveTypes.TODO_CONTINUATION)}
|
|
66128
66661
|
|
|
66129
66662
|
Incomplete tasks remain in your todo list. Continue working on the next pending task.
|
|
66130
66663
|
|
|
66131
66664
|
- Proceed without asking for permission
|
|
66132
66665
|
- Mark each task complete when finished
|
|
66133
66666
|
- Do not stop until all tasks are done`;
|
|
66134
|
-
var
|
|
66135
|
-
var
|
|
66136
|
-
var
|
|
66137
|
-
var
|
|
66138
|
-
var
|
|
66139
|
-
var
|
|
66140
|
-
var
|
|
66667
|
+
var COUNTDOWN_SECONDS2 = 2;
|
|
66668
|
+
var TOAST_DURATION_MS2 = 900;
|
|
66669
|
+
var COUNTDOWN_GRACE_PERIOD_MS2 = 500;
|
|
66670
|
+
var ABORT_WINDOW_MS2 = 3000;
|
|
66671
|
+
var CONTINUATION_COOLDOWN_MS3 = 30000;
|
|
66672
|
+
var MAX_CONSECUTIVE_FAILURES2 = 5;
|
|
66673
|
+
var FAILURE_RESET_WINDOW_MS2 = 5 * 60 * 1000;
|
|
66141
66674
|
|
|
66142
66675
|
// src/hooks/todo-continuation-enforcer/handler.ts
|
|
66143
66676
|
init_logger();
|
|
@@ -66146,7 +66679,7 @@ init_logger();
|
|
|
66146
66679
|
init_logger();
|
|
66147
66680
|
|
|
66148
66681
|
// src/hooks/todo-continuation-enforcer/abort-detection.ts
|
|
66149
|
-
function
|
|
66682
|
+
function isLastAssistantMessageAborted2(messages) {
|
|
66150
66683
|
if (!messages || messages.length === 0)
|
|
66151
66684
|
return false;
|
|
66152
66685
|
for (let i = messages.length - 1;i >= 0; i--) {
|
|
@@ -66172,28 +66705,28 @@ function getIncompleteCount(todos) {
|
|
|
66172
66705
|
}
|
|
66173
66706
|
|
|
66174
66707
|
// src/hooks/todo-continuation-enforcer/continuation-injection.ts
|
|
66175
|
-
function
|
|
66708
|
+
function hasWritePermission2(tools) {
|
|
66176
66709
|
const editPermission = tools?.edit;
|
|
66177
66710
|
const writePermission = tools?.write;
|
|
66178
66711
|
return !tools || editPermission !== false && editPermission !== "deny" && writePermission !== false && writePermission !== "deny";
|
|
66179
66712
|
}
|
|
66180
|
-
async function
|
|
66713
|
+
async function injectContinuation2(args) {
|
|
66181
66714
|
const {
|
|
66182
66715
|
ctx,
|
|
66183
66716
|
sessionID,
|
|
66184
66717
|
backgroundManager,
|
|
66185
|
-
skipAgents =
|
|
66718
|
+
skipAgents = DEFAULT_SKIP_AGENTS2,
|
|
66186
66719
|
resolvedInfo,
|
|
66187
66720
|
sessionStateStore
|
|
66188
66721
|
} = args;
|
|
66189
66722
|
const state3 = sessionStateStore.getExistingState(sessionID);
|
|
66190
66723
|
if (state3?.isRecovering) {
|
|
66191
|
-
log(`[${
|
|
66724
|
+
log(`[${HOOK_NAME16}] Skipped injection: in recovery`, { sessionID });
|
|
66192
66725
|
return;
|
|
66193
66726
|
}
|
|
66194
66727
|
const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((task) => task.status === "running") : false;
|
|
66195
66728
|
if (hasRunningBgTasks) {
|
|
66196
|
-
log(`[${
|
|
66729
|
+
log(`[${HOOK_NAME16}] Skipped injection: background tasks running`, { sessionID });
|
|
66197
66730
|
return;
|
|
66198
66731
|
}
|
|
66199
66732
|
let todos = [];
|
|
@@ -66201,12 +66734,12 @@ async function injectContinuation(args) {
|
|
|
66201
66734
|
const response = await ctx.client.session.todo({ path: { id: sessionID } });
|
|
66202
66735
|
todos = normalizeSDKResponse(response, [], { preferResponseOnMissingData: true });
|
|
66203
66736
|
} catch (error51) {
|
|
66204
|
-
log(`[${
|
|
66737
|
+
log(`[${HOOK_NAME16}] Failed to fetch todos`, { sessionID, error: String(error51) });
|
|
66205
66738
|
return;
|
|
66206
66739
|
}
|
|
66207
66740
|
const freshIncompleteCount = getIncompleteCount(todos);
|
|
66208
66741
|
if (freshIncompleteCount === 0) {
|
|
66209
|
-
log(`[${
|
|
66742
|
+
log(`[${HOOK_NAME16}] Skipped injection: no incomplete todos`, { sessionID });
|
|
66210
66743
|
return;
|
|
66211
66744
|
}
|
|
66212
66745
|
let agentName = resolvedInfo?.agent;
|
|
@@ -66229,17 +66762,17 @@ async function injectContinuation(args) {
|
|
|
66229
66762
|
tools = tools ?? previousMessage?.tools;
|
|
66230
66763
|
}
|
|
66231
66764
|
if (agentName && skipAgents.some((s) => getAgentConfigKey(s) === getAgentConfigKey(agentName))) {
|
|
66232
|
-
log(`[${
|
|
66765
|
+
log(`[${HOOK_NAME16}] Skipped: agent in skipAgents list`, { sessionID, agent: agentName });
|
|
66233
66766
|
return;
|
|
66234
66767
|
}
|
|
66235
|
-
if (!
|
|
66236
|
-
log(`[${
|
|
66768
|
+
if (!hasWritePermission2(tools)) {
|
|
66769
|
+
log(`[${HOOK_NAME16}] Skipped: agent lacks write permission`, { sessionID, agent: agentName });
|
|
66237
66770
|
return;
|
|
66238
66771
|
}
|
|
66239
66772
|
const incompleteTodos = todos.filter((todo) => todo.status !== "completed" && todo.status !== "cancelled");
|
|
66240
66773
|
const todoList = incompleteTodos.map((todo) => `- [${todo.status}] ${todo.content}`).join(`
|
|
66241
66774
|
`);
|
|
66242
|
-
const prompt = `${
|
|
66775
|
+
const prompt = `${CONTINUATION_PROMPT3}
|
|
66243
66776
|
|
|
66244
66777
|
[Status: ${todos.length - freshIncompleteCount}/${todos.length} completed, ${freshIncompleteCount} remaining]
|
|
66245
66778
|
|
|
@@ -66250,7 +66783,7 @@ ${todoList}`;
|
|
|
66250
66783
|
injectionState.inFlight = true;
|
|
66251
66784
|
}
|
|
66252
66785
|
try {
|
|
66253
|
-
log(`[${
|
|
66786
|
+
log(`[${HOOK_NAME16}] Injecting continuation`, {
|
|
66254
66787
|
sessionID,
|
|
66255
66788
|
agent: agentName,
|
|
66256
66789
|
model,
|
|
@@ -66265,14 +66798,14 @@ ${todoList}`;
|
|
|
66265
66798
|
},
|
|
66266
66799
|
query: { directory: ctx.directory }
|
|
66267
66800
|
});
|
|
66268
|
-
log(`[${
|
|
66801
|
+
log(`[${HOOK_NAME16}] Injection successful`, { sessionID });
|
|
66269
66802
|
if (injectionState) {
|
|
66270
66803
|
injectionState.inFlight = false;
|
|
66271
66804
|
injectionState.lastInjectedAt = Date.now();
|
|
66272
66805
|
injectionState.consecutiveFailures = 0;
|
|
66273
66806
|
}
|
|
66274
66807
|
} catch (error51) {
|
|
66275
|
-
log(`[${
|
|
66808
|
+
log(`[${HOOK_NAME16}] Injection failed`, { sessionID, error: String(error51) });
|
|
66276
66809
|
if (injectionState) {
|
|
66277
66810
|
injectionState.inFlight = false;
|
|
66278
66811
|
injectionState.lastInjectedAt = Date.now();
|
|
@@ -66282,19 +66815,19 @@ ${todoList}`;
|
|
|
66282
66815
|
}
|
|
66283
66816
|
|
|
66284
66817
|
// src/hooks/todo-continuation-enforcer/countdown.ts
|
|
66285
|
-
async function
|
|
66818
|
+
async function showCountdownToast2(ctx, seconds, incompleteCount) {
|
|
66286
66819
|
await ctx.client.tui.showToast({
|
|
66287
66820
|
body: {
|
|
66288
66821
|
title: "Todo Continuation",
|
|
66289
66822
|
message: `Resuming in ${seconds}s... (${incompleteCount} tasks remaining)`,
|
|
66290
66823
|
variant: "warning",
|
|
66291
|
-
duration:
|
|
66824
|
+
duration: TOAST_DURATION_MS2
|
|
66292
66825
|
}
|
|
66293
66826
|
}).catch((err) => {
|
|
66294
66827
|
log("[todo-continuation] Countdown toast failed:", err);
|
|
66295
66828
|
});
|
|
66296
66829
|
}
|
|
66297
|
-
function
|
|
66830
|
+
function startCountdown2(args) {
|
|
66298
66831
|
const {
|
|
66299
66832
|
ctx,
|
|
66300
66833
|
sessionID,
|
|
@@ -66306,18 +66839,18 @@ function startCountdown(args) {
|
|
|
66306
66839
|
} = args;
|
|
66307
66840
|
const state3 = sessionStateStore.getState(sessionID);
|
|
66308
66841
|
sessionStateStore.cancelCountdown(sessionID);
|
|
66309
|
-
let secondsRemaining =
|
|
66310
|
-
|
|
66842
|
+
let secondsRemaining = COUNTDOWN_SECONDS2;
|
|
66843
|
+
showCountdownToast2(ctx, secondsRemaining, incompleteCount);
|
|
66311
66844
|
state3.countdownStartedAt = Date.now();
|
|
66312
66845
|
state3.countdownInterval = setInterval(() => {
|
|
66313
66846
|
secondsRemaining--;
|
|
66314
66847
|
if (secondsRemaining > 0) {
|
|
66315
|
-
|
|
66848
|
+
showCountdownToast2(ctx, secondsRemaining, incompleteCount);
|
|
66316
66849
|
}
|
|
66317
66850
|
}, 1000);
|
|
66318
66851
|
state3.countdownTimer = setTimeout(() => {
|
|
66319
66852
|
sessionStateStore.cancelCountdown(sessionID);
|
|
66320
|
-
|
|
66853
|
+
injectContinuation2({
|
|
66321
66854
|
ctx,
|
|
66322
66855
|
sessionID,
|
|
66323
66856
|
backgroundManager,
|
|
@@ -66325,34 +66858,34 @@ function startCountdown(args) {
|
|
|
66325
66858
|
resolvedInfo,
|
|
66326
66859
|
sessionStateStore
|
|
66327
66860
|
});
|
|
66328
|
-
},
|
|
66329
|
-
log(`[${
|
|
66861
|
+
}, COUNTDOWN_SECONDS2 * 1000);
|
|
66862
|
+
log(`[${HOOK_NAME16}] Countdown started`, {
|
|
66330
66863
|
sessionID,
|
|
66331
|
-
seconds:
|
|
66864
|
+
seconds: COUNTDOWN_SECONDS2,
|
|
66332
66865
|
incompleteCount
|
|
66333
66866
|
});
|
|
66334
66867
|
}
|
|
66335
66868
|
|
|
66336
66869
|
// src/hooks/todo-continuation-enforcer/idle-event.ts
|
|
66337
|
-
async function
|
|
66870
|
+
async function handleSessionIdle2(args) {
|
|
66338
66871
|
const {
|
|
66339
66872
|
ctx,
|
|
66340
66873
|
sessionID,
|
|
66341
66874
|
sessionStateStore,
|
|
66342
66875
|
backgroundManager,
|
|
66343
|
-
skipAgents =
|
|
66876
|
+
skipAgents = DEFAULT_SKIP_AGENTS2,
|
|
66344
66877
|
isContinuationStopped
|
|
66345
66878
|
} = args;
|
|
66346
|
-
log(`[${
|
|
66879
|
+
log(`[${HOOK_NAME16}] session.idle`, { sessionID });
|
|
66347
66880
|
const state3 = sessionStateStore.getState(sessionID);
|
|
66348
66881
|
if (state3.isRecovering) {
|
|
66349
|
-
log(`[${
|
|
66882
|
+
log(`[${HOOK_NAME16}] Skipped: in recovery`, { sessionID });
|
|
66350
66883
|
return;
|
|
66351
66884
|
}
|
|
66352
66885
|
if (state3.abortDetectedAt) {
|
|
66353
66886
|
const timeSinceAbort = Date.now() - state3.abortDetectedAt;
|
|
66354
|
-
if (timeSinceAbort <
|
|
66355
|
-
log(`[${
|
|
66887
|
+
if (timeSinceAbort < ABORT_WINDOW_MS2) {
|
|
66888
|
+
log(`[${HOOK_NAME16}] Skipped: abort detected via event ${timeSinceAbort}ms ago`, { sessionID });
|
|
66356
66889
|
state3.abortDetectedAt = undefined;
|
|
66357
66890
|
return;
|
|
66358
66891
|
}
|
|
@@ -66360,7 +66893,7 @@ async function handleSessionIdle(args) {
|
|
|
66360
66893
|
}
|
|
66361
66894
|
const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((task) => task.status === "running") : false;
|
|
66362
66895
|
if (hasRunningBgTasks) {
|
|
66363
|
-
log(`[${
|
|
66896
|
+
log(`[${HOOK_NAME16}] Skipped: background tasks running`, { sessionID });
|
|
66364
66897
|
return;
|
|
66365
66898
|
}
|
|
66366
66899
|
try {
|
|
@@ -66369,52 +66902,52 @@ async function handleSessionIdle(args) {
|
|
|
66369
66902
|
query: { directory: ctx.directory }
|
|
66370
66903
|
});
|
|
66371
66904
|
const messages = normalizeSDKResponse(messagesResp, []);
|
|
66372
|
-
if (
|
|
66373
|
-
log(`[${
|
|
66905
|
+
if (isLastAssistantMessageAborted2(messages)) {
|
|
66906
|
+
log(`[${HOOK_NAME16}] Skipped: last assistant message was aborted (API fallback)`, { sessionID });
|
|
66374
66907
|
return;
|
|
66375
66908
|
}
|
|
66376
66909
|
} catch (error51) {
|
|
66377
|
-
log(`[${
|
|
66910
|
+
log(`[${HOOK_NAME16}] Messages fetch failed, continuing`, { sessionID, error: String(error51) });
|
|
66378
66911
|
}
|
|
66379
66912
|
let todos = [];
|
|
66380
66913
|
try {
|
|
66381
66914
|
const response = await ctx.client.session.todo({ path: { id: sessionID } });
|
|
66382
66915
|
todos = normalizeSDKResponse(response, [], { preferResponseOnMissingData: true });
|
|
66383
66916
|
} catch (error51) {
|
|
66384
|
-
log(`[${
|
|
66917
|
+
log(`[${HOOK_NAME16}] Todo fetch failed`, { sessionID, error: String(error51) });
|
|
66385
66918
|
return;
|
|
66386
66919
|
}
|
|
66387
66920
|
if (!todos || todos.length === 0) {
|
|
66388
|
-
log(`[${
|
|
66921
|
+
log(`[${HOOK_NAME16}] No todos`, { sessionID });
|
|
66389
66922
|
return;
|
|
66390
66923
|
}
|
|
66391
66924
|
const incompleteCount = getIncompleteCount(todos);
|
|
66392
66925
|
if (incompleteCount === 0) {
|
|
66393
|
-
log(`[${
|
|
66926
|
+
log(`[${HOOK_NAME16}] All todos complete`, { sessionID, total: todos.length });
|
|
66394
66927
|
return;
|
|
66395
66928
|
}
|
|
66396
66929
|
if (state3.inFlight) {
|
|
66397
|
-
log(`[${
|
|
66930
|
+
log(`[${HOOK_NAME16}] Skipped: injection in flight`, { sessionID });
|
|
66398
66931
|
return;
|
|
66399
66932
|
}
|
|
66400
|
-
if (state3.consecutiveFailures >=
|
|
66933
|
+
if (state3.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES2 && state3.lastInjectedAt && Date.now() - state3.lastInjectedAt >= FAILURE_RESET_WINDOW_MS2) {
|
|
66401
66934
|
state3.consecutiveFailures = 0;
|
|
66402
|
-
log(`[${
|
|
66935
|
+
log(`[${HOOK_NAME16}] Reset consecutive failures after recovery window`, {
|
|
66403
66936
|
sessionID,
|
|
66404
|
-
failureResetWindowMs:
|
|
66937
|
+
failureResetWindowMs: FAILURE_RESET_WINDOW_MS2
|
|
66405
66938
|
});
|
|
66406
66939
|
}
|
|
66407
|
-
if (state3.consecutiveFailures >=
|
|
66408
|
-
log(`[${
|
|
66940
|
+
if (state3.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES2) {
|
|
66941
|
+
log(`[${HOOK_NAME16}] Skipped: max consecutive failures reached`, {
|
|
66409
66942
|
sessionID,
|
|
66410
66943
|
consecutiveFailures: state3.consecutiveFailures,
|
|
66411
|
-
maxConsecutiveFailures:
|
|
66944
|
+
maxConsecutiveFailures: MAX_CONSECUTIVE_FAILURES2
|
|
66412
66945
|
});
|
|
66413
66946
|
return;
|
|
66414
66947
|
}
|
|
66415
|
-
const effectiveCooldown =
|
|
66948
|
+
const effectiveCooldown = CONTINUATION_COOLDOWN_MS3 * 2 ** Math.min(state3.consecutiveFailures, 5);
|
|
66416
66949
|
if (state3.lastInjectedAt && Date.now() - state3.lastInjectedAt < effectiveCooldown) {
|
|
66417
|
-
log(`[${
|
|
66950
|
+
log(`[${HOOK_NAME16}] Skipped: cooldown active`, {
|
|
66418
66951
|
sessionID,
|
|
66419
66952
|
effectiveCooldown,
|
|
66420
66953
|
consecutiveFailures: state3.consecutiveFailures
|
|
@@ -66444,23 +66977,23 @@ async function handleSessionIdle(args) {
|
|
|
66444
66977
|
}
|
|
66445
66978
|
}
|
|
66446
66979
|
} catch (error51) {
|
|
66447
|
-
log(`[${
|
|
66980
|
+
log(`[${HOOK_NAME16}] Failed to fetch messages for agent check`, { sessionID, error: String(error51) });
|
|
66448
66981
|
}
|
|
66449
|
-
log(`[${
|
|
66982
|
+
log(`[${HOOK_NAME16}] Agent check`, { sessionID, agentName: resolvedInfo?.agent, skipAgents, hasCompactionMessage });
|
|
66450
66983
|
const resolvedAgentName = resolvedInfo?.agent;
|
|
66451
66984
|
if (resolvedAgentName && skipAgents.some((s) => getAgentConfigKey(s) === getAgentConfigKey(resolvedAgentName))) {
|
|
66452
|
-
log(`[${
|
|
66985
|
+
log(`[${HOOK_NAME16}] Skipped: agent in skipAgents list`, { sessionID, agent: resolvedAgentName });
|
|
66453
66986
|
return;
|
|
66454
66987
|
}
|
|
66455
66988
|
if (hasCompactionMessage && !resolvedInfo?.agent) {
|
|
66456
|
-
log(`[${
|
|
66989
|
+
log(`[${HOOK_NAME16}] Skipped: compaction occurred but no agent info resolved`, { sessionID });
|
|
66457
66990
|
return;
|
|
66458
66991
|
}
|
|
66459
66992
|
if (isContinuationStopped?.(sessionID)) {
|
|
66460
|
-
log(`[${
|
|
66993
|
+
log(`[${HOOK_NAME16}] Skipped: continuation stopped for session`, { sessionID });
|
|
66461
66994
|
return;
|
|
66462
66995
|
}
|
|
66463
|
-
|
|
66996
|
+
startCountdown2({
|
|
66464
66997
|
ctx,
|
|
66465
66998
|
sessionID,
|
|
66466
66999
|
incompleteCount,
|
|
@@ -66474,7 +67007,7 @@ async function handleSessionIdle(args) {
|
|
|
66474
67007
|
|
|
66475
67008
|
// src/hooks/todo-continuation-enforcer/non-idle-events.ts
|
|
66476
67009
|
init_logger();
|
|
66477
|
-
function
|
|
67010
|
+
function handleNonIdleEvent2(args) {
|
|
66478
67011
|
const { eventType, properties, sessionStateStore } = args;
|
|
66479
67012
|
if (eventType === "message.updated") {
|
|
66480
67013
|
const info = properties?.info;
|
|
@@ -66486,8 +67019,8 @@ function handleNonIdleEvent(args) {
|
|
|
66486
67019
|
const state3 = sessionStateStore.getExistingState(sessionID);
|
|
66487
67020
|
if (state3?.countdownStartedAt) {
|
|
66488
67021
|
const elapsed = Date.now() - state3.countdownStartedAt;
|
|
66489
|
-
if (elapsed <
|
|
66490
|
-
log(`[${
|
|
67022
|
+
if (elapsed < COUNTDOWN_GRACE_PERIOD_MS2) {
|
|
67023
|
+
log(`[${HOOK_NAME16}] Ignoring user message in grace period`, { sessionID, elapsed });
|
|
66491
67024
|
return;
|
|
66492
67025
|
}
|
|
66493
67026
|
}
|
|
@@ -66531,7 +67064,7 @@ function handleNonIdleEvent(args) {
|
|
|
66531
67064
|
const sessionInfo = properties?.info;
|
|
66532
67065
|
if (sessionInfo?.id) {
|
|
66533
67066
|
sessionStateStore.cleanup(sessionInfo.id);
|
|
66534
|
-
log(`[${
|
|
67067
|
+
log(`[${HOOK_NAME16}] Session deleted: cleaned up`, { sessionID: sessionInfo.id });
|
|
66535
67068
|
}
|
|
66536
67069
|
return;
|
|
66537
67070
|
}
|
|
@@ -66543,7 +67076,7 @@ function createTodoContinuationHandler(args) {
|
|
|
66543
67076
|
ctx,
|
|
66544
67077
|
sessionStateStore,
|
|
66545
67078
|
backgroundManager,
|
|
66546
|
-
skipAgents =
|
|
67079
|
+
skipAgents = DEFAULT_SKIP_AGENTS2,
|
|
66547
67080
|
isContinuationStopped
|
|
66548
67081
|
} = args;
|
|
66549
67082
|
return async ({ event }) => {
|
|
@@ -66556,17 +67089,17 @@ function createTodoContinuationHandler(args) {
|
|
|
66556
67089
|
if (error51?.name === "MessageAbortedError" || error51?.name === "AbortError") {
|
|
66557
67090
|
const state3 = sessionStateStore.getState(sessionID);
|
|
66558
67091
|
state3.abortDetectedAt = Date.now();
|
|
66559
|
-
log(`[${
|
|
67092
|
+
log(`[${HOOK_NAME16}] Abort detected via session.error`, { sessionID, errorName: error51.name });
|
|
66560
67093
|
}
|
|
66561
67094
|
sessionStateStore.cancelCountdown(sessionID);
|
|
66562
|
-
log(`[${
|
|
67095
|
+
log(`[${HOOK_NAME16}] session.error`, { sessionID });
|
|
66563
67096
|
return;
|
|
66564
67097
|
}
|
|
66565
67098
|
if (event.type === "session.idle") {
|
|
66566
67099
|
const sessionID = props?.sessionID;
|
|
66567
67100
|
if (!sessionID)
|
|
66568
67101
|
return;
|
|
66569
|
-
await
|
|
67102
|
+
await handleSessionIdle2({
|
|
66570
67103
|
ctx,
|
|
66571
67104
|
sessionID,
|
|
66572
67105
|
sessionStateStore,
|
|
@@ -66576,7 +67109,7 @@ function createTodoContinuationHandler(args) {
|
|
|
66576
67109
|
});
|
|
66577
67110
|
return;
|
|
66578
67111
|
}
|
|
66579
|
-
|
|
67112
|
+
handleNonIdleEvent2({
|
|
66580
67113
|
eventType: event.type,
|
|
66581
67114
|
properties: props,
|
|
66582
67115
|
sessionStateStore
|
|
@@ -66585,20 +67118,20 @@ function createTodoContinuationHandler(args) {
|
|
|
66585
67118
|
}
|
|
66586
67119
|
|
|
66587
67120
|
// src/hooks/todo-continuation-enforcer/session-state.ts
|
|
66588
|
-
var
|
|
66589
|
-
var
|
|
66590
|
-
function
|
|
67121
|
+
var SESSION_STATE_TTL_MS2 = 10 * 60 * 1000;
|
|
67122
|
+
var SESSION_STATE_PRUNE_INTERVAL_MS2 = 2 * 60 * 1000;
|
|
67123
|
+
function createSessionStateStore2() {
|
|
66591
67124
|
const sessions = new Map;
|
|
66592
67125
|
let pruneInterval;
|
|
66593
67126
|
pruneInterval = setInterval(() => {
|
|
66594
67127
|
const now = Date.now();
|
|
66595
67128
|
for (const [sessionID, tracked] of sessions.entries()) {
|
|
66596
|
-
if (now - tracked.lastAccessedAt >
|
|
67129
|
+
if (now - tracked.lastAccessedAt > SESSION_STATE_TTL_MS2) {
|
|
66597
67130
|
cancelCountdown(sessionID);
|
|
66598
67131
|
sessions.delete(sessionID);
|
|
66599
67132
|
}
|
|
66600
67133
|
}
|
|
66601
|
-
},
|
|
67134
|
+
}, SESSION_STATE_PRUNE_INTERVAL_MS2);
|
|
66602
67135
|
if (typeof pruneInterval === "object" && "unref" in pruneInterval) {
|
|
66603
67136
|
pruneInterval.unref();
|
|
66604
67137
|
}
|
|
@@ -66666,21 +67199,21 @@ function createSessionStateStore() {
|
|
|
66666
67199
|
function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
66667
67200
|
const {
|
|
66668
67201
|
backgroundManager,
|
|
66669
|
-
skipAgents =
|
|
67202
|
+
skipAgents = DEFAULT_SKIP_AGENTS2,
|
|
66670
67203
|
isContinuationStopped
|
|
66671
67204
|
} = options;
|
|
66672
|
-
const sessionStateStore =
|
|
67205
|
+
const sessionStateStore = createSessionStateStore2();
|
|
66673
67206
|
const markRecovering = (sessionID) => {
|
|
66674
67207
|
const state3 = sessionStateStore.getState(sessionID);
|
|
66675
67208
|
state3.isRecovering = true;
|
|
66676
67209
|
sessionStateStore.cancelCountdown(sessionID);
|
|
66677
|
-
log(`[${
|
|
67210
|
+
log(`[${HOOK_NAME16}] Session marked as recovering`, { sessionID });
|
|
66678
67211
|
};
|
|
66679
67212
|
const markRecoveryComplete = (sessionID) => {
|
|
66680
67213
|
const state3 = sessionStateStore.getExistingState(sessionID);
|
|
66681
67214
|
if (state3) {
|
|
66682
67215
|
state3.isRecovering = false;
|
|
66683
|
-
log(`[${
|
|
67216
|
+
log(`[${HOOK_NAME16}] Session recovery complete`, { sessionID });
|
|
66684
67217
|
}
|
|
66685
67218
|
};
|
|
66686
67219
|
const handler = createTodoContinuationHandler({
|
|
@@ -66692,7 +67225,7 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
66692
67225
|
});
|
|
66693
67226
|
const cancelAllCountdowns = () => {
|
|
66694
67227
|
sessionStateStore.cancelAllCountdowns();
|
|
66695
|
-
log(`[${
|
|
67228
|
+
log(`[${HOOK_NAME16}] All countdowns cancelled`);
|
|
66696
67229
|
};
|
|
66697
67230
|
return {
|
|
66698
67231
|
handler,
|
|
@@ -66701,38 +67234,6 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
66701
67234
|
cancelAllCountdowns
|
|
66702
67235
|
};
|
|
66703
67236
|
}
|
|
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
67237
|
// src/hooks/tool-output-truncator.ts
|
|
66737
67238
|
var DEFAULT_MAX_TOKENS = 50000;
|
|
66738
67239
|
var WEBFETCH_MAX_TOKENS = 1e4;
|
|
@@ -66948,8 +67449,8 @@ This is a reminder only. No automatic action was taken.`;
|
|
|
66948
67449
|
}
|
|
66949
67450
|
// src/hooks/unstable-agent-babysitter/unstable-agent-babysitter-hook.ts
|
|
66950
67451
|
init_logger();
|
|
66951
|
-
var
|
|
66952
|
-
var
|
|
67452
|
+
var HOOK_NAME17 = "unstable-agent-babysitter";
|
|
67453
|
+
var DEFAULT_TIMEOUT_MS = 120000;
|
|
66953
67454
|
var COOLDOWN_MS = 5 * 60 * 1000;
|
|
66954
67455
|
async function resolveMainSessionTarget(ctx, sessionID) {
|
|
66955
67456
|
let agent = getSessionAgent(sessionID);
|
|
@@ -66968,7 +67469,7 @@ async function resolveMainSessionTarget(ctx, sessionID) {
|
|
|
66968
67469
|
}
|
|
66969
67470
|
}
|
|
66970
67471
|
} catch (error51) {
|
|
66971
|
-
log(`[${
|
|
67472
|
+
log(`[${HOOK_NAME17}] Failed to resolve main session agent`, { sessionID, error: String(error51) });
|
|
66972
67473
|
}
|
|
66973
67474
|
return { agent, model };
|
|
66974
67475
|
}
|
|
@@ -67001,7 +67502,7 @@ async function getThinkingSummary(ctx, sessionID) {
|
|
|
67001
67502
|
return combined;
|
|
67002
67503
|
return `${combined.slice(0, THINKING_SUMMARY_MAX_CHARS)}...`;
|
|
67003
67504
|
} catch (error51) {
|
|
67004
|
-
log(`[${
|
|
67505
|
+
log(`[${HOOK_NAME17}] Failed to fetch thinking summary`, { sessionID, error: String(error51) });
|
|
67005
67506
|
return null;
|
|
67006
67507
|
}
|
|
67007
67508
|
}
|
|
@@ -67020,7 +67521,7 @@ function createUnstableAgentBabysitterHook(ctx, options) {
|
|
|
67020
67521
|
const tasks = options.backgroundManager.getTasksByParentSession(mainSessionID);
|
|
67021
67522
|
if (tasks.length === 0)
|
|
67022
67523
|
return;
|
|
67023
|
-
const timeoutMs = options.config?.timeout_ms ??
|
|
67524
|
+
const timeoutMs = options.config?.timeout_ms ?? DEFAULT_TIMEOUT_MS;
|
|
67024
67525
|
const now = Date.now();
|
|
67025
67526
|
for (const task of tasks) {
|
|
67026
67527
|
if (task.status !== "running")
|
|
@@ -67050,9 +67551,9 @@ function createUnstableAgentBabysitterHook(ctx, options) {
|
|
|
67050
67551
|
query: { directory: ctx.directory }
|
|
67051
67552
|
});
|
|
67052
67553
|
reminderCooldowns.set(task.id, now);
|
|
67053
|
-
log(`[${
|
|
67554
|
+
log(`[${HOOK_NAME17}] Reminder injected`, { taskId: task.id, sessionID: mainSessionID });
|
|
67054
67555
|
} catch (error51) {
|
|
67055
|
-
log(`[${
|
|
67556
|
+
log(`[${HOOK_NAME17}] Reminder injection failed`, { taskId: task.id, error: String(error51) });
|
|
67056
67557
|
}
|
|
67057
67558
|
}
|
|
67058
67559
|
};
|
|
@@ -67203,7 +67704,7 @@ function createWebFetchRedirectGuardHook(_ctx) {
|
|
|
67203
67704
|
}
|
|
67204
67705
|
// src/hooks/write-existing-file-guard/hook.ts
|
|
67205
67706
|
import { existsSync as existsSync46 } from "fs";
|
|
67206
|
-
import { isAbsolute as isAbsolute4, join as
|
|
67707
|
+
import { isAbsolute as isAbsolute4, join as join53, normalize, resolve as resolve7, sep } from "path";
|
|
67207
67708
|
function createWriteExistingFileGuardHook(ctx) {
|
|
67208
67709
|
return {
|
|
67209
67710
|
"tool.execute.before": async (input, output) => {
|
|
@@ -67218,7 +67719,7 @@ function createWriteExistingFileGuardHook(ctx) {
|
|
|
67218
67719
|
}
|
|
67219
67720
|
const resolvedPath = normalize(isAbsolute4(filePath) ? filePath : resolve7(ctx.directory, filePath));
|
|
67220
67721
|
if (existsSync46(resolvedPath)) {
|
|
67221
|
-
const matrixxRoot =
|
|
67722
|
+
const matrixxRoot = join53(ctx.directory, ".matrixx") + sep;
|
|
67222
67723
|
const isMatrixMarkdown = resolvedPath.startsWith(matrixxRoot) && resolvedPath.endsWith(".md");
|
|
67223
67724
|
if (isMatrixMarkdown) {
|
|
67224
67725
|
log("[write-existing-file-guard] Allowing .matrixx/*.md overwrite", {
|
|
@@ -67281,7 +67782,12 @@ function createContinuationHooks(args) {
|
|
|
67281
67782
|
const stopContinuationGuard = isHookEnabled("stop-continuation-guard") ? safeHook("stop-continuation-guard", () => createStopContinuationGuardHook(ctx, { backgroundManager })) : null;
|
|
67282
67783
|
const compactionContextInjector = isHookEnabled("compaction-context-injector") ? safeHook("compaction-context-injector", () => createCompactionContextInjector(backgroundManager)) : null;
|
|
67283
67784
|
const compactionTodoPreserver = isHookEnabled("compaction-todo-preserver") ? safeHook("compaction-todo-preserver", () => createCompactionTodoPreserverHook(ctx)) : null;
|
|
67284
|
-
const
|
|
67785
|
+
const isTaskSystem = isTaskSystemEnabled(pluginConfig);
|
|
67786
|
+
const todoContinuationEnforcer = !isTaskSystem && isHookEnabled("todo-continuation-enforcer") ? safeHook("todo-continuation-enforcer", () => createTodoContinuationEnforcer(ctx, {
|
|
67787
|
+
backgroundManager,
|
|
67788
|
+
isContinuationStopped: stopContinuationGuard?.isStopped
|
|
67789
|
+
})) : null;
|
|
67790
|
+
const taskContinuationEnforcer = isTaskSystem && isHookEnabled("task-continuation-enforcer") ? safeHook("task-continuation-enforcer", () => createTaskContinuationEnforcer(ctx, {
|
|
67285
67791
|
backgroundManager,
|
|
67286
67792
|
isContinuationStopped: stopContinuationGuard?.isStopped
|
|
67287
67793
|
})) : null;
|
|
@@ -67293,6 +67799,10 @@ function createContinuationHooks(args) {
|
|
|
67293
67799
|
onAbortCallbacks.push(todoContinuationEnforcer.markRecovering);
|
|
67294
67800
|
onRecoveryCompleteCallbacks.push(todoContinuationEnforcer.markRecoveryComplete);
|
|
67295
67801
|
}
|
|
67802
|
+
if (taskContinuationEnforcer) {
|
|
67803
|
+
onAbortCallbacks.push(taskContinuationEnforcer.markRecovering);
|
|
67804
|
+
onRecoveryCompleteCallbacks.push(taskContinuationEnforcer.markRecoveryComplete);
|
|
67805
|
+
}
|
|
67296
67806
|
if (onAbortCallbacks.length > 0) {
|
|
67297
67807
|
sessionRecovery.setOnAbortCallback((sessionID) => {
|
|
67298
67808
|
for (const callback of onAbortCallbacks)
|
|
@@ -67321,6 +67831,7 @@ function createContinuationHooks(args) {
|
|
|
67321
67831
|
compactionContextInjector,
|
|
67322
67832
|
compactionTodoPreserver,
|
|
67323
67833
|
todoContinuationEnforcer,
|
|
67834
|
+
taskContinuationEnforcer,
|
|
67324
67835
|
unstableAgentBabysitter,
|
|
67325
67836
|
backgroundNotificationHook,
|
|
67326
67837
|
architectHook,
|
|
@@ -67638,7 +68149,7 @@ function getLanguageId(ext) {
|
|
|
67638
68149
|
}
|
|
67639
68150
|
// src/tools/lsp/server-installation.ts
|
|
67640
68151
|
import { existsSync as existsSync47 } from "fs";
|
|
67641
|
-
import { join as
|
|
68152
|
+
import { join as join54 } from "path";
|
|
67642
68153
|
function isServerInstalled(command) {
|
|
67643
68154
|
if (command.length === 0)
|
|
67644
68155
|
return false;
|
|
@@ -67666,23 +68177,23 @@ function isServerInstalled(command) {
|
|
|
67666
68177
|
const paths = pathEnv.split(pathSeparator);
|
|
67667
68178
|
for (const p of paths) {
|
|
67668
68179
|
for (const suffix of exts) {
|
|
67669
|
-
if (existsSync47(
|
|
68180
|
+
if (existsSync47(join54(p, cmd + suffix))) {
|
|
67670
68181
|
return true;
|
|
67671
68182
|
}
|
|
67672
68183
|
}
|
|
67673
68184
|
}
|
|
67674
68185
|
const cwd = process.cwd();
|
|
67675
68186
|
const configDir = getOpenCodeConfigDir({ binary: "opencode" });
|
|
67676
|
-
const dataDir =
|
|
68187
|
+
const dataDir = join54(getDataDir(), "opencode");
|
|
67677
68188
|
const additionalBases = [
|
|
67678
|
-
|
|
67679
|
-
|
|
67680
|
-
|
|
67681
|
-
|
|
68189
|
+
join54(cwd, "node_modules", ".bin"),
|
|
68190
|
+
join54(configDir, "bin"),
|
|
68191
|
+
join54(configDir, "node_modules", ".bin"),
|
|
68192
|
+
join54(dataDir, "bin")
|
|
67682
68193
|
];
|
|
67683
68194
|
for (const base of additionalBases) {
|
|
67684
68195
|
for (const suffix of exts) {
|
|
67685
|
-
if (existsSync47(
|
|
68196
|
+
if (existsSync47(join54(base, cmd + suffix))) {
|
|
67686
68197
|
return true;
|
|
67687
68198
|
}
|
|
67688
68199
|
}
|
|
@@ -67694,7 +68205,7 @@ function isServerInstalled(command) {
|
|
|
67694
68205
|
}
|
|
67695
68206
|
// src/tools/lsp/server-config-loader.ts
|
|
67696
68207
|
import { existsSync as existsSync48, readFileSync as readFileSync34 } from "fs";
|
|
67697
|
-
import { join as
|
|
68208
|
+
import { join as join55 } from "path";
|
|
67698
68209
|
function loadJsonFile(path12) {
|
|
67699
68210
|
if (!existsSync48(path12))
|
|
67700
68211
|
return null;
|
|
@@ -67708,9 +68219,9 @@ function getConfigPaths3() {
|
|
|
67708
68219
|
const cwd = process.cwd();
|
|
67709
68220
|
const configDir = getOpenCodeConfigDir({ binary: "opencode" });
|
|
67710
68221
|
return {
|
|
67711
|
-
project: detectConfigFile(
|
|
67712
|
-
user: detectConfigFile(
|
|
67713
|
-
opencode: detectConfigFile(
|
|
68222
|
+
project: detectConfigFile(join55(cwd, ".opencode", "matrixx")).path,
|
|
68223
|
+
user: detectConfigFile(join55(configDir, "matrixx")).path,
|
|
68224
|
+
opencode: detectConfigFile(join55(configDir, "opencode")).path
|
|
67714
68225
|
};
|
|
67715
68226
|
}
|
|
67716
68227
|
function loadAllConfigs() {
|
|
@@ -68559,7 +69070,7 @@ class LSPServerManager {
|
|
|
68559
69070
|
var lspManager = LSPServerManager.getInstance();
|
|
68560
69071
|
// src/tools/lsp/lsp-client-wrapper.ts
|
|
68561
69072
|
import { existsSync as existsSync50, statSync as statSync10 } from "fs";
|
|
68562
|
-
import { dirname as dirname16, extname as extname3, join as
|
|
69073
|
+
import { dirname as dirname16, extname as extname3, join as join56, resolve as resolve9 } from "path";
|
|
68563
69074
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
68564
69075
|
function findWorkspaceRoot(filePath) {
|
|
68565
69076
|
let dir = resolve9(filePath);
|
|
@@ -68575,7 +69086,7 @@ function findWorkspaceRoot(filePath) {
|
|
|
68575
69086
|
let prevDir = "";
|
|
68576
69087
|
while (dir !== prevDir) {
|
|
68577
69088
|
for (const marker of markers) {
|
|
68578
|
-
if (existsSync50(
|
|
69089
|
+
if (existsSync50(join56(dir, marker))) {
|
|
68579
69090
|
return dir;
|
|
68580
69091
|
}
|
|
68581
69092
|
}
|
|
@@ -81420,20 +81931,20 @@ var CLI_LANGUAGES = [
|
|
|
81420
81931
|
"tsx",
|
|
81421
81932
|
"yaml"
|
|
81422
81933
|
];
|
|
81423
|
-
var
|
|
81934
|
+
var DEFAULT_TIMEOUT_MS2 = 300000;
|
|
81424
81935
|
var DEFAULT_MAX_OUTPUT_BYTES = 1 * 1024 * 1024;
|
|
81425
81936
|
var DEFAULT_MAX_MATCHES = 500;
|
|
81426
81937
|
|
|
81427
81938
|
// src/tools/ast-grep/sg-cli-path.ts
|
|
81428
81939
|
import { existsSync as existsSync52, statSync as statSync11 } from "fs";
|
|
81429
81940
|
import { createRequire as createRequire4 } from "module";
|
|
81430
|
-
import { dirname as dirname17, join as
|
|
81941
|
+
import { dirname as dirname17, join as join58 } from "path";
|
|
81431
81942
|
|
|
81432
81943
|
// src/tools/ast-grep/downloader.ts
|
|
81433
81944
|
import { existsSync as existsSync51 } from "fs";
|
|
81434
81945
|
import { createRequire as createRequire3 } from "module";
|
|
81435
|
-
import { homedir as
|
|
81436
|
-
import { join as
|
|
81946
|
+
import { homedir as homedir9 } from "os";
|
|
81947
|
+
import { join as join57 } from "path";
|
|
81437
81948
|
init_logger();
|
|
81438
81949
|
var REPO2 = "ast-grep/ast-grep";
|
|
81439
81950
|
var DEFAULT_VERSION = "0.40.0";
|
|
@@ -81458,12 +81969,12 @@ var PLATFORM_MAP2 = {
|
|
|
81458
81969
|
function getCacheDir3() {
|
|
81459
81970
|
if (process.platform === "win32") {
|
|
81460
81971
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
81461
|
-
const base2 = localAppData ||
|
|
81462
|
-
return
|
|
81972
|
+
const base2 = localAppData || join57(homedir9(), "AppData", "Local");
|
|
81973
|
+
return join57(base2, "matrixx", "bin");
|
|
81463
81974
|
}
|
|
81464
81975
|
const xdgCache = process.env.XDG_CACHE_HOME;
|
|
81465
|
-
const base = xdgCache ||
|
|
81466
|
-
return
|
|
81976
|
+
const base = xdgCache || join57(homedir9(), ".cache");
|
|
81977
|
+
return join57(base, "matrixx", "bin");
|
|
81467
81978
|
}
|
|
81468
81979
|
function getBinaryName3() {
|
|
81469
81980
|
return process.platform === "win32" ? "sg.exe" : "sg";
|
|
@@ -81480,7 +81991,7 @@ async function downloadAstGrep(version3 = DEFAULT_VERSION) {
|
|
|
81480
81991
|
}
|
|
81481
81992
|
const cacheDir = getCacheDir3();
|
|
81482
81993
|
const binaryName = getBinaryName3();
|
|
81483
|
-
const binaryPath =
|
|
81994
|
+
const binaryPath = join57(cacheDir, binaryName);
|
|
81484
81995
|
if (existsSync51(binaryPath)) {
|
|
81485
81996
|
return binaryPath;
|
|
81486
81997
|
}
|
|
@@ -81489,7 +82000,7 @@ async function downloadAstGrep(version3 = DEFAULT_VERSION) {
|
|
|
81489
82000
|
const downloadUrl = `https://github.com/${REPO2}/releases/download/${version3}/${assetName}`;
|
|
81490
82001
|
log(`[matrixx] Downloading ast-grep binary...`);
|
|
81491
82002
|
try {
|
|
81492
|
-
const archivePath =
|
|
82003
|
+
const archivePath = join57(cacheDir, assetName);
|
|
81493
82004
|
ensureCacheDir(cacheDir);
|
|
81494
82005
|
await downloadArchive(downloadUrl, archivePath);
|
|
81495
82006
|
await extractZipArchive(archivePath, cacheDir);
|
|
@@ -81543,7 +82054,7 @@ function findSgCliPathSync() {
|
|
|
81543
82054
|
const require2 = createRequire4(import.meta.url);
|
|
81544
82055
|
const cliPackageJsonPath = require2.resolve("@ast-grep/cli/package.json");
|
|
81545
82056
|
const cliDirectory = dirname17(cliPackageJsonPath);
|
|
81546
|
-
const sgPath =
|
|
82057
|
+
const sgPath = join58(cliDirectory, binaryName);
|
|
81547
82058
|
if (existsSync52(sgPath) && isValidBinary(sgPath)) {
|
|
81548
82059
|
return sgPath;
|
|
81549
82060
|
}
|
|
@@ -81555,7 +82066,7 @@ function findSgCliPathSync() {
|
|
|
81555
82066
|
const packageJsonPath = require2.resolve(`${platformPackage}/package.json`);
|
|
81556
82067
|
const packageDirectory = dirname17(packageJsonPath);
|
|
81557
82068
|
const astGrepBinaryName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
|
|
81558
|
-
const binaryPath =
|
|
82069
|
+
const binaryPath = join58(packageDirectory, astGrepBinaryName);
|
|
81559
82070
|
if (existsSync52(binaryPath) && isValidBinary(binaryPath)) {
|
|
81560
82071
|
return binaryPath;
|
|
81561
82072
|
}
|
|
@@ -81715,7 +82226,7 @@ async function runSg(options) {
|
|
|
81715
82226
|
};
|
|
81716
82227
|
}
|
|
81717
82228
|
}
|
|
81718
|
-
const timeout =
|
|
82229
|
+
const timeout = DEFAULT_TIMEOUT_MS2;
|
|
81719
82230
|
const proc = spawn9([cliPath, ...args], {
|
|
81720
82231
|
stdout: "pipe",
|
|
81721
82232
|
stderr: "pipe"
|
|
@@ -83818,8 +84329,6 @@ function createBddPipelineTool(opts) {
|
|
|
83818
84329
|
});
|
|
83819
84330
|
}
|
|
83820
84331
|
function makeDeps(args) {
|
|
83821
|
-
const featureName = args.featurePath.split("/").pop()?.replace(/\.feature$/i, "") ?? "feature";
|
|
83822
|
-
const _featureDir = resolvePath2(args.outDir, featureName);
|
|
83823
84332
|
return {
|
|
83824
84333
|
parseFeature: async (path12) => {
|
|
83825
84334
|
const raw = await args.parseTool.execute({ filePath: path12 }, dummyCtx(args));
|
|
@@ -83988,17 +84497,17 @@ var {spawn: spawn10 } = globalThis.Bun;
|
|
|
83988
84497
|
// src/tools/grep/constants.ts
|
|
83989
84498
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
83990
84499
|
import { existsSync as existsSync59 } from "fs";
|
|
83991
|
-
import { dirname as dirname18, join as
|
|
84500
|
+
import { dirname as dirname18, join as join60 } from "path";
|
|
83992
84501
|
|
|
83993
84502
|
// src/tools/grep/downloader.ts
|
|
83994
|
-
import { existsSync as existsSync58, readdirSync as
|
|
83995
|
-
import { join as
|
|
84503
|
+
import { existsSync as existsSync58, readdirSync as readdirSync18 } from "fs";
|
|
84504
|
+
import { join as join59 } from "path";
|
|
83996
84505
|
function findFileRecursive(dir, filename) {
|
|
83997
84506
|
try {
|
|
83998
|
-
const entries =
|
|
84507
|
+
const entries = readdirSync18(dir, { withFileTypes: true, recursive: true });
|
|
83999
84508
|
for (const entry of entries) {
|
|
84000
84509
|
if (entry.isFile() && entry.name === filename) {
|
|
84001
|
-
return
|
|
84510
|
+
return join59(entry.parentPath ?? dir, entry.name);
|
|
84002
84511
|
}
|
|
84003
84512
|
}
|
|
84004
84513
|
} catch {
|
|
@@ -84019,11 +84528,11 @@ function getPlatformKey() {
|
|
|
84019
84528
|
}
|
|
84020
84529
|
function getInstallDir() {
|
|
84021
84530
|
const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
|
|
84022
|
-
return
|
|
84531
|
+
return join59(homeDir, ".cache", "matrixx", "bin");
|
|
84023
84532
|
}
|
|
84024
84533
|
function getRgPath() {
|
|
84025
84534
|
const isWindows = process.platform === "win32";
|
|
84026
|
-
return
|
|
84535
|
+
return join59(getInstallDir(), isWindows ? "rg.exe" : "rg");
|
|
84027
84536
|
}
|
|
84028
84537
|
async function extractTarGz2(archivePath, destDir) {
|
|
84029
84538
|
const platformKey = getPlatformKey();
|
|
@@ -84040,7 +84549,7 @@ async function extractZip2(archivePath, destDir) {
|
|
|
84040
84549
|
const binaryName = process.platform === "win32" ? "rg.exe" : "rg";
|
|
84041
84550
|
const foundPath = findFileRecursive(destDir, binaryName);
|
|
84042
84551
|
if (foundPath) {
|
|
84043
|
-
const destPath =
|
|
84552
|
+
const destPath = join59(destDir, binaryName);
|
|
84044
84553
|
if (foundPath !== destPath) {
|
|
84045
84554
|
const { renameSync: renameSync4 } = await import("fs");
|
|
84046
84555
|
renameSync4(foundPath, destPath);
|
|
@@ -84061,7 +84570,7 @@ async function downloadAndInstallRipgrep() {
|
|
|
84061
84570
|
ensureCacheDir(installDir);
|
|
84062
84571
|
const filename = `ripgrep-${RG_VERSION}-${config4.platform}.${config4.extension}`;
|
|
84063
84572
|
const url3 = `https://github.com/BurntSushi/ripgrep/releases/download/${RG_VERSION}/${filename}`;
|
|
84064
|
-
const archivePath =
|
|
84573
|
+
const archivePath = join59(installDir, filename);
|
|
84065
84574
|
try {
|
|
84066
84575
|
await downloadArchive(url3, archivePath);
|
|
84067
84576
|
if (config4.extension === "tar.gz") {
|
|
@@ -84106,11 +84615,11 @@ function getOpenCodeBundledRg() {
|
|
|
84106
84615
|
const isWindows = process.platform === "win32";
|
|
84107
84616
|
const rgName = isWindows ? "rg.exe" : "rg";
|
|
84108
84617
|
const candidates = [
|
|
84109
|
-
|
|
84110
|
-
|
|
84111
|
-
|
|
84112
|
-
|
|
84113
|
-
|
|
84618
|
+
join60(getDataDir(), "opencode", "bin", rgName),
|
|
84619
|
+
join60(execDir, rgName),
|
|
84620
|
+
join60(execDir, "bin", rgName),
|
|
84621
|
+
join60(execDir, "..", "bin", rgName),
|
|
84622
|
+
join60(execDir, "..", "libexec", rgName)
|
|
84114
84623
|
];
|
|
84115
84624
|
for (const candidate of candidates) {
|
|
84116
84625
|
if (existsSync59(candidate)) {
|
|
@@ -84166,7 +84675,7 @@ var DEFAULT_MAX_DEPTH = 20;
|
|
|
84166
84675
|
var DEFAULT_MAX_FILESIZE = "10M";
|
|
84167
84676
|
var DEFAULT_MAX_COUNT = 500;
|
|
84168
84677
|
var DEFAULT_MAX_COLUMNS = 1000;
|
|
84169
|
-
var
|
|
84678
|
+
var DEFAULT_TIMEOUT_MS3 = 300000;
|
|
84170
84679
|
var DEFAULT_MAX_OUTPUT_BYTES2 = 10 * 1024 * 1024;
|
|
84171
84680
|
var RG_SAFETY_FLAGS = [
|
|
84172
84681
|
"--no-follow",
|
|
@@ -84178,7 +84687,7 @@ var RG_SAFETY_FLAGS = [
|
|
|
84178
84687
|
var GREP_SAFETY_FLAGS = ["-n", "-H", "--color=never"];
|
|
84179
84688
|
|
|
84180
84689
|
// src/tools/glob/constants.ts
|
|
84181
|
-
var
|
|
84690
|
+
var DEFAULT_TIMEOUT_MS4 = 60000;
|
|
84182
84691
|
var DEFAULT_LIMIT = 100;
|
|
84183
84692
|
var DEFAULT_MAX_DEPTH2 = 20;
|
|
84184
84693
|
var DEFAULT_MAX_OUTPUT_BYTES3 = 10 * 1024 * 1024;
|
|
@@ -84241,7 +84750,7 @@ async function getFileMtime(filePath) {
|
|
|
84241
84750
|
}
|
|
84242
84751
|
async function runRgFiles(options, resolvedCli) {
|
|
84243
84752
|
const cli = resolvedCli ?? resolveGrepCli();
|
|
84244
|
-
const timeout = Math.min(options.timeout ??
|
|
84753
|
+
const timeout = Math.min(options.timeout ?? DEFAULT_TIMEOUT_MS4, DEFAULT_TIMEOUT_MS4);
|
|
84245
84754
|
const limit = Math.min(options.limit ?? DEFAULT_LIMIT, DEFAULT_LIMIT);
|
|
84246
84755
|
const isRg = cli.backend === "rg";
|
|
84247
84756
|
const isWindows = process.platform === "win32";
|
|
@@ -84463,7 +84972,7 @@ function parseOutput(output) {
|
|
|
84463
84972
|
async function runRg(options) {
|
|
84464
84973
|
const cli = resolveGrepCli();
|
|
84465
84974
|
const args = buildArgs(options, cli.backend);
|
|
84466
|
-
const timeout = Math.min(options.timeout ??
|
|
84975
|
+
const timeout = Math.min(options.timeout ?? DEFAULT_TIMEOUT_MS3, DEFAULT_TIMEOUT_MS3);
|
|
84467
84976
|
if (cli.backend === "rg") {
|
|
84468
84977
|
args.push("--", options.pattern);
|
|
84469
84978
|
} else {
|
|
@@ -84618,12 +85127,12 @@ var HandoffSchema = exports_external.object({
|
|
|
84618
85127
|
});
|
|
84619
85128
|
// src/features/handoff/storage.ts
|
|
84620
85129
|
import { existsSync as existsSync60, mkdirSync as mkdirSync17, readFileSync as readFileSync39, renameSync as renameSync4, writeFileSync as writeFileSync23 } from "fs";
|
|
84621
|
-
import { join as
|
|
85130
|
+
import { join as join61 } from "path";
|
|
84622
85131
|
function getHandoffFilePath(directory) {
|
|
84623
|
-
return
|
|
85132
|
+
return join61(directory, HANDOFF_FILE_PATH);
|
|
84624
85133
|
}
|
|
84625
85134
|
function getHandoffConsumedFilePath(directory) {
|
|
84626
|
-
return
|
|
85135
|
+
return join61(directory, HANDOFF_CONSUMED_FILE_PATH);
|
|
84627
85136
|
}
|
|
84628
85137
|
function handoffFileExists(directory) {
|
|
84629
85138
|
try {
|
|
@@ -84661,7 +85170,7 @@ function writeHandoffFile(directory, content) {
|
|
|
84661
85170
|
}
|
|
84662
85171
|
function ensureHandoffDir(directory) {
|
|
84663
85172
|
try {
|
|
84664
|
-
mkdirSync17(
|
|
85173
|
+
mkdirSync17(join61(directory, ".matrixx"), { recursive: true });
|
|
84665
85174
|
return true;
|
|
84666
85175
|
} catch {
|
|
84667
85176
|
return false;
|
|
@@ -84816,20 +85325,20 @@ ${body}
|
|
|
84816
85325
|
}
|
|
84817
85326
|
|
|
84818
85327
|
// src/tools/handoff/list-handler.ts
|
|
84819
|
-
import { existsSync as existsSync61, readdirSync as
|
|
84820
|
-
import { join as
|
|
85328
|
+
import { existsSync as existsSync61, readdirSync as readdirSync19, statSync as statSync13 } from "fs";
|
|
85329
|
+
import { join as join62 } from "path";
|
|
84821
85330
|
var MATRIX_DIR = ".matrixx";
|
|
84822
85331
|
function isHandoffFile(name) {
|
|
84823
85332
|
return name === "handoff.md" || name.startsWith("handoff.consumed");
|
|
84824
85333
|
}
|
|
84825
85334
|
function handleList(directory) {
|
|
84826
|
-
const matrixxDir =
|
|
85335
|
+
const matrixxDir = join62(directory, MATRIX_DIR);
|
|
84827
85336
|
if (!existsSync61(matrixxDir)) {
|
|
84828
85337
|
return "No handoffs found in .matrixx/";
|
|
84829
85338
|
}
|
|
84830
85339
|
let entries;
|
|
84831
85340
|
try {
|
|
84832
|
-
entries =
|
|
85341
|
+
entries = readdirSync19(matrixxDir);
|
|
84833
85342
|
} catch {
|
|
84834
85343
|
return "No handoffs found in .matrixx/";
|
|
84835
85344
|
}
|
|
@@ -84841,7 +85350,7 @@ function handleList(directory) {
|
|
|
84841
85350
|
lines.push(`Handoff files in ${matrixxDir}:`);
|
|
84842
85351
|
lines.push("");
|
|
84843
85352
|
for (const name of handoffs) {
|
|
84844
|
-
const fullPath =
|
|
85353
|
+
const fullPath = join62(matrixxDir, name);
|
|
84845
85354
|
let size = "?";
|
|
84846
85355
|
let mtime = "?";
|
|
84847
85356
|
try {
|
|
@@ -84938,7 +85447,7 @@ function createHandoffTools(ctx) {
|
|
|
84938
85447
|
init_logger();
|
|
84939
85448
|
|
|
84940
85449
|
// src/tools/interactive-bash/constants.ts
|
|
84941
|
-
var
|
|
85450
|
+
var DEFAULT_TIMEOUT_MS5 = 60000;
|
|
84942
85451
|
var BLOCKED_TMUX_SUBCOMMANDS = [
|
|
84943
85452
|
"capture-pane",
|
|
84944
85453
|
"capturep",
|
|
@@ -85035,7 +85544,7 @@ The Bash tool can execute these commands directly. Do NOT retry with interactive
|
|
|
85035
85544
|
});
|
|
85036
85545
|
const timeoutPromise = new Promise((_, reject) => {
|
|
85037
85546
|
const id = setTimeout(() => {
|
|
85038
|
-
const timeoutError = new Error(`Timeout after ${
|
|
85547
|
+
const timeoutError = new Error(`Timeout after ${DEFAULT_TIMEOUT_MS5}ms`);
|
|
85039
85548
|
try {
|
|
85040
85549
|
proc.kill();
|
|
85041
85550
|
proc.exited.catch((err) => {
|
|
@@ -85043,7 +85552,7 @@ The Bash tool can execute these commands directly. Do NOT retry with interactive
|
|
|
85043
85552
|
});
|
|
85044
85553
|
} catch {}
|
|
85045
85554
|
reject(timeoutError);
|
|
85046
|
-
},
|
|
85555
|
+
}, DEFAULT_TIMEOUT_MS5);
|
|
85047
85556
|
proc.exited.then(() => clearTimeout(id)).catch(() => clearTimeout(id));
|
|
85048
85557
|
});
|
|
85049
85558
|
const [stdout, stderr, exitCode] = await Promise.race([
|
|
@@ -85067,7 +85576,7 @@ The Bash tool can execute these commands directly. Do NOT retry with interactive
|
|
|
85067
85576
|
// src/tools/pdf-extract-figures/tools.ts
|
|
85068
85577
|
import { existsSync as existsSync62, mkdtempSync, writeFileSync as writeFileSync24 } from "fs";
|
|
85069
85578
|
import { tmpdir as tmpdir5 } from "os";
|
|
85070
|
-
import { join as
|
|
85579
|
+
import { join as join63 } from "path";
|
|
85071
85580
|
var PYTHON_SCRIPT = `#!/usr/bin/env python3
|
|
85072
85581
|
import sys
|
|
85073
85582
|
import json
|
|
@@ -85218,11 +85727,11 @@ async function runExtraction(args) {
|
|
|
85218
85727
|
}
|
|
85219
85728
|
let tmpDir;
|
|
85220
85729
|
try {
|
|
85221
|
-
tmpDir = mkdtempSync(
|
|
85730
|
+
tmpDir = mkdtempSync(join63(tmpdir5(), "matrixx-pdf-extract-"));
|
|
85222
85731
|
} catch {
|
|
85223
85732
|
return "Error: Could not create temporary directory.";
|
|
85224
85733
|
}
|
|
85225
|
-
const scriptPath =
|
|
85734
|
+
const scriptPath = join63(tmpDir, "extract_pdf_figures.py");
|
|
85226
85735
|
writeFileSync24(scriptPath, PYTHON_SCRIPT);
|
|
85227
85736
|
const payload = buildArgsPayload(args);
|
|
85228
85737
|
log("[pdf_extract_figures] Running extraction", { file_path: args.file_path, payload });
|
|
@@ -85270,9 +85779,9 @@ function createPdfExtractFiguresTool() {
|
|
|
85270
85779
|
return { pdf_extract_figures };
|
|
85271
85780
|
}
|
|
85272
85781
|
// src/tools/session-manager/constants.ts
|
|
85273
|
-
import { join as
|
|
85274
|
-
var TODO_DIR =
|
|
85275
|
-
var TRANSCRIPT_DIR2 =
|
|
85782
|
+
import { join as join64 } from "path";
|
|
85783
|
+
var TODO_DIR = join64(getOpenCodeConfigDir({ binary: "opencode" }), "todos");
|
|
85784
|
+
var TRANSCRIPT_DIR2 = join64(getOpenCodeConfigDir({ binary: "opencode" }), "transcripts");
|
|
85276
85785
|
var SESSION_LIST_DESCRIPTION = `List all OpenCode sessions with optional filtering.
|
|
85277
85786
|
|
|
85278
85787
|
Returns a list of available session IDs with metadata including message count, date range, and agents used.
|
|
@@ -85346,7 +85855,7 @@ Has Transcript: Yes (234 entries)`;
|
|
|
85346
85855
|
// src/tools/session-manager/storage.ts
|
|
85347
85856
|
import { existsSync as existsSync63 } from "fs";
|
|
85348
85857
|
import { readdir, readFile as readFile4 } from "fs/promises";
|
|
85349
|
-
import { join as
|
|
85858
|
+
import { join as join65 } from "path";
|
|
85350
85859
|
var sdkClient = null;
|
|
85351
85860
|
function setStorageClient(client2) {
|
|
85352
85861
|
sdkClient = client2;
|
|
@@ -85373,13 +85882,13 @@ async function getMainSessions(options) {
|
|
|
85373
85882
|
for (const projectDir of projectDirs) {
|
|
85374
85883
|
if (!projectDir.isDirectory())
|
|
85375
85884
|
continue;
|
|
85376
|
-
const projectPath =
|
|
85885
|
+
const projectPath = join65(SESSION_STORAGE, projectDir.name);
|
|
85377
85886
|
const sessionFiles = await readdir(projectPath);
|
|
85378
85887
|
for (const file3 of sessionFiles) {
|
|
85379
85888
|
if (!file3.endsWith(".json"))
|
|
85380
85889
|
continue;
|
|
85381
85890
|
try {
|
|
85382
|
-
const content = await readFile4(
|
|
85891
|
+
const content = await readFile4(join65(projectPath, file3), "utf-8");
|
|
85383
85892
|
const meta3 = JSON.parse(content);
|
|
85384
85893
|
if (meta3.parentID)
|
|
85385
85894
|
continue;
|
|
@@ -85412,7 +85921,7 @@ async function getAllSessions() {
|
|
|
85412
85921
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
85413
85922
|
for (const entry of entries) {
|
|
85414
85923
|
if (entry.isDirectory()) {
|
|
85415
|
-
const sessionPath =
|
|
85924
|
+
const sessionPath = join65(dir, entry.name);
|
|
85416
85925
|
const files = await readdir(sessionPath);
|
|
85417
85926
|
if (files.some((f) => f.endsWith(".json"))) {
|
|
85418
85927
|
sessions.push(entry.name);
|
|
@@ -85482,7 +85991,7 @@ async function readSessionMessages(sessionID) {
|
|
|
85482
85991
|
if (!file3.endsWith(".json"))
|
|
85483
85992
|
continue;
|
|
85484
85993
|
try {
|
|
85485
|
-
const content = await readFile4(
|
|
85994
|
+
const content = await readFile4(join65(messageDir, file3), "utf-8");
|
|
85486
85995
|
const meta3 = JSON.parse(content);
|
|
85487
85996
|
const parts = await readParts2(meta3.id);
|
|
85488
85997
|
messages2.push({
|
|
@@ -85506,7 +86015,7 @@ async function readSessionMessages(sessionID) {
|
|
|
85506
86015
|
});
|
|
85507
86016
|
}
|
|
85508
86017
|
async function readParts2(messageID) {
|
|
85509
|
-
const partDir =
|
|
86018
|
+
const partDir = join65(PART_STORAGE, messageID);
|
|
85510
86019
|
if (!existsSync63(partDir))
|
|
85511
86020
|
return [];
|
|
85512
86021
|
const parts = [];
|
|
@@ -85516,7 +86025,7 @@ async function readParts2(messageID) {
|
|
|
85516
86025
|
if (!file3.endsWith(".json"))
|
|
85517
86026
|
continue;
|
|
85518
86027
|
try {
|
|
85519
|
-
const content = await readFile4(
|
|
86028
|
+
const content = await readFile4(join65(partDir, file3), "utf-8");
|
|
85520
86029
|
parts.push(JSON.parse(content));
|
|
85521
86030
|
} catch {}
|
|
85522
86031
|
}
|
|
@@ -85547,7 +86056,7 @@ async function readSessionTodos(sessionID) {
|
|
|
85547
86056
|
const todoFiles = allFiles.filter((f) => f.includes(sessionID) && f.endsWith(".json"));
|
|
85548
86057
|
for (const file3 of todoFiles) {
|
|
85549
86058
|
try {
|
|
85550
|
-
const content = await readFile4(
|
|
86059
|
+
const content = await readFile4(join65(TODO_DIR, file3), "utf-8");
|
|
85551
86060
|
const data = JSON.parse(content);
|
|
85552
86061
|
if (Array.isArray(data)) {
|
|
85553
86062
|
return data.map((item) => ({
|
|
@@ -85567,7 +86076,7 @@ async function readSessionTodos(sessionID) {
|
|
|
85567
86076
|
async function readSessionTranscript(sessionID) {
|
|
85568
86077
|
if (!existsSync63(TRANSCRIPT_DIR2))
|
|
85569
86078
|
return 0;
|
|
85570
|
-
const transcriptFile =
|
|
86079
|
+
const transcriptFile = join65(TRANSCRIPT_DIR2, `${sessionID}.jsonl`);
|
|
85571
86080
|
if (!existsSync63(transcriptFile))
|
|
85572
86081
|
return 0;
|
|
85573
86082
|
try {
|
|
@@ -86078,17 +86587,17 @@ function createSkillTool(options = {}) {
|
|
|
86078
86587
|
}
|
|
86079
86588
|
var skill = createSkillTool();
|
|
86080
86589
|
// src/tools/slashcommand/command-discovery.ts
|
|
86081
|
-
import { existsSync as existsSync64, readdirSync as
|
|
86082
|
-
import { basename as basename6, join as
|
|
86590
|
+
import { existsSync as existsSync64, readdirSync as readdirSync20, readFileSync as readFileSync40 } from "fs";
|
|
86591
|
+
import { basename as basename6, join as join66 } from "path";
|
|
86083
86592
|
function discoverCommandsFromDir2(commandsDir, scope) {
|
|
86084
86593
|
if (!existsSync64(commandsDir))
|
|
86085
86594
|
return [];
|
|
86086
|
-
const entries =
|
|
86595
|
+
const entries = readdirSync20(commandsDir, { withFileTypes: true });
|
|
86087
86596
|
const commands2 = [];
|
|
86088
86597
|
for (const entry of entries) {
|
|
86089
86598
|
if (!isMarkdownFile(entry))
|
|
86090
86599
|
continue;
|
|
86091
|
-
const commandPath =
|
|
86600
|
+
const commandPath = join66(commandsDir, entry.name);
|
|
86092
86601
|
const commandName = basename6(entry.name, ".md");
|
|
86093
86602
|
try {
|
|
86094
86603
|
const content = readFileSync40(commandPath, "utf-8");
|
|
@@ -86115,8 +86624,8 @@ function discoverCommandsFromDir2(commandsDir, scope) {
|
|
|
86115
86624
|
}
|
|
86116
86625
|
function discoverCommandsSync(directory) {
|
|
86117
86626
|
const configDir = getOpenCodeConfigDir({ binary: "opencode" });
|
|
86118
|
-
const opencodeGlobalDir =
|
|
86119
|
-
const opencodeProjectDir =
|
|
86627
|
+
const opencodeGlobalDir = join66(configDir, "command");
|
|
86628
|
+
const opencodeProjectDir = join66(directory ?? process.cwd(), ".opencode", "command");
|
|
86120
86629
|
const opencodeGlobalCommands = discoverCommandsFromDir2(opencodeGlobalDir, "opencode");
|
|
86121
86630
|
const opencodeProjectCommands = discoverCommandsFromDir2(opencodeProjectDir, "opencode-project");
|
|
86122
86631
|
const builtinCommandsMap = loadBuiltinCommands();
|
|
@@ -87287,8 +87796,8 @@ function createAssemblyTool(options) {
|
|
|
87287
87796
|
}
|
|
87288
87797
|
// src/tools/dcp-switch-profile/tools.ts
|
|
87289
87798
|
import { existsSync as existsSync65, writeFileSync as writeFileSync25 } from "fs";
|
|
87290
|
-
import { homedir as
|
|
87291
|
-
import { join as
|
|
87799
|
+
import { homedir as homedir10 } from "os";
|
|
87800
|
+
import { join as join67 } from "path";
|
|
87292
87801
|
|
|
87293
87802
|
// src/config/schema/dcp.ts
|
|
87294
87803
|
var DcpCompressOverrideSchema = exports_external.object({
|
|
@@ -87461,8 +87970,8 @@ var DcpConfigSchema = exports_external.object({
|
|
|
87461
87970
|
});
|
|
87462
87971
|
|
|
87463
87972
|
// src/tools/dcp-switch-profile/tools.ts
|
|
87464
|
-
var DCP_PLUGIN_DIR =
|
|
87465
|
-
var DCP_SYMLINK =
|
|
87973
|
+
var DCP_PLUGIN_DIR = join67(homedir10(), ".config", "opencode", "node_modules", "@tarquinen", "opencode-dcp");
|
|
87974
|
+
var DCP_SYMLINK = join67(homedir10(), ".config", "opencode", "dcp.jsonc");
|
|
87466
87975
|
var VALID_PROFILES = ["economy", "balanced", "performance", "ultimate"];
|
|
87467
87976
|
function checkDcpInstalled() {
|
|
87468
87977
|
if (!existsSync65(DCP_PLUGIN_DIR)) {
|
|
@@ -87871,6 +88380,11 @@ function createDelegateAgent(ctx, backgroundManager, disabledAgents = []) {
|
|
|
87871
88380
|
}
|
|
87872
88381
|
const normalizedAgent = args.subagent_type.toLowerCase();
|
|
87873
88382
|
args = { ...args, subagent_type: normalizedAgent };
|
|
88383
|
+
const callerAgent = (toolCtx.agent || "").toLowerCase();
|
|
88384
|
+
const MOUSE_RESEARCH_ONLY = ["trinity", "operator"];
|
|
88385
|
+
if (callerAgent === "mouse" && !MOUSE_RESEARCH_ONLY.includes(normalizedAgent)) {
|
|
88386
|
+
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.`;
|
|
88387
|
+
}
|
|
87874
88388
|
if (disabledAgents.some((disabled) => disabled.toLowerCase() === normalizedAgent)) {
|
|
87875
88389
|
return `Error: Agent "${normalizedAgent}" is disabled via disabled_agents configuration. Remove it from disabled_agents in your matrixx.json to use it.`;
|
|
87876
88390
|
}
|
|
@@ -91338,6 +91852,109 @@ Original error: ${createResult.error}`;
|
|
|
91338
91852
|
}
|
|
91339
91853
|
});
|
|
91340
91854
|
}
|
|
91855
|
+
// src/tools/task/task-cleanup.ts
|
|
91856
|
+
import { existsSync as existsSync66, readdirSync as readdirSync21, readFileSync as readFileSync41, unlinkSync as unlinkSync10 } from "fs";
|
|
91857
|
+
import { join as join68 } from "path";
|
|
91858
|
+
init_logger();
|
|
91859
|
+
function parseOlderThan(value) {
|
|
91860
|
+
const match = value.trim().match(/^(\d+)(d|h|m)$/);
|
|
91861
|
+
if (!match)
|
|
91862
|
+
return null;
|
|
91863
|
+
const amount = Number(match[1]);
|
|
91864
|
+
const unit = match[2];
|
|
91865
|
+
if (unit === "d")
|
|
91866
|
+
return amount * 24 * 60 * 60 * 1000;
|
|
91867
|
+
if (unit === "h")
|
|
91868
|
+
return amount * 60 * 60 * 1000;
|
|
91869
|
+
return amount * 60 * 1000;
|
|
91870
|
+
}
|
|
91871
|
+
function getTaskTimestamp(task) {
|
|
91872
|
+
const raw = task;
|
|
91873
|
+
const candidate = raw.time_updated ?? raw.time_created ?? raw.updatedAt ?? raw.createdAt ?? raw.timeUpdated ?? raw.timeCreated ?? null;
|
|
91874
|
+
if (candidate === null || candidate === undefined)
|
|
91875
|
+
return Date.now();
|
|
91876
|
+
if (typeof candidate === "number")
|
|
91877
|
+
return candidate;
|
|
91878
|
+
if (typeof candidate === "string") {
|
|
91879
|
+
const parsed = Date.parse(candidate);
|
|
91880
|
+
if (!Number.isNaN(parsed))
|
|
91881
|
+
return parsed;
|
|
91882
|
+
const asNum = Number(candidate);
|
|
91883
|
+
if (!Number.isNaN(asNum))
|
|
91884
|
+
return asNum;
|
|
91885
|
+
return Date.now();
|
|
91886
|
+
}
|
|
91887
|
+
return Date.now();
|
|
91888
|
+
}
|
|
91889
|
+
function createTaskCleanupTool(config4, ctx) {
|
|
91890
|
+
return tool({
|
|
91891
|
+
description: `Delete completed task files from storage.
|
|
91892
|
+
|
|
91893
|
+
Scans getTaskDir()/*.json, filters status==="completed" and optionally olderThan.
|
|
91894
|
+
olderThan supports "7d", "24h", "30m" format (regex ^(\\d+)(d|h|m)$).
|
|
91895
|
+
Returns counts: {deleted, remaining, deletedIds}`,
|
|
91896
|
+
args: {
|
|
91897
|
+
olderThan: tool.schema.string().optional().describe('Only delete completed tasks older than duration (e.g. "7d", "24h", "30m")'),
|
|
91898
|
+
all: tool.schema.boolean().optional().describe("Delete all completed tasks (default true when olderThan not set)")
|
|
91899
|
+
},
|
|
91900
|
+
execute: async (args, context) => {
|
|
91901
|
+
try {
|
|
91902
|
+
const olderThan = args.olderThan;
|
|
91903
|
+
let olderThanMs = null;
|
|
91904
|
+
if (olderThan !== undefined) {
|
|
91905
|
+
const parsed = parseOlderThan(olderThan);
|
|
91906
|
+
if (parsed === null) {
|
|
91907
|
+
return JSON.stringify({
|
|
91908
|
+
error: "validation_error",
|
|
91909
|
+
message: `Invalid olderThan format: "${olderThan}". Expected e.g. "7d", "24h", "30m"`
|
|
91910
|
+
});
|
|
91911
|
+
}
|
|
91912
|
+
olderThanMs = parsed;
|
|
91913
|
+
}
|
|
91914
|
+
const directory = context?.directory ?? ctx?.directory ?? process.cwd();
|
|
91915
|
+
const taskDir = getTaskDir(config4, directory);
|
|
91916
|
+
if (!existsSync66(taskDir)) {
|
|
91917
|
+
return JSON.stringify({ deleted: 0, remaining: 0, deletedIds: [] });
|
|
91918
|
+
}
|
|
91919
|
+
const files = readdirSync21(taskDir).filter((f) => f.endsWith(".json") && f.startsWith("T-"));
|
|
91920
|
+
const total = files.length;
|
|
91921
|
+
const deletedIds = [];
|
|
91922
|
+
for (const file3 of files) {
|
|
91923
|
+
const filePath = join68(taskDir, file3);
|
|
91924
|
+
let raw;
|
|
91925
|
+
try {
|
|
91926
|
+
const content = readFileSync41(filePath, "utf-8");
|
|
91927
|
+
raw = JSON.parse(content);
|
|
91928
|
+
} catch {
|
|
91929
|
+
continue;
|
|
91930
|
+
}
|
|
91931
|
+
if (raw.status !== "completed")
|
|
91932
|
+
continue;
|
|
91933
|
+
const id = typeof raw.id === "string" ? raw.id : file3.replace(".json", "");
|
|
91934
|
+
if (olderThanMs !== null) {
|
|
91935
|
+
const ts = getTaskTimestamp(raw);
|
|
91936
|
+
const age = Date.now() - ts;
|
|
91937
|
+
if (age <= olderThanMs)
|
|
91938
|
+
continue;
|
|
91939
|
+
}
|
|
91940
|
+
try {
|
|
91941
|
+
unlinkSync10(filePath);
|
|
91942
|
+
deletedIds.push(id);
|
|
91943
|
+
} catch (err) {
|
|
91944
|
+
log("[task-cleanup] Failed to delete", { file: file3, error: String(err) });
|
|
91945
|
+
}
|
|
91946
|
+
}
|
|
91947
|
+
return JSON.stringify({
|
|
91948
|
+
deleted: deletedIds.length,
|
|
91949
|
+
remaining: total - deletedIds.length,
|
|
91950
|
+
deletedIds
|
|
91951
|
+
});
|
|
91952
|
+
} catch (error95) {
|
|
91953
|
+
return JSON.stringify({ error: "internal_error", message: error95 instanceof Error ? error95.message : String(error95) });
|
|
91954
|
+
}
|
|
91955
|
+
}
|
|
91956
|
+
});
|
|
91957
|
+
}
|
|
91341
91958
|
// src/tools/task/task-create.ts
|
|
91342
91959
|
import { join as join69 } from "path";
|
|
91343
91960
|
function createTaskCreateTool(config4, ctx) {
|
|
@@ -91371,8 +91988,12 @@ Calculate dependencies carefully to maximize parallel execution:
|
|
|
91371
91988
|
async function handleCreate2(args, config4, ctx, context) {
|
|
91372
91989
|
try {
|
|
91373
91990
|
const validatedArgs = TaskCreateInputSchema.parse(args);
|
|
91374
|
-
const
|
|
91375
|
-
|
|
91991
|
+
const directory = context?.directory ?? ctx?.directory ?? process.cwd();
|
|
91992
|
+
try {
|
|
91993
|
+
migrateLegacyTasksIfNeeded(config4, directory);
|
|
91994
|
+
} catch {}
|
|
91995
|
+
const taskDir = getTaskDir(config4, directory);
|
|
91996
|
+
const lock = await acquireLockWithRetry(taskDir);
|
|
91376
91997
|
if (!lock.acquired) {
|
|
91377
91998
|
return JSON.stringify({ error: "task_lock_unavailable" });
|
|
91378
91999
|
}
|
|
@@ -91389,11 +92010,11 @@ async function handleCreate2(args, config4, ctx, context) {
|
|
|
91389
92010
|
metadata: validatedArgs.metadata,
|
|
91390
92011
|
repoURL: validatedArgs.repoURL,
|
|
91391
92012
|
parentID: validatedArgs.parentID,
|
|
91392
|
-
threadID: context.sessionID
|
|
92013
|
+
threadID: context.sessionID,
|
|
92014
|
+
projectRoot: directory
|
|
91393
92015
|
};
|
|
91394
92016
|
const validatedTask = TaskObjectSchema.parse(task);
|
|
91395
92017
|
writeJsonAtomic(join69(taskDir, `${taskId}.json`), validatedTask);
|
|
91396
|
-
await syncTaskTodoUpdate(ctx, validatedTask, context.sessionID);
|
|
91397
92018
|
return JSON.stringify({
|
|
91398
92019
|
task: {
|
|
91399
92020
|
id: validatedTask.id,
|
|
@@ -91425,7 +92046,7 @@ function parseTaskId(id) {
|
|
|
91425
92046
|
return null;
|
|
91426
92047
|
return id;
|
|
91427
92048
|
}
|
|
91428
|
-
function createTaskGetTool(config4) {
|
|
92049
|
+
function createTaskGetTool(config4, ctx) {
|
|
91429
92050
|
return tool({
|
|
91430
92051
|
description: `Retrieve a task by ID.
|
|
91431
92052
|
|
|
@@ -91435,14 +92056,15 @@ Returns null if the task does not exist or the file is invalid.`,
|
|
|
91435
92056
|
args: {
|
|
91436
92057
|
id: tool.schema.string().describe("Task ID to retrieve (format: T-{uuid})")
|
|
91437
92058
|
},
|
|
91438
|
-
execute: async (args) => {
|
|
92059
|
+
execute: async (args, context) => {
|
|
91439
92060
|
try {
|
|
91440
92061
|
const validatedArgs = TaskGetInputSchema.parse(args);
|
|
91441
92062
|
const taskId = parseTaskId(validatedArgs.id);
|
|
91442
92063
|
if (!taskId) {
|
|
91443
92064
|
return JSON.stringify({ error: "invalid_task_id" });
|
|
91444
92065
|
}
|
|
91445
|
-
const
|
|
92066
|
+
const directory = context?.directory ?? ctx?.directory ?? process.cwd();
|
|
92067
|
+
const taskDir = getTaskDir(config4, directory);
|
|
91446
92068
|
const taskPath = join70(taskDir, `${taskId}.json`);
|
|
91447
92069
|
const task = readJsonSafe(taskPath, TaskObjectSchema);
|
|
91448
92070
|
return JSON.stringify({ task: task ?? null });
|
|
@@ -91456,9 +92078,9 @@ Returns null if the task does not exist or the file is invalid.`,
|
|
|
91456
92078
|
});
|
|
91457
92079
|
}
|
|
91458
92080
|
// src/tools/task/task-list.ts
|
|
91459
|
-
import { existsSync as
|
|
92081
|
+
import { existsSync as existsSync67, readdirSync as readdirSync22 } from "fs";
|
|
91460
92082
|
import { join as join71 } from "path";
|
|
91461
|
-
function createTaskList(config4) {
|
|
92083
|
+
function createTaskList(config4, ctx) {
|
|
91462
92084
|
return tool({
|
|
91463
92085
|
description: `List all active tasks with summary information.
|
|
91464
92086
|
|
|
@@ -91466,12 +92088,13 @@ Returns tasks excluding completed and deleted statuses by default.
|
|
|
91466
92088
|
For each task's blockedBy field, filters to only include unresolved (non-completed) blockers.
|
|
91467
92089
|
Returns summary format: id, subject, status, owner, blockedBy (not full description).`,
|
|
91468
92090
|
args: {},
|
|
91469
|
-
execute: async () => {
|
|
91470
|
-
const
|
|
91471
|
-
|
|
92091
|
+
execute: async (_args, context) => {
|
|
92092
|
+
const directory = context?.directory ?? ctx?.directory ?? process.cwd();
|
|
92093
|
+
const taskDir = getTaskDir(config4, directory);
|
|
92094
|
+
if (!existsSync67(taskDir)) {
|
|
91472
92095
|
return JSON.stringify({ tasks: [] });
|
|
91473
92096
|
}
|
|
91474
|
-
const files =
|
|
92097
|
+
const files = readdirSync22(taskDir).filter((f) => f.endsWith(".json") && f.startsWith("T-")).map((f) => f.replace(".json", ""));
|
|
91475
92098
|
if (files.length === 0) {
|
|
91476
92099
|
return JSON.stringify({ tasks: [] });
|
|
91477
92100
|
}
|
|
@@ -91516,8 +92139,6 @@ function createTaskUpdateTool(config4, ctx) {
|
|
|
91516
92139
|
|
|
91517
92140
|
Supports updating: subject, description, status, activeForm, owner, metadata.
|
|
91518
92141
|
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
92142
|
|
|
91522
92143
|
**IMPORTANT - Dependency Management:**
|
|
91523
92144
|
Use \`addBlockedBy\` to declare dependencies on other tasks.
|
|
@@ -91545,8 +92166,9 @@ async function handleUpdate(args, config4, ctx, context) {
|
|
|
91545
92166
|
if (!taskId) {
|
|
91546
92167
|
return JSON.stringify({ error: "invalid_task_id" });
|
|
91547
92168
|
}
|
|
91548
|
-
const
|
|
91549
|
-
const
|
|
92169
|
+
const directory = context?.directory ?? ctx?.directory ?? process.cwd();
|
|
92170
|
+
const taskDir = getTaskDir(config4, directory);
|
|
92171
|
+
const lock = await acquireLockWithRetry(taskDir);
|
|
91550
92172
|
if (!lock.acquired) {
|
|
91551
92173
|
return JSON.stringify({ error: "task_lock_unavailable" });
|
|
91552
92174
|
}
|
|
@@ -91589,7 +92211,6 @@ async function handleUpdate(args, config4, ctx, context) {
|
|
|
91589
92211
|
}
|
|
91590
92212
|
const validatedTask = TaskObjectSchema.parse(task);
|
|
91591
92213
|
writeJsonAtomic(taskPath, validatedTask);
|
|
91592
|
-
await syncTaskTodoUpdate(ctx, validatedTask, context.sessionID);
|
|
91593
92214
|
return JSON.stringify({ task: validatedTask });
|
|
91594
92215
|
} finally {
|
|
91595
92216
|
lock.release();
|
|
@@ -91722,14 +92343,13 @@ function createToolGuardHooks(args) {
|
|
|
91722
92343
|
const envFileWriteGuard = isHookEnabled("env-file-write-guard") ? safeHook("env-file-write-guard", () => createEnvFileWriteGuardHook(pluginConfig.security?.env_file_guard)) : null;
|
|
91723
92344
|
const jsonErrorRecovery = isHookEnabled("json-error-recovery") ? safeHook("json-error-recovery", () => createJsonErrorRecoveryHook(ctx)) : null;
|
|
91724
92345
|
const bashFileReadGuard = isHookEnabled("bash-file-read-guard") ? safeHook("bash-file-read-guard", () => createBashFileReadGuardHook()) : null;
|
|
91725
|
-
const todoDescriptionOverride = isHookEnabled("todo-description-override") ? safeHook("todo-description-override", () => createTodoDescriptionOverrideHook()) : null;
|
|
91726
92346
|
const readImageResizer = isHookEnabled("read-image-resizer") ? safeHook("read-image-resizer", () => createReadImageResizerHook(ctx)) : null;
|
|
91727
92347
|
const webfetchRedirectGuard = isHookEnabled("webfetch-redirect-guard") ? safeHook("webfetch-redirect-guard", () => createWebFetchRedirectGuardHook(ctx)) : null;
|
|
91728
92348
|
const hashlineEditDiffEnhancer = isHookEnabled("hashline-edit-diff-enhancer") ? safeHook("hashline-edit-diff-enhancer", () => createHashlineEditDiffEnhancerHook({ hashline_edit: { enabled: pluginConfig.experimental?.hashline_edit ?? false } })) : null;
|
|
91729
92349
|
const qualityGate = isHookEnabled("quality-gate") ? safeHook("quality-gate", () => createQualityGateHook()) : null;
|
|
91730
92350
|
const taskNotepad = isHookEnabled("task-notepad") ? safeHook("task-notepad", () => createTaskNotepadHook(ctx)) : null;
|
|
92351
|
+
const taskEditGuard = isHookEnabled("task-edit-guard") ? safeHook("task-edit-guard", () => createTaskEditGuardHook(ctx)) : null;
|
|
91731
92352
|
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
92353
|
return {
|
|
91734
92354
|
commentChecker,
|
|
91735
92355
|
toolOutputTruncator,
|
|
@@ -91744,14 +92364,13 @@ function createToolGuardHooks(args) {
|
|
|
91744
92364
|
envFileWriteGuard,
|
|
91745
92365
|
jsonErrorRecovery,
|
|
91746
92366
|
bashFileReadGuard,
|
|
91747
|
-
todoDescriptionOverride,
|
|
91748
92367
|
readImageResizer,
|
|
91749
92368
|
webfetchRedirectGuard,
|
|
91750
92369
|
hashlineEditDiffEnhancer,
|
|
91751
92370
|
qualityGate,
|
|
91752
92371
|
taskNotepad,
|
|
91753
|
-
|
|
91754
|
-
|
|
92372
|
+
taskEditGuard,
|
|
92373
|
+
evolutionWatcher
|
|
91755
92374
|
};
|
|
91756
92375
|
}
|
|
91757
92376
|
|
|
@@ -92205,19 +92824,19 @@ function detectRepetitiveToolUse(window2) {
|
|
|
92205
92824
|
}
|
|
92206
92825
|
|
|
92207
92826
|
// src/features/background-agent/message-dir.ts
|
|
92208
|
-
import { existsSync as
|
|
92827
|
+
import { existsSync as existsSync68, readdirSync as readdirSync23, readFileSync as readFileSync42 } from "fs";
|
|
92209
92828
|
import { join as join73 } from "path";
|
|
92210
92829
|
var MESSAGE_DIR_CACHE_CAP = 1000;
|
|
92211
92830
|
var messageDirCache = new Map;
|
|
92212
92831
|
function computeMessageDir(sessionID) {
|
|
92213
|
-
if (!
|
|
92832
|
+
if (!existsSync68(MESSAGE_STORAGE))
|
|
92214
92833
|
return null;
|
|
92215
92834
|
const directPath = join73(MESSAGE_STORAGE, sessionID);
|
|
92216
|
-
if (
|
|
92835
|
+
if (existsSync68(directPath))
|
|
92217
92836
|
return directPath;
|
|
92218
|
-
for (const dir of
|
|
92837
|
+
for (const dir of readdirSync23(MESSAGE_STORAGE)) {
|
|
92219
92838
|
const sessionPath = join73(MESSAGE_STORAGE, dir, sessionID);
|
|
92220
|
-
if (
|
|
92839
|
+
if (existsSync68(sessionPath))
|
|
92221
92840
|
return sessionPath;
|
|
92222
92841
|
}
|
|
92223
92842
|
return null;
|
|
@@ -92251,11 +92870,11 @@ function hasPartialAgentOrModel(message) {
|
|
|
92251
92870
|
}
|
|
92252
92871
|
function findNearestMessageExcludingCompaction(messageDir) {
|
|
92253
92872
|
try {
|
|
92254
|
-
const files =
|
|
92873
|
+
const files = readdirSync23(messageDir).filter((name) => name.endsWith(".json")).sort().reverse();
|
|
92255
92874
|
const parsedByFile = new Map;
|
|
92256
92875
|
for (const file3 of files) {
|
|
92257
92876
|
try {
|
|
92258
|
-
const content =
|
|
92877
|
+
const content = readFileSync42(join73(messageDir, file3), "utf-8");
|
|
92259
92878
|
const parsed = JSON.parse(content);
|
|
92260
92879
|
parsedByFile.set(file3, parsed);
|
|
92261
92880
|
if (hasFullAgentAndModel(parsed)) {
|
|
@@ -94695,6 +95314,12 @@ ${useWhen.map((w) => `| ${w} | Oracle FIRST, then implement |`).join(`
|
|
|
94695
95314
|
${avoidWhen.map((w) => `- ${w}`).join(`
|
|
94696
95315
|
`)}
|
|
94697
95316
|
|
|
95317
|
+
### HARD ROUTING RULE \u2014 Plan File Creation (NON-NEGOTIABLE):
|
|
95318
|
+
- ANY task that creates or edits \`.matrixx/plans/*.md\` MUST use \`task(subagent_type='oracle')\` \u2014 NEVER \`task(category='...')\` / Mouse
|
|
95319
|
+
- ANY prompt containing \`Oracle plan\`, \`work plan\`, \`proposal\`, \`TL;DR\`, \`root cause\`, \`pure-host\`, \`fix options\` or similar planning language MUST route to \`oracle\`
|
|
95320
|
+
- Mouse is a leaf executor and CANNOT produce plans. Oracle owns all plan generation. Mouse \u2192 Oracle is a violation (extra hop, diluted context).
|
|
95321
|
+
- If you already created tasks for research + drafting, the drafting task MUST still be \`oracle\`, not \`mouse\`.
|
|
95322
|
+
|
|
94698
95323
|
### Usage Pattern:
|
|
94699
95324
|
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
95325
|
|
|
@@ -95823,8 +96448,8 @@ function createBddContractAgent(model) {
|
|
|
95823
96448
|
createBddContractAgent.mode = MODE2;
|
|
95824
96449
|
|
|
95825
96450
|
// src/agents/builtin-agents/resolve-file-uri.ts
|
|
95826
|
-
import { existsSync as
|
|
95827
|
-
import { homedir as
|
|
96451
|
+
import { existsSync as existsSync69, readFileSync as readFileSync43 } from "fs";
|
|
96452
|
+
import { homedir as homedir11 } from "os";
|
|
95828
96453
|
import { isAbsolute as isAbsolute5, resolve as resolve10 } from "path";
|
|
95829
96454
|
function resolvePromptAppend(promptAppend, configDir) {
|
|
95830
96455
|
if (!promptAppend.startsWith("file://"))
|
|
@@ -95833,16 +96458,16 @@ function resolvePromptAppend(promptAppend, configDir) {
|
|
|
95833
96458
|
let filePath;
|
|
95834
96459
|
try {
|
|
95835
96460
|
const decoded = decodeURIComponent(encoded);
|
|
95836
|
-
const expanded = decoded.startsWith("~/") ? decoded.replace(/^~\//, `${
|
|
96461
|
+
const expanded = decoded.startsWith("~/") ? decoded.replace(/^~\//, `${homedir11()}/`) : decoded;
|
|
95837
96462
|
filePath = isAbsolute5(expanded) ? expanded : resolve10(configDir ?? process.cwd(), expanded);
|
|
95838
96463
|
} catch {
|
|
95839
96464
|
return `[WARNING: Malformed file URI (invalid percent-encoding): ${promptAppend}]`;
|
|
95840
96465
|
}
|
|
95841
|
-
if (!
|
|
96466
|
+
if (!existsSync69(filePath)) {
|
|
95842
96467
|
return `[WARNING: Could not resolve file URI: ${promptAppend}]`;
|
|
95843
96468
|
}
|
|
95844
96469
|
try {
|
|
95845
|
-
return
|
|
96470
|
+
return readFileSync43(filePath, "utf8");
|
|
95846
96471
|
} catch {
|
|
95847
96472
|
return `[WARNING: Could not read file: ${promptAppend}]`;
|
|
95848
96473
|
}
|
|
@@ -97557,7 +98182,8 @@ var ORACLE_PLAN_BUILDER_METADATA = {
|
|
|
97557
98182
|
{ domain: "Architecture decisions", trigger: "Multi-system tradeoffs, unfamiliar patterns" },
|
|
97558
98183
|
{ domain: "Work planning", trigger: "Complex feature implementation, refactoring" },
|
|
97559
98184
|
{ domain: "Complex task breakdown", trigger: "Large ambiguous requests needing decomposition" },
|
|
97560
|
-
{ domain: "Self-review", trigger: "After completing significant implementation" }
|
|
98185
|
+
{ domain: "Self-review", trigger: "After completing significant implementation" },
|
|
98186
|
+
{ domain: "Plan generation", trigger: "ANY .matrixx/plans/*.md creation or planning doc \u2192 oracle ONLY, never Mouse" }
|
|
97561
98187
|
],
|
|
97562
98188
|
useWhen: [
|
|
97563
98189
|
"Complex architecture design",
|
|
@@ -100589,20 +101215,22 @@ function buildConstraintsSection(useTaskSystem) {
|
|
|
100589
101215
|
return `<Critical_Constraints>
|
|
100590
101216
|
BLOCKED ACTIONS (will fail if attempted):
|
|
100591
101217
|
- task (agent delegation tool): BLOCKED \u2014 you cannot delegate work to other agents
|
|
101218
|
+
- delegate_agent with subagent_type=oracle/construct/merovingian/smith/sentinel/cipher: BLOCKED \u2014 Mouse is a leaf executor
|
|
100592
101219
|
|
|
100593
101220
|
ALLOWED tools:
|
|
100594
|
-
- delegate_agent: You
|
|
101221
|
+
- delegate_agent with subagent_type=trinity/operator ONLY: You MAY spawn explore/librarian agents for research
|
|
100595
101222
|
- task_create, task_update, task_list, task_get: ALLOWED \u2014 use these for tracking your work
|
|
100596
101223
|
|
|
100597
|
-
You work ALONE for implementation. No delegation of implementation tasks.
|
|
101224
|
+
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
101225
|
</Critical_Constraints>`;
|
|
100599
101226
|
}
|
|
100600
101227
|
return `<Critical_Constraints>
|
|
100601
101228
|
BLOCKED ACTIONS (will fail if attempted):
|
|
100602
101229
|
- task (agent delegation tool): BLOCKED \u2014 you cannot delegate work to other agents
|
|
101230
|
+
- delegate_agent with subagent_type=oracle/construct/merovingian/smith/sentinel/cipher: BLOCKED \u2014 Mouse is a leaf executor
|
|
100603
101231
|
|
|
100604
|
-
ALLOWED: delegate_agent - You
|
|
100605
|
-
You work ALONE for implementation. No delegation of implementation tasks.
|
|
101232
|
+
ALLOWED: delegate_agent with subagent_type=trinity/operator ONLY - You MAY spawn explore/librarian agents for research.
|
|
101233
|
+
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
101234
|
</Critical_Constraints>`;
|
|
100607
101235
|
}
|
|
100608
101236
|
function buildTodoDisciplineSection2(useTaskSystem) {
|
|
@@ -100707,20 +101335,22 @@ function buildConstraintsSection2(useTaskSystem) {
|
|
|
100707
101335
|
return `<Critical_Constraints>
|
|
100708
101336
|
BLOCKED ACTIONS (will fail if attempted):
|
|
100709
101337
|
- task (agent delegation tool): BLOCKED \u2014 you cannot delegate work to other agents
|
|
101338
|
+
- delegate_agent with subagent_type=oracle/construct/merovingian/smith/sentinel/cipher: BLOCKED \u2014 Mouse is a leaf executor
|
|
100710
101339
|
|
|
100711
101340
|
ALLOWED tools:
|
|
100712
|
-
- delegate_agent: You
|
|
101341
|
+
- delegate_agent with subagent_type=trinity/operator ONLY: You MAY spawn explore/librarian agents for research
|
|
100713
101342
|
- task_create, task_update, task_list, task_get: ALLOWED \u2014 use these for tracking your work
|
|
100714
101343
|
|
|
100715
|
-
You work ALONE for implementation. No delegation of implementation tasks.
|
|
101344
|
+
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
101345
|
</Critical_Constraints>`;
|
|
100717
101346
|
}
|
|
100718
101347
|
return `<Critical_Constraints>
|
|
100719
101348
|
BLOCKED ACTIONS (will fail if attempted):
|
|
100720
101349
|
- task (agent delegation tool): BLOCKED \u2014 you cannot delegate work to other agents
|
|
101350
|
+
- delegate_agent with subagent_type=oracle/construct/merovingian/smith/sentinel/cipher: BLOCKED \u2014 Mouse is a leaf executor
|
|
100721
101351
|
|
|
100722
|
-
ALLOWED: delegate_agent - You
|
|
100723
|
-
You work ALONE for implementation. No delegation of implementation tasks.
|
|
101352
|
+
ALLOWED: delegate_agent with subagent_type=trinity/operator ONLY - You MAY spawn explore/librarian agents for research.
|
|
101353
|
+
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
101354
|
</Critical_Constraints>`;
|
|
100725
101355
|
}
|
|
100726
101356
|
function buildTodoDisciplineSection3(useTaskSystem) {
|
|
@@ -100821,17 +101451,18 @@ BLOCKED (will fail if attempted):
|
|
|
100821
101451
|
| Tool | Status | Description |
|
|
100822
101452
|
|------|--------|-------------|
|
|
100823
101453
|
| task | BLOCKED | Agent delegation tool \u2014 you cannot spawn other agents |
|
|
101454
|
+
| delegate_agent with oracle/construct/merovingian/smith/sentinel/cipher | BLOCKED | Mouse is leaf \u2014 only trinity/operator allowed |
|
|
100824
101455
|
|
|
100825
101456
|
ALLOWED:
|
|
100826
101457
|
| Tool | Usage |
|
|
100827
101458
|
|------|-------|
|
|
100828
|
-
| delegate_agent | Spawn explore/librarian for research ONLY |
|
|
101459
|
+
| delegate_agent with trinity/operator ONLY | Spawn explore/librarian for research ONLY |
|
|
100829
101460
|
| task_create | Create tasks to track your work |
|
|
100830
101461
|
| task_update | Update task status (in_progress, completed) |
|
|
100831
101462
|
| task_list | List active tasks |
|
|
100832
101463
|
| task_get | Get task details by ID |
|
|
100833
101464
|
|
|
100834
|
-
You work ALONE for implementation. No delegation.
|
|
101465
|
+
You work ALONE for implementation. No delegation. Plan files (.matrixx/plans/*.md) are OWNED by oracle \u2014 never create them via Mouse.
|
|
100835
101466
|
</blocked_actions>`;
|
|
100836
101467
|
}
|
|
100837
101468
|
return `<blocked_actions>
|
|
@@ -100839,13 +101470,14 @@ BLOCKED (will fail if attempted):
|
|
|
100839
101470
|
| Tool | Status | Description |
|
|
100840
101471
|
|------|--------|-------------|
|
|
100841
101472
|
| task | BLOCKED | Agent delegation tool \u2014 you cannot spawn other agents |
|
|
101473
|
+
| delegate_agent with oracle/construct/merovingian/smith/sentinel/cipher | BLOCKED | Mouse is leaf \u2014 only trinity/operator allowed |
|
|
100842
101474
|
|
|
100843
101475
|
ALLOWED:
|
|
100844
101476
|
| Tool | Usage |
|
|
100845
101477
|
|------|-------|
|
|
100846
|
-
| delegate_agent | Spawn explore/librarian for research ONLY |
|
|
101478
|
+
| delegate_agent with trinity/operator ONLY | Spawn explore/librarian for research ONLY |
|
|
100847
101479
|
|
|
100848
|
-
You work ALONE for implementation. No delegation.
|
|
101480
|
+
You work ALONE for implementation. No delegation. Plan files (.matrixx/plans/*.md) are OWNED by oracle \u2014 never create them via Mouse.
|
|
100849
101481
|
</blocked_actions>`;
|
|
100850
101482
|
}
|
|
100851
101483
|
function buildGptTaskDisciplineSection(useTaskSystem) {
|
|
@@ -100885,11 +101517,12 @@ Execute tasks directly. You NEVER delegate.
|
|
|
100885
101517
|
|
|
100886
101518
|
<rules>
|
|
100887
101519
|
- task tool: BLOCKED \u2014 cannot delegate to other agents
|
|
100888
|
-
- delegate_agent:
|
|
101520
|
+
- delegate_agent with oracle/construct/merovingian/smith/sentinel/cipher: BLOCKED \u2014 Mouse is leaf, only trinity/operator allowed
|
|
101521
|
+
- delegate_agent with trinity/operator ONLY: ALLOWED for research (explore/librarian)
|
|
100889
101522
|
- ${useTaskSystem ? "task_create/task_update" : "todowrite/todoread"}: REQUIRED for tracking
|
|
100890
101523
|
- Implement ONLY what is requested \u2014 no scope creep
|
|
101524
|
+
- Plan files (.matrixx/plans/*.md): OWNED by oracle \u2014 never create via Mouse
|
|
100891
101525
|
</rules>
|
|
100892
|
-
|
|
100893
101526
|
${taskDiscipline}
|
|
100894
101527
|
|
|
100895
101528
|
<verify>
|
|
@@ -100981,15 +101614,21 @@ function buildQwenBlockedActions(useTaskSystem) {
|
|
|
100981
101614
|
return `| Tool | Status | Notes |
|
|
100982
101615
|
|------|--------|-------|
|
|
100983
101616
|
| task | BLOCKED | Cannot delegate implementation |
|
|
100984
|
-
| delegate_agent |
|
|
101617
|
+
| delegate_agent with oracle/construct/merovingian/smith/sentinel/cipher | BLOCKED | Mouse is leaf \u2014 only trinity/operator allowed |
|
|
101618
|
+
| delegate_agent with trinity/operator ONLY | ALLOWED | Research only (explore/librarian) |
|
|
100985
101619
|
| task_create | ALLOWED | Track your work |
|
|
100986
101620
|
| task_update | ALLOWED | Update status |
|
|
100987
|
-
| task_list / task_get | ALLOWED | View tasks
|
|
101621
|
+
| task_list / task_get | ALLOWED | View tasks |
|
|
101622
|
+
|
|
101623
|
+
Plan files (.matrixx/plans/*.md) are OWNED by oracle \u2014 never create them via Mouse.`;
|
|
100988
101624
|
}
|
|
100989
101625
|
return `| Tool | Status | Notes |
|
|
100990
101626
|
|------|--------|-------|
|
|
100991
101627
|
| task | BLOCKED | Cannot delegate implementation |
|
|
100992
|
-
| delegate_agent |
|
|
101628
|
+
| delegate_agent with oracle/construct/merovingian/smith/sentinel/cipher | BLOCKED | Mouse is leaf \u2014 only trinity/operator allowed |
|
|
101629
|
+
| delegate_agent with trinity/operator ONLY | ALLOWED | Research only (explore/librarian) |
|
|
101630
|
+
|
|
101631
|
+
Plan files (.matrixx/plans/*.md) are OWNED by oracle \u2014 never create them via Mouse.`;
|
|
100993
101632
|
}
|
|
100994
101633
|
function buildQwenDisciplineSection(useTaskSystem) {
|
|
100995
101634
|
if (useTaskSystem) {
|
|
@@ -101256,7 +101895,7 @@ async function applyAgentConfig(params) {
|
|
|
101256
101895
|
if (!params.pluginConfig.tdd_enforcer?.enabled) {
|
|
101257
101896
|
disabledSkills.add("tdd-enforcer");
|
|
101258
101897
|
}
|
|
101259
|
-
const useTaskSystem = params.pluginConfig
|
|
101898
|
+
const useTaskSystem = isTaskSystemEnabled(params.pluginConfig);
|
|
101260
101899
|
const availableToolNames = getAvailableToolNames();
|
|
101261
101900
|
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
101901
|
const rawPluginAgents = params.pluginComponents.agents;
|
|
@@ -101562,7 +102201,9 @@ function agentByKey(agentResult, key) {
|
|
|
101562
102201
|
return agentResult[key];
|
|
101563
102202
|
}
|
|
101564
102203
|
function applyToolConfig(params) {
|
|
101565
|
-
const
|
|
102204
|
+
const isTaskSystem = isTaskSystemEnabled(params.pluginConfig);
|
|
102205
|
+
const denyTodoTools = isTaskSystem ? { todowrite: "deny", todoread: "deny" } : {};
|
|
102206
|
+
const denyTaskTools = !isTaskSystem ? { "task_*": "deny", task: "deny" } : {};
|
|
101566
102207
|
params.config.tools = {
|
|
101567
102208
|
...params.config.tools,
|
|
101568
102209
|
"grep_app_*": false,
|
|
@@ -101571,7 +102212,7 @@ function applyToolConfig(params) {
|
|
|
101571
102212
|
LspCodeActionResolve: false,
|
|
101572
102213
|
"task_*": false,
|
|
101573
102214
|
teammate: false,
|
|
101574
|
-
...
|
|
102215
|
+
...isTaskSystem ? { todowrite: false, todoread: false } : { "task_*": false, task: false }
|
|
101575
102216
|
};
|
|
101576
102217
|
const isCliRunMode = process.env.OPENCODE_CLI_RUN_MODE === "true";
|
|
101577
102218
|
const questionPermission = isCliRunMode ? "deny" : "allow";
|
|
@@ -101587,11 +102228,8 @@ function applyToolConfig(params) {
|
|
|
101587
102228
|
if (architect) {
|
|
101588
102229
|
architect.permission = {
|
|
101589
102230
|
...architect.permission,
|
|
101590
|
-
task: "allow",
|
|
101591
102231
|
delegate_agent: "deny",
|
|
101592
|
-
"task_*": "allow",
|
|
101593
|
-
teammate: "allow",
|
|
101594
|
-
...denyTodoTools
|
|
102232
|
+
...isTaskSystem ? { task: "allow", "task_*": "allow", teammate: "allow", ...denyTodoTools } : { todowrite: "allow", todoread: "allow", ...denyTaskTools }
|
|
101595
102233
|
};
|
|
101596
102234
|
}
|
|
101597
102235
|
const morpheus = agentByKey(params.agentResult, "morpheus");
|
|
@@ -101599,11 +102237,8 @@ function applyToolConfig(params) {
|
|
|
101599
102237
|
morpheus.permission = {
|
|
101600
102238
|
...morpheus.permission,
|
|
101601
102239
|
delegate_agent: "deny",
|
|
101602
|
-
task: "allow",
|
|
101603
102240
|
question: questionPermission,
|
|
101604
|
-
"task_*": "allow",
|
|
101605
|
-
teammate: "allow",
|
|
101606
|
-
...denyTodoTools
|
|
102241
|
+
...isTaskSystem ? { task: "allow", "task_*": "allow", teammate: "allow", ...denyTodoTools } : { todowrite: "allow", todoread: "allow", ...denyTaskTools }
|
|
101607
102242
|
};
|
|
101608
102243
|
}
|
|
101609
102244
|
const keymaker = agentByKey(params.agentResult, "keymaker");
|
|
@@ -101611,9 +102246,8 @@ function applyToolConfig(params) {
|
|
|
101611
102246
|
keymaker.permission = {
|
|
101612
102247
|
...keymaker.permission,
|
|
101613
102248
|
delegate_agent: "deny",
|
|
101614
|
-
task: "allow",
|
|
101615
102249
|
question: questionPermission,
|
|
101616
|
-
...denyTodoTools
|
|
102250
|
+
...isTaskSystem ? { task: "allow", ...denyTodoTools } : { todowrite: "allow", todoread: "allow", ...denyTaskTools }
|
|
101617
102251
|
};
|
|
101618
102252
|
}
|
|
101619
102253
|
const oracle = agentByKey(params.agentResult, "oracle");
|
|
@@ -101621,21 +102255,15 @@ function applyToolConfig(params) {
|
|
|
101621
102255
|
oracle.permission = {
|
|
101622
102256
|
...oracle.permission,
|
|
101623
102257
|
delegate_agent: "deny",
|
|
101624
|
-
task: "allow",
|
|
101625
102258
|
question: questionPermission,
|
|
101626
|
-
"task_*": "allow",
|
|
101627
|
-
teammate: "allow",
|
|
101628
|
-
...denyTodoTools
|
|
102259
|
+
...isTaskSystem ? { task: "allow", "task_*": "allow", teammate: "allow", ...denyTodoTools } : { todowrite: "allow", todoread: "allow", ...denyTaskTools }
|
|
101629
102260
|
};
|
|
101630
102261
|
}
|
|
101631
102262
|
const mouse = agentByKey(params.agentResult, "mouse");
|
|
101632
102263
|
if (mouse) {
|
|
101633
102264
|
mouse.permission = {
|
|
101634
102265
|
...mouse.permission,
|
|
101635
|
-
task: "allow",
|
|
101636
|
-
"task_*": "allow",
|
|
101637
|
-
teammate: "allow",
|
|
101638
|
-
...denyTodoTools
|
|
102266
|
+
...isTaskSystem ? { task: "allow", "task_*": "allow", teammate: "allow", ...denyTodoTools } : { todowrite: "allow", todoread: "allow", ...denyTaskTools }
|
|
101639
102267
|
};
|
|
101640
102268
|
}
|
|
101641
102269
|
params.config.permission = {
|
|
@@ -101811,12 +102439,13 @@ function createToolRegistry(args) {
|
|
|
101811
102439
|
skills: skillContext.builtinSkills,
|
|
101812
102440
|
client: ctx.client
|
|
101813
102441
|
});
|
|
101814
|
-
const taskSystemEnabled = pluginConfig
|
|
102442
|
+
const taskSystemEnabled = isTaskSystemEnabled(pluginConfig);
|
|
101815
102443
|
const taskToolsRecord = taskSystemEnabled ? {
|
|
101816
102444
|
task_create: createTaskCreateTool(pluginConfig, ctx),
|
|
101817
|
-
task_get: createTaskGetTool(pluginConfig),
|
|
101818
|
-
task_list: createTaskList(pluginConfig),
|
|
101819
|
-
task_update: createTaskUpdateTool(pluginConfig, ctx)
|
|
102445
|
+
task_get: createTaskGetTool(pluginConfig, ctx),
|
|
102446
|
+
task_list: createTaskList(pluginConfig, ctx),
|
|
102447
|
+
task_update: createTaskUpdateTool(pluginConfig, ctx),
|
|
102448
|
+
task_cleanup: createTaskCleanupTool(pluginConfig, ctx)
|
|
101820
102449
|
} : {};
|
|
101821
102450
|
const hashlineEnabled = pluginConfig.experimental?.hashline_edit ?? false;
|
|
101822
102451
|
const hashlineToolsRecord = hashlineEnabled ? { edit: createHashlineEditTool(ctx) } : {};
|
|
@@ -102182,7 +102811,7 @@ var ExperimentalConfigSchema = exports_external.object({
|
|
|
102182
102811
|
auto_resume: exports_external.boolean().optional(),
|
|
102183
102812
|
preemptive_compaction: exports_external.boolean().optional(),
|
|
102184
102813
|
truncate_all_tool_outputs: exports_external.boolean().optional(),
|
|
102185
|
-
task_system: exports_external.boolean().optional(),
|
|
102814
|
+
task_system: exports_external.boolean().optional().default(true),
|
|
102186
102815
|
plugin_load_timeout_ms: exports_external.number().min(1000).optional(),
|
|
102187
102816
|
safe_hook_creation: exports_external.boolean().optional(),
|
|
102188
102817
|
hashline_edit: exports_external.boolean().optional()
|
|
@@ -102196,6 +102825,7 @@ var HeadroomConfigSchema = exports_external.object({
|
|
|
102196
102825
|
});
|
|
102197
102826
|
// src/config/schema/hooks.ts
|
|
102198
102827
|
var HookNameSchema = exports_external.enum([
|
|
102828
|
+
"task-continuation-enforcer",
|
|
102199
102829
|
"todo-continuation-enforcer",
|
|
102200
102830
|
"context-window-monitor",
|
|
102201
102831
|
"session-recovery",
|
|
@@ -102243,7 +102873,6 @@ var HookNameSchema = exports_external.enum([
|
|
|
102243
102873
|
"env-file-write-guard",
|
|
102244
102874
|
"json-error-recovery",
|
|
102245
102875
|
"bash-file-read-guard",
|
|
102246
|
-
"todo-description-override",
|
|
102247
102876
|
"runtime-fallback",
|
|
102248
102877
|
"read-image-resizer",
|
|
102249
102878
|
"webfetch-redirect-guard",
|
|
@@ -102251,14 +102880,14 @@ var HookNameSchema = exports_external.enum([
|
|
|
102251
102880
|
"tool-pair-validator",
|
|
102252
102881
|
"quality-gate",
|
|
102253
102882
|
"task-notepad",
|
|
102254
|
-
"task-todo-mirror",
|
|
102255
102883
|
"design-intent-preserver",
|
|
102256
102884
|
"rtk-bash-rewriter",
|
|
102257
102885
|
"mcp-startup-notification",
|
|
102258
102886
|
"evolution-watcher",
|
|
102259
102887
|
"evolution-compressor",
|
|
102260
102888
|
"evolution-quality-gate",
|
|
102261
|
-
"evolution-hitl"
|
|
102889
|
+
"evolution-hitl",
|
|
102890
|
+
"task-edit-guard"
|
|
102262
102891
|
]);
|
|
102263
102892
|
// src/config/schema/matrix-loop.ts
|
|
102264
102893
|
var MatrixLoopConfigSchema = exports_external.object({
|
|
@@ -102276,7 +102905,8 @@ var MatrixxSelfConfigSkillConfigSchema = exports_external.object({
|
|
|
102276
102905
|
var MorpheusTasksConfigSchema = exports_external.object({
|
|
102277
102906
|
storage_path: exports_external.string().optional(),
|
|
102278
102907
|
task_list_id: exports_external.string().optional(),
|
|
102279
|
-
claude_code_compat: exports_external.boolean().default(false)
|
|
102908
|
+
claude_code_compat: exports_external.boolean().default(false),
|
|
102909
|
+
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
102910
|
});
|
|
102281
102911
|
var MorpheusConfigSchema = exports_external.object({
|
|
102282
102912
|
tasks: MorpheusTasksConfigSchema.optional()
|
|
@@ -102637,7 +103267,6 @@ function createChatMessageHandler2(args) {
|
|
|
102637
103267
|
applyAgentVariant(pluginConfig, input.agent, message);
|
|
102638
103268
|
}
|
|
102639
103269
|
}
|
|
102640
|
-
await hooks2.taskTodoMirror?.["chat.message"]?.(input, output);
|
|
102641
103270
|
await hooks2.stopContinuationGuard?.["chat.message"]?.(input);
|
|
102642
103271
|
await hooks2.keywordDetector?.["chat.message"]?.(input, output);
|
|
102643
103272
|
await hooks2.autoSlashCommand?.["chat.message"]?.(input, output);
|
|
@@ -102787,8 +103416,8 @@ function createEventHandler2(args) {
|
|
|
102787
103416
|
await Promise.resolve(hooks2.autoUpdateChecker?.event?.(input));
|
|
102788
103417
|
await Promise.resolve(hooks2.backgroundNotificationHook?.event?.(input));
|
|
102789
103418
|
await Promise.resolve(hooks2.sessionNotification?.(input));
|
|
102790
|
-
await Promise.resolve(hooks2.taskTodoMirror?.event?.(input));
|
|
102791
103419
|
await Promise.resolve(hooks2.todoContinuationEnforcer?.handler?.(input));
|
|
103420
|
+
await Promise.resolve(hooks2.taskContinuationEnforcer?.handler?.(input));
|
|
102792
103421
|
await Promise.resolve(hooks2.unstableAgentBabysitter?.event?.(input));
|
|
102793
103422
|
await Promise.resolve(hooks2.contextWindowMonitor?.event?.(input));
|
|
102794
103423
|
await Promise.resolve(hooks2.directoryAgentsInjector?.event?.(input));
|
|
@@ -102938,7 +103567,6 @@ function setCachedTransform(sessionID, messagesHash, messages2) {
|
|
|
102938
103567
|
}
|
|
102939
103568
|
function createMessagesTransformHandler(args) {
|
|
102940
103569
|
return async (input, output) => {
|
|
102941
|
-
await args.hooks.taskTodoMirror?.["experimental.chat.messages.transform"]?.(input, output);
|
|
102942
103570
|
const sessionID = extractSessionID(output.messages);
|
|
102943
103571
|
if (!sessionID) {
|
|
102944
103572
|
await args.hooks.contextInjectorMessagesTransform?.["experimental.chat.messages.transform"]?.(input, output);
|
|
@@ -102962,9 +103590,7 @@ function createMessagesTransformHandler(args) {
|
|
|
102962
103590
|
// src/plugin/tool-definition.ts
|
|
102963
103591
|
function createToolDefinitionHandler(args) {
|
|
102964
103592
|
const { hooks: hooks2 } = args;
|
|
102965
|
-
return async (input, output) => {
|
|
102966
|
-
await hooks2.todoDescriptionOverride?.["tool.definition"]?.(input, output);
|
|
102967
|
-
};
|
|
103593
|
+
return async (input, output) => {};
|
|
102968
103594
|
}
|
|
102969
103595
|
|
|
102970
103596
|
// src/plugin/tool-execute-after.ts
|
|
@@ -103001,7 +103627,6 @@ function createToolExecuteAfterHandler2(args) {
|
|
|
103001
103627
|
await hooks2.jsonErrorRecovery?.["tool.execute.after"]?.(input, output);
|
|
103002
103628
|
await hooks2.readImageResizer?.["tool.execute.after"]?.(input, output);
|
|
103003
103629
|
await hooks2.taskNotepad?.["tool.execute.after"]?.(input, output);
|
|
103004
|
-
await hooks2.taskTodoMirror?.["tool.execute.after"]?.(input, output);
|
|
103005
103630
|
};
|
|
103006
103631
|
await Promise.all([
|
|
103007
103632
|
hooks2.preemptiveCompaction?.["tool.execute.after"]?.(input, output),
|
|
@@ -103013,22 +103638,22 @@ function createToolExecuteAfterHandler2(args) {
|
|
|
103013
103638
|
|
|
103014
103639
|
// src/features/assembly-state/manager.ts
|
|
103015
103640
|
init_logger();
|
|
103016
|
-
var
|
|
103641
|
+
var HOOK_NAME18 = "assembly-state";
|
|
103017
103642
|
var disabledSessions = new Set;
|
|
103018
103643
|
function isAssemblyDisabled(sessionID) {
|
|
103019
103644
|
return disabledSessions.has(sessionID);
|
|
103020
103645
|
}
|
|
103021
103646
|
function disableAssembly(sessionID) {
|
|
103022
103647
|
disabledSessions.add(sessionID);
|
|
103023
|
-
log(`[${
|
|
103648
|
+
log(`[${HOOK_NAME18}] Assembly disabled for session`, { sessionID });
|
|
103024
103649
|
disabledSessions.add(sessionID);
|
|
103025
|
-
log(`[${
|
|
103650
|
+
log(`[${HOOK_NAME18}] Consensus disabled for session`, { sessionID });
|
|
103026
103651
|
}
|
|
103027
103652
|
function enableAssembly(sessionID) {
|
|
103028
103653
|
disabledSessions.delete(sessionID);
|
|
103029
|
-
log(`[${
|
|
103654
|
+
log(`[${HOOK_NAME18}] Assembly enabled for session`, { sessionID });
|
|
103030
103655
|
disabledSessions.delete(sessionID);
|
|
103031
|
-
log(`[${
|
|
103656
|
+
log(`[${HOOK_NAME18}] Consensus enabled for session`, { sessionID });
|
|
103032
103657
|
}
|
|
103033
103658
|
// src/plugin/session-agent-resolver.ts
|
|
103034
103659
|
async function resolveSessionAgent(client2, sessionId, timeoutMs = 5000) {
|