opencode-matrixx 2.6.6 → 2.6.7
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/dist/cli.js +51 -4
- package/dist/config/schema/background-task.d.ts +37 -0
- package/dist/config/schema/dcp.d.ts +46 -0
- package/dist/config/schema/matrixx-config.d.ts +32 -0
- package/dist/features/background-agent/admission.d.ts +24 -0
- package/dist/features/background-agent/concurrency.d.ts +9 -0
- package/dist/features/background-agent/constants.d.ts +13 -0
- package/dist/features/background-agent/handle-index.d.ts +12 -0
- package/dist/features/background-agent/manager.d.ts +59 -4
- package/dist/features/background-agent/notification-builder.d.ts +1 -0
- package/dist/features/background-agent/reconcile.d.ts +33 -0
- package/dist/features/background-agent/revive.d.ts +39 -0
- package/dist/features/background-agent/session-output.d.ts +40 -0
- package/dist/features/background-agent/task-history.d.ts +20 -0
- package/dist/features/background-agent/types.d.ts +21 -1
- package/dist/features/background-agent/wake-scheduler.d.ts +47 -0
- package/dist/features/background-agent/wallclock.d.ts +37 -0
- package/dist/features/bdd/schema.d.ts +5 -5
- package/dist/hooks/context-mode-enforcer/constants.d.ts +1 -1
- package/dist/index.js +1909 -716
- package/dist/matrixx.schema.json +165 -3
- package/dist/plugin-handlers/agent-config-handler.d.ts +2 -1
- package/dist/shared/context-mode-enforcement.d.ts +35 -0
- package/dist/shared/index.d.ts +1 -1
- package/dist/shared/revive-outcome.d.ts +15 -0
- package/dist/shared/saturated-outcome.d.ts +18 -0
- package/dist/shared/wallclock-outcome.d.ts +18 -0
- package/dist/tools/background-task/constants.d.ts +1 -0
- package/dist/tools/background-task/create-background-revive.d.ts +3 -0
- package/dist/tools/background-task/index.d.ts +1 -1
- package/dist/tools/background-task/tools.d.ts +1 -0
- package/dist/tools/background-task/types.d.ts +9 -0
- package/dist/tools/dcp-switch-profile/tools.d.ts +1 -0
- package/dist/tools/index.d.ts +1 -1
- package/package.json +1 -1
- package/dist/shared/context-mode-prompt.d.ts +0 -12
- package/dist/shared/context-mode-prompt.test.d.ts +0 -1
package/dist/index.js
CHANGED
|
@@ -4550,14 +4550,14 @@ __export(exports_storage, {
|
|
|
4550
4550
|
readState: () => readState,
|
|
4551
4551
|
writeState: () => writeState
|
|
4552
4552
|
});
|
|
4553
|
-
import { existsSync as
|
|
4554
|
-
import { dirname as dirname9, join as
|
|
4553
|
+
import { existsSync as existsSync35, mkdirSync as mkdirSync13, readFileSync as readFileSync28, unlinkSync as unlinkSync7, writeFileSync as writeFileSync15 } from "fs";
|
|
4554
|
+
import { dirname as dirname9, join as join44 } from "path";
|
|
4555
4555
|
function getStateFilePath(directory, customPath) {
|
|
4556
|
-
return customPath ?
|
|
4556
|
+
return customPath ? join44(directory, customPath) : join44(directory, DEFAULT_STATE_FILE);
|
|
4557
4557
|
}
|
|
4558
4558
|
function readState(directory, customPath) {
|
|
4559
4559
|
const filePath = getStateFilePath(directory, customPath);
|
|
4560
|
-
if (!
|
|
4560
|
+
if (!existsSync35(filePath)) {
|
|
4561
4561
|
return null;
|
|
4562
4562
|
}
|
|
4563
4563
|
try {
|
|
@@ -4597,7 +4597,7 @@ function writeState(directory, state3, customPath) {
|
|
|
4597
4597
|
const filePath = getStateFilePath(directory, customPath);
|
|
4598
4598
|
try {
|
|
4599
4599
|
const dir = dirname9(filePath);
|
|
4600
|
-
if (!
|
|
4600
|
+
if (!existsSync35(dir)) {
|
|
4601
4601
|
mkdirSync13(dir, { recursive: true });
|
|
4602
4602
|
}
|
|
4603
4603
|
const sessionIdLine = state3.session_id ? `session_id: "${state3.session_id}"
|
|
@@ -4624,7 +4624,7 @@ ${state3.prompt}
|
|
|
4624
4624
|
function clearState(directory, customPath) {
|
|
4625
4625
|
const filePath = getStateFilePath(directory, customPath);
|
|
4626
4626
|
try {
|
|
4627
|
-
if (
|
|
4627
|
+
if (existsSync35(filePath)) {
|
|
4628
4628
|
unlinkSync7(filePath);
|
|
4629
4629
|
}
|
|
4630
4630
|
return true;
|
|
@@ -30087,7 +30087,7 @@ Write final summary to \`.matrixx/slop-scan-<timestamp>.md\`.
|
|
|
30087
30087
|
});
|
|
30088
30088
|
|
|
30089
30089
|
// src/hooks/agent-usage-reminder/constants.ts
|
|
30090
|
-
import { join as
|
|
30090
|
+
import { join as join16 } from "path";
|
|
30091
30091
|
|
|
30092
30092
|
// src/shared/agent-tool-restrictions.ts
|
|
30093
30093
|
var EXPLORATION_AGENT_DENYLIST = {
|
|
@@ -45003,6 +45003,73 @@ async function updateConnectedProvidersCache(client) {
|
|
|
45003
45003
|
log("[connected-providers-cache] Error updating cache", { error: String(err) });
|
|
45004
45004
|
}
|
|
45005
45005
|
}
|
|
45006
|
+
// src/shared/context-mode-enforcement.ts
|
|
45007
|
+
import { existsSync as existsSync3 } from "fs";
|
|
45008
|
+
import { homedir as homedir2 } from "os";
|
|
45009
|
+
import { join as join5 } from "path";
|
|
45010
|
+
var CONTEXT_MODE_DEFAULT_BLOCKED_TOOLS = ["grep", "glob"];
|
|
45011
|
+
function resolveContextModeEnforcement(contextMode) {
|
|
45012
|
+
return {
|
|
45013
|
+
enabled: contextMode?.enabled ?? true,
|
|
45014
|
+
enforce: contextMode?.enforce ?? false,
|
|
45015
|
+
blockedTools: (contextMode?.blocked_tools ?? [...CONTEXT_MODE_DEFAULT_BLOCKED_TOOLS]).map((t) => t.toLowerCase())
|
|
45016
|
+
};
|
|
45017
|
+
}
|
|
45018
|
+
function hasGrepGlobToolNames(toolNames) {
|
|
45019
|
+
return toolNames.some((n) => n === "grep" || n === "glob");
|
|
45020
|
+
}
|
|
45021
|
+
function resolveGrepGlobUsable(toolNames, contextMode) {
|
|
45022
|
+
if (!hasGrepGlobToolNames(toolNames))
|
|
45023
|
+
return false;
|
|
45024
|
+
const cfg = contextMode !== undefined ? contextMode : _contextModeForPrompts;
|
|
45025
|
+
return !willBlockGrepGlob(cfg);
|
|
45026
|
+
}
|
|
45027
|
+
var _disciplinePathOverride;
|
|
45028
|
+
var _contextModeForPrompts;
|
|
45029
|
+
function setContextModeForPrompts(contextMode) {
|
|
45030
|
+
_contextModeForPrompts = contextMode;
|
|
45031
|
+
}
|
|
45032
|
+
function getContextModeForPrompts() {
|
|
45033
|
+
return _contextModeForPrompts;
|
|
45034
|
+
}
|
|
45035
|
+
function willBlockGrepGlob(contextMode) {
|
|
45036
|
+
const { enforce, blockedTools } = resolveContextModeEnforcement(contextMode);
|
|
45037
|
+
if (!enforce)
|
|
45038
|
+
return false;
|
|
45039
|
+
if (!(blockedTools.includes("grep") || blockedTools.includes("glob")))
|
|
45040
|
+
return false;
|
|
45041
|
+
return hasWorkingSubstitute();
|
|
45042
|
+
}
|
|
45043
|
+
function resolveContextModeDisciplinePath() {
|
|
45044
|
+
if (_disciplinePathOverride !== undefined)
|
|
45045
|
+
return _disciplinePathOverride;
|
|
45046
|
+
try {
|
|
45047
|
+
const resolved = __require.resolve("context-mode/configs/opencode/AGENTS.md");
|
|
45048
|
+
if (existsSync3(resolved))
|
|
45049
|
+
return resolved;
|
|
45050
|
+
} catch {}
|
|
45051
|
+
try {
|
|
45052
|
+
const cacheDir = getOpenCodeCacheDir();
|
|
45053
|
+
const p = join5(cacheDir, "packages/context-mode@latest/node_modules/context-mode/configs/opencode/AGENTS.md");
|
|
45054
|
+
if (existsSync3(p))
|
|
45055
|
+
return p;
|
|
45056
|
+
} catch {}
|
|
45057
|
+
try {
|
|
45058
|
+
const base = process.env.XDG_CACHE_HOME ?? join5(homedir2(), ".cache");
|
|
45059
|
+
const p = join5(base, "opencode/packages/context-mode@latest/node_modules/context-mode/configs/opencode/AGENTS.md");
|
|
45060
|
+
if (existsSync3(p))
|
|
45061
|
+
return p;
|
|
45062
|
+
} catch {}
|
|
45063
|
+
try {
|
|
45064
|
+
const p2 = join5(homedir2(), ".cache/opencode/packages/context-mode@latest/node_modules/context-mode/configs/opencode/AGENTS.md");
|
|
45065
|
+
if (existsSync3(p2))
|
|
45066
|
+
return p2;
|
|
45067
|
+
} catch {}
|
|
45068
|
+
return null;
|
|
45069
|
+
}
|
|
45070
|
+
function hasWorkingSubstitute() {
|
|
45071
|
+
return resolveContextModeDisciplinePath() !== null;
|
|
45072
|
+
}
|
|
45006
45073
|
// src/shared/deep-merge.ts
|
|
45007
45074
|
var DANGEROUS_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
45008
45075
|
var MAX_DEPTH = 50;
|
|
@@ -45218,7 +45285,7 @@ import * as os3 from "os";
|
|
|
45218
45285
|
import * as path4 from "path";
|
|
45219
45286
|
|
|
45220
45287
|
// src/shared/jsonc-parser.ts
|
|
45221
|
-
import { existsSync as
|
|
45288
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
|
|
45222
45289
|
|
|
45223
45290
|
// node_modules/jsonc-parser/lib/esm/impl/scanner.js
|
|
45224
45291
|
function createScanner(text, ignoreTrivia = false) {
|
|
@@ -46092,10 +46159,10 @@ function parseJsoncSafe(content) {
|
|
|
46092
46159
|
function detectConfigFile(basePath) {
|
|
46093
46160
|
const jsoncPath = `${basePath}.jsonc`;
|
|
46094
46161
|
const jsonPath = `${basePath}.json`;
|
|
46095
|
-
if (
|
|
46162
|
+
if (existsSync4(jsoncPath)) {
|
|
46096
46163
|
return { format: "jsonc", path: jsoncPath };
|
|
46097
46164
|
}
|
|
46098
|
-
if (
|
|
46165
|
+
if (existsSync4(jsonPath)) {
|
|
46099
46166
|
return { format: "json", path: jsonPath };
|
|
46100
46167
|
}
|
|
46101
46168
|
return { format: "none", path: jsonPath };
|
|
@@ -46190,8 +46257,8 @@ Both matrixx and ${pluginName} listen to session.idle events.
|
|
|
46190
46257
|
2. Or set "notification": { "force_enable": true } in matrixx.json`;
|
|
46191
46258
|
}
|
|
46192
46259
|
// src/shared/file-reference-resolver.ts
|
|
46193
|
-
import { existsSync as
|
|
46194
|
-
import { isAbsolute, join as
|
|
46260
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, statSync } from "fs";
|
|
46261
|
+
import { isAbsolute, join as join7 } from "path";
|
|
46195
46262
|
var FILE_REFERENCE_PATTERN = /@([^\s@]+)/g;
|
|
46196
46263
|
function findFileReferences(text) {
|
|
46197
46264
|
const matches = [];
|
|
@@ -46210,10 +46277,10 @@ function resolveFilePath(filePath, cwd) {
|
|
|
46210
46277
|
if (isAbsolute(filePath)) {
|
|
46211
46278
|
return filePath;
|
|
46212
46279
|
}
|
|
46213
|
-
return
|
|
46280
|
+
return join7(cwd, filePath);
|
|
46214
46281
|
}
|
|
46215
46282
|
function readFileContent(resolvedPath) {
|
|
46216
|
-
if (!
|
|
46283
|
+
if (!existsSync6(resolvedPath)) {
|
|
46217
46284
|
return `[file not found: ${resolvedPath}]`;
|
|
46218
46285
|
}
|
|
46219
46286
|
const stat = statSync(resolvedPath);
|
|
@@ -46279,7 +46346,7 @@ init_frontmatter();
|
|
|
46279
46346
|
// src/shared/git-worktree/collect-git-diff-stats.ts
|
|
46280
46347
|
import { execFileSync } from "child_process";
|
|
46281
46348
|
import { readFileSync as readFileSync5 } from "fs";
|
|
46282
|
-
import { join as
|
|
46349
|
+
import { join as join8 } from "path";
|
|
46283
46350
|
|
|
46284
46351
|
// src/shared/git-worktree/parse-diff-numstat.ts
|
|
46285
46352
|
function parseGitDiffNumstat(output, statusMap) {
|
|
@@ -46352,7 +46419,7 @@ function collectGitDiffStats(directory) {
|
|
|
46352
46419
|
const untrackedNumstat = untrackedOutput ? untrackedOutput.split(`
|
|
46353
46420
|
`).filter(Boolean).map((filePath) => {
|
|
46354
46421
|
try {
|
|
46355
|
-
const content = readFileSync5(
|
|
46422
|
+
const content = readFileSync5(join8(directory, filePath), "utf-8");
|
|
46356
46423
|
const lineCount = content.split(`
|
|
46357
46424
|
`).length - (content.endsWith(`
|
|
46358
46425
|
`) ? 1 : 0);
|
|
@@ -46440,8 +46507,8 @@ function isAbortError(error51) {
|
|
|
46440
46507
|
init_logger();
|
|
46441
46508
|
|
|
46442
46509
|
// src/shared/model-availability.ts
|
|
46443
|
-
import { existsSync as
|
|
46444
|
-
import { join as
|
|
46510
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
|
|
46511
|
+
import { join as join9 } from "path";
|
|
46445
46512
|
init_logger();
|
|
46446
46513
|
function normalizeModelName(name) {
|
|
46447
46514
|
return name.toLowerCase().replace(/(opus|sonnet|haiku)-(\d+)[.-](\d+)/g, "$1-$2.$3");
|
|
@@ -46577,8 +46644,8 @@ async function fetchAvailableModels(client, options) {
|
|
|
46577
46644
|
}
|
|
46578
46645
|
}
|
|
46579
46646
|
log("[fetchAvailableModels] provider-models cache not found, falling back to models.json");
|
|
46580
|
-
const cacheFile =
|
|
46581
|
-
if (!
|
|
46647
|
+
const cacheFile = join9(getOpenCodeCacheDir(), "models.json");
|
|
46648
|
+
if (!existsSync7(cacheFile)) {
|
|
46582
46649
|
log("[fetchAvailableModels] models.json cache file not found, falling back to client");
|
|
46583
46650
|
} else {
|
|
46584
46651
|
try {
|
|
@@ -46683,8 +46750,8 @@ function isModelCacheAvailable() {
|
|
|
46683
46750
|
if (hasProviderModelsCache()) {
|
|
46684
46751
|
return true;
|
|
46685
46752
|
}
|
|
46686
|
-
const cacheFile =
|
|
46687
|
-
return
|
|
46753
|
+
const cacheFile = join9(getOpenCodeCacheDir(), "models.json");
|
|
46754
|
+
return existsSync7(cacheFile);
|
|
46688
46755
|
}
|
|
46689
46756
|
// src/shared/model-resolution-pipeline.ts
|
|
46690
46757
|
init_logger();
|
|
@@ -46973,9 +47040,9 @@ async function promptSyncWithModelSuggestionRetry(client, args) {
|
|
|
46973
47040
|
}
|
|
46974
47041
|
}
|
|
46975
47042
|
// src/shared/opencode-config-dir.ts
|
|
46976
|
-
import { existsSync as
|
|
46977
|
-
import { homedir as
|
|
46978
|
-
import { join as
|
|
47043
|
+
import { existsSync as existsSync8 } from "fs";
|
|
47044
|
+
import { homedir as homedir4 } from "os";
|
|
47045
|
+
import { join as join10, resolve } from "path";
|
|
46979
47046
|
var TAURI_APP_IDENTIFIER = "ai.opencode.desktop";
|
|
46980
47047
|
var TAURI_APP_IDENTIFIER_DEV = "ai.opencode.desktop.dev";
|
|
46981
47048
|
function isDevBuild(version2) {
|
|
@@ -46987,14 +47054,14 @@ function getTauriConfigDir(identifier) {
|
|
|
46987
47054
|
const platform = process.platform;
|
|
46988
47055
|
switch (platform) {
|
|
46989
47056
|
case "darwin":
|
|
46990
|
-
return
|
|
47057
|
+
return join10(homedir4(), "Library", "Application Support", identifier);
|
|
46991
47058
|
case "win32": {
|
|
46992
|
-
const appData = process.env.APPDATA ||
|
|
46993
|
-
return
|
|
47059
|
+
const appData = process.env.APPDATA || join10(homedir4(), "AppData", "Roaming");
|
|
47060
|
+
return join10(appData, identifier);
|
|
46994
47061
|
}
|
|
46995
47062
|
default: {
|
|
46996
|
-
const xdgConfig = process.env.XDG_CONFIG_HOME ||
|
|
46997
|
-
return
|
|
47063
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || join10(homedir4(), ".config");
|
|
47064
|
+
return join10(xdgConfig, identifier);
|
|
46998
47065
|
}
|
|
46999
47066
|
}
|
|
47000
47067
|
}
|
|
@@ -47004,21 +47071,21 @@ function getCliConfigDir() {
|
|
|
47004
47071
|
return resolve(envConfigDir);
|
|
47005
47072
|
}
|
|
47006
47073
|
if (process.platform === "win32") {
|
|
47007
|
-
const crossPlatformDir =
|
|
47008
|
-
const crossPlatformConfig =
|
|
47009
|
-
if (
|
|
47074
|
+
const crossPlatformDir = join10(homedir4(), ".config", "opencode");
|
|
47075
|
+
const crossPlatformConfig = join10(crossPlatformDir, "opencode.json");
|
|
47076
|
+
if (existsSync8(crossPlatformConfig)) {
|
|
47010
47077
|
return crossPlatformDir;
|
|
47011
47078
|
}
|
|
47012
|
-
const appData = process.env.APPDATA ||
|
|
47013
|
-
const appdataDir =
|
|
47014
|
-
const appdataConfig =
|
|
47015
|
-
if (
|
|
47079
|
+
const appData = process.env.APPDATA || join10(homedir4(), "AppData", "Roaming");
|
|
47080
|
+
const appdataDir = join10(appData, "opencode");
|
|
47081
|
+
const appdataConfig = join10(appdataDir, "opencode.json");
|
|
47082
|
+
if (existsSync8(appdataConfig)) {
|
|
47016
47083
|
return appdataDir;
|
|
47017
47084
|
}
|
|
47018
47085
|
return crossPlatformDir;
|
|
47019
47086
|
}
|
|
47020
|
-
const xdgConfig = process.env.XDG_CONFIG_HOME ||
|
|
47021
|
-
return
|
|
47087
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || join10(homedir4(), ".config");
|
|
47088
|
+
return join10(xdgConfig, "opencode");
|
|
47022
47089
|
}
|
|
47023
47090
|
function getOpenCodeConfigDir(options) {
|
|
47024
47091
|
const { binary, version: version2, checkExisting = true } = options;
|
|
@@ -47029,9 +47096,9 @@ function getOpenCodeConfigDir(options) {
|
|
|
47029
47096
|
const tauriDir = getTauriConfigDir(identifier);
|
|
47030
47097
|
if (checkExisting) {
|
|
47031
47098
|
const legacyDir = getCliConfigDir();
|
|
47032
|
-
const legacyConfig =
|
|
47033
|
-
const legacyConfigC =
|
|
47034
|
-
if (
|
|
47099
|
+
const legacyConfig = join10(legacyDir, "opencode.json");
|
|
47100
|
+
const legacyConfigC = join10(legacyDir, "opencode.jsonc");
|
|
47101
|
+
if (existsSync8(legacyConfig) || existsSync8(legacyConfigC)) {
|
|
47035
47102
|
return legacyDir;
|
|
47036
47103
|
}
|
|
47037
47104
|
}
|
|
@@ -47041,10 +47108,10 @@ function getOpenCodeConfigPaths(options) {
|
|
|
47041
47108
|
const configDir = getOpenCodeConfigDir(options);
|
|
47042
47109
|
return {
|
|
47043
47110
|
configDir,
|
|
47044
|
-
configJson:
|
|
47045
|
-
configJsonc:
|
|
47046
|
-
packageJson:
|
|
47047
|
-
matrixxConfig:
|
|
47111
|
+
configJson: join10(configDir, "opencode.json"),
|
|
47112
|
+
configJsonc: join10(configDir, "opencode.jsonc"),
|
|
47113
|
+
packageJson: join10(configDir, "package.json"),
|
|
47114
|
+
matrixxConfig: join10(configDir, "matrixx.json")
|
|
47048
47115
|
};
|
|
47049
47116
|
}
|
|
47050
47117
|
// src/shared/opencode-http-api.ts
|
|
@@ -47291,12 +47358,12 @@ async function deletePart(client, sessionID, messageID, partID) {
|
|
|
47291
47358
|
}
|
|
47292
47359
|
// src/shared/opencode-message-dir.ts
|
|
47293
47360
|
init_logger();
|
|
47294
|
-
import { existsSync as
|
|
47295
|
-
import { join as
|
|
47361
|
+
import { existsSync as existsSync10, readdirSync } from "fs";
|
|
47362
|
+
import { join as join13 } from "path";
|
|
47296
47363
|
|
|
47297
47364
|
// src/shared/opencode-storage-detection.ts
|
|
47298
|
-
import { existsSync as
|
|
47299
|
-
import { join as
|
|
47365
|
+
import { existsSync as existsSync9 } from "fs";
|
|
47366
|
+
import { join as join11 } from "path";
|
|
47300
47367
|
|
|
47301
47368
|
// src/shared/opencode-version.ts
|
|
47302
47369
|
import { execSync } from "child_process";
|
|
@@ -47361,8 +47428,8 @@ function isSqliteBackend() {
|
|
|
47361
47428
|
return false;
|
|
47362
47429
|
const check2 = () => {
|
|
47363
47430
|
const versionOk = isOpenCodeVersionAtLeast(OPENCODE_SQLITE_VERSION);
|
|
47364
|
-
const dbPath =
|
|
47365
|
-
return versionOk &&
|
|
47431
|
+
const dbPath = join11(getDataDir(), "opencode", "opencode.db");
|
|
47432
|
+
return versionOk && existsSync9(dbPath);
|
|
47366
47433
|
};
|
|
47367
47434
|
if (cachedResult === FALSE_PENDING_RETRY) {
|
|
47368
47435
|
const result2 = check2();
|
|
@@ -47379,11 +47446,11 @@ function isSqliteBackend() {
|
|
|
47379
47446
|
}
|
|
47380
47447
|
|
|
47381
47448
|
// src/shared/opencode-storage-paths.ts
|
|
47382
|
-
import { join as
|
|
47449
|
+
import { join as join12 } from "path";
|
|
47383
47450
|
var OPENCODE_STORAGE = getOpenCodeStorageDir();
|
|
47384
|
-
var MESSAGE_STORAGE =
|
|
47385
|
-
var PART_STORAGE =
|
|
47386
|
-
var SESSION_STORAGE =
|
|
47451
|
+
var MESSAGE_STORAGE = join12(OPENCODE_STORAGE, "message");
|
|
47452
|
+
var PART_STORAGE = join12(OPENCODE_STORAGE, "part");
|
|
47453
|
+
var SESSION_STORAGE = join12(OPENCODE_STORAGE, "session");
|
|
47387
47454
|
|
|
47388
47455
|
// src/shared/opencode-message-dir.ts
|
|
47389
47456
|
function getMessageDir(sessionID) {
|
|
@@ -47393,16 +47460,16 @@ function getMessageDir(sessionID) {
|
|
|
47393
47460
|
return null;
|
|
47394
47461
|
if (isSqliteBackend())
|
|
47395
47462
|
return null;
|
|
47396
|
-
if (!
|
|
47463
|
+
if (!existsSync10(MESSAGE_STORAGE))
|
|
47397
47464
|
return null;
|
|
47398
|
-
const directPath =
|
|
47399
|
-
if (
|
|
47465
|
+
const directPath = join13(MESSAGE_STORAGE, sessionID);
|
|
47466
|
+
if (existsSync10(directPath)) {
|
|
47400
47467
|
return directPath;
|
|
47401
47468
|
}
|
|
47402
47469
|
try {
|
|
47403
47470
|
for (const dir of readdirSync(MESSAGE_STORAGE)) {
|
|
47404
|
-
const sessionPath =
|
|
47405
|
-
if (
|
|
47471
|
+
const sessionPath = join13(MESSAGE_STORAGE, dir, sessionID);
|
|
47472
|
+
if (existsSync10(sessionPath)) {
|
|
47406
47473
|
return sessionPath;
|
|
47407
47474
|
}
|
|
47408
47475
|
}
|
|
@@ -47519,8 +47586,8 @@ function resetMessageCursor(sessionID) {
|
|
|
47519
47586
|
sessionCursors.clear();
|
|
47520
47587
|
}
|
|
47521
47588
|
// src/features/hook-message-injector/injector.ts
|
|
47522
|
-
import { existsSync as
|
|
47523
|
-
import { join as
|
|
47589
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "fs";
|
|
47590
|
+
import { join as join14 } from "path";
|
|
47524
47591
|
init_logger();
|
|
47525
47592
|
function convertSDKMessageToStoredMessage(msg) {
|
|
47526
47593
|
const info = msg.info;
|
|
@@ -47594,7 +47661,7 @@ function findNearestMessageWithFields(messageDir) {
|
|
|
47594
47661
|
const files = readdirSync2(messageDir).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
47595
47662
|
for (const file2 of files) {
|
|
47596
47663
|
try {
|
|
47597
|
-
const content = readFileSync7(
|
|
47664
|
+
const content = readFileSync7(join14(messageDir, file2), "utf-8");
|
|
47598
47665
|
const msg = JSON.parse(content);
|
|
47599
47666
|
if (msg.agent && msg.model?.providerID && msg.model?.modelID) {
|
|
47600
47667
|
return msg;
|
|
@@ -47603,7 +47670,7 @@ function findNearestMessageWithFields(messageDir) {
|
|
|
47603
47670
|
}
|
|
47604
47671
|
for (const file2 of files) {
|
|
47605
47672
|
try {
|
|
47606
|
-
const content = readFileSync7(
|
|
47673
|
+
const content = readFileSync7(join14(messageDir, file2), "utf-8");
|
|
47607
47674
|
const msg = JSON.parse(content);
|
|
47608
47675
|
if (msg.agent || msg.model?.providerID && msg.model?.modelID) {
|
|
47609
47676
|
return msg;
|
|
@@ -47623,7 +47690,7 @@ function findFirstMessageWithAgent(messageDir) {
|
|
|
47623
47690
|
const files = readdirSync2(messageDir).filter((f) => f.endsWith(".json")).sort();
|
|
47624
47691
|
for (const file2 of files) {
|
|
47625
47692
|
try {
|
|
47626
|
-
const content = readFileSync7(
|
|
47693
|
+
const content = readFileSync7(join14(messageDir, file2), "utf-8");
|
|
47627
47694
|
const msg = JSON.parse(content);
|
|
47628
47695
|
if (msg.agent) {
|
|
47629
47696
|
return msg.agent;
|
|
@@ -48068,10 +48135,10 @@ async function spawnTmuxPane(sessionId, description, config2, serverUrl, targetP
|
|
|
48068
48135
|
return { success: true, paneId };
|
|
48069
48136
|
}
|
|
48070
48137
|
// src/shared/transcript-path.ts
|
|
48071
|
-
import { join as
|
|
48072
|
-
var TRANSCRIPT_DIR =
|
|
48138
|
+
import { join as join15 } from "path";
|
|
48139
|
+
var TRANSCRIPT_DIR = join15(getOpenCodeConfigDir({ binary: "opencode" }), "transcripts");
|
|
48073
48140
|
function getTranscriptPath(sessionId) {
|
|
48074
|
-
return
|
|
48141
|
+
return join15(TRANSCRIPT_DIR, `${sessionId}.jsonl`);
|
|
48075
48142
|
}
|
|
48076
48143
|
// src/shared/truncate-description.ts
|
|
48077
48144
|
function truncateDescription(description, maxLength = 120) {
|
|
@@ -48092,7 +48159,7 @@ function withTimeout(promise2, timeoutMs, errorMessage) {
|
|
|
48092
48159
|
return Promise.race([promise2, timeoutPromise]).finally(() => clearTimeout(timeoutId));
|
|
48093
48160
|
}
|
|
48094
48161
|
// src/hooks/agent-usage-reminder/constants.ts
|
|
48095
|
-
var AGENT_USAGE_REMINDER_STORAGE =
|
|
48162
|
+
var AGENT_USAGE_REMINDER_STORAGE = join16(OPENCODE_STORAGE, "agent-usage-reminder");
|
|
48096
48163
|
var TARGET_TOOLS = new Set([
|
|
48097
48164
|
"grep",
|
|
48098
48165
|
"safe_grep",
|
|
@@ -48138,19 +48205,19 @@ ALWAYS prefer: Multiple parallel task calls > Direct tool calls
|
|
|
48138
48205
|
|
|
48139
48206
|
// src/hooks/agent-usage-reminder/storage.ts
|
|
48140
48207
|
import {
|
|
48141
|
-
existsSync as
|
|
48208
|
+
existsSync as existsSync12,
|
|
48142
48209
|
mkdirSync as mkdirSync4,
|
|
48143
48210
|
readFileSync as readFileSync8,
|
|
48144
48211
|
unlinkSync as unlinkSync2,
|
|
48145
48212
|
writeFileSync as writeFileSync3
|
|
48146
48213
|
} from "fs";
|
|
48147
|
-
import { join as
|
|
48214
|
+
import { join as join17 } from "path";
|
|
48148
48215
|
function getStoragePath(sessionID) {
|
|
48149
|
-
return
|
|
48216
|
+
return join17(AGENT_USAGE_REMINDER_STORAGE, `${sessionID}.json`);
|
|
48150
48217
|
}
|
|
48151
48218
|
function loadAgentUsageState(sessionID) {
|
|
48152
48219
|
const filePath = getStoragePath(sessionID);
|
|
48153
|
-
if (!
|
|
48220
|
+
if (!existsSync12(filePath))
|
|
48154
48221
|
return null;
|
|
48155
48222
|
try {
|
|
48156
48223
|
const content = readFileSync8(filePath, "utf-8");
|
|
@@ -48160,7 +48227,7 @@ function loadAgentUsageState(sessionID) {
|
|
|
48160
48227
|
}
|
|
48161
48228
|
}
|
|
48162
48229
|
function saveAgentUsageState(state) {
|
|
48163
|
-
if (!
|
|
48230
|
+
if (!existsSync12(AGENT_USAGE_REMINDER_STORAGE)) {
|
|
48164
48231
|
mkdirSync4(AGENT_USAGE_REMINDER_STORAGE, { recursive: true });
|
|
48165
48232
|
}
|
|
48166
48233
|
const filePath = getStoragePath(state.sessionID);
|
|
@@ -48168,7 +48235,7 @@ function saveAgentUsageState(state) {
|
|
|
48168
48235
|
}
|
|
48169
48236
|
function clearAgentUsageState(sessionID) {
|
|
48170
48237
|
const filePath = getStoragePath(sessionID);
|
|
48171
|
-
if (
|
|
48238
|
+
if (existsSync12(filePath)) {
|
|
48172
48239
|
unlinkSync2(filePath);
|
|
48173
48240
|
}
|
|
48174
48241
|
}
|
|
@@ -48249,16 +48316,16 @@ var META_TAG_SUFFIX = "-->";
|
|
|
48249
48316
|
var MAX_PLAN_FILE_BYTES = 102400;
|
|
48250
48317
|
// src/features/mission-state/plan-storage.ts
|
|
48251
48318
|
import {
|
|
48252
|
-
existsSync as
|
|
48319
|
+
existsSync as existsSync13,
|
|
48253
48320
|
mkdirSync as mkdirSync5,
|
|
48254
48321
|
readFileSync as readFileSync9,
|
|
48255
48322
|
renameSync,
|
|
48256
48323
|
writeFileSync as writeFileSync4
|
|
48257
48324
|
} from "fs";
|
|
48258
|
-
import { join as
|
|
48325
|
+
import { join as join18 } from "path";
|
|
48259
48326
|
function ensurePlanDir(directory) {
|
|
48260
48327
|
try {
|
|
48261
|
-
mkdirSync5(
|
|
48328
|
+
mkdirSync5(join18(directory, PLANS_DIR), { recursive: true });
|
|
48262
48329
|
return true;
|
|
48263
48330
|
} catch {
|
|
48264
48331
|
return false;
|
|
@@ -48266,7 +48333,7 @@ function ensurePlanDir(directory) {
|
|
|
48266
48333
|
}
|
|
48267
48334
|
function readPlanFile(planPath) {
|
|
48268
48335
|
try {
|
|
48269
|
-
if (!
|
|
48336
|
+
if (!existsSync13(planPath))
|
|
48270
48337
|
return null;
|
|
48271
48338
|
const content = readFileSync9(planPath, "utf-8");
|
|
48272
48339
|
if (content.length > MAX_PLAN_FILE_BYTES)
|
|
@@ -48325,17 +48392,17 @@ function parseMetadataComment(content) {
|
|
|
48325
48392
|
}
|
|
48326
48393
|
}
|
|
48327
48394
|
// src/features/mission-state/rehydrate.ts
|
|
48328
|
-
import { existsSync as
|
|
48395
|
+
import { existsSync as existsSync15 } from "fs";
|
|
48329
48396
|
|
|
48330
48397
|
// src/features/mission-state/storage.ts
|
|
48331
|
-
import { existsSync as
|
|
48332
|
-
import { basename, dirname, join as
|
|
48398
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync6, readdirSync as readdirSync3, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
|
|
48399
|
+
import { basename, dirname, join as join19 } from "path";
|
|
48333
48400
|
function getMissionFilePath(directory) {
|
|
48334
|
-
return
|
|
48401
|
+
return join19(directory, MISSION_DIR, MISSION_FILE);
|
|
48335
48402
|
}
|
|
48336
48403
|
function readMissionState(directory) {
|
|
48337
48404
|
const filePath = getMissionFilePath(directory);
|
|
48338
|
-
if (!
|
|
48405
|
+
if (!existsSync14(filePath)) {
|
|
48339
48406
|
return null;
|
|
48340
48407
|
}
|
|
48341
48408
|
try {
|
|
@@ -48356,7 +48423,7 @@ function writeMissionState(directory, state) {
|
|
|
48356
48423
|
const filePath = getMissionFilePath(directory);
|
|
48357
48424
|
try {
|
|
48358
48425
|
const dir = dirname(filePath);
|
|
48359
|
-
if (!
|
|
48426
|
+
if (!existsSync14(dir)) {
|
|
48360
48427
|
mkdirSync6(dir, { recursive: true });
|
|
48361
48428
|
}
|
|
48362
48429
|
writeFileSync5(filePath, JSON.stringify(state, null, 2), "utf-8");
|
|
@@ -48383,7 +48450,7 @@ function appendSessionId(directory, sessionId) {
|
|
|
48383
48450
|
function clearMissionState(directory) {
|
|
48384
48451
|
const filePath = getMissionFilePath(directory);
|
|
48385
48452
|
try {
|
|
48386
|
-
if (
|
|
48453
|
+
if (existsSync14(filePath)) {
|
|
48387
48454
|
const { unlinkSync: unlinkSync3 } = __require("fs");
|
|
48388
48455
|
unlinkSync3(filePath);
|
|
48389
48456
|
}
|
|
@@ -48393,13 +48460,13 @@ function clearMissionState(directory) {
|
|
|
48393
48460
|
}
|
|
48394
48461
|
}
|
|
48395
48462
|
function findOraclePlans(directory) {
|
|
48396
|
-
const plansDir =
|
|
48397
|
-
if (!
|
|
48463
|
+
const plansDir = join19(directory, ORACLE_PLANS_DIR);
|
|
48464
|
+
if (!existsSync14(plansDir)) {
|
|
48398
48465
|
return [];
|
|
48399
48466
|
}
|
|
48400
48467
|
try {
|
|
48401
48468
|
const files = readdirSync3(plansDir);
|
|
48402
|
-
return files.filter((f) => f.endsWith(".md")).map((f) =>
|
|
48469
|
+
return files.filter((f) => f.endsWith(".md")).map((f) => join19(plansDir, f)).sort((a, b) => {
|
|
48403
48470
|
const aStat = __require("fs").statSync(a);
|
|
48404
48471
|
const bStat = __require("fs").statSync(b);
|
|
48405
48472
|
return bStat.mtimeMs - aStat.mtimeMs;
|
|
@@ -48409,7 +48476,7 @@ function findOraclePlans(directory) {
|
|
|
48409
48476
|
}
|
|
48410
48477
|
}
|
|
48411
48478
|
function getPlanProgress(planPath) {
|
|
48412
|
-
if (!
|
|
48479
|
+
if (!existsSync14(planPath)) {
|
|
48413
48480
|
return { total: 0, completed: 0, isComplete: true };
|
|
48414
48481
|
}
|
|
48415
48482
|
try {
|
|
@@ -48450,7 +48517,7 @@ function buildRehydrationContext(directory) {
|
|
|
48450
48517
|
if (!mission?.active_plan)
|
|
48451
48518
|
return null;
|
|
48452
48519
|
const planPath = mission.active_plan;
|
|
48453
|
-
if (!
|
|
48520
|
+
if (!existsSync15(planPath))
|
|
48454
48521
|
return null;
|
|
48455
48522
|
const content = readPlanFile(planPath);
|
|
48456
48523
|
if (!content)
|
|
@@ -48496,6 +48563,9 @@ function unregisterSubagentSession(sessionID) {
|
|
|
48496
48563
|
subagentSessions.delete(sessionID);
|
|
48497
48564
|
subagentParentMap.delete(sessionID);
|
|
48498
48565
|
}
|
|
48566
|
+
function getParentSessionID(subagentSessionID) {
|
|
48567
|
+
return subagentParentMap.get(subagentSessionID);
|
|
48568
|
+
}
|
|
48499
48569
|
function getSubagentSessionIDs(parentSessionID) {
|
|
48500
48570
|
const ids = [];
|
|
48501
48571
|
for (const subID of subagentSessions) {
|
|
@@ -49340,8 +49410,8 @@ function findSlashCommandPartIndex(parts) {
|
|
|
49340
49410
|
return -1;
|
|
49341
49411
|
}
|
|
49342
49412
|
// src/hooks/auto-slash-command/executor.ts
|
|
49343
|
-
import { existsSync as
|
|
49344
|
-
import { basename as basename2, dirname as dirname2, join as
|
|
49413
|
+
import { existsSync as existsSync16, readdirSync as readdirSync4, readFileSync as readFileSync11 } from "fs";
|
|
49414
|
+
import { basename as basename2, dirname as dirname2, join as join20 } from "path";
|
|
49345
49415
|
|
|
49346
49416
|
// src/features/builtin-commands/templates/assembly.ts
|
|
49347
49417
|
var ASSEMBLY_TEMPLATE = `Toggle assembly tool availability for the current session.
|
|
@@ -51743,7 +51813,7 @@ function sanitizeModelField(model, source = "other") {
|
|
|
51743
51813
|
|
|
51744
51814
|
// src/hooks/auto-slash-command/executor.ts
|
|
51745
51815
|
function discoverCommandsFromDir(commandsDir, scope) {
|
|
51746
|
-
if (!
|
|
51816
|
+
if (!existsSync16(commandsDir)) {
|
|
51747
51817
|
return [];
|
|
51748
51818
|
}
|
|
51749
51819
|
const entries = readdirSync4(commandsDir, { withFileTypes: true });
|
|
@@ -51751,7 +51821,7 @@ function discoverCommandsFromDir(commandsDir, scope) {
|
|
|
51751
51821
|
for (const entry of entries) {
|
|
51752
51822
|
if (!isMarkdownFile(entry))
|
|
51753
51823
|
continue;
|
|
51754
|
-
const commandPath =
|
|
51824
|
+
const commandPath = join20(commandsDir, entry.name);
|
|
51755
51825
|
const commandName = basename2(entry.name, ".md");
|
|
51756
51826
|
try {
|
|
51757
51827
|
const content = readFileSync11(commandPath, "utf-8");
|
|
@@ -51817,8 +51887,8 @@ async function discoverPluginCommands(client) {
|
|
|
51817
51887
|
}
|
|
51818
51888
|
async function discoverAllCommands(options) {
|
|
51819
51889
|
const configDir = getOpenCodeConfigDir({ binary: "opencode" });
|
|
51820
|
-
const opencodeGlobalDir =
|
|
51821
|
-
const opencodeProjectDir =
|
|
51890
|
+
const opencodeGlobalDir = join20(configDir, "command");
|
|
51891
|
+
const opencodeProjectDir = join20(process.cwd(), ".opencode", "command");
|
|
51822
51892
|
const opencodeGlobalCommands = discoverCommandsFromDir(opencodeGlobalDir, "opencode");
|
|
51823
51893
|
const opencodeProjectCommands = discoverCommandsFromDir(opencodeProjectDir, "opencode-project");
|
|
51824
51894
|
const builtinCommandsMap = loadBuiltinCommands();
|
|
@@ -52799,27 +52869,27 @@ function createCategorySkillReminderHook(_ctx, availableSkills = []) {
|
|
|
52799
52869
|
// src/hooks/comment-checker/hook.ts
|
|
52800
52870
|
import * as fs11 from "fs";
|
|
52801
52871
|
import { tmpdir as tmpdir4 } from "os";
|
|
52802
|
-
import { join as
|
|
52872
|
+
import { join as join27 } from "path";
|
|
52803
52873
|
|
|
52804
52874
|
// src/hooks/comment-checker/cli-runner.ts
|
|
52805
|
-
import { existsSync as
|
|
52875
|
+
import { existsSync as existsSync24 } from "fs";
|
|
52806
52876
|
|
|
52807
52877
|
// src/hooks/comment-checker/cli.ts
|
|
52808
52878
|
import * as fs10 from "fs";
|
|
52809
|
-
import { existsSync as
|
|
52879
|
+
import { existsSync as existsSync23 } from "fs";
|
|
52810
52880
|
import { createRequire as createRequire2 } from "module";
|
|
52811
52881
|
import { tmpdir as tmpdir3 } from "os";
|
|
52812
|
-
import { dirname as dirname5, join as
|
|
52882
|
+
import { dirname as dirname5, join as join26 } from "path";
|
|
52813
52883
|
var {spawn: spawn8 } = globalThis.Bun;
|
|
52814
52884
|
|
|
52815
52885
|
// src/hooks/comment-checker/downloader.ts
|
|
52816
|
-
import { appendFileSync as appendFileSync2, existsSync as
|
|
52886
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync22 } from "fs";
|
|
52817
52887
|
import { createRequire } from "module";
|
|
52818
|
-
import { homedir as
|
|
52819
|
-
import { join as
|
|
52888
|
+
import { homedir as homedir7, tmpdir as tmpdir2 } from "os";
|
|
52889
|
+
import { join as join25 } from "path";
|
|
52820
52890
|
init_logger();
|
|
52821
52891
|
var DEBUG = process.env.COMMENT_CHECKER_DEBUG === "1";
|
|
52822
|
-
var DEBUG_FILE =
|
|
52892
|
+
var DEBUG_FILE = join25(tmpdir2(), "comment-checker-debug.log");
|
|
52823
52893
|
function debugLog(...args) {
|
|
52824
52894
|
if (DEBUG) {
|
|
52825
52895
|
const msg = `[${new Date().toISOString()}] [comment-checker:downloader] ${args.map((a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)).join(" ")}
|
|
@@ -52838,12 +52908,12 @@ var PLATFORM_MAP = {
|
|
|
52838
52908
|
function getCacheDir2() {
|
|
52839
52909
|
if (process.platform === "win32") {
|
|
52840
52910
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
52841
|
-
const base2 = localAppData ||
|
|
52842
|
-
return
|
|
52911
|
+
const base2 = localAppData || join25(homedir7(), "AppData", "Local");
|
|
52912
|
+
return join25(base2, "matrixx", "bin");
|
|
52843
52913
|
}
|
|
52844
52914
|
const xdgCache = process.env.XDG_CACHE_HOME;
|
|
52845
|
-
const base = xdgCache ||
|
|
52846
|
-
return
|
|
52915
|
+
const base = xdgCache || join25(homedir7(), ".cache");
|
|
52916
|
+
return join25(base, "matrixx", "bin");
|
|
52847
52917
|
}
|
|
52848
52918
|
function getBinaryName() {
|
|
52849
52919
|
return process.platform === "win32" ? "comment-checker.exe" : "comment-checker";
|
|
@@ -52869,8 +52939,8 @@ async function downloadCommentChecker() {
|
|
|
52869
52939
|
}
|
|
52870
52940
|
const cacheDir = getCacheDir2();
|
|
52871
52941
|
const binaryName = getBinaryName();
|
|
52872
|
-
const binaryPath =
|
|
52873
|
-
if (
|
|
52942
|
+
const binaryPath = join25(cacheDir, binaryName);
|
|
52943
|
+
if (existsSync22(binaryPath)) {
|
|
52874
52944
|
debugLog("Binary already cached at:", binaryPath);
|
|
52875
52945
|
return binaryPath;
|
|
52876
52946
|
}
|
|
@@ -52882,7 +52952,7 @@ async function downloadCommentChecker() {
|
|
|
52882
52952
|
log(`[matrixx] Downloading comment-checker binary...`);
|
|
52883
52953
|
try {
|
|
52884
52954
|
ensureCacheDir(cacheDir);
|
|
52885
|
-
const archivePath =
|
|
52955
|
+
const archivePath = join25(cacheDir, assetName);
|
|
52886
52956
|
await downloadArchive(downloadUrl, archivePath);
|
|
52887
52957
|
debugLog(`Downloaded archive to: ${archivePath}`);
|
|
52888
52958
|
if (ext === "tar.gz") {
|
|
@@ -52914,7 +52984,7 @@ async function ensureCommentCheckerBinary() {
|
|
|
52914
52984
|
|
|
52915
52985
|
// src/hooks/comment-checker/cli.ts
|
|
52916
52986
|
var DEBUG2 = process.env.COMMENT_CHECKER_DEBUG === "1";
|
|
52917
|
-
var DEBUG_FILE2 =
|
|
52987
|
+
var DEBUG_FILE2 = join26(tmpdir3(), "comment-checker-debug.log");
|
|
52918
52988
|
function debugLog2(...args) {
|
|
52919
52989
|
if (DEBUG2) {
|
|
52920
52990
|
const msg = `[${new Date().toISOString()}] [comment-checker:cli] ${args.map((a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)).join(" ")}
|
|
@@ -52940,8 +53010,8 @@ function findCommentCheckerPathSync() {
|
|
|
52940
53010
|
const require2 = createRequire2(import.meta.url);
|
|
52941
53011
|
const cliPkgPath = require2.resolve("@code-yeongyu/comment-checker/package.json");
|
|
52942
53012
|
const cliDir = dirname5(cliPkgPath);
|
|
52943
|
-
const binaryPath =
|
|
52944
|
-
if (
|
|
53013
|
+
const binaryPath = join26(cliDir, "bin", binaryName);
|
|
53014
|
+
if (existsSync23(binaryPath)) {
|
|
52945
53015
|
debugLog2("found binary in main package:", binaryPath);
|
|
52946
53016
|
return binaryPath;
|
|
52947
53017
|
}
|
|
@@ -52962,7 +53032,7 @@ async function getCommentCheckerPath() {
|
|
|
52962
53032
|
}
|
|
52963
53033
|
initPromise2 = (async () => {
|
|
52964
53034
|
const syncPath = findCommentCheckerPathSync();
|
|
52965
|
-
if (syncPath &&
|
|
53035
|
+
if (syncPath && existsSync23(syncPath)) {
|
|
52966
53036
|
resolvedCliPath = syncPath;
|
|
52967
53037
|
debugLog2("using sync-resolved path:", syncPath);
|
|
52968
53038
|
return syncPath;
|
|
@@ -52998,7 +53068,7 @@ async function runCommentChecker(input, cliPath, customPrompt) {
|
|
|
52998
53068
|
debugLog2("comment-checker binary not found");
|
|
52999
53069
|
return { hasComments: false, message: "" };
|
|
53000
53070
|
}
|
|
53001
|
-
if (!
|
|
53071
|
+
if (!existsSync23(binaryPath)) {
|
|
53002
53072
|
debugLog2("comment-checker binary does not exist:", binaryPath);
|
|
53003
53073
|
return { hasComments: false, message: "" };
|
|
53004
53074
|
}
|
|
@@ -53157,7 +53227,7 @@ ${result.message}`;
|
|
|
53157
53227
|
}
|
|
53158
53228
|
}
|
|
53159
53229
|
function isCliPathUsable(cliPath) {
|
|
53160
|
-
return Boolean(cliPath &&
|
|
53230
|
+
return Boolean(cliPath && existsSync24(cliPath));
|
|
53161
53231
|
}
|
|
53162
53232
|
|
|
53163
53233
|
// src/hooks/comment-checker/pending-calls.ts
|
|
@@ -53195,7 +53265,7 @@ function takePendingCall(callID) {
|
|
|
53195
53265
|
|
|
53196
53266
|
// src/hooks/comment-checker/hook.ts
|
|
53197
53267
|
var DEBUG3 = process.env.COMMENT_CHECKER_DEBUG === "1";
|
|
53198
|
-
var DEBUG_FILE3 =
|
|
53268
|
+
var DEBUG_FILE3 = join27(tmpdir4(), "comment-checker-debug.log");
|
|
53199
53269
|
function debugLog3(...args) {
|
|
53200
53270
|
if (DEBUG3) {
|
|
53201
53271
|
const msg = `[${new Date().toISOString()}] [comment-checker:hook] ${args.map((a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)).join(" ")}
|
|
@@ -53469,18 +53539,18 @@ function createCompactionTodoPreserverHook(ctx) {
|
|
|
53469
53539
|
// src/hooks/context-mode-enforcer/constants.ts
|
|
53470
53540
|
var HOOK_NAME3 = "context-mode-enforcer";
|
|
53471
53541
|
var WARN_MESSAGE_READ = "Use ctx_search / ctx_batch_execute / ctx_execute for analysis \u2014 raw read/grep/glob bypasses FTS5 sandbox and pollutes context. Use Read only with hashline IDs for Edit (non-plan paths; .matrixx/plans/*.md must use plan_read/plan_update).";
|
|
53472
|
-
var BLOCK_MESSAGE_GREP_GLOB = "Blocked: raw grep/glob is forbidden when context-mode is enforced. Use ctx_search
|
|
53542
|
+
var BLOCK_MESSAGE_GREP_GLOB = "Blocked: raw grep/glob is forbidden when context-mode is enforced. " + "Use ctx_search for indexed-KB hits, or ctx_batch_execute / ctx_execute running shell `rg` for codebase search. " + "Raw grep/glob bypasses the FTS5 sandbox and pollutes context.";
|
|
53473
53543
|
var WARN_MESSAGE_BASH_READ = "Use ctx_* / Read tool instead of `cat`/`head`/`tail`/`grep` via bash for analysis. Raw bash file reads bypass sandbox.";
|
|
53474
53544
|
|
|
53475
53545
|
// src/hooks/context-mode-enforcer/hook.ts
|
|
53476
53546
|
var BASH_FILE_READ_RE = /^\s*(cat|head|tail)\s+/;
|
|
53477
53547
|
var BASH_GREP_RE = /\bgrep\b/;
|
|
53478
53548
|
function resolveContextModeConfig(pluginConfig) {
|
|
53479
|
-
const
|
|
53549
|
+
const { enabled, enforce, blockedTools } = resolveContextModeEnforcement(pluginConfig.context_mode);
|
|
53480
53550
|
return {
|
|
53481
|
-
enabled
|
|
53482
|
-
enforce
|
|
53483
|
-
blockedTools: new Set(
|
|
53551
|
+
enabled,
|
|
53552
|
+
enforce,
|
|
53553
|
+
blockedTools: new Set(blockedTools)
|
|
53484
53554
|
};
|
|
53485
53555
|
}
|
|
53486
53556
|
function createContextModeEnforcerHook(pluginConfig) {
|
|
@@ -53525,7 +53595,7 @@ Bash command: ${command.slice(0, 200)}`);
|
|
|
53525
53595
|
if (isGrepGlob) {
|
|
53526
53596
|
if (!isBlocked)
|
|
53527
53597
|
return;
|
|
53528
|
-
if (enforce) {
|
|
53598
|
+
if (enforce && hasWorkingSubstitute()) {
|
|
53529
53599
|
log(`[${HOOK_NAME3}] BLOCKED ${tool}`, { sessionID: input.sessionID });
|
|
53530
53600
|
throw new Error(BLOCK_MESSAGE_GREP_GLOB);
|
|
53531
53601
|
}
|
|
@@ -53576,13 +53646,13 @@ function incrementEmptyContentAttempt(autoCompactState, sessionID) {
|
|
|
53576
53646
|
}
|
|
53577
53647
|
|
|
53578
53648
|
// src/hooks/context-window-limit-recovery/tool-result-store.ts
|
|
53579
|
-
import { existsSync as
|
|
53580
|
-
import { join as
|
|
53649
|
+
import { existsSync as existsSync25, readdirSync as readdirSync5, readFileSync as readFileSync19, writeFileSync as writeFileSync8 } from "fs";
|
|
53650
|
+
import { join as join28 } from "path";
|
|
53581
53651
|
init_logger();
|
|
53582
53652
|
var TRUNCATION_MESSAGE = "[TOOL RESULT TRUNCATED - Context limit exceeded. Original output was too large and has been truncated to recover the session. Please re-run this tool if you need the full output.]";
|
|
53583
53653
|
function getMessageIds(sessionID) {
|
|
53584
53654
|
const messageDir = getMessageDir(sessionID);
|
|
53585
|
-
if (!messageDir || !
|
|
53655
|
+
if (!messageDir || !existsSync25(messageDir))
|
|
53586
53656
|
return [];
|
|
53587
53657
|
const ids = [];
|
|
53588
53658
|
for (const file2 of readdirSync5(messageDir)) {
|
|
@@ -53597,14 +53667,14 @@ function findToolResultsBySize(sessionID) {
|
|
|
53597
53667
|
const messageIds = getMessageIds(sessionID);
|
|
53598
53668
|
const results = [];
|
|
53599
53669
|
for (const messageID of messageIds) {
|
|
53600
|
-
const partDir =
|
|
53601
|
-
if (!
|
|
53670
|
+
const partDir = join28(PART_STORAGE, messageID);
|
|
53671
|
+
if (!existsSync25(partDir))
|
|
53602
53672
|
continue;
|
|
53603
53673
|
for (const file2 of readdirSync5(partDir)) {
|
|
53604
53674
|
if (!file2.endsWith(".json"))
|
|
53605
53675
|
continue;
|
|
53606
53676
|
try {
|
|
53607
|
-
const partPath =
|
|
53677
|
+
const partPath = join28(partDir, file2);
|
|
53608
53678
|
const content = readFileSync19(partPath, "utf-8");
|
|
53609
53679
|
const part = JSON.parse(content);
|
|
53610
53680
|
if (part.type === "tool" && part.state?.output && !part.truncated) {
|
|
@@ -53817,20 +53887,20 @@ init_logger();
|
|
|
53817
53887
|
init_logger();
|
|
53818
53888
|
|
|
53819
53889
|
// src/hooks/session-recovery/storage/messages-reader.ts
|
|
53820
|
-
import { existsSync as
|
|
53821
|
-
import { join as
|
|
53890
|
+
import { existsSync as existsSync26, readdirSync as readdirSync6, readFileSync as readFileSync20 } from "fs";
|
|
53891
|
+
import { join as join29 } from "path";
|
|
53822
53892
|
function readMessages(sessionID) {
|
|
53823
53893
|
if (isSqliteBackend())
|
|
53824
53894
|
return [];
|
|
53825
53895
|
const messageDir = getMessageDir(sessionID);
|
|
53826
|
-
if (!messageDir || !
|
|
53896
|
+
if (!messageDir || !existsSync26(messageDir))
|
|
53827
53897
|
return [];
|
|
53828
53898
|
const messages = [];
|
|
53829
53899
|
for (const file2 of readdirSync6(messageDir)) {
|
|
53830
53900
|
if (!file2.endsWith(".json"))
|
|
53831
53901
|
continue;
|
|
53832
53902
|
try {
|
|
53833
|
-
const content = readFileSync20(
|
|
53903
|
+
const content = readFileSync20(join29(messageDir, file2), "utf-8");
|
|
53834
53904
|
messages.push(JSON.parse(content));
|
|
53835
53905
|
} catch {}
|
|
53836
53906
|
}
|
|
@@ -53848,20 +53918,20 @@ var THINKING_TYPES = new Set(["thinking", "redacted_thinking", "reasoning"]);
|
|
|
53848
53918
|
var META_TYPES = new Set(["step-start", "step-finish"]);
|
|
53849
53919
|
|
|
53850
53920
|
// src/hooks/session-recovery/storage/parts-reader.ts
|
|
53851
|
-
import { existsSync as
|
|
53852
|
-
import { join as
|
|
53921
|
+
import { existsSync as existsSync27, readdirSync as readdirSync7, readFileSync as readFileSync21 } from "fs";
|
|
53922
|
+
import { join as join30 } from "path";
|
|
53853
53923
|
function readParts(messageID) {
|
|
53854
53924
|
if (isSqliteBackend())
|
|
53855
53925
|
return [];
|
|
53856
|
-
const partDir =
|
|
53857
|
-
if (!
|
|
53926
|
+
const partDir = join30(PART_STORAGE, messageID);
|
|
53927
|
+
if (!existsSync27(partDir))
|
|
53858
53928
|
return [];
|
|
53859
53929
|
const parts = [];
|
|
53860
53930
|
for (const file2 of readdirSync7(partDir)) {
|
|
53861
53931
|
if (!file2.endsWith(".json"))
|
|
53862
53932
|
continue;
|
|
53863
53933
|
try {
|
|
53864
|
-
const content = readFileSync21(
|
|
53934
|
+
const content = readFileSync21(join30(partDir, file2), "utf-8");
|
|
53865
53935
|
parts.push(JSON.parse(content));
|
|
53866
53936
|
} catch {}
|
|
53867
53937
|
}
|
|
@@ -53925,22 +53995,22 @@ function findEmptyMessageByIndex(sessionID, targetIndex) {
|
|
|
53925
53995
|
return null;
|
|
53926
53996
|
}
|
|
53927
53997
|
// src/hooks/session-recovery/storage/empty-text.ts
|
|
53928
|
-
import { existsSync as
|
|
53929
|
-
import { join as
|
|
53998
|
+
import { existsSync as existsSync28, readdirSync as readdirSync8, readFileSync as readFileSync22, writeFileSync as writeFileSync9 } from "fs";
|
|
53999
|
+
import { join as join31 } from "path";
|
|
53930
54000
|
function replaceEmptyTextParts(messageID, replacementText) {
|
|
53931
54001
|
if (isSqliteBackend()) {
|
|
53932
54002
|
log("[session-recovery] Disabled on SQLite backend: replaceEmptyTextParts (use async variant)");
|
|
53933
54003
|
return false;
|
|
53934
54004
|
}
|
|
53935
|
-
const partDir =
|
|
53936
|
-
if (!
|
|
54005
|
+
const partDir = join31(PART_STORAGE, messageID);
|
|
54006
|
+
if (!existsSync28(partDir))
|
|
53937
54007
|
return false;
|
|
53938
54008
|
let anyReplaced = false;
|
|
53939
54009
|
for (const file2 of readdirSync8(partDir)) {
|
|
53940
54010
|
if (!file2.endsWith(".json"))
|
|
53941
54011
|
continue;
|
|
53942
54012
|
try {
|
|
53943
|
-
const filePath =
|
|
54013
|
+
const filePath = join31(partDir, file2);
|
|
53944
54014
|
const content = readFileSync22(filePath, "utf-8");
|
|
53945
54015
|
const part = JSON.parse(content);
|
|
53946
54016
|
if (part.type === "text") {
|
|
@@ -54022,15 +54092,15 @@ function generatePartId() {
|
|
|
54022
54092
|
return `prt_${timestamp}${random}`;
|
|
54023
54093
|
}
|
|
54024
54094
|
// src/hooks/session-recovery/storage/text-part-injector.ts
|
|
54025
|
-
import { existsSync as
|
|
54026
|
-
import { join as
|
|
54095
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync7, writeFileSync as writeFileSync10 } from "fs";
|
|
54096
|
+
import { join as join32 } from "path";
|
|
54027
54097
|
function injectTextPart(sessionID, messageID, text) {
|
|
54028
54098
|
if (isSqliteBackend()) {
|
|
54029
54099
|
log("[session-recovery] Disabled on SQLite backend: injectTextPart (use async variant)");
|
|
54030
54100
|
return false;
|
|
54031
54101
|
}
|
|
54032
|
-
const partDir =
|
|
54033
|
-
if (!
|
|
54102
|
+
const partDir = join32(PART_STORAGE, messageID);
|
|
54103
|
+
if (!existsSync29(partDir)) {
|
|
54034
54104
|
mkdirSync7(partDir, { recursive: true });
|
|
54035
54105
|
}
|
|
54036
54106
|
const partId = generatePartId();
|
|
@@ -54043,7 +54113,7 @@ function injectTextPart(sessionID, messageID, text) {
|
|
|
54043
54113
|
synthetic: true
|
|
54044
54114
|
};
|
|
54045
54115
|
try {
|
|
54046
|
-
writeFileSync10(
|
|
54116
|
+
writeFileSync10(join32(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
|
|
54047
54117
|
return true;
|
|
54048
54118
|
} catch {
|
|
54049
54119
|
return false;
|
|
@@ -54082,8 +54152,8 @@ function findMessagesWithThinkingBlocks(sessionID) {
|
|
|
54082
54152
|
return result;
|
|
54083
54153
|
}
|
|
54084
54154
|
// src/hooks/session-recovery/storage/thinking-prepend.ts
|
|
54085
|
-
import { existsSync as
|
|
54086
|
-
import { join as
|
|
54155
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync8, writeFileSync as writeFileSync11 } from "fs";
|
|
54156
|
+
import { join as join33 } from "path";
|
|
54087
54157
|
function findLastThinkingContent(sessionID, beforeMessageID) {
|
|
54088
54158
|
const messages = readMessages(sessionID);
|
|
54089
54159
|
const currentIndex = messages.findIndex((message) => message.id === beforeMessageID);
|
|
@@ -54112,8 +54182,8 @@ function prependThinkingPart(sessionID, messageID) {
|
|
|
54112
54182
|
log("[session-recovery] Disabled on SQLite backend: prependThinkingPart (use async variant)");
|
|
54113
54183
|
return false;
|
|
54114
54184
|
}
|
|
54115
|
-
const partDir =
|
|
54116
|
-
if (!
|
|
54185
|
+
const partDir = join33(PART_STORAGE, messageID);
|
|
54186
|
+
if (!existsSync30(partDir)) {
|
|
54117
54187
|
mkdirSync8(partDir, { recursive: true });
|
|
54118
54188
|
}
|
|
54119
54189
|
const previousThinking = findLastThinkingContent(sessionID, messageID);
|
|
@@ -54127,7 +54197,7 @@ function prependThinkingPart(sessionID, messageID) {
|
|
|
54127
54197
|
synthetic: true
|
|
54128
54198
|
};
|
|
54129
54199
|
try {
|
|
54130
|
-
writeFileSync11(
|
|
54200
|
+
writeFileSync11(join33(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
|
|
54131
54201
|
return true;
|
|
54132
54202
|
} catch {
|
|
54133
54203
|
return false;
|
|
@@ -54178,22 +54248,22 @@ async function prependThinkingPartAsync(client, sessionID, messageID) {
|
|
|
54178
54248
|
}
|
|
54179
54249
|
}
|
|
54180
54250
|
// src/hooks/session-recovery/storage/thinking-strip.ts
|
|
54181
|
-
import { existsSync as
|
|
54182
|
-
import { join as
|
|
54251
|
+
import { existsSync as existsSync31, readdirSync as readdirSync9, readFileSync as readFileSync23, unlinkSync as unlinkSync3 } from "fs";
|
|
54252
|
+
import { join as join34 } from "path";
|
|
54183
54253
|
function stripThinkingParts(messageID) {
|
|
54184
54254
|
if (isSqliteBackend()) {
|
|
54185
54255
|
log("[session-recovery] Disabled on SQLite backend: stripThinkingParts (use async variant)");
|
|
54186
54256
|
return false;
|
|
54187
54257
|
}
|
|
54188
|
-
const partDir =
|
|
54189
|
-
if (!
|
|
54258
|
+
const partDir = join34(PART_STORAGE, messageID);
|
|
54259
|
+
if (!existsSync31(partDir))
|
|
54190
54260
|
return false;
|
|
54191
54261
|
let anyRemoved = false;
|
|
54192
54262
|
for (const file2 of readdirSync9(partDir)) {
|
|
54193
54263
|
if (!file2.endsWith(".json"))
|
|
54194
54264
|
continue;
|
|
54195
54265
|
try {
|
|
54196
|
-
const filePath =
|
|
54266
|
+
const filePath = join34(partDir, file2);
|
|
54197
54267
|
const content = readFileSync23(filePath, "utf-8");
|
|
54198
54268
|
const part = JSON.parse(content);
|
|
54199
54269
|
if (THINKING_TYPES.has(part.type)) {
|
|
@@ -54950,7 +55020,7 @@ var DELEGATE_TASK_ERROR_PATTERNS = [
|
|
|
54950
55020
|
{
|
|
54951
55021
|
pattern: "run_in_background",
|
|
54952
55022
|
errorType: "missing_run_in_background",
|
|
54953
|
-
fixHint: "Add run_in_background=false (
|
|
55023
|
+
fixHint: "Add run_in_background=false (sync, awaits result inline; the default) or run_in_background=true (async, returns task_id immediately for parallel independent work such as exploration or fan-out waves)"
|
|
54954
55024
|
},
|
|
54955
55025
|
{
|
|
54956
55026
|
pattern: "load_skills",
|
|
@@ -55064,7 +55134,7 @@ function buildRetryGuidance(errorInfo) {
|
|
|
55064
55134
|
description="Task description",
|
|
55065
55135
|
prompt="Detailed prompt...",
|
|
55066
55136
|
category="blue-pill", // OR subagent_type="trinity"
|
|
55067
|
-
run_in_background=false,
|
|
55137
|
+
run_in_background=false, // false is default \u2014 use true for any parallel independent work (returns task_id; async). Sync awaits result inline.
|
|
55068
55138
|
load_skills=[]
|
|
55069
55139
|
)
|
|
55070
55140
|
\`\`\`
|
|
@@ -55089,7 +55159,7 @@ ${guidance}`;
|
|
|
55089
55159
|
};
|
|
55090
55160
|
}
|
|
55091
55161
|
// src/hooks/design-intent-preserver/hook.ts
|
|
55092
|
-
import { join as
|
|
55162
|
+
import { join as join36 } from "path";
|
|
55093
55163
|
|
|
55094
55164
|
// src/hooks/design-intent-preserver/constants.ts
|
|
55095
55165
|
var HOOK_NAME4 = "design-intent-preserver";
|
|
@@ -55109,7 +55179,7 @@ If design intent must change, record why in .matrixx/drafts/design-changes.md fi
|
|
|
55109
55179
|
|
|
55110
55180
|
// src/hooks/design-intent-preserver/phase-detector.ts
|
|
55111
55181
|
import { readdirSync as readdirSync10, readFileSync as readFileSync24, statSync as statSync3 } from "fs";
|
|
55112
|
-
import { join as
|
|
55182
|
+
import { join as join35 } from "path";
|
|
55113
55183
|
function extractPromptText2(parts) {
|
|
55114
55184
|
return parts.filter((p) => p.type === "text" && p.text !== undefined).map((p) => p.text ?? "").join(" ");
|
|
55115
55185
|
}
|
|
@@ -55127,7 +55197,7 @@ function findMostRecentPlanFile(plansDir) {
|
|
|
55127
55197
|
return null;
|
|
55128
55198
|
let mostRecent = null;
|
|
55129
55199
|
for (const entry of entries) {
|
|
55130
|
-
const fullPath =
|
|
55200
|
+
const fullPath = join35(plansDir, entry);
|
|
55131
55201
|
try {
|
|
55132
55202
|
const stats = statSync3(fullPath);
|
|
55133
55203
|
if (!stats.isFile())
|
|
@@ -55162,7 +55232,7 @@ function createDesignIntentPreserverHook(_ctx) {
|
|
|
55162
55232
|
if (!hasPhaseTransition(promptText)) {
|
|
55163
55233
|
return;
|
|
55164
55234
|
}
|
|
55165
|
-
const plansDir =
|
|
55235
|
+
const plansDir = join36(_ctx.directory, PLANS_DIR2);
|
|
55166
55236
|
const planPath = findMostRecentPlanFile(plansDir);
|
|
55167
55237
|
if (!planPath) {
|
|
55168
55238
|
return;
|
|
@@ -55234,11 +55304,11 @@ import { dirname as dirname7 } from "path";
|
|
|
55234
55304
|
|
|
55235
55305
|
// src/hooks/directory-agents-injector/finder.ts
|
|
55236
55306
|
import { access } from "fs/promises";
|
|
55237
|
-
import { dirname as dirname6, join as
|
|
55307
|
+
import { dirname as dirname6, join as join38, resolve as resolve2 } from "path";
|
|
55238
55308
|
|
|
55239
55309
|
// src/hooks/directory-agents-injector/constants.ts
|
|
55240
|
-
import { join as
|
|
55241
|
-
var AGENTS_INJECTOR_STORAGE =
|
|
55310
|
+
import { join as join37 } from "path";
|
|
55311
|
+
var AGENTS_INJECTOR_STORAGE = join37(OPENCODE_STORAGE, "directory-agents");
|
|
55242
55312
|
var AGENTS_FILENAME = "AGENTS.md";
|
|
55243
55313
|
|
|
55244
55314
|
// src/hooks/directory-agents-injector/finder.ts
|
|
@@ -55255,7 +55325,7 @@ async function findAgentsMdUp(input) {
|
|
|
55255
55325
|
while (true) {
|
|
55256
55326
|
const isRootDir = current === input.rootDir;
|
|
55257
55327
|
if (!isRootDir) {
|
|
55258
|
-
const agentsPath =
|
|
55328
|
+
const agentsPath = join38(current, AGENTS_FILENAME);
|
|
55259
55329
|
const exists = await access(agentsPath).then(() => true).catch(() => false);
|
|
55260
55330
|
if (exists) {
|
|
55261
55331
|
found.push(agentsPath);
|
|
@@ -55275,18 +55345,18 @@ async function findAgentsMdUp(input) {
|
|
|
55275
55345
|
|
|
55276
55346
|
// src/shared/session-injected-paths.ts
|
|
55277
55347
|
import {
|
|
55278
|
-
existsSync as
|
|
55348
|
+
existsSync as existsSync32,
|
|
55279
55349
|
mkdirSync as mkdirSync9,
|
|
55280
55350
|
readFileSync as readFileSync25,
|
|
55281
55351
|
unlinkSync as unlinkSync4,
|
|
55282
55352
|
writeFileSync as writeFileSync12
|
|
55283
55353
|
} from "fs";
|
|
55284
|
-
import { join as
|
|
55354
|
+
import { join as join39 } from "path";
|
|
55285
55355
|
function createInjectedPathsStorage(storageDir) {
|
|
55286
|
-
const getStoragePath2 = (sessionID) =>
|
|
55356
|
+
const getStoragePath2 = (sessionID) => join39(storageDir, `${sessionID}.json`);
|
|
55287
55357
|
const loadInjectedPaths = (sessionID) => {
|
|
55288
55358
|
const filePath = getStoragePath2(sessionID);
|
|
55289
|
-
if (!
|
|
55359
|
+
if (!existsSync32(filePath))
|
|
55290
55360
|
return new Set;
|
|
55291
55361
|
try {
|
|
55292
55362
|
const content = readFileSync25(filePath, "utf-8");
|
|
@@ -55297,7 +55367,7 @@ function createInjectedPathsStorage(storageDir) {
|
|
|
55297
55367
|
}
|
|
55298
55368
|
};
|
|
55299
55369
|
const saveInjectedPaths = (sessionID, paths) => {
|
|
55300
|
-
if (!
|
|
55370
|
+
if (!existsSync32(storageDir)) {
|
|
55301
55371
|
mkdirSync9(storageDir, { recursive: true });
|
|
55302
55372
|
}
|
|
55303
55373
|
const data = {
|
|
@@ -55309,7 +55379,7 @@ function createInjectedPathsStorage(storageDir) {
|
|
|
55309
55379
|
};
|
|
55310
55380
|
const clearInjectedPaths = (sessionID) => {
|
|
55311
55381
|
const filePath = getStoragePath2(sessionID);
|
|
55312
|
-
if (
|
|
55382
|
+
if (existsSync32(filePath)) {
|
|
55313
55383
|
unlinkSync4(filePath);
|
|
55314
55384
|
}
|
|
55315
55385
|
};
|
|
@@ -58822,8 +58892,8 @@ ${originalText}`;
|
|
|
58822
58892
|
init_logger();
|
|
58823
58893
|
|
|
58824
58894
|
// src/hooks/interactive-bash-session/constants.ts
|
|
58825
|
-
import { join as
|
|
58826
|
-
var INTERACTIVE_BASH_SESSION_STORAGE =
|
|
58895
|
+
import { join as join42 } from "path";
|
|
58896
|
+
var INTERACTIVE_BASH_SESSION_STORAGE = join42(OPENCODE_STORAGE, "interactive-bash-session");
|
|
58827
58897
|
var MATRIXX_SESSION_PREFIX = "matrixx-";
|
|
58828
58898
|
function buildSessionReminderMessage(sessions) {
|
|
58829
58899
|
if (sessions.length === 0)
|
|
@@ -58918,19 +58988,19 @@ function findSubcommand(tokens) {
|
|
|
58918
58988
|
|
|
58919
58989
|
// src/hooks/interactive-bash-session/storage.ts
|
|
58920
58990
|
import {
|
|
58921
|
-
existsSync as
|
|
58991
|
+
existsSync as existsSync34,
|
|
58922
58992
|
mkdirSync as mkdirSync12,
|
|
58923
58993
|
readFileSync as readFileSync27,
|
|
58924
58994
|
unlinkSync as unlinkSync6,
|
|
58925
58995
|
writeFileSync as writeFileSync14
|
|
58926
58996
|
} from "fs";
|
|
58927
|
-
import { join as
|
|
58997
|
+
import { join as join43 } from "path";
|
|
58928
58998
|
function getStoragePath2(sessionID) {
|
|
58929
|
-
return
|
|
58999
|
+
return join43(INTERACTIVE_BASH_SESSION_STORAGE, `${sessionID}.json`);
|
|
58930
59000
|
}
|
|
58931
59001
|
function loadInteractiveBashSessionState(sessionID) {
|
|
58932
59002
|
const filePath = getStoragePath2(sessionID);
|
|
58933
|
-
if (!
|
|
59003
|
+
if (!existsSync34(filePath))
|
|
58934
59004
|
return null;
|
|
58935
59005
|
try {
|
|
58936
59006
|
const content = readFileSync27(filePath, "utf-8");
|
|
@@ -58945,7 +59015,7 @@ function loadInteractiveBashSessionState(sessionID) {
|
|
|
58945
59015
|
}
|
|
58946
59016
|
}
|
|
58947
59017
|
function saveInteractiveBashSessionState(state3) {
|
|
58948
|
-
if (!
|
|
59018
|
+
if (!existsSync34(INTERACTIVE_BASH_SESSION_STORAGE)) {
|
|
58949
59019
|
mkdirSync12(INTERACTIVE_BASH_SESSION_STORAGE, { recursive: true });
|
|
58950
59020
|
}
|
|
58951
59021
|
const filePath = getStoragePath2(state3.sessionID);
|
|
@@ -58958,7 +59028,7 @@ function saveInteractiveBashSessionState(state3) {
|
|
|
58958
59028
|
}
|
|
58959
59029
|
function clearInteractiveBashSessionState(sessionID) {
|
|
58960
59030
|
const filePath = getStoragePath2(sessionID);
|
|
58961
|
-
if (
|
|
59031
|
+
if (existsSync34(filePath)) {
|
|
58962
59032
|
unlinkSync6(filePath);
|
|
58963
59033
|
}
|
|
58964
59034
|
}
|
|
@@ -59302,7 +59372,7 @@ init_logger();
|
|
|
59302
59372
|
|
|
59303
59373
|
// src/hooks/matrix-loop/completion-promise-detector.ts
|
|
59304
59374
|
init_logger();
|
|
59305
|
-
import { existsSync as
|
|
59375
|
+
import { existsSync as existsSync36, readFileSync as readFileSync29 } from "fs";
|
|
59306
59376
|
function escapeRegex2(str) {
|
|
59307
59377
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
59308
59378
|
}
|
|
@@ -59313,7 +59383,7 @@ function detectCompletionInTranscript(transcriptPath, promise2) {
|
|
|
59313
59383
|
if (!transcriptPath)
|
|
59314
59384
|
return false;
|
|
59315
59385
|
try {
|
|
59316
|
-
if (!
|
|
59386
|
+
if (!existsSync36(transcriptPath))
|
|
59317
59387
|
return false;
|
|
59318
59388
|
const content = readFileSync29(transcriptPath, "utf-8");
|
|
59319
59389
|
const pattern = buildPromisePattern(promise2);
|
|
@@ -60096,13 +60166,13 @@ function createOracleMdOnlyHook(ctx) {
|
|
|
60096
60166
|
};
|
|
60097
60167
|
}
|
|
60098
60168
|
// src/hooks/plan-persister/hook.ts
|
|
60099
|
-
import { existsSync as
|
|
60169
|
+
import { existsSync as existsSync38, readdirSync as readdirSync12 } from "fs";
|
|
60100
60170
|
|
|
60101
60171
|
// src/features/task-storage/storage.ts
|
|
60102
60172
|
init_logger();
|
|
60103
60173
|
import { randomUUID } from "crypto";
|
|
60104
|
-
import { existsSync as
|
|
60105
|
-
import { basename as basename5, dirname as dirname10, isAbsolute as isAbsolute3, join as
|
|
60174
|
+
import { existsSync as existsSync37, mkdirSync as mkdirSync14, readdirSync as readdirSync11, readFileSync as readFileSync30, renameSync as renameSync3, unlinkSync as unlinkSync8, writeFileSync as writeFileSync16 } from "fs";
|
|
60175
|
+
import { basename as basename5, dirname as dirname10, isAbsolute as isAbsolute3, join as join45 } from "path";
|
|
60106
60176
|
|
|
60107
60177
|
// src/shared/task-system-gating.ts
|
|
60108
60178
|
var TASK_SYSTEM_DEFAULT = true;
|
|
@@ -60126,18 +60196,18 @@ function isTaskSystemEnabled(config2) {
|
|
|
60126
60196
|
|
|
60127
60197
|
// src/features/task-storage/storage.ts
|
|
60128
60198
|
function getProjectTaskDir(directory) {
|
|
60129
|
-
return
|
|
60199
|
+
return join45(directory, ".matrixx", "tasks");
|
|
60130
60200
|
}
|
|
60131
60201
|
function getTaskDir(config2 = {}, directory) {
|
|
60132
60202
|
const tasksConfig = resolveTasksConfig(config2);
|
|
60133
60203
|
const storagePath = tasksConfig.storage_path;
|
|
60134
60204
|
if (storagePath) {
|
|
60135
|
-
return isAbsolute3(storagePath) ? storagePath :
|
|
60205
|
+
return isAbsolute3(storagePath) ? storagePath : join45(directory ?? process.cwd(), storagePath);
|
|
60136
60206
|
}
|
|
60137
60207
|
if (tasksConfig.scope === "global" || !directory) {
|
|
60138
60208
|
const configDir = getOpenCodeConfigDir({ binary: "opencode" });
|
|
60139
60209
|
const listId = resolveTaskListId(config2);
|
|
60140
|
-
return
|
|
60210
|
+
return join45(configDir, "tasks", listId);
|
|
60141
60211
|
}
|
|
60142
60212
|
return getProjectTaskDir(directory);
|
|
60143
60213
|
}
|
|
@@ -60154,13 +60224,13 @@ function resolveTaskListId(config2 = {}) {
|
|
|
60154
60224
|
return sanitizePathSegment(basename5(process.cwd()));
|
|
60155
60225
|
}
|
|
60156
60226
|
function ensureDir2(dirPath) {
|
|
60157
|
-
if (!
|
|
60227
|
+
if (!existsSync37(dirPath)) {
|
|
60158
60228
|
mkdirSync14(dirPath, { recursive: true });
|
|
60159
60229
|
}
|
|
60160
60230
|
}
|
|
60161
60231
|
function readJsonSafe(filePath, schema) {
|
|
60162
60232
|
try {
|
|
60163
|
-
if (!
|
|
60233
|
+
if (!existsSync37(filePath)) {
|
|
60164
60234
|
return null;
|
|
60165
60235
|
}
|
|
60166
60236
|
const content = readFileSync30(filePath, "utf-8");
|
|
@@ -60183,7 +60253,7 @@ function writeJsonAtomic(filePath, data) {
|
|
|
60183
60253
|
renameSync3(tempPath, filePath);
|
|
60184
60254
|
} catch (error51) {
|
|
60185
60255
|
try {
|
|
60186
|
-
if (
|
|
60256
|
+
if (existsSync37(tempPath)) {
|
|
60187
60257
|
unlinkSync8(tempPath);
|
|
60188
60258
|
}
|
|
60189
60259
|
} catch {}
|
|
@@ -60207,8 +60277,8 @@ function generateTaskId() {
|
|
|
60207
60277
|
}
|
|
60208
60278
|
function migrateLegacyTasksIfNeeded(config2, directory) {
|
|
60209
60279
|
const projectDir = getProjectTaskDir(directory);
|
|
60210
|
-
const legacyDir =
|
|
60211
|
-
if (
|
|
60280
|
+
const legacyDir = join45(getOpenCodeConfigDir({ binary: "opencode" }), "tasks", resolveTaskListId(config2));
|
|
60281
|
+
if (existsSync37(projectDir)) {
|
|
60212
60282
|
try {
|
|
60213
60283
|
const existing = readdirSync11(projectDir).filter((f) => f.startsWith("T-") && f.endsWith(".json"));
|
|
60214
60284
|
if (existing.length > 0) {
|
|
@@ -60216,7 +60286,7 @@ function migrateLegacyTasksIfNeeded(config2, directory) {
|
|
|
60216
60286
|
}
|
|
60217
60287
|
} catch {}
|
|
60218
60288
|
}
|
|
60219
|
-
if (!
|
|
60289
|
+
if (!existsSync37(legacyDir)) {
|
|
60220
60290
|
return { migrated: 0, legacyDir, projectDir };
|
|
60221
60291
|
}
|
|
60222
60292
|
let files = [];
|
|
@@ -60231,9 +60301,9 @@ function migrateLegacyTasksIfNeeded(config2, directory) {
|
|
|
60231
60301
|
ensureDir2(projectDir);
|
|
60232
60302
|
let migrated = 0;
|
|
60233
60303
|
for (const file2 of files) {
|
|
60234
|
-
const src =
|
|
60235
|
-
const dest =
|
|
60236
|
-
if (
|
|
60304
|
+
const src = join45(legacyDir, file2);
|
|
60305
|
+
const dest = join45(projectDir, file2);
|
|
60306
|
+
if (existsSync37(dest))
|
|
60237
60307
|
continue;
|
|
60238
60308
|
try {
|
|
60239
60309
|
const content = readFileSync30(src, "utf-8");
|
|
@@ -60247,7 +60317,7 @@ function migrateLegacyTasksIfNeeded(config2, directory) {
|
|
|
60247
60317
|
return { migrated, legacyDir, projectDir };
|
|
60248
60318
|
}
|
|
60249
60319
|
function acquireLock(dirPath) {
|
|
60250
|
-
const lockPath =
|
|
60320
|
+
const lockPath = join45(dirPath, ".lock");
|
|
60251
60321
|
const lockId = randomUUID();
|
|
60252
60322
|
const createLock = (timestamp) => {
|
|
60253
60323
|
writeFileSync16(lockPath, JSON.stringify({ id: lockId, timestamp }), {
|
|
@@ -60295,7 +60365,7 @@ function acquireLock(dirPath) {
|
|
|
60295
60365
|
acquired: true,
|
|
60296
60366
|
release: () => {
|
|
60297
60367
|
try {
|
|
60298
|
-
if (!
|
|
60368
|
+
if (!existsSync37(lockPath))
|
|
60299
60369
|
return;
|
|
60300
60370
|
const lockContent = readFileSync30(lockPath, "utf-8");
|
|
60301
60371
|
const lockData = JSON.parse(lockContent);
|
|
@@ -60437,12 +60507,12 @@ function createPlanPersister(ctx, options) {
|
|
|
60437
60507
|
if (!mission.session_ids?.includes(sessionID))
|
|
60438
60508
|
return;
|
|
60439
60509
|
const planPath = mission.active_plan;
|
|
60440
|
-
if (!
|
|
60510
|
+
if (!existsSync38(planPath))
|
|
60441
60511
|
return;
|
|
60442
60512
|
let todos = [];
|
|
60443
60513
|
try {
|
|
60444
60514
|
const taskDir = getTaskDir({}, directory);
|
|
60445
|
-
if (
|
|
60515
|
+
if (existsSync38(taskDir)) {
|
|
60446
60516
|
const files = readdirSync12(taskDir).filter((f) => f.startsWith("T-") && f.endsWith(".json"));
|
|
60447
60517
|
const tasks = [];
|
|
60448
60518
|
for (const f of files) {
|
|
@@ -61770,12 +61840,12 @@ function createRtkBashRewriterHook(_ctx, config2) {
|
|
|
61770
61840
|
};
|
|
61771
61841
|
}
|
|
61772
61842
|
// src/hooks/rules-injector/project-root-finder.ts
|
|
61773
|
-
import { existsSync as
|
|
61774
|
-
import { dirname as dirname11, join as
|
|
61843
|
+
import { existsSync as existsSync39, statSync as statSync5 } from "fs";
|
|
61844
|
+
import { dirname as dirname11, join as join47 } from "path";
|
|
61775
61845
|
|
|
61776
61846
|
// src/hooks/rules-injector/constants.ts
|
|
61777
|
-
import { join as
|
|
61778
|
-
var RULES_INJECTOR_STORAGE =
|
|
61847
|
+
import { join as join46 } from "path";
|
|
61848
|
+
var RULES_INJECTOR_STORAGE = join46(OPENCODE_STORAGE, "rules-injector");
|
|
61779
61849
|
var PROJECT_MARKERS = [
|
|
61780
61850
|
".git",
|
|
61781
61851
|
"pyproject.toml",
|
|
@@ -61806,8 +61876,8 @@ function findProjectRoot(startPath) {
|
|
|
61806
61876
|
}
|
|
61807
61877
|
while (true) {
|
|
61808
61878
|
for (const marker of PROJECT_MARKERS) {
|
|
61809
|
-
const markerPath =
|
|
61810
|
-
if (
|
|
61879
|
+
const markerPath = join47(current, marker);
|
|
61880
|
+
if (existsSync39(markerPath)) {
|
|
61811
61881
|
return current;
|
|
61812
61882
|
}
|
|
61813
61883
|
}
|
|
@@ -61819,12 +61889,12 @@ function findProjectRoot(startPath) {
|
|
|
61819
61889
|
}
|
|
61820
61890
|
}
|
|
61821
61891
|
// src/hooks/rules-injector/rule-file-finder.ts
|
|
61822
|
-
import { existsSync as
|
|
61823
|
-
import { dirname as dirname12, join as
|
|
61892
|
+
import { existsSync as existsSync41, statSync as statSync6 } from "fs";
|
|
61893
|
+
import { dirname as dirname12, join as join49 } from "path";
|
|
61824
61894
|
|
|
61825
61895
|
// src/hooks/rules-injector/rule-file-scanner.ts
|
|
61826
|
-
import { existsSync as
|
|
61827
|
-
import { join as
|
|
61896
|
+
import { existsSync as existsSync40, readdirSync as readdirSync13, realpathSync as realpathSync2 } from "fs";
|
|
61897
|
+
import { join as join48 } from "path";
|
|
61828
61898
|
function isGitHubInstructionsDir(dir) {
|
|
61829
61899
|
return dir.includes(".github/instructions") || dir.endsWith(".github/instructions");
|
|
61830
61900
|
}
|
|
@@ -61835,12 +61905,12 @@ function isValidRuleFile(fileName, dir) {
|
|
|
61835
61905
|
return RULE_EXTENSIONS.some((ext) => fileName.endsWith(ext));
|
|
61836
61906
|
}
|
|
61837
61907
|
function findRuleFilesRecursive(dir, results) {
|
|
61838
|
-
if (!
|
|
61908
|
+
if (!existsSync40(dir))
|
|
61839
61909
|
return;
|
|
61840
61910
|
try {
|
|
61841
61911
|
const entries = readdirSync13(dir, { withFileTypes: true });
|
|
61842
61912
|
for (const entry of entries) {
|
|
61843
|
-
const fullPath =
|
|
61913
|
+
const fullPath = join48(dir, entry.name);
|
|
61844
61914
|
if (entry.isDirectory()) {
|
|
61845
61915
|
findRuleFilesRecursive(fullPath, results);
|
|
61846
61916
|
} else if (entry.isFile()) {
|
|
@@ -61867,7 +61937,7 @@ function findRuleFiles(projectRoot, currentFile) {
|
|
|
61867
61937
|
let distance = 0;
|
|
61868
61938
|
while (true) {
|
|
61869
61939
|
for (const [parent, subdir] of PROJECT_RULE_SUBDIRS) {
|
|
61870
|
-
const ruleDir =
|
|
61940
|
+
const ruleDir = join49(currentDir, parent, subdir);
|
|
61871
61941
|
const files = [];
|
|
61872
61942
|
findRuleFilesRecursive(ruleDir, files);
|
|
61873
61943
|
for (const filePath of files) {
|
|
@@ -61893,8 +61963,8 @@ function findRuleFiles(projectRoot, currentFile) {
|
|
|
61893
61963
|
}
|
|
61894
61964
|
if (projectRoot) {
|
|
61895
61965
|
for (const ruleFile of PROJECT_RULE_FILES) {
|
|
61896
|
-
const filePath =
|
|
61897
|
-
if (
|
|
61966
|
+
const filePath = join49(projectRoot, ruleFile);
|
|
61967
|
+
if (existsSync41(filePath)) {
|
|
61898
61968
|
try {
|
|
61899
61969
|
const stat = statSync6(filePath);
|
|
61900
61970
|
if (stat.isFile()) {
|
|
@@ -61924,19 +61994,19 @@ function findRuleFiles(projectRoot, currentFile) {
|
|
|
61924
61994
|
}
|
|
61925
61995
|
// src/hooks/rules-injector/storage.ts
|
|
61926
61996
|
import {
|
|
61927
|
-
existsSync as
|
|
61997
|
+
existsSync as existsSync42,
|
|
61928
61998
|
mkdirSync as mkdirSync15,
|
|
61929
61999
|
readFileSync as readFileSync31,
|
|
61930
62000
|
unlinkSync as unlinkSync9,
|
|
61931
62001
|
writeFileSync as writeFileSync17
|
|
61932
62002
|
} from "fs";
|
|
61933
|
-
import { join as
|
|
62003
|
+
import { join as join50 } from "path";
|
|
61934
62004
|
function getStoragePath3(sessionID) {
|
|
61935
|
-
return
|
|
62005
|
+
return join50(RULES_INJECTOR_STORAGE, `${sessionID}.json`);
|
|
61936
62006
|
}
|
|
61937
62007
|
function loadInjectedRules(sessionID) {
|
|
61938
62008
|
const filePath = getStoragePath3(sessionID);
|
|
61939
|
-
if (!
|
|
62009
|
+
if (!existsSync42(filePath))
|
|
61940
62010
|
return { contentHashes: new Set, realPaths: new Set };
|
|
61941
62011
|
try {
|
|
61942
62012
|
const content = readFileSync31(filePath, "utf-8");
|
|
@@ -61950,7 +62020,7 @@ function loadInjectedRules(sessionID) {
|
|
|
61950
62020
|
}
|
|
61951
62021
|
}
|
|
61952
62022
|
function saveInjectedRules(sessionID, data) {
|
|
61953
|
-
if (!
|
|
62023
|
+
if (!existsSync42(RULES_INJECTOR_STORAGE)) {
|
|
61954
62024
|
mkdirSync15(RULES_INJECTOR_STORAGE, { recursive: true });
|
|
61955
62025
|
}
|
|
61956
62026
|
const storageData = {
|
|
@@ -61963,7 +62033,7 @@ function saveInjectedRules(sessionID, data) {
|
|
|
61963
62033
|
}
|
|
61964
62034
|
function clearInjectedRules(sessionID) {
|
|
61965
62035
|
const filePath = getStoragePath3(sessionID);
|
|
61966
|
-
if (
|
|
62036
|
+
if (existsSync42(filePath)) {
|
|
61967
62037
|
unlinkSync9(filePath);
|
|
61968
62038
|
}
|
|
61969
62039
|
}
|
|
@@ -64921,8 +64991,8 @@ var FAILURE_RESET_WINDOW_MS = 5 * 60 * 1000;
|
|
|
64921
64991
|
init_logger();
|
|
64922
64992
|
|
|
64923
64993
|
// src/hooks/task-continuation-enforcer/idle-event.ts
|
|
64924
|
-
import { existsSync as
|
|
64925
|
-
import { join as
|
|
64994
|
+
import { existsSync as existsSync44, readdirSync as readdirSync15 } from "fs";
|
|
64995
|
+
import { join as join53 } from "path";
|
|
64926
64996
|
init_logger();
|
|
64927
64997
|
|
|
64928
64998
|
// src/hooks/task-continuation-enforcer/abort-detection.ts
|
|
@@ -64945,12 +65015,12 @@ function isLastAssistantMessageAborted(messages) {
|
|
|
64945
65015
|
init_logger();
|
|
64946
65016
|
|
|
64947
65017
|
// src/hooks/task-continuation-enforcer/continuation-injection.ts
|
|
64948
|
-
import { existsSync as
|
|
64949
|
-
import { join as
|
|
65018
|
+
import { existsSync as existsSync43, readdirSync as readdirSync14 } from "fs";
|
|
65019
|
+
import { join as join52 } from "path";
|
|
64950
65020
|
init_logger();
|
|
64951
65021
|
// src/hooks/task-continuation-enforcer/staleness.ts
|
|
64952
65022
|
import { statSync as statSync8 } from "fs";
|
|
64953
|
-
import { join as
|
|
65023
|
+
import { join as join51 } from "path";
|
|
64954
65024
|
var DEFAULT_STALE_AFTER_HOURS = 24;
|
|
64955
65025
|
var HOUR_MS = 60 * 60 * 1000;
|
|
64956
65026
|
function getStaleAfterMs(config2) {
|
|
@@ -64970,7 +65040,7 @@ function isTaskStale(taskPath, staleAfterMs) {
|
|
|
64970
65040
|
return ageMs !== null && ageMs > staleAfterMs;
|
|
64971
65041
|
}
|
|
64972
65042
|
function filterFreshIncompleteTasks(tasks, taskDir, staleAfterMs) {
|
|
64973
|
-
return tasks.filter((t) => !isTaskStale(
|
|
65043
|
+
return tasks.filter((t) => !isTaskStale(join51(taskDir, `${t.id}.json`), staleAfterMs));
|
|
64974
65044
|
}
|
|
64975
65045
|
function formatTaskAge(ageMs) {
|
|
64976
65046
|
const hours = Math.floor(ageMs / HOUR_MS);
|
|
@@ -65058,7 +65128,7 @@ async function injectContinuation(args) {
|
|
|
65058
65128
|
let taskDir = "";
|
|
65059
65129
|
try {
|
|
65060
65130
|
taskDir = getTaskDir(config2, ctx.directory);
|
|
65061
|
-
if (!
|
|
65131
|
+
if (!existsSync43(taskDir)) {
|
|
65062
65132
|
const hadBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).length > 0 : false;
|
|
65063
65133
|
if (hadBgTasks) {
|
|
65064
65134
|
log(`[${HOOK_NAME14}] Bootstrap injection: no task dir (hadBgTasks)`, { sessionID, taskDir });
|
|
@@ -65141,7 +65211,7 @@ async function injectContinuation(args) {
|
|
|
65141
65211
|
const incompleteTasks = getIncompleteTasks(filteredTasks);
|
|
65142
65212
|
const freshIncompleteTasks = filterFreshIncompleteTasks(incompleteTasks, taskDir, staleAfterMs);
|
|
65143
65213
|
const taskList = incompleteTasks.map((task) => {
|
|
65144
|
-
const ageMs = getTaskAgeMs(
|
|
65214
|
+
const ageMs = getTaskAgeMs(join52(taskDir, `${task.id}.json`));
|
|
65145
65215
|
const staleSuffix = ageMs !== null && ageMs > staleAfterMs ? ` (stale: ${formatTaskAge(ageMs)})` : "";
|
|
65146
65216
|
return `- [${task.status}] ${task.subject} (${task.id})${staleSuffix}`;
|
|
65147
65217
|
}).join(`
|
|
@@ -65320,7 +65390,7 @@ async function handleSessionIdle(args) {
|
|
|
65320
65390
|
let taskDir = "";
|
|
65321
65391
|
try {
|
|
65322
65392
|
taskDir = getTaskDir(config2, ctx.directory);
|
|
65323
|
-
if (!
|
|
65393
|
+
if (!existsSync44(taskDir)) {
|
|
65324
65394
|
const hadBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).length > 0 : false;
|
|
65325
65395
|
if (hadBgTasks) {
|
|
65326
65396
|
log(`[${HOOK_NAME14}] No task dir \u2014 bootstrap (hadBgTasks)`, { sessionID, taskDir });
|
|
@@ -65359,7 +65429,7 @@ async function handleSessionIdle(args) {
|
|
|
65359
65429
|
const incompleteTasks = getIncompleteTasks(filteredTasks);
|
|
65360
65430
|
incompleteCount = incompleteTasks.length;
|
|
65361
65431
|
const staleAfterMs = getStaleAfterMs(config2);
|
|
65362
|
-
const staleIncompleteCount = incompleteTasks.filter((t) => isTaskStale(
|
|
65432
|
+
const staleIncompleteCount = incompleteTasks.filter((t) => isTaskStale(join53(taskDir, `${t.id}.json`), staleAfterMs)).length;
|
|
65363
65433
|
const activeIncompleteCount = incompleteCount - staleIncompleteCount;
|
|
65364
65434
|
if (activeIncompleteCount === 0 && staleIncompleteCount > 0) {
|
|
65365
65435
|
log(`[${HOOK_NAME14}] Skipped: only stale tasks remain`, {
|
|
@@ -65806,8 +65876,8 @@ var TASK_NOTEPAD_FRAGMENT = "task-notepad";
|
|
|
65806
65876
|
var TODO_FETCH_ERROR_FRAGMENT = "Failed to fetch todos for task-notepad";
|
|
65807
65877
|
// src/hooks/task-notepad/hook.ts
|
|
65808
65878
|
init_logger();
|
|
65809
|
-
import { existsSync as
|
|
65810
|
-
import { dirname as dirname13, join as
|
|
65879
|
+
import { existsSync as existsSync45, mkdirSync as mkdirSync16, readdirSync as readdirSync16, readFileSync as readFileSync33, statSync as statSync9, writeFileSync as writeFileSync18 } from "fs";
|
|
65880
|
+
import { dirname as dirname13, join as join54 } from "path";
|
|
65811
65881
|
function createTaskNotepadHook(ctx) {
|
|
65812
65882
|
return {
|
|
65813
65883
|
"tool.execute.after": async (input, output) => {
|
|
@@ -65858,8 +65928,8 @@ async function fetchTodos(ctx, sessionID) {
|
|
|
65858
65928
|
}
|
|
65859
65929
|
}
|
|
65860
65930
|
function resolveActivePlan(directory) {
|
|
65861
|
-
const plansDir =
|
|
65862
|
-
if (!
|
|
65931
|
+
const plansDir = join54(directory, PLAN_DIR_NAME, PLANS_SUBDIR);
|
|
65932
|
+
if (!existsSync45(plansDir))
|
|
65863
65933
|
return null;
|
|
65864
65934
|
let entries;
|
|
65865
65935
|
try {
|
|
@@ -65871,7 +65941,7 @@ function resolveActivePlan(directory) {
|
|
|
65871
65941
|
for (const entry of entries) {
|
|
65872
65942
|
if (!entry.endsWith(PLAN_EXTENSION))
|
|
65873
65943
|
continue;
|
|
65874
|
-
const fullPath =
|
|
65944
|
+
const fullPath = join54(plansDir, entry);
|
|
65875
65945
|
try {
|
|
65876
65946
|
const stats = statSync9(fullPath);
|
|
65877
65947
|
if (!stats.isFile())
|
|
@@ -65890,7 +65960,7 @@ function resolveActivePlan(directory) {
|
|
|
65890
65960
|
}
|
|
65891
65961
|
function ensureNotepad(directory, planName, todo) {
|
|
65892
65962
|
const notepadPath = notepadPathFor(directory, planName, todo.id);
|
|
65893
|
-
if (
|
|
65963
|
+
if (existsSync45(notepadPath))
|
|
65894
65964
|
return;
|
|
65895
65965
|
mkdirSync16(dirname13(notepadPath), { recursive: true });
|
|
65896
65966
|
const content = renderNotepad(todo);
|
|
@@ -65899,7 +65969,7 @@ function ensureNotepad(directory, planName, todo) {
|
|
|
65899
65969
|
}
|
|
65900
65970
|
function appendCompletion(directory, planName, todo) {
|
|
65901
65971
|
const notepadPath = notepadPathFor(directory, planName, todo.id);
|
|
65902
|
-
if (!
|
|
65972
|
+
if (!existsSync45(notepadPath))
|
|
65903
65973
|
return;
|
|
65904
65974
|
const existing = readFileSync33(notepadPath, "utf-8");
|
|
65905
65975
|
if (existing.includes("**Status**: completed"))
|
|
@@ -65914,7 +65984,7 @@ function appendCompletion(directory, planName, todo) {
|
|
|
65914
65984
|
log(`[${HOOK_NAME16}] Appended completion to notepad`, { plan: planName, taskID: todo.id });
|
|
65915
65985
|
}
|
|
65916
65986
|
function notepadPathFor(directory, planName, taskID) {
|
|
65917
|
-
return
|
|
65987
|
+
return join54(directory, PLAN_DIR_NAME, NOTEPAD_DIR_NAME, planName, TASKS_SUBDIR, `${taskID}${NOTEPAD_EXTENSION}`);
|
|
65918
65988
|
}
|
|
65919
65989
|
function renderNotepad(todo) {
|
|
65920
65990
|
const priority = todo.priority ?? "medium";
|
|
@@ -67589,8 +67659,8 @@ function createWebFetchRedirectGuardHook(_ctx) {
|
|
|
67589
67659
|
};
|
|
67590
67660
|
}
|
|
67591
67661
|
// src/hooks/write-existing-file-guard/hook.ts
|
|
67592
|
-
import { existsSync as
|
|
67593
|
-
import { isAbsolute as isAbsolute4, join as
|
|
67662
|
+
import { existsSync as existsSync46 } from "fs";
|
|
67663
|
+
import { isAbsolute as isAbsolute4, join as join55, normalize, resolve as resolve6, sep } from "path";
|
|
67594
67664
|
function createWriteExistingFileGuardHook(ctx) {
|
|
67595
67665
|
return {
|
|
67596
67666
|
"tool.execute.before": async (input, output) => {
|
|
@@ -67604,8 +67674,8 @@ function createWriteExistingFileGuardHook(ctx) {
|
|
|
67604
67674
|
return;
|
|
67605
67675
|
}
|
|
67606
67676
|
const resolvedPath = normalize(isAbsolute4(filePath) ? filePath : resolve6(ctx.directory, filePath));
|
|
67607
|
-
if (
|
|
67608
|
-
const matrixxRoot =
|
|
67677
|
+
if (existsSync46(resolvedPath)) {
|
|
67678
|
+
const matrixxRoot = join55(ctx.directory, ".matrixx") + sep;
|
|
67609
67679
|
const isMatrixMarkdown = resolvedPath.startsWith(matrixxRoot) && resolvedPath.endsWith(".md");
|
|
67610
67680
|
const normalizedPath = resolvedPath.replace(/\\/g, "/");
|
|
67611
67681
|
const isPlansPath = normalizedPath.includes(".matrixx/plans");
|
|
@@ -68050,14 +68120,14 @@ function getLanguageId(ext) {
|
|
|
68050
68120
|
return EXT_TO_LANG[ext] || "plaintext";
|
|
68051
68121
|
}
|
|
68052
68122
|
// src/tools/lsp/server-installation.ts
|
|
68053
|
-
import { existsSync as
|
|
68054
|
-
import { join as
|
|
68123
|
+
import { existsSync as existsSync47 } from "fs";
|
|
68124
|
+
import { join as join56 } from "path";
|
|
68055
68125
|
function isServerInstalled(command) {
|
|
68056
68126
|
if (command.length === 0)
|
|
68057
68127
|
return false;
|
|
68058
68128
|
const cmd = command[0];
|
|
68059
68129
|
if (cmd.includes("/") || cmd.includes("\\")) {
|
|
68060
|
-
if (
|
|
68130
|
+
if (existsSync47(cmd))
|
|
68061
68131
|
return true;
|
|
68062
68132
|
}
|
|
68063
68133
|
const isWindows = process.platform === "win32";
|
|
@@ -68079,23 +68149,23 @@ function isServerInstalled(command) {
|
|
|
68079
68149
|
const paths = pathEnv.split(pathSeparator);
|
|
68080
68150
|
for (const p of paths) {
|
|
68081
68151
|
for (const suffix of exts) {
|
|
68082
|
-
if (
|
|
68152
|
+
if (existsSync47(join56(p, cmd + suffix))) {
|
|
68083
68153
|
return true;
|
|
68084
68154
|
}
|
|
68085
68155
|
}
|
|
68086
68156
|
}
|
|
68087
68157
|
const cwd = process.cwd();
|
|
68088
68158
|
const configDir = getOpenCodeConfigDir({ binary: "opencode" });
|
|
68089
|
-
const dataDir =
|
|
68159
|
+
const dataDir = join56(getDataDir(), "opencode");
|
|
68090
68160
|
const additionalBases = [
|
|
68091
|
-
|
|
68092
|
-
|
|
68093
|
-
|
|
68094
|
-
|
|
68161
|
+
join56(cwd, "node_modules", ".bin"),
|
|
68162
|
+
join56(configDir, "bin"),
|
|
68163
|
+
join56(configDir, "node_modules", ".bin"),
|
|
68164
|
+
join56(dataDir, "bin")
|
|
68095
68165
|
];
|
|
68096
68166
|
for (const base of additionalBases) {
|
|
68097
68167
|
for (const suffix of exts) {
|
|
68098
|
-
if (
|
|
68168
|
+
if (existsSync47(join56(base, cmd + suffix))) {
|
|
68099
68169
|
return true;
|
|
68100
68170
|
}
|
|
68101
68171
|
}
|
|
@@ -68106,10 +68176,10 @@ function isServerInstalled(command) {
|
|
|
68106
68176
|
return false;
|
|
68107
68177
|
}
|
|
68108
68178
|
// src/tools/lsp/server-config-loader.ts
|
|
68109
|
-
import { existsSync as
|
|
68110
|
-
import { join as
|
|
68179
|
+
import { existsSync as existsSync48, readFileSync as readFileSync34 } from "fs";
|
|
68180
|
+
import { join as join57 } from "path";
|
|
68111
68181
|
function loadJsonFile(path12) {
|
|
68112
|
-
if (!
|
|
68182
|
+
if (!existsSync48(path12))
|
|
68113
68183
|
return null;
|
|
68114
68184
|
try {
|
|
68115
68185
|
return parseJsonc(readFileSync34(path12, "utf-8"));
|
|
@@ -68121,9 +68191,9 @@ function getConfigPaths3() {
|
|
|
68121
68191
|
const cwd = process.cwd();
|
|
68122
68192
|
const configDir = getOpenCodeConfigDir({ binary: "opencode" });
|
|
68123
68193
|
return {
|
|
68124
|
-
project: detectConfigFile(
|
|
68125
|
-
user: detectConfigFile(
|
|
68126
|
-
opencode: detectConfigFile(
|
|
68194
|
+
project: detectConfigFile(join57(cwd, ".opencode", "matrixx")).path,
|
|
68195
|
+
user: detectConfigFile(join57(configDir, "matrixx")).path,
|
|
68196
|
+
opencode: detectConfigFile(join57(configDir, "opencode")).path
|
|
68127
68197
|
};
|
|
68128
68198
|
}
|
|
68129
68199
|
function loadAllConfigs() {
|
|
@@ -68241,14 +68311,14 @@ import { Readable, Writable } from "stream";
|
|
|
68241
68311
|
// src/tools/lsp/lsp-process.ts
|
|
68242
68312
|
init_logger();
|
|
68243
68313
|
import { spawn as nodeSpawn } from "child_process";
|
|
68244
|
-
import { existsSync as
|
|
68314
|
+
import { existsSync as existsSync49, statSync as statSync10 } from "fs";
|
|
68245
68315
|
var {spawn: bunSpawn } = globalThis.Bun;
|
|
68246
68316
|
function shouldUseNodeSpawn() {
|
|
68247
68317
|
return process.platform === "win32";
|
|
68248
68318
|
}
|
|
68249
68319
|
function validateCwd(cwd) {
|
|
68250
68320
|
try {
|
|
68251
|
-
if (!
|
|
68321
|
+
if (!existsSync49(cwd)) {
|
|
68252
68322
|
return { valid: false, error: `Working directory does not exist: ${cwd}` };
|
|
68253
68323
|
}
|
|
68254
68324
|
const stats = statSync10(cwd);
|
|
@@ -68971,8 +69041,8 @@ class LSPServerManager {
|
|
|
68971
69041
|
}
|
|
68972
69042
|
var lspManager = LSPServerManager.getInstance();
|
|
68973
69043
|
// src/tools/lsp/lsp-client-wrapper.ts
|
|
68974
|
-
import { existsSync as
|
|
68975
|
-
import { dirname as dirname14, extname as extname3, join as
|
|
69044
|
+
import { existsSync as existsSync50, statSync as statSync11 } from "fs";
|
|
69045
|
+
import { dirname as dirname14, extname as extname3, join as join58, resolve as resolve8 } from "path";
|
|
68976
69046
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
68977
69047
|
function findWorkspaceRoot(filePath) {
|
|
68978
69048
|
let dir = resolve8(filePath);
|
|
@@ -68988,7 +69058,7 @@ function findWorkspaceRoot(filePath) {
|
|
|
68988
69058
|
let prevDir = "";
|
|
68989
69059
|
while (dir !== prevDir) {
|
|
68990
69060
|
for (const marker of markers) {
|
|
68991
|
-
if (
|
|
69061
|
+
if (existsSync50(join58(dir, marker))) {
|
|
68992
69062
|
return dir;
|
|
68993
69063
|
}
|
|
68994
69064
|
}
|
|
@@ -81798,12 +81868,12 @@ var lsp_symbols = tool({
|
|
|
81798
81868
|
}
|
|
81799
81869
|
});
|
|
81800
81870
|
// src/tools/ast-grep/cli.ts
|
|
81801
|
-
import { existsSync as
|
|
81871
|
+
import { existsSync as existsSync54 } from "fs";
|
|
81802
81872
|
var {spawn: spawn9 } = globalThis.Bun;
|
|
81803
81873
|
|
|
81804
81874
|
// src/tools/ast-grep/cli-binary-path-resolution.ts
|
|
81805
81875
|
init_logger();
|
|
81806
|
-
import { existsSync as
|
|
81876
|
+
import { existsSync as existsSync53 } from "fs";
|
|
81807
81877
|
|
|
81808
81878
|
// src/tools/ast-grep/language-support.ts
|
|
81809
81879
|
var CLI_LANGUAGES = [
|
|
@@ -81838,15 +81908,15 @@ var DEFAULT_MAX_OUTPUT_BYTES = 1 * 1024 * 1024;
|
|
|
81838
81908
|
var DEFAULT_MAX_MATCHES = 500;
|
|
81839
81909
|
|
|
81840
81910
|
// src/tools/ast-grep/sg-cli-path.ts
|
|
81841
|
-
import { existsSync as
|
|
81911
|
+
import { existsSync as existsSync52, statSync as statSync12 } from "fs";
|
|
81842
81912
|
import { createRequire as createRequire4 } from "module";
|
|
81843
|
-
import { dirname as dirname15, join as
|
|
81913
|
+
import { dirname as dirname15, join as join60 } from "path";
|
|
81844
81914
|
|
|
81845
81915
|
// src/tools/ast-grep/downloader.ts
|
|
81846
|
-
import { existsSync as
|
|
81916
|
+
import { existsSync as existsSync51 } from "fs";
|
|
81847
81917
|
import { createRequire as createRequire3 } from "module";
|
|
81848
|
-
import { homedir as
|
|
81849
|
-
import { join as
|
|
81918
|
+
import { homedir as homedir9 } from "os";
|
|
81919
|
+
import { join as join59 } from "path";
|
|
81850
81920
|
init_logger();
|
|
81851
81921
|
var REPO2 = "ast-grep/ast-grep";
|
|
81852
81922
|
var DEFAULT_VERSION = "0.40.0";
|
|
@@ -81871,12 +81941,12 @@ var PLATFORM_MAP2 = {
|
|
|
81871
81941
|
function getCacheDir3() {
|
|
81872
81942
|
if (process.platform === "win32") {
|
|
81873
81943
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
81874
|
-
const base2 = localAppData ||
|
|
81875
|
-
return
|
|
81944
|
+
const base2 = localAppData || join59(homedir9(), "AppData", "Local");
|
|
81945
|
+
return join59(base2, "matrixx", "bin");
|
|
81876
81946
|
}
|
|
81877
81947
|
const xdgCache = process.env.XDG_CACHE_HOME;
|
|
81878
|
-
const base = xdgCache ||
|
|
81879
|
-
return
|
|
81948
|
+
const base = xdgCache || join59(homedir9(), ".cache");
|
|
81949
|
+
return join59(base, "matrixx", "bin");
|
|
81880
81950
|
}
|
|
81881
81951
|
function getBinaryName3() {
|
|
81882
81952
|
return process.platform === "win32" ? "sg.exe" : "sg";
|
|
@@ -81893,8 +81963,8 @@ async function downloadAstGrep(version3 = DEFAULT_VERSION) {
|
|
|
81893
81963
|
}
|
|
81894
81964
|
const cacheDir = getCacheDir3();
|
|
81895
81965
|
const binaryName = getBinaryName3();
|
|
81896
|
-
const binaryPath =
|
|
81897
|
-
if (
|
|
81966
|
+
const binaryPath = join59(cacheDir, binaryName);
|
|
81967
|
+
if (existsSync51(binaryPath)) {
|
|
81898
81968
|
return binaryPath;
|
|
81899
81969
|
}
|
|
81900
81970
|
const { arch, os: os7 } = platformInfo;
|
|
@@ -81902,7 +81972,7 @@ async function downloadAstGrep(version3 = DEFAULT_VERSION) {
|
|
|
81902
81972
|
const downloadUrl = `https://github.com/${REPO2}/releases/download/${version3}/${assetName}`;
|
|
81903
81973
|
log(`[matrixx] Downloading ast-grep binary...`);
|
|
81904
81974
|
try {
|
|
81905
|
-
const archivePath =
|
|
81975
|
+
const archivePath = join59(cacheDir, assetName);
|
|
81906
81976
|
ensureCacheDir(cacheDir);
|
|
81907
81977
|
await downloadArchive(downloadUrl, archivePath);
|
|
81908
81978
|
await extractZipArchive(archivePath, cacheDir);
|
|
@@ -81956,8 +82026,8 @@ function findSgCliPathSync() {
|
|
|
81956
82026
|
const require2 = createRequire4(import.meta.url);
|
|
81957
82027
|
const cliPackageJsonPath = require2.resolve("@ast-grep/cli/package.json");
|
|
81958
82028
|
const cliDirectory = dirname15(cliPackageJsonPath);
|
|
81959
|
-
const sgPath =
|
|
81960
|
-
if (
|
|
82029
|
+
const sgPath = join60(cliDirectory, binaryName);
|
|
82030
|
+
if (existsSync52(sgPath) && isValidBinary(sgPath)) {
|
|
81961
82031
|
return sgPath;
|
|
81962
82032
|
}
|
|
81963
82033
|
} catch {}
|
|
@@ -81968,8 +82038,8 @@ function findSgCliPathSync() {
|
|
|
81968
82038
|
const packageJsonPath = require2.resolve(`${platformPackage}/package.json`);
|
|
81969
82039
|
const packageDirectory = dirname15(packageJsonPath);
|
|
81970
82040
|
const astGrepBinaryName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
|
|
81971
|
-
const binaryPath =
|
|
81972
|
-
if (
|
|
82041
|
+
const binaryPath = join60(packageDirectory, astGrepBinaryName);
|
|
82042
|
+
if (existsSync52(binaryPath) && isValidBinary(binaryPath)) {
|
|
81973
82043
|
return binaryPath;
|
|
81974
82044
|
}
|
|
81975
82045
|
} catch {}
|
|
@@ -81977,7 +82047,7 @@ function findSgCliPathSync() {
|
|
|
81977
82047
|
if (process.platform === "darwin") {
|
|
81978
82048
|
const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
|
|
81979
82049
|
for (const path12 of homebrewPaths) {
|
|
81980
|
-
if (
|
|
82050
|
+
if (existsSync52(path12) && isValidBinary(path12)) {
|
|
81981
82051
|
return path12;
|
|
81982
82052
|
}
|
|
81983
82053
|
}
|
|
@@ -82003,7 +82073,7 @@ function setSgCliPath(path12) {
|
|
|
82003
82073
|
var resolvedCliPath3 = null;
|
|
82004
82074
|
var initPromise3 = null;
|
|
82005
82075
|
async function getAstGrepPath() {
|
|
82006
|
-
if (resolvedCliPath3 !== null &&
|
|
82076
|
+
if (resolvedCliPath3 !== null && existsSync53(resolvedCliPath3)) {
|
|
82007
82077
|
return resolvedCliPath3;
|
|
82008
82078
|
}
|
|
82009
82079
|
if (initPromise3) {
|
|
@@ -82011,7 +82081,7 @@ async function getAstGrepPath() {
|
|
|
82011
82081
|
}
|
|
82012
82082
|
initPromise3 = (async () => {
|
|
82013
82083
|
const syncPath = findSgCliPathSync();
|
|
82014
|
-
if (syncPath &&
|
|
82084
|
+
if (syncPath && existsSync53(syncPath)) {
|
|
82015
82085
|
resolvedCliPath3 = syncPath;
|
|
82016
82086
|
setSgCliPath(syncPath);
|
|
82017
82087
|
return syncPath;
|
|
@@ -82110,7 +82180,7 @@ async function runSg(options) {
|
|
|
82110
82180
|
const paths = options.paths && options.paths.length > 0 ? options.paths : ["."];
|
|
82111
82181
|
args.push(...paths);
|
|
82112
82182
|
let cliPath = getSgCliPath();
|
|
82113
|
-
if (!cliPath || !
|
|
82183
|
+
if (!cliPath || !existsSync54(cliPath)) {
|
|
82114
82184
|
const downloadedPath = await getAstGrepPath();
|
|
82115
82185
|
if (downloadedPath) {
|
|
82116
82186
|
cliPath = downloadedPath;
|
|
@@ -84398,18 +84468,18 @@ var {spawn: spawn10 } = globalThis.Bun;
|
|
|
84398
84468
|
|
|
84399
84469
|
// src/tools/grep/constants.ts
|
|
84400
84470
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
84401
|
-
import { existsSync as
|
|
84402
|
-
import { dirname as dirname16, join as
|
|
84471
|
+
import { existsSync as existsSync59 } from "fs";
|
|
84472
|
+
import { dirname as dirname16, join as join62 } from "path";
|
|
84403
84473
|
|
|
84404
84474
|
// src/tools/grep/downloader.ts
|
|
84405
|
-
import { existsSync as
|
|
84406
|
-
import { join as
|
|
84475
|
+
import { existsSync as existsSync58, readdirSync as readdirSync18 } from "fs";
|
|
84476
|
+
import { join as join61 } from "path";
|
|
84407
84477
|
function findFileRecursive(dir, filename) {
|
|
84408
84478
|
try {
|
|
84409
84479
|
const entries = readdirSync18(dir, { withFileTypes: true, recursive: true });
|
|
84410
84480
|
for (const entry of entries) {
|
|
84411
84481
|
if (entry.isFile() && entry.name === filename) {
|
|
84412
|
-
return
|
|
84482
|
+
return join61(entry.parentPath ?? dir, entry.name);
|
|
84413
84483
|
}
|
|
84414
84484
|
}
|
|
84415
84485
|
} catch {
|
|
@@ -84430,11 +84500,11 @@ function getPlatformKey() {
|
|
|
84430
84500
|
}
|
|
84431
84501
|
function getInstallDir() {
|
|
84432
84502
|
const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
|
|
84433
|
-
return
|
|
84503
|
+
return join61(homeDir, ".cache", "matrixx", "bin");
|
|
84434
84504
|
}
|
|
84435
84505
|
function getRgPath() {
|
|
84436
84506
|
const isWindows = process.platform === "win32";
|
|
84437
|
-
return
|
|
84507
|
+
return join61(getInstallDir(), isWindows ? "rg.exe" : "rg");
|
|
84438
84508
|
}
|
|
84439
84509
|
async function extractTarGz2(archivePath, destDir) {
|
|
84440
84510
|
const platformKey = getPlatformKey();
|
|
@@ -84451,7 +84521,7 @@ async function extractZip2(archivePath, destDir) {
|
|
|
84451
84521
|
const binaryName = process.platform === "win32" ? "rg.exe" : "rg";
|
|
84452
84522
|
const foundPath = findFileRecursive(destDir, binaryName);
|
|
84453
84523
|
if (foundPath) {
|
|
84454
|
-
const destPath =
|
|
84524
|
+
const destPath = join61(destDir, binaryName);
|
|
84455
84525
|
if (foundPath !== destPath) {
|
|
84456
84526
|
const { renameSync: renameSync4 } = await import("fs");
|
|
84457
84527
|
renameSync4(foundPath, destPath);
|
|
@@ -84466,13 +84536,13 @@ async function downloadAndInstallRipgrep() {
|
|
|
84466
84536
|
}
|
|
84467
84537
|
const installDir = getInstallDir();
|
|
84468
84538
|
const rgPath = getRgPath();
|
|
84469
|
-
if (
|
|
84539
|
+
if (existsSync58(rgPath)) {
|
|
84470
84540
|
return rgPath;
|
|
84471
84541
|
}
|
|
84472
84542
|
ensureCacheDir(installDir);
|
|
84473
84543
|
const filename = `ripgrep-${RG_VERSION}-${config4.platform}.${config4.extension}`;
|
|
84474
84544
|
const url3 = `https://github.com/BurntSushi/ripgrep/releases/download/${RG_VERSION}/${filename}`;
|
|
84475
|
-
const archivePath =
|
|
84545
|
+
const archivePath = join61(installDir, filename);
|
|
84476
84546
|
try {
|
|
84477
84547
|
await downloadArchive(url3, archivePath);
|
|
84478
84548
|
if (config4.extension === "tar.gz") {
|
|
@@ -84481,7 +84551,7 @@ async function downloadAndInstallRipgrep() {
|
|
|
84481
84551
|
await extractZip2(archivePath, installDir);
|
|
84482
84552
|
}
|
|
84483
84553
|
ensureExecutable(rgPath);
|
|
84484
|
-
if (!
|
|
84554
|
+
if (!existsSync58(rgPath)) {
|
|
84485
84555
|
throw new Error("ripgrep binary not found after extraction");
|
|
84486
84556
|
}
|
|
84487
84557
|
return rgPath;
|
|
@@ -84493,7 +84563,7 @@ async function downloadAndInstallRipgrep() {
|
|
|
84493
84563
|
}
|
|
84494
84564
|
function getInstalledRipgrepPath() {
|
|
84495
84565
|
const rgPath = getRgPath();
|
|
84496
|
-
return
|
|
84566
|
+
return existsSync58(rgPath) ? rgPath : null;
|
|
84497
84567
|
}
|
|
84498
84568
|
|
|
84499
84569
|
// src/tools/grep/constants.ts
|
|
@@ -84517,14 +84587,14 @@ function getOpenCodeBundledRg() {
|
|
|
84517
84587
|
const isWindows = process.platform === "win32";
|
|
84518
84588
|
const rgName = isWindows ? "rg.exe" : "rg";
|
|
84519
84589
|
const candidates = [
|
|
84520
|
-
|
|
84521
|
-
|
|
84522
|
-
|
|
84523
|
-
|
|
84524
|
-
|
|
84590
|
+
join62(getDataDir(), "opencode", "bin", rgName),
|
|
84591
|
+
join62(execDir, rgName),
|
|
84592
|
+
join62(execDir, "bin", rgName),
|
|
84593
|
+
join62(execDir, "..", "bin", rgName),
|
|
84594
|
+
join62(execDir, "..", "libexec", rgName)
|
|
84525
84595
|
];
|
|
84526
84596
|
for (const candidate of candidates) {
|
|
84527
|
-
if (
|
|
84597
|
+
if (existsSync59(candidate)) {
|
|
84528
84598
|
return candidate;
|
|
84529
84599
|
}
|
|
84530
84600
|
}
|
|
@@ -85028,17 +85098,17 @@ var HandoffSchema = exports_external.object({
|
|
|
85028
85098
|
sections: HandoffSectionSchema
|
|
85029
85099
|
});
|
|
85030
85100
|
// src/features/handoff/storage.ts
|
|
85031
|
-
import { existsSync as
|
|
85032
|
-
import { join as
|
|
85101
|
+
import { existsSync as existsSync60, mkdirSync as mkdirSync17, readFileSync as readFileSync39, renameSync as renameSync4, writeFileSync as writeFileSync22 } from "fs";
|
|
85102
|
+
import { join as join63 } from "path";
|
|
85033
85103
|
function getHandoffFilePath(directory) {
|
|
85034
|
-
return
|
|
85104
|
+
return join63(directory, HANDOFF_FILE_PATH);
|
|
85035
85105
|
}
|
|
85036
85106
|
function getHandoffConsumedFilePath(directory) {
|
|
85037
|
-
return
|
|
85107
|
+
return join63(directory, HANDOFF_CONSUMED_FILE_PATH);
|
|
85038
85108
|
}
|
|
85039
85109
|
function handoffFileExists(directory) {
|
|
85040
85110
|
try {
|
|
85041
|
-
return
|
|
85111
|
+
return existsSync60(getHandoffFilePath(directory));
|
|
85042
85112
|
} catch {
|
|
85043
85113
|
return false;
|
|
85044
85114
|
}
|
|
@@ -85046,7 +85116,7 @@ function handoffFileExists(directory) {
|
|
|
85046
85116
|
function readHandoffFile(directory) {
|
|
85047
85117
|
const filePath = getHandoffFilePath(directory);
|
|
85048
85118
|
try {
|
|
85049
|
-
if (!
|
|
85119
|
+
if (!existsSync60(filePath))
|
|
85050
85120
|
return null;
|
|
85051
85121
|
return readFileSync39(filePath, "utf-8");
|
|
85052
85122
|
} catch {
|
|
@@ -85072,7 +85142,7 @@ function writeHandoffFile(directory, content) {
|
|
|
85072
85142
|
}
|
|
85073
85143
|
function ensureHandoffDir(directory) {
|
|
85074
85144
|
try {
|
|
85075
|
-
mkdirSync17(
|
|
85145
|
+
mkdirSync17(join63(directory, ".matrixx"), { recursive: true });
|
|
85076
85146
|
return true;
|
|
85077
85147
|
} catch {
|
|
85078
85148
|
return false;
|
|
@@ -85227,15 +85297,15 @@ ${body}
|
|
|
85227
85297
|
}
|
|
85228
85298
|
|
|
85229
85299
|
// src/tools/handoff/list-handler.ts
|
|
85230
|
-
import { existsSync as
|
|
85231
|
-
import { join as
|
|
85300
|
+
import { existsSync as existsSync61, readdirSync as readdirSync19, statSync as statSync14 } from "fs";
|
|
85301
|
+
import { join as join64 } from "path";
|
|
85232
85302
|
var MATRIX_DIR = ".matrixx";
|
|
85233
85303
|
function isHandoffFile(name) {
|
|
85234
85304
|
return name === "handoff.md" || name.startsWith("handoff.consumed");
|
|
85235
85305
|
}
|
|
85236
85306
|
function handleList(directory) {
|
|
85237
|
-
const matrixxDir =
|
|
85238
|
-
if (!
|
|
85307
|
+
const matrixxDir = join64(directory, MATRIX_DIR);
|
|
85308
|
+
if (!existsSync61(matrixxDir)) {
|
|
85239
85309
|
return "No handoffs found in .matrixx/";
|
|
85240
85310
|
}
|
|
85241
85311
|
let entries;
|
|
@@ -85252,7 +85322,7 @@ function handleList(directory) {
|
|
|
85252
85322
|
lines.push(`Handoff files in ${matrixxDir}:`);
|
|
85253
85323
|
lines.push("");
|
|
85254
85324
|
for (const name of handoffs) {
|
|
85255
|
-
const fullPath =
|
|
85325
|
+
const fullPath = join64(matrixxDir, name);
|
|
85256
85326
|
let size = "?";
|
|
85257
85327
|
let mtime = "?";
|
|
85258
85328
|
try {
|
|
@@ -85476,9 +85546,9 @@ The Bash tool can execute these commands directly. Do NOT retry with interactive
|
|
|
85476
85546
|
}
|
|
85477
85547
|
});
|
|
85478
85548
|
// src/tools/pdf-extract-figures/tools.ts
|
|
85479
|
-
import { existsSync as
|
|
85549
|
+
import { existsSync as existsSync62, mkdtempSync, writeFileSync as writeFileSync23 } from "fs";
|
|
85480
85550
|
import { tmpdir as tmpdir5 } from "os";
|
|
85481
|
-
import { join as
|
|
85551
|
+
import { join as join65 } from "path";
|
|
85482
85552
|
var PYTHON_SCRIPT = `#!/usr/bin/env python3
|
|
85483
85553
|
import sys
|
|
85484
85554
|
import json
|
|
@@ -85580,7 +85650,7 @@ function validateArgs(args) {
|
|
|
85580
85650
|
if (!args.file_path) {
|
|
85581
85651
|
return "Error: 'file_path' is required.";
|
|
85582
85652
|
}
|
|
85583
|
-
if (!
|
|
85653
|
+
if (!existsSync62(args.file_path)) {
|
|
85584
85654
|
return `Error: File not found: ${args.file_path}`;
|
|
85585
85655
|
}
|
|
85586
85656
|
if (!args.file_path.toLowerCase().endsWith(".pdf")) {
|
|
@@ -85629,11 +85699,11 @@ async function runExtraction(args) {
|
|
|
85629
85699
|
}
|
|
85630
85700
|
let tmpDir;
|
|
85631
85701
|
try {
|
|
85632
|
-
tmpDir = mkdtempSync(
|
|
85702
|
+
tmpDir = mkdtempSync(join65(tmpdir5(), "matrixx-pdf-extract-"));
|
|
85633
85703
|
} catch {
|
|
85634
85704
|
return "Error: Could not create temporary directory.";
|
|
85635
85705
|
}
|
|
85636
|
-
const scriptPath =
|
|
85706
|
+
const scriptPath = join65(tmpDir, "extract_pdf_figures.py");
|
|
85637
85707
|
writeFileSync23(scriptPath, PYTHON_SCRIPT);
|
|
85638
85708
|
const payload = buildArgsPayload(args);
|
|
85639
85709
|
log("[pdf_extract_figures] Running extraction", { file_path: args.file_path, payload });
|
|
@@ -85681,11 +85751,11 @@ function createPdfExtractFiguresTool() {
|
|
|
85681
85751
|
return { pdf_extract_figures };
|
|
85682
85752
|
}
|
|
85683
85753
|
// src/tools/plan/plan-create.ts
|
|
85684
|
-
import { existsSync as
|
|
85754
|
+
import { existsSync as existsSync63 } from "fs";
|
|
85685
85755
|
import { basename as basename7 } from "path";
|
|
85686
85756
|
|
|
85687
85757
|
// src/tools/plan/types.ts
|
|
85688
|
-
import { basename as basename6, isAbsolute as isAbsolute5, join as
|
|
85758
|
+
import { basename as basename6, isAbsolute as isAbsolute5, join as join66, relative as relative4, resolve as resolve9 } from "path";
|
|
85689
85759
|
|
|
85690
85760
|
// src/tools/plan/constants.ts
|
|
85691
85761
|
var PLANS_DIR3 = ".matrixx/plans";
|
|
@@ -85711,8 +85781,8 @@ function validatePlanFilePath(filePath, directory) {
|
|
|
85711
85781
|
if (!PLAN_FILENAME_KEBAB_REGEX.test(base)) {
|
|
85712
85782
|
return { error: `fileName must match kebab-case ^[a-z0-9-]+\\.md$: ${base}` };
|
|
85713
85783
|
}
|
|
85714
|
-
const resolved = isAbsolute5(filePath) ? resolve9(filePath) : resolve9(
|
|
85715
|
-
const plansDir = resolve9(
|
|
85784
|
+
const resolved = isAbsolute5(filePath) ? resolve9(filePath) : resolve9(join66(directory, filePath));
|
|
85785
|
+
const plansDir = resolve9(join66(directory, PLANS_DIR3));
|
|
85716
85786
|
const relToPlans = relative4(plansDir, resolved);
|
|
85717
85787
|
if (relToPlans.startsWith("..") || isAbsolute5(relToPlans)) {
|
|
85718
85788
|
return { error: `filePath must be inside ${PLANS_DIR3}: ${filePath}` };
|
|
@@ -85755,7 +85825,7 @@ Uses atomicWrite (.tmp.pid+rename) + upsertMetadataComment. Warns if file alread
|
|
|
85755
85825
|
}
|
|
85756
85826
|
const resolved = validation.resolved;
|
|
85757
85827
|
ensurePlanDir(directory);
|
|
85758
|
-
if (
|
|
85828
|
+
if (existsSync63(resolved)) {
|
|
85759
85829
|
return JSON.stringify({
|
|
85760
85830
|
error: "file_exists",
|
|
85761
85831
|
message: `Plan file already exists: ${resolved}. Use plan_read + plan_update to modify.`,
|
|
@@ -85786,7 +85856,7 @@ Uses atomicWrite (.tmp.pid+rename) + upsertMetadataComment. Warns if file alread
|
|
|
85786
85856
|
});
|
|
85787
85857
|
}
|
|
85788
85858
|
// src/tools/plan/plan-delete.ts
|
|
85789
|
-
import { existsSync as
|
|
85859
|
+
import { existsSync as existsSync64, unlinkSync as unlinkSync10 } from "fs";
|
|
85790
85860
|
function createPlanDeleteTool(ctx) {
|
|
85791
85861
|
return tool({
|
|
85792
85862
|
description: `Delete a plan file under .matrixx/plans/*.md. Scoped unlink inside PLANS_DIR, refuses outside paths.`,
|
|
@@ -85802,7 +85872,7 @@ function createPlanDeleteTool(ctx) {
|
|
|
85802
85872
|
return JSON.stringify({ error: "invalid_file_path", message: validation.error });
|
|
85803
85873
|
}
|
|
85804
85874
|
const resolved = validation.resolved;
|
|
85805
|
-
if (!
|
|
85875
|
+
if (!existsSync64(resolved)) {
|
|
85806
85876
|
return JSON.stringify({ error: "file_not_found", message: `File not found: ${resolved}` });
|
|
85807
85877
|
}
|
|
85808
85878
|
unlinkSync10(resolved);
|
|
@@ -85815,8 +85885,8 @@ function createPlanDeleteTool(ctx) {
|
|
|
85815
85885
|
});
|
|
85816
85886
|
}
|
|
85817
85887
|
// src/tools/plan/plan-list.ts
|
|
85818
|
-
import { existsSync as
|
|
85819
|
-
import { join as
|
|
85888
|
+
import { existsSync as existsSync65, readdirSync as readdirSync20, statSync as statSync15 } from "fs";
|
|
85889
|
+
import { join as join67 } from "path";
|
|
85820
85890
|
function createPlanListTool(ctx) {
|
|
85821
85891
|
return tool({
|
|
85822
85892
|
description: `List plan files under .matrixx/plans/*.md. Filters *.md with kebab-case, sorts by mtime.`,
|
|
@@ -85824,13 +85894,13 @@ function createPlanListTool(ctx) {
|
|
|
85824
85894
|
execute: async (_args, context) => {
|
|
85825
85895
|
try {
|
|
85826
85896
|
const directory = resolveDirectory(context?.directory, ctx?.directory);
|
|
85827
|
-
const plansDir =
|
|
85828
|
-
if (!
|
|
85897
|
+
const plansDir = join67(directory, PLANS_DIR3);
|
|
85898
|
+
if (!existsSync65(plansDir)) {
|
|
85829
85899
|
return JSON.stringify({ plans: [] });
|
|
85830
85900
|
}
|
|
85831
85901
|
const entries = readdirSync20(plansDir).filter((f) => f.endsWith(".md") && PLAN_FILENAME_KEBAB_REGEX.test(f));
|
|
85832
85902
|
const plans = entries.map((fileName) => {
|
|
85833
|
-
const filePath =
|
|
85903
|
+
const filePath = join67(plansDir, fileName);
|
|
85834
85904
|
try {
|
|
85835
85905
|
const stat2 = statSync15(filePath);
|
|
85836
85906
|
return {
|
|
@@ -85853,7 +85923,7 @@ function createPlanListTool(ctx) {
|
|
|
85853
85923
|
});
|
|
85854
85924
|
}
|
|
85855
85925
|
// src/tools/plan/plan-read.ts
|
|
85856
|
-
import { existsSync as
|
|
85926
|
+
import { existsSync as existsSync66, statSync as statSync16 } from "fs";
|
|
85857
85927
|
function createPlanReadTool(ctx) {
|
|
85858
85928
|
return tool({
|
|
85859
85929
|
description: `Read a plan file from .matrixx/plans/*.md with hashline-tagged output (1#AB|content) + lsp-style. Enforces scoping and ${MAX_PLAN_FILE_BYTES2} byte cap.`,
|
|
@@ -85869,7 +85939,7 @@ function createPlanReadTool(ctx) {
|
|
|
85869
85939
|
return JSON.stringify({ error: "invalid_file_path", message: validation.error });
|
|
85870
85940
|
}
|
|
85871
85941
|
const resolved = validation.resolved;
|
|
85872
|
-
if (!
|
|
85942
|
+
if (!existsSync66(resolved)) {
|
|
85873
85943
|
return JSON.stringify({ error: "file_not_found", message: `File not found: ${resolved}` });
|
|
85874
85944
|
}
|
|
85875
85945
|
try {
|
|
@@ -85901,7 +85971,7 @@ function createPlanReadTool(ctx) {
|
|
|
85901
85971
|
});
|
|
85902
85972
|
}
|
|
85903
85973
|
// src/tools/plan/plan-update.ts
|
|
85904
|
-
import { existsSync as
|
|
85974
|
+
import { existsSync as existsSync67 } from "fs";
|
|
85905
85975
|
|
|
85906
85976
|
// src/features/tool-metadata-store/store.ts
|
|
85907
85977
|
var pendingStore = new Map;
|
|
@@ -86964,7 +87034,7 @@ function createPlanUpdateTool(ctx) {
|
|
|
86964
87034
|
return JSON.stringify({ error: "invalid_file_path", message: validation.error });
|
|
86965
87035
|
}
|
|
86966
87036
|
const resolved = validation.resolved;
|
|
86967
|
-
if (!
|
|
87037
|
+
if (!existsSync67(resolved)) {
|
|
86968
87038
|
return JSON.stringify({ error: "file_not_found", message: `File not found: ${resolved}` });
|
|
86969
87039
|
}
|
|
86970
87040
|
const result = await executeHashlineEditTool({ filePath: resolved, edits }, context, ctx);
|
|
@@ -86977,9 +87047,9 @@ function createPlanUpdateTool(ctx) {
|
|
|
86977
87047
|
});
|
|
86978
87048
|
}
|
|
86979
87049
|
// src/tools/preset/tools.ts
|
|
86980
|
-
import { existsSync as
|
|
86981
|
-
import { homedir as
|
|
86982
|
-
import { dirname as dirname17, join as
|
|
87050
|
+
import { existsSync as existsSync68, mkdirSync as mkdirSync18, readFileSync as readFileSync40, writeFileSync as writeFileSync24 } from "fs";
|
|
87051
|
+
import { homedir as homedir10 } from "os";
|
|
87052
|
+
import { dirname as dirname17, join as join68 } from "path";
|
|
86983
87053
|
|
|
86984
87054
|
// src/features/preset-state/manager.ts
|
|
86985
87055
|
init_logger();
|
|
@@ -86998,14 +87068,14 @@ var ACTIONS = ["list", "show", "set"];
|
|
|
86998
87068
|
var SCOPES = ["global", "project"];
|
|
86999
87069
|
function resolveConfigPath(scope, directory) {
|
|
87000
87070
|
if (scope === "global") {
|
|
87001
|
-
return
|
|
87071
|
+
return join68(homedir10(), ".config", "opencode", "matrixx.jsonc");
|
|
87002
87072
|
}
|
|
87003
|
-
return
|
|
87073
|
+
return join68(directory ?? process.cwd(), ".opencode", "matrixx.jsonc");
|
|
87004
87074
|
}
|
|
87005
87075
|
function persistActivePreset(presetName, scope, directory) {
|
|
87006
87076
|
const configPath = resolveConfigPath(scope, directory);
|
|
87007
87077
|
let raw = {};
|
|
87008
|
-
if (
|
|
87078
|
+
if (existsSync68(configPath)) {
|
|
87009
87079
|
const content = readFileSync40(configPath, "utf-8");
|
|
87010
87080
|
const parsed = parseJsoncSafe(content);
|
|
87011
87081
|
if (parsed.errors.length > 0) {
|
|
@@ -87119,9 +87189,9 @@ function createPresetTool(options) {
|
|
|
87119
87189
|
return { preset };
|
|
87120
87190
|
}
|
|
87121
87191
|
// src/tools/session-manager/constants.ts
|
|
87122
|
-
import { join as
|
|
87123
|
-
var TODO_DIR =
|
|
87124
|
-
var TRANSCRIPT_DIR2 =
|
|
87192
|
+
import { join as join69 } from "path";
|
|
87193
|
+
var TODO_DIR = join69(getOpenCodeConfigDir({ binary: "opencode" }), "todos");
|
|
87194
|
+
var TRANSCRIPT_DIR2 = join69(getOpenCodeConfigDir({ binary: "opencode" }), "transcripts");
|
|
87125
87195
|
var SESSION_LIST_DESCRIPTION = `List all OpenCode sessions with optional filtering.
|
|
87126
87196
|
|
|
87127
87197
|
Returns a list of available session IDs with metadata including message count, date range, and agents used.
|
|
@@ -87193,9 +87263,9 @@ Agents Used: build, oracle, librarian
|
|
|
87193
87263
|
Has Todos: Yes (12 items, 8 completed)
|
|
87194
87264
|
Has Transcript: Yes (234 entries)`;
|
|
87195
87265
|
// src/tools/session-manager/storage.ts
|
|
87196
|
-
import { existsSync as
|
|
87266
|
+
import { existsSync as existsSync69 } from "fs";
|
|
87197
87267
|
import { readdir, readFile as readFile3 } from "fs/promises";
|
|
87198
|
-
import { join as
|
|
87268
|
+
import { join as join70 } from "path";
|
|
87199
87269
|
var sdkClient = null;
|
|
87200
87270
|
function setStorageClient(client2) {
|
|
87201
87271
|
sdkClient = client2;
|
|
@@ -87214,7 +87284,7 @@ async function getMainSessions(options) {
|
|
|
87214
87284
|
return [];
|
|
87215
87285
|
}
|
|
87216
87286
|
}
|
|
87217
|
-
if (!
|
|
87287
|
+
if (!existsSync69(SESSION_STORAGE))
|
|
87218
87288
|
return [];
|
|
87219
87289
|
const sessions = [];
|
|
87220
87290
|
try {
|
|
@@ -87222,13 +87292,13 @@ async function getMainSessions(options) {
|
|
|
87222
87292
|
for (const projectDir of projectDirs) {
|
|
87223
87293
|
if (!projectDir.isDirectory())
|
|
87224
87294
|
continue;
|
|
87225
|
-
const projectPath =
|
|
87295
|
+
const projectPath = join70(SESSION_STORAGE, projectDir.name);
|
|
87226
87296
|
const sessionFiles = await readdir(projectPath);
|
|
87227
87297
|
for (const file3 of sessionFiles) {
|
|
87228
87298
|
if (!file3.endsWith(".json"))
|
|
87229
87299
|
continue;
|
|
87230
87300
|
try {
|
|
87231
|
-
const content = await readFile3(
|
|
87301
|
+
const content = await readFile3(join70(projectPath, file3), "utf-8");
|
|
87232
87302
|
const meta3 = JSON.parse(content);
|
|
87233
87303
|
if (meta3.parentID)
|
|
87234
87304
|
continue;
|
|
@@ -87253,7 +87323,7 @@ async function getAllSessions() {
|
|
|
87253
87323
|
return [];
|
|
87254
87324
|
}
|
|
87255
87325
|
}
|
|
87256
|
-
if (!
|
|
87326
|
+
if (!existsSync69(MESSAGE_STORAGE))
|
|
87257
87327
|
return [];
|
|
87258
87328
|
const sessions = [];
|
|
87259
87329
|
async function scanDirectory(dir) {
|
|
@@ -87261,7 +87331,7 @@ async function getAllSessions() {
|
|
|
87261
87331
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
87262
87332
|
for (const entry of entries) {
|
|
87263
87333
|
if (entry.isDirectory()) {
|
|
87264
|
-
const sessionPath =
|
|
87334
|
+
const sessionPath = join70(dir, entry.name);
|
|
87265
87335
|
const files = await readdir(sessionPath);
|
|
87266
87336
|
if (files.some((f) => f.endsWith(".json"))) {
|
|
87267
87337
|
sessions.push(entry.name);
|
|
@@ -87322,7 +87392,7 @@ async function readSessionMessages(sessionID) {
|
|
|
87322
87392
|
}
|
|
87323
87393
|
}
|
|
87324
87394
|
const messageDir = getMessageDir(sessionID);
|
|
87325
|
-
if (!messageDir || !
|
|
87395
|
+
if (!messageDir || !existsSync69(messageDir))
|
|
87326
87396
|
return [];
|
|
87327
87397
|
const messages2 = [];
|
|
87328
87398
|
try {
|
|
@@ -87331,7 +87401,7 @@ async function readSessionMessages(sessionID) {
|
|
|
87331
87401
|
if (!file3.endsWith(".json"))
|
|
87332
87402
|
continue;
|
|
87333
87403
|
try {
|
|
87334
|
-
const content = await readFile3(
|
|
87404
|
+
const content = await readFile3(join70(messageDir, file3), "utf-8");
|
|
87335
87405
|
const meta3 = JSON.parse(content);
|
|
87336
87406
|
const parts = await readParts2(meta3.id);
|
|
87337
87407
|
messages2.push({
|
|
@@ -87355,8 +87425,8 @@ async function readSessionMessages(sessionID) {
|
|
|
87355
87425
|
});
|
|
87356
87426
|
}
|
|
87357
87427
|
async function readParts2(messageID) {
|
|
87358
|
-
const partDir =
|
|
87359
|
-
if (!
|
|
87428
|
+
const partDir = join70(PART_STORAGE, messageID);
|
|
87429
|
+
if (!existsSync69(partDir))
|
|
87360
87430
|
return [];
|
|
87361
87431
|
const parts = [];
|
|
87362
87432
|
try {
|
|
@@ -87365,7 +87435,7 @@ async function readParts2(messageID) {
|
|
|
87365
87435
|
if (!file3.endsWith(".json"))
|
|
87366
87436
|
continue;
|
|
87367
87437
|
try {
|
|
87368
|
-
const content = await readFile3(
|
|
87438
|
+
const content = await readFile3(join70(partDir, file3), "utf-8");
|
|
87369
87439
|
parts.push(JSON.parse(content));
|
|
87370
87440
|
} catch {}
|
|
87371
87441
|
}
|
|
@@ -87389,14 +87459,14 @@ async function readSessionTodos(sessionID) {
|
|
|
87389
87459
|
return [];
|
|
87390
87460
|
}
|
|
87391
87461
|
}
|
|
87392
|
-
if (!
|
|
87462
|
+
if (!existsSync69(TODO_DIR))
|
|
87393
87463
|
return [];
|
|
87394
87464
|
try {
|
|
87395
87465
|
const allFiles = await readdir(TODO_DIR);
|
|
87396
87466
|
const todoFiles = allFiles.filter((f) => f.includes(sessionID) && f.endsWith(".json"));
|
|
87397
87467
|
for (const file3 of todoFiles) {
|
|
87398
87468
|
try {
|
|
87399
|
-
const content = await readFile3(
|
|
87469
|
+
const content = await readFile3(join70(TODO_DIR, file3), "utf-8");
|
|
87400
87470
|
const data = JSON.parse(content);
|
|
87401
87471
|
if (Array.isArray(data)) {
|
|
87402
87472
|
return data.map((item) => ({
|
|
@@ -87414,10 +87484,10 @@ async function readSessionTodos(sessionID) {
|
|
|
87414
87484
|
return [];
|
|
87415
87485
|
}
|
|
87416
87486
|
async function readSessionTranscript(sessionID) {
|
|
87417
|
-
if (!
|
|
87487
|
+
if (!existsSync69(TRANSCRIPT_DIR2))
|
|
87418
87488
|
return 0;
|
|
87419
|
-
const transcriptFile =
|
|
87420
|
-
if (!
|
|
87489
|
+
const transcriptFile = join70(TRANSCRIPT_DIR2, `${sessionID}.jsonl`);
|
|
87490
|
+
if (!existsSync69(transcriptFile))
|
|
87421
87491
|
return 0;
|
|
87422
87492
|
try {
|
|
87423
87493
|
const content = await readFile3(transcriptFile, "utf-8");
|
|
@@ -87927,17 +87997,17 @@ function createSkillTool(options = {}) {
|
|
|
87927
87997
|
}
|
|
87928
87998
|
var skill = createSkillTool();
|
|
87929
87999
|
// src/tools/slashcommand/command-discovery.ts
|
|
87930
|
-
import { existsSync as
|
|
87931
|
-
import { basename as basename8, join as
|
|
88000
|
+
import { existsSync as existsSync70, readdirSync as readdirSync21, readFileSync as readFileSync41 } from "fs";
|
|
88001
|
+
import { basename as basename8, join as join71 } from "path";
|
|
87932
88002
|
function discoverCommandsFromDir2(commandsDir, scope) {
|
|
87933
|
-
if (!
|
|
88003
|
+
if (!existsSync70(commandsDir))
|
|
87934
88004
|
return [];
|
|
87935
88005
|
const entries = readdirSync21(commandsDir, { withFileTypes: true });
|
|
87936
88006
|
const commands2 = [];
|
|
87937
88007
|
for (const entry of entries) {
|
|
87938
88008
|
if (!isMarkdownFile(entry))
|
|
87939
88009
|
continue;
|
|
87940
|
-
const commandPath =
|
|
88010
|
+
const commandPath = join71(commandsDir, entry.name);
|
|
87941
88011
|
const commandName = basename8(entry.name, ".md");
|
|
87942
88012
|
try {
|
|
87943
88013
|
const content = readFileSync41(commandPath, "utf-8");
|
|
@@ -87964,8 +88034,8 @@ function discoverCommandsFromDir2(commandsDir, scope) {
|
|
|
87964
88034
|
}
|
|
87965
88035
|
function discoverCommandsSync(directory) {
|
|
87966
88036
|
const configDir = getOpenCodeConfigDir({ binary: "opencode" });
|
|
87967
|
-
const opencodeGlobalDir =
|
|
87968
|
-
const opencodeProjectDir =
|
|
88037
|
+
const opencodeGlobalDir = join71(configDir, "command");
|
|
88038
|
+
const opencodeProjectDir = join71(directory ?? process.cwd(), ".opencode", "command");
|
|
87969
88039
|
const opencodeGlobalCommands = discoverCommandsFromDir2(opencodeGlobalDir, "opencode");
|
|
87970
88040
|
const opencodeProjectCommands = discoverCommandsFromDir2(opencodeProjectDir, "opencode-project");
|
|
87971
88041
|
const builtinCommandsMap = loadBuiltinCommands();
|
|
@@ -88199,6 +88269,14 @@ var slashcommand = createSlashcommandTool();
|
|
|
88199
88269
|
// src/tools/background-task/constants.ts
|
|
88200
88270
|
var BACKGROUND_OUTPUT_DESCRIPTION = `Get output from background task. Use full_session=true to fetch session messages with filters. System notifies on completion, so block=true rarely needed.`;
|
|
88201
88271
|
var BACKGROUND_CANCEL_DESCRIPTION = `Cancel running background task(s). Use all=true to cancel ALL before final answer.`;
|
|
88272
|
+
var BACKGROUND_REVIVE_DESCRIPTION = `Revive a retained terminal background task with a NEW instruction.
|
|
88273
|
+
|
|
88274
|
+
Call with no arguments (or list=true) to DISCOVER revivable tasks.
|
|
88275
|
+
|
|
88276
|
+
Revivable: cancelled, stopped, interrupt, error, completed \u2014 provided the task still has a session ID.
|
|
88277
|
+
Not revivable: pending/running (still active \u2014 use background_output), statusUncertain (unknown liveness \u2014 pass force=true to acknowledge), and tasks that never produced a session (e.g. queue-saturated).
|
|
88278
|
+
|
|
88279
|
+
Handles are retained for 30 minutes, so revive works after a task was pruned or the plugin restarted.`;
|
|
88202
88280
|
// src/tools/background-task/create-background-cancel.ts
|
|
88203
88281
|
function createBackgroundCancel(manager, _client) {
|
|
88204
88282
|
return tool({
|
|
@@ -88367,6 +88445,14 @@ ${truncated}
|
|
|
88367
88445
|
statusNote = `
|
|
88368
88446
|
|
|
88369
88447
|
> **Interrupted**: The task was interrupted by a prompt error. The session may contain partial results.`;
|
|
88448
|
+
} else if (task.status === "stopped") {
|
|
88449
|
+
statusNote = `
|
|
88450
|
+
|
|
88451
|
+
> **Stopped**: the session ended without a terminal result \u2014 partial output may exist.`;
|
|
88452
|
+
} else if (task.status === "statusUncertain") {
|
|
88453
|
+
statusNote = `
|
|
88454
|
+
|
|
88455
|
+
> **Uncertain**: liveness could not be determined after restart; re-check with background_output.`;
|
|
88370
88456
|
}
|
|
88371
88457
|
const durationLabel = task.status === "pending" ? "Queued for" : "Duration";
|
|
88372
88458
|
return `# Task Status
|
|
@@ -88525,6 +88611,18 @@ function getTimeString(value) {
|
|
|
88525
88611
|
return typeof value === "string" ? value : "";
|
|
88526
88612
|
}
|
|
88527
88613
|
async function formatTaskResult(task, client2) {
|
|
88614
|
+
if (task.compactedResult) {
|
|
88615
|
+
const duration6 = formatDuration(task.startedAt ?? new Date, task.completedAt);
|
|
88616
|
+
return `Task Result
|
|
88617
|
+
|
|
88618
|
+
Task ID: ${task.id}
|
|
88619
|
+
Description: ${task.description}
|
|
88620
|
+
Duration: ${duration6}
|
|
88621
|
+
Session ID: ${task.sessionID}
|
|
88622
|
+
|
|
88623
|
+
---
|
|
88624
|
+
${task.compactedResult}`;
|
|
88625
|
+
}
|
|
88528
88626
|
if (!task.sessionID) {
|
|
88529
88627
|
return `Error: Task has no sessionID`;
|
|
88530
88628
|
}
|
|
@@ -88729,7 +88827,7 @@ function createBackgroundOutput(manager, client2) {
|
|
|
88729
88827
|
if (resolvedTask.status === "completed") {
|
|
88730
88828
|
return await formatTaskResult(resolvedTask, client2);
|
|
88731
88829
|
}
|
|
88732
|
-
if (resolvedTask.status === "error" || resolvedTask.status === "cancelled" || resolvedTask.status === "interrupt") {
|
|
88830
|
+
if (resolvedTask.status === "error" || resolvedTask.status === "cancelled" || resolvedTask.status === "interrupt" || resolvedTask.status === "stopped" || resolvedTask.status === "statusUncertain") {
|
|
88733
88831
|
return formatTaskStatus(resolvedTask);
|
|
88734
88832
|
}
|
|
88735
88833
|
const statusOutput = formatTaskStatus(resolvedTask);
|
|
@@ -88740,6 +88838,262 @@ function createBackgroundOutput(manager, client2) {
|
|
|
88740
88838
|
}
|
|
88741
88839
|
});
|
|
88742
88840
|
}
|
|
88841
|
+
// src/tools/background-task/create-background-revive.ts
|
|
88842
|
+
import { existsSync as existsSync72 } from "fs";
|
|
88843
|
+
|
|
88844
|
+
// src/features/background-agent/handle-index.ts
|
|
88845
|
+
import { existsSync as existsSync71, mkdirSync as mkdirSync19, readdirSync as readdirSync22, readFileSync as readFileSync42, renameSync as renameSync5, unlinkSync as unlinkSync11, writeFileSync as writeFileSync25 } from "fs";
|
|
88846
|
+
import { dirname as dirname19, join as join72 } from "path";
|
|
88847
|
+
|
|
88848
|
+
// src/features/background-agent/constants.ts
|
|
88849
|
+
var TASK_TTL_MS = 30 * 60 * 1000;
|
|
88850
|
+
var MIN_STABILITY_TIME_MS = 10 * 1000;
|
|
88851
|
+
var DEFAULT_STALE_TIMEOUT_MS = 180000;
|
|
88852
|
+
var DEFAULT_MESSAGE_STALENESS_TIMEOUT_MS = 600000;
|
|
88853
|
+
var DEFAULT_MAX_TOOL_CALLS = 4000;
|
|
88854
|
+
var DEFAULT_CIRCUIT_BREAKER_CONSECUTIVE_THRESHOLD = 20;
|
|
88855
|
+
var DEFAULT_CIRCUIT_BREAKER_ENABLED = true;
|
|
88856
|
+
var MIN_RUNTIME_BEFORE_STALE_MS = 30000;
|
|
88857
|
+
var MIN_IDLE_TIME_MS = 5000;
|
|
88858
|
+
var POLLING_INTERVAL_MS = 3000;
|
|
88859
|
+
var PRUNE_THROTTLE_MS = 30000;
|
|
88860
|
+
var TASK_CLEANUP_DELAY_MS = 10 * 60 * 1000;
|
|
88861
|
+
var DEFAULT_ADMISSION_TIMEOUT_MS = 0;
|
|
88862
|
+
var DEFAULT_WALL_CLOCK_TIMEOUT_MS = 0;
|
|
88863
|
+
var DEFAULT_WALL_CLOCK_ABORT_GRACE_MS = 5000;
|
|
88864
|
+
var DEFAULT_NESTED_ADMISSION_ENABLED = true;
|
|
88865
|
+
var DEFAULT_NESTED_ADMISSION_MODE = "bypass";
|
|
88866
|
+
var DEFAULT_NESTED_MAX_DEPTH = 2;
|
|
88867
|
+
var DEFAULT_WAKE_INTERVAL_MS = 300000;
|
|
88868
|
+
var MIN_WAKE_INTERVAL_MS = 60000;
|
|
88869
|
+
var RECONCILE_CONFIRMATION_GRACE_MS = 5000;
|
|
88870
|
+
|
|
88871
|
+
// src/features/background-agent/handle-index.ts
|
|
88872
|
+
var BG_HANDLE_DIR_NAME = "bg-handles";
|
|
88873
|
+
var BG_HANDLE_FILE_PREFIX = "bg_";
|
|
88874
|
+
var BgHandleSchema = exports_external.object({
|
|
88875
|
+
taskId: exports_external.string(),
|
|
88876
|
+
parentSessionID: exports_external.string(),
|
|
88877
|
+
parentMessageID: exports_external.string(),
|
|
88878
|
+
description: exports_external.string(),
|
|
88879
|
+
agent: exports_external.string(),
|
|
88880
|
+
status: exports_external.enum(["pending", "running", "completed", "error", "cancelled", "interrupt", "stopped", "statusUncertain"]),
|
|
88881
|
+
sessionID: exports_external.string().optional(),
|
|
88882
|
+
terminalReason: exports_external.enum(["queue-saturated", "no-output", "uncertain", "aborted", "stale", "nested-depth-exceeded", "wall-clock-timeout"]).optional(),
|
|
88883
|
+
model: exports_external.object({
|
|
88884
|
+
providerID: exports_external.string(),
|
|
88885
|
+
modelID: exports_external.string(),
|
|
88886
|
+
variant: exports_external.string().optional(),
|
|
88887
|
+
temperature: exports_external.number().optional()
|
|
88888
|
+
}).optional(),
|
|
88889
|
+
category: exports_external.string().optional(),
|
|
88890
|
+
concurrencyGroup: exports_external.string().optional(),
|
|
88891
|
+
queuedAt: exports_external.number().optional(),
|
|
88892
|
+
startedAt: exports_external.number().optional(),
|
|
88893
|
+
completedAt: exports_external.number().optional()
|
|
88894
|
+
}).strict();
|
|
88895
|
+
function getBgHandleDir(directory) {
|
|
88896
|
+
return join72(directory, ".matrixx", BG_HANDLE_DIR_NAME);
|
|
88897
|
+
}
|
|
88898
|
+
function getHandlePath(directory, taskId) {
|
|
88899
|
+
return join72(getBgHandleDir(directory), `${taskId}.json`);
|
|
88900
|
+
}
|
|
88901
|
+
function ensureDir3(dirPath) {
|
|
88902
|
+
if (!existsSync71(dirPath)) {
|
|
88903
|
+
mkdirSync19(dirPath, { recursive: true });
|
|
88904
|
+
}
|
|
88905
|
+
}
|
|
88906
|
+
function toHandle(task) {
|
|
88907
|
+
return {
|
|
88908
|
+
taskId: task.id,
|
|
88909
|
+
parentSessionID: task.parentSessionID,
|
|
88910
|
+
parentMessageID: task.parentMessageID,
|
|
88911
|
+
description: task.description,
|
|
88912
|
+
agent: task.agent,
|
|
88913
|
+
status: task.status,
|
|
88914
|
+
sessionID: task.sessionID,
|
|
88915
|
+
terminalReason: task.terminalReason,
|
|
88916
|
+
model: task.model,
|
|
88917
|
+
category: task.category,
|
|
88918
|
+
concurrencyGroup: task.concurrencyGroup,
|
|
88919
|
+
queuedAt: task.queuedAt?.getTime(),
|
|
88920
|
+
startedAt: task.startedAt?.getTime(),
|
|
88921
|
+
completedAt: task.completedAt?.getTime()
|
|
88922
|
+
};
|
|
88923
|
+
}
|
|
88924
|
+
function writeHandle(directory, task) {
|
|
88925
|
+
const filePath = getHandlePath(directory, task.id);
|
|
88926
|
+
ensureDir3(dirname19(filePath));
|
|
88927
|
+
const tempPath = `${filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
88928
|
+
try {
|
|
88929
|
+
writeFileSync25(tempPath, JSON.stringify(toHandle(task), null, 2), "utf-8");
|
|
88930
|
+
renameSync5(tempPath, filePath);
|
|
88931
|
+
} catch (error95) {
|
|
88932
|
+
try {
|
|
88933
|
+
if (existsSync71(tempPath)) {
|
|
88934
|
+
unlinkSync11(tempPath);
|
|
88935
|
+
}
|
|
88936
|
+
} catch {}
|
|
88937
|
+
throw error95;
|
|
88938
|
+
}
|
|
88939
|
+
}
|
|
88940
|
+
function readHandleFile(filePath) {
|
|
88941
|
+
try {
|
|
88942
|
+
if (!existsSync71(filePath)) {
|
|
88943
|
+
return null;
|
|
88944
|
+
}
|
|
88945
|
+
const parsed = JSON.parse(readFileSync42(filePath, "utf-8"));
|
|
88946
|
+
const result = BgHandleSchema.safeParse(parsed);
|
|
88947
|
+
return result.success ? result.data : null;
|
|
88948
|
+
} catch {
|
|
88949
|
+
return null;
|
|
88950
|
+
}
|
|
88951
|
+
}
|
|
88952
|
+
function readHandles(directory) {
|
|
88953
|
+
const dir = getBgHandleDir(directory);
|
|
88954
|
+
if (!existsSync71(dir)) {
|
|
88955
|
+
return [];
|
|
88956
|
+
}
|
|
88957
|
+
const handles = [];
|
|
88958
|
+
for (const name of readdirSync22(dir)) {
|
|
88959
|
+
if (!name.startsWith(BG_HANDLE_FILE_PREFIX) || !name.endsWith(".json")) {
|
|
88960
|
+
continue;
|
|
88961
|
+
}
|
|
88962
|
+
const handle = readHandleFile(join72(dir, name));
|
|
88963
|
+
if (handle) {
|
|
88964
|
+
handles.push(handle);
|
|
88965
|
+
}
|
|
88966
|
+
}
|
|
88967
|
+
return handles;
|
|
88968
|
+
}
|
|
88969
|
+
function sweepStaleHandles(directory, ttlMs = TASK_TTL_MS, now = Date.now()) {
|
|
88970
|
+
const dir = getBgHandleDir(directory);
|
|
88971
|
+
if (!existsSync71(dir)) {
|
|
88972
|
+
return 0;
|
|
88973
|
+
}
|
|
88974
|
+
let removed = 0;
|
|
88975
|
+
for (const name of readdirSync22(dir)) {
|
|
88976
|
+
if (!name.startsWith(BG_HANDLE_FILE_PREFIX) || !name.endsWith(".json")) {
|
|
88977
|
+
continue;
|
|
88978
|
+
}
|
|
88979
|
+
const filePath = join72(dir, name);
|
|
88980
|
+
const handle = readHandleFile(filePath);
|
|
88981
|
+
const timestamp = handle?.completedAt ?? handle?.startedAt ?? handle?.queuedAt;
|
|
88982
|
+
if (handle && timestamp !== undefined && now - timestamp <= ttlMs) {
|
|
88983
|
+
continue;
|
|
88984
|
+
}
|
|
88985
|
+
try {
|
|
88986
|
+
unlinkSync11(filePath);
|
|
88987
|
+
removed++;
|
|
88988
|
+
} catch {}
|
|
88989
|
+
}
|
|
88990
|
+
if (removed > 0) {
|
|
88991
|
+
log(`[bg-handles] Swept ${removed} stale handle file(s) from ${dir}`);
|
|
88992
|
+
}
|
|
88993
|
+
return removed;
|
|
88994
|
+
}
|
|
88995
|
+
|
|
88996
|
+
// src/shared/revive-outcome.ts
|
|
88997
|
+
function formatReviveOutcome(taskId, sessionID, status) {
|
|
88998
|
+
const metadata = JSON.stringify({
|
|
88999
|
+
task_id: taskId,
|
|
89000
|
+
session_id: sessionID,
|
|
89001
|
+
status,
|
|
89002
|
+
reason: "revived"
|
|
89003
|
+
});
|
|
89004
|
+
return `Background task revived.
|
|
89005
|
+
|
|
89006
|
+
Task ID: ${taskId}
|
|
89007
|
+
Session ID: ${sessionID}
|
|
89008
|
+
Status: ${status}
|
|
89009
|
+
|
|
89010
|
+
<task_metadata>${metadata}</task_metadata>`;
|
|
89011
|
+
}
|
|
89012
|
+
function formatUnrevivable(taskId, reason, detail) {
|
|
89013
|
+
const metadata = JSON.stringify({
|
|
89014
|
+
task_id: taskId,
|
|
89015
|
+
status: "unrevived",
|
|
89016
|
+
reason
|
|
89017
|
+
});
|
|
89018
|
+
const detailLine = detail === undefined ? "" : `Detail: ${detail}
|
|
89019
|
+
`;
|
|
89020
|
+
return `Background task NOT revived.
|
|
89021
|
+
|
|
89022
|
+
Task ID: ${taskId}
|
|
89023
|
+
Reason: ${reason}
|
|
89024
|
+
${detailLine}
|
|
89025
|
+
<task_metadata>${metadata}</task_metadata>`;
|
|
89026
|
+
}
|
|
89027
|
+
|
|
89028
|
+
// src/tools/background-task/create-background-revive.ts
|
|
89029
|
+
function formatListing(entries) {
|
|
89030
|
+
if (entries.length === 0) {
|
|
89031
|
+
return `No revivable background tasks found.
|
|
89032
|
+
|
|
89033
|
+
Handles are retained for 30 minutes after a task reaches a terminal state.`;
|
|
89034
|
+
}
|
|
89035
|
+
const rows = entries.map((entry) => `| \`${entry.taskId}\` | ${entry.description} | ${entry.agent} | ${entry.status} | ${entry.terminalReason ?? "-"} | \`${entry.sessionID}\` |`).join(`
|
|
89036
|
+
`);
|
|
89037
|
+
return `Revivable background tasks (${entries.length}):
|
|
89038
|
+
|
|
89039
|
+
| Task ID | Description | Agent | Status | Reason | Session ID |
|
|
89040
|
+
|---------|-------------|-------|--------|--------|------------|
|
|
89041
|
+
${rows}
|
|
89042
|
+
|
|
89043
|
+
Revive one with background_revive(taskId="<id>", prompt="<new instruction>").`;
|
|
89044
|
+
}
|
|
89045
|
+
function resolveFailure(identifier, message, directory) {
|
|
89046
|
+
if (message.includes("not found for revive")) {
|
|
89047
|
+
const handleOnDisk = existsSync72(getHandlePath(directory, identifier));
|
|
89048
|
+
return { reason: handleOnDisk ? "unknown-task" : "expired", detail: message };
|
|
89049
|
+
}
|
|
89050
|
+
if (message.includes("still active"))
|
|
89051
|
+
return { reason: "active", detail: message };
|
|
89052
|
+
if (message.includes("unknown liveness"))
|
|
89053
|
+
return { reason: "uncertain", detail: message };
|
|
89054
|
+
if (message.includes("no session to revive") || message.includes("has no sessionID")) {
|
|
89055
|
+
return { reason: "no-session", detail: message };
|
|
89056
|
+
}
|
|
89057
|
+
return { reason: "unknown-task", detail: message };
|
|
89058
|
+
}
|
|
89059
|
+
function createBackgroundRevive(manager, directory) {
|
|
89060
|
+
return tool({
|
|
89061
|
+
description: BACKGROUND_REVIVE_DESCRIPTION,
|
|
89062
|
+
args: {
|
|
89063
|
+
taskId: tool.schema.string().optional().describe("Task ID to revive (provide this or session_id)"),
|
|
89064
|
+
session_id: tool.schema.string().optional().describe("Session ID of the task to revive (alternative to taskId)"),
|
|
89065
|
+
prompt: tool.schema.string().optional().describe("NEW instruction for the revived session (required unless list=true)"),
|
|
89066
|
+
list: tool.schema.boolean().optional().describe("List revivable tasks instead of reviving one (default: false)"),
|
|
89067
|
+
force: tool.schema.boolean().optional().describe("Acknowledge unknown liveness for statusUncertain tasks (default: false)"),
|
|
89068
|
+
parentSessionID: tool.schema.string().optional().describe("Restrict discovery to this parent session")
|
|
89069
|
+
},
|
|
89070
|
+
async execute(args, toolContext) {
|
|
89071
|
+
const isListing = args.list === true || args.taskId === undefined && args.session_id === undefined;
|
|
89072
|
+
if (isListing) {
|
|
89073
|
+
return formatListing(manager.listRevivable(args.parentSessionID ?? toolContext.sessionID));
|
|
89074
|
+
}
|
|
89075
|
+
const identifier = args.taskId ?? args.session_id;
|
|
89076
|
+
if (args.prompt === undefined || args.prompt.trim().length === 0) {
|
|
89077
|
+
return formatUnrevivable(identifier, "unknown-task", "a prompt is required to revive a task");
|
|
89078
|
+
}
|
|
89079
|
+
try {
|
|
89080
|
+
const task = await manager.revive({
|
|
89081
|
+
taskId: args.taskId,
|
|
89082
|
+
sessionId: args.session_id,
|
|
89083
|
+
prompt: args.prompt,
|
|
89084
|
+
parentSessionID: toolContext.sessionID,
|
|
89085
|
+
parentMessageID: toolContext.messageID,
|
|
89086
|
+
force: args.force
|
|
89087
|
+
});
|
|
89088
|
+
return formatReviveOutcome(task.id, task.sessionID ?? "", task.status);
|
|
89089
|
+
} catch (error95) {
|
|
89090
|
+
const message = error95 instanceof Error ? error95.message : String(error95);
|
|
89091
|
+
const { reason, detail } = resolveFailure(identifier, message, directory);
|
|
89092
|
+
return formatUnrevivable(identifier, reason, detail);
|
|
89093
|
+
}
|
|
89094
|
+
}
|
|
89095
|
+
});
|
|
89096
|
+
}
|
|
88743
89097
|
// src/tools/assembly/constants.ts
|
|
88744
89098
|
var DEFAULT_VOTERS = 3;
|
|
88745
89099
|
var DEFAULT_ROUNDS = 2;
|
|
@@ -89105,9 +89459,9 @@ function createAssemblyTool(options) {
|
|
|
89105
89459
|
});
|
|
89106
89460
|
}
|
|
89107
89461
|
// src/tools/dcp-switch-profile/tools.ts
|
|
89108
|
-
import { existsSync as
|
|
89109
|
-
import { homedir as
|
|
89110
|
-
import { join as
|
|
89462
|
+
import { existsSync as existsSync73, writeFileSync as writeFileSync26 } from "fs";
|
|
89463
|
+
import { homedir as homedir11 } from "os";
|
|
89464
|
+
import { join as join73 } from "path";
|
|
89111
89465
|
|
|
89112
89466
|
// src/config/schema/dcp.ts
|
|
89113
89467
|
var DcpCompressOverrideSchema = exports_external.object({
|
|
@@ -89192,12 +89546,37 @@ var BUILTIN_DCP_PROFILES = {
|
|
|
89192
89546
|
turnProtection: { enabled: true, turns: 5 },
|
|
89193
89547
|
experimental: { allowSubAgents: true },
|
|
89194
89548
|
strategies: { purgeErrors: { turns: 4 } }
|
|
89549
|
+
},
|
|
89550
|
+
brutal: {
|
|
89551
|
+
pruneNotification: "off",
|
|
89552
|
+
compress: {
|
|
89553
|
+
maxContextLimit: "15%",
|
|
89554
|
+
minContextLimit: "10%",
|
|
89555
|
+
nudgeFrequency: 1,
|
|
89556
|
+
nudgeForce: "strong",
|
|
89557
|
+
iterationNudgeThreshold: 1
|
|
89558
|
+
},
|
|
89559
|
+
turnProtection: { enabled: false },
|
|
89560
|
+
experimental: { allowSubAgents: false },
|
|
89561
|
+
strategies: { purgeErrors: { turns: 1 } }
|
|
89195
89562
|
}
|
|
89196
89563
|
};
|
|
89564
|
+
var DcpHandoffCompressionSchema = exports_external.object({
|
|
89565
|
+
enabled: exports_external.boolean().default(true),
|
|
89566
|
+
maxMessages: exports_external.number().int().min(1).default(6),
|
|
89567
|
+
keepFirst: exports_external.number().int().min(0).default(2),
|
|
89568
|
+
keepLast: exports_external.number().int().min(0).default(3)
|
|
89569
|
+
});
|
|
89197
89570
|
var DcpConfigSchema = exports_external.object({
|
|
89198
89571
|
enabled: exports_external.boolean().default(true),
|
|
89199
89572
|
profiles: exports_external.record(exports_external.string(), DcpProfileDefinitionSchema).default(BUILTIN_DCP_PROFILES),
|
|
89200
89573
|
default_profile: exports_external.string().optional(),
|
|
89574
|
+
handoffCompression: DcpHandoffCompressionSchema.default({
|
|
89575
|
+
enabled: true,
|
|
89576
|
+
maxMessages: 6,
|
|
89577
|
+
keepFirst: 2,
|
|
89578
|
+
keepLast: 3
|
|
89579
|
+
}),
|
|
89201
89580
|
base: exports_external.object({
|
|
89202
89581
|
autoUpdate: exports_external.boolean().default(false),
|
|
89203
89582
|
debug: exports_external.boolean().default(false),
|
|
@@ -89254,7 +89633,8 @@ var DcpConfigSchema = exports_external.object({
|
|
|
89254
89633
|
enabled: false,
|
|
89255
89634
|
automaticStrategies: true
|
|
89256
89635
|
}),
|
|
89257
|
-
protectedFilePatterns: exports_external.array(exports_external.string()).default([])
|
|
89636
|
+
protectedFilePatterns: exports_external.array(exports_external.string()).default([]),
|
|
89637
|
+
experimental: DcpExperimentalSchema.default({ allowSubAgents: true })
|
|
89258
89638
|
}).default({
|
|
89259
89639
|
autoUpdate: false,
|
|
89260
89640
|
debug: false,
|
|
@@ -89275,25 +89655,54 @@ var DcpConfigSchema = exports_external.object({
|
|
|
89275
89655
|
},
|
|
89276
89656
|
commands: { enabled: true, protectedTools: [] },
|
|
89277
89657
|
manualMode: { enabled: false, automaticStrategies: true },
|
|
89278
|
-
protectedFilePatterns: []
|
|
89658
|
+
protectedFilePatterns: [],
|
|
89659
|
+
experimental: { allowSubAgents: true }
|
|
89279
89660
|
})
|
|
89280
89661
|
});
|
|
89281
89662
|
|
|
89282
89663
|
// src/tools/dcp-switch-profile/tools.ts
|
|
89283
|
-
var DCP_PLUGIN_DIR =
|
|
89284
|
-
var DCP_SYMLINK =
|
|
89285
|
-
var VALID_PROFILES = ["economy", "balanced", "performance", "ultimate"];
|
|
89664
|
+
var DCP_PLUGIN_DIR = join73(homedir11(), ".config", "opencode", "node_modules", "@tarquinen", "opencode-dcp");
|
|
89665
|
+
var DCP_SYMLINK = join73(homedir11(), ".config", "opencode", "dcp.jsonc");
|
|
89666
|
+
var VALID_PROFILES = ["economy", "balanced", "performance", "ultimate", "brutal"];
|
|
89286
89667
|
function checkDcpInstalled() {
|
|
89287
|
-
if (!
|
|
89668
|
+
if (!existsSync73(DCP_PLUGIN_DIR)) {
|
|
89288
89669
|
return `DCP is not installed at ${DCP_PLUGIN_DIR}. Install it with: npm install --prefix ~/.config/opencode @tarquinen/opencode-dcp`;
|
|
89289
89670
|
}
|
|
89290
89671
|
return null;
|
|
89291
89672
|
}
|
|
89673
|
+
function deepMergeProfile(builtin, override) {
|
|
89674
|
+
const result = { ...builtin };
|
|
89675
|
+
if (override.pruneNotification !== undefined) {
|
|
89676
|
+
result.pruneNotification = override.pruneNotification;
|
|
89677
|
+
}
|
|
89678
|
+
const subObjects = ["compress", "turnProtection", "experimental"];
|
|
89679
|
+
for (const key of subObjects) {
|
|
89680
|
+
const builtinObj = builtin[key];
|
|
89681
|
+
const overrideObj = override[key];
|
|
89682
|
+
if (builtinObj || overrideObj) {
|
|
89683
|
+
result[key] = { ...builtinObj ?? {}, ...overrideObj ?? {} };
|
|
89684
|
+
}
|
|
89685
|
+
}
|
|
89686
|
+
const builtinStrategies = builtin.strategies;
|
|
89687
|
+
const overrideStrategies = override.strategies;
|
|
89688
|
+
if (builtinStrategies || overrideStrategies) {
|
|
89689
|
+
const merged = { ...builtinStrategies ?? {}, ...overrideStrategies ?? {} };
|
|
89690
|
+
const builtinPurge = builtinStrategies?.purgeErrors;
|
|
89691
|
+
const overridePurge = overrideStrategies?.purgeErrors;
|
|
89692
|
+
if (builtinPurge || overridePurge) {
|
|
89693
|
+
merged.purgeErrors = { ...builtinPurge ?? {}, ...overridePurge ?? {} };
|
|
89694
|
+
}
|
|
89695
|
+
result.strategies = merged;
|
|
89696
|
+
}
|
|
89697
|
+
return result;
|
|
89698
|
+
}
|
|
89292
89699
|
function buildInlineConfig(profile, options) {
|
|
89293
89700
|
const dcpConfig = options?.pluginConfig?.dcp;
|
|
89294
89701
|
const base = dcpConfig?.base ? dcpConfig.base : {};
|
|
89295
89702
|
const validProfiles = BUILTIN_DCP_PROFILES;
|
|
89296
|
-
const
|
|
89703
|
+
const builtinProfile = validProfiles[profile] ?? {};
|
|
89704
|
+
const userOverride = dcpConfig?.profiles?.[profile];
|
|
89705
|
+
const profileConfig = userOverride ? deepMergeProfile(builtinProfile, userOverride) : builtinProfile;
|
|
89297
89706
|
const compressBase = base.compress ?? {};
|
|
89298
89707
|
const strategiesBase = base.strategies ?? {};
|
|
89299
89708
|
const commandsCfg = base.commands ?? {};
|
|
@@ -89303,6 +89712,7 @@ function buildInlineConfig(profile, options) {
|
|
|
89303
89712
|
const profileExp = profileConfig.experimental ?? {};
|
|
89304
89713
|
const profilePurge = profileConfig.strategies?.purgeErrors ?? {};
|
|
89305
89714
|
const basePurge = strategiesBase.purgeErrors ?? {};
|
|
89715
|
+
const baseExp = base.experimental ?? {};
|
|
89306
89716
|
return {
|
|
89307
89717
|
$schema: "https://raw.githubusercontent.com/Opencode-DCP/opencode-dynamic-context-pruning/v3.1.14/dcp.schema.json",
|
|
89308
89718
|
enabled: true,
|
|
@@ -89329,7 +89739,7 @@ function buildInlineConfig(profile, options) {
|
|
|
89329
89739
|
turns: profileTurn.turns ?? 2
|
|
89330
89740
|
},
|
|
89331
89741
|
experimental: {
|
|
89332
|
-
allowSubAgents: profileExp.allowSubAgents ?? true,
|
|
89742
|
+
allowSubAgents: profileExp.allowSubAgents ?? baseExp.allowSubAgents ?? true,
|
|
89333
89743
|
customPrompts: false
|
|
89334
89744
|
},
|
|
89335
89745
|
protectedFilePatterns: base.protectedFilePatterns ?? [],
|
|
@@ -89355,14 +89765,16 @@ function buildInlineConfig(profile, options) {
|
|
|
89355
89765
|
};
|
|
89356
89766
|
}
|
|
89357
89767
|
function switchProfile(profile, options) {
|
|
89358
|
-
|
|
89359
|
-
|
|
89768
|
+
const customProfiles = Object.keys(options?.pluginConfig?.dcp?.profiles ?? {});
|
|
89769
|
+
const allValid = [...VALID_PROFILES, ...customProfiles];
|
|
89770
|
+
if (!allValid.includes(profile)) {
|
|
89771
|
+
return `Error: Invalid profile "${profile}". Valid profiles: ${allValid.join(", ")}`;
|
|
89360
89772
|
}
|
|
89361
89773
|
const dcpError = checkDcpInstalled();
|
|
89362
89774
|
if (dcpError)
|
|
89363
89775
|
return dcpError;
|
|
89364
89776
|
const inlineConfig = buildInlineConfig(profile, options);
|
|
89365
|
-
|
|
89777
|
+
writeFileSync26(DCP_SYMLINK, `${JSON.stringify(inlineConfig, null, 2)}
|
|
89366
89778
|
`);
|
|
89367
89779
|
return `\u2713 Switched to DCP profile: ${profile}
|
|
89368
89780
|
|
|
@@ -89370,9 +89782,9 @@ Restart OpenCode session for changes to take effect.`;
|
|
|
89370
89782
|
}
|
|
89371
89783
|
function createDcpSwitchProfileTool(options) {
|
|
89372
89784
|
const dcp_switch_profile = tool({
|
|
89373
|
-
description: "Switch the active DCP (Dynamic Context Pruning) profile tier. " + "Reads profile parameters from the Matrixx plugin configuration and writes a full inline DCP config " + "to ~/.config/opencode/dcp.jsonc. No external files needed. " + "
|
|
89785
|
+
description: "Switch the active DCP (Dynamic Context Pruning) profile tier. " + "Reads profile parameters from the Matrixx plugin configuration and writes a full inline DCP config " + "to ~/.config/opencode/dcp.jsonc. No external files needed. " + "Built-in profiles: economy, balanced, performance, ultimate. " + "Custom profiles (e.g. brutal) are read from dcp.profiles in opencode.jsonc.",
|
|
89374
89786
|
args: {
|
|
89375
|
-
profile: tool.schema.
|
|
89787
|
+
profile: tool.schema.string().describe("Target DCP profile tier: economy (most aggressive), balanced, performance, ultimate (least aggressive), or a custom profile name from config (e.g., brutal)")
|
|
89376
89788
|
},
|
|
89377
89789
|
async execute(args) {
|
|
89378
89790
|
const profile = args.profile;
|
|
@@ -89393,6 +89805,39 @@ var CALL_DELEGATE_AGENT_DESCRIPTION = `Spawn explore/librarian agent. run_in_bac
|
|
|
89393
89805
|
Available: {agents}
|
|
89394
89806
|
|
|
89395
89807
|
Pass \`session_id=<id>\` to continue previous agent with full context. Prompts MUST be in English. Use \`background_output\` for async results.`;
|
|
89808
|
+
// src/shared/saturated-outcome.ts
|
|
89809
|
+
var LAUNCH_TERMINAL_STATUSES = [
|
|
89810
|
+
"error",
|
|
89811
|
+
"cancelled",
|
|
89812
|
+
"interrupt",
|
|
89813
|
+
"stopped",
|
|
89814
|
+
"statusUncertain"
|
|
89815
|
+
];
|
|
89816
|
+
function isLaunchTerminalStatus(status) {
|
|
89817
|
+
return LAUNCH_TERMINAL_STATUSES.includes(status);
|
|
89818
|
+
}
|
|
89819
|
+
function isQueueSaturated(status, terminalReason) {
|
|
89820
|
+
return status === "stopped" && terminalReason === "queue-saturated";
|
|
89821
|
+
}
|
|
89822
|
+
function formatSaturatedOutcome(taskId) {
|
|
89823
|
+
const metadata = JSON.stringify({
|
|
89824
|
+
task_id: taskId,
|
|
89825
|
+
status: "stopped",
|
|
89826
|
+
reason: "queue-saturated"
|
|
89827
|
+
});
|
|
89828
|
+
return `Background task NOT admitted (queue saturated).
|
|
89829
|
+
|
|
89830
|
+
Task ID: ${taskId}
|
|
89831
|
+
Status: stopped
|
|
89832
|
+
Reason: queue-saturated
|
|
89833
|
+
|
|
89834
|
+
<task_metadata>${metadata}</task_metadata>`;
|
|
89835
|
+
}
|
|
89836
|
+
function formatLaunchFailure(taskId, status) {
|
|
89837
|
+
return `Task failed to start (status: ${status}).
|
|
89838
|
+
|
|
89839
|
+
Task ID: ${taskId}`;
|
|
89840
|
+
}
|
|
89396
89841
|
// src/tools/delegate-agent/background-executor.ts
|
|
89397
89842
|
async function executeBackground(args, toolContext, manager, client2) {
|
|
89398
89843
|
try {
|
|
@@ -89429,10 +89874,11 @@ async function executeBackground(args, toolContext, manager, client2) {
|
|
|
89429
89874
|
Task ID: ${task.id}`;
|
|
89430
89875
|
}
|
|
89431
89876
|
const updated = manager.getTask(task.id);
|
|
89432
|
-
if (updated
|
|
89433
|
-
|
|
89434
|
-
|
|
89435
|
-
|
|
89877
|
+
if (updated && isLaunchTerminalStatus(updated.status)) {
|
|
89878
|
+
if (isQueueSaturated(updated.status, updated.terminalReason)) {
|
|
89879
|
+
return formatSaturatedOutcome(task.id);
|
|
89880
|
+
}
|
|
89881
|
+
return formatLaunchFailure(task.id, updated.status);
|
|
89436
89882
|
}
|
|
89437
89883
|
await new Promise((resolve10) => setTimeout(resolve10, WAIT_FOR_SESSION_INTERVAL_MS));
|
|
89438
89884
|
sessionId = manager.getTask(task.id)?.sessionID;
|
|
@@ -90411,7 +90857,7 @@ ${task.agent ? `subagent: ${task.agent}
|
|
|
90411
90857
|
}
|
|
90412
90858
|
// src/tools/delegate-task/timing.ts
|
|
90413
90859
|
var POLL_INTERVAL_MS3 = 1000;
|
|
90414
|
-
var
|
|
90860
|
+
var MIN_STABILITY_TIME_MS2 = 1e4;
|
|
90415
90861
|
var STABILITY_POLLS_REQUIRED = 3;
|
|
90416
90862
|
var WAIT_FOR_SESSION_INTERVAL_MS = 100;
|
|
90417
90863
|
var WAIT_FOR_SESSION_TIMEOUT_MS = 30000;
|
|
@@ -90420,7 +90866,7 @@ var SESSION_CONTINUATION_STABILITY_MS = 5000;
|
|
|
90420
90866
|
function getTimingConfig() {
|
|
90421
90867
|
return {
|
|
90422
90868
|
POLL_INTERVAL_MS: POLL_INTERVAL_MS3,
|
|
90423
|
-
MIN_STABILITY_TIME_MS,
|
|
90869
|
+
MIN_STABILITY_TIME_MS: MIN_STABILITY_TIME_MS2,
|
|
90424
90870
|
STABILITY_POLLS_REQUIRED,
|
|
90425
90871
|
WAIT_FOR_SESSION_INTERVAL_MS,
|
|
90426
90872
|
WAIT_FOR_SESSION_TIMEOUT_MS,
|
|
@@ -90461,6 +90907,12 @@ Task ID: ${task.id}`;
|
|
|
90461
90907
|
}
|
|
90462
90908
|
await new Promise((resolve10) => setTimeout(resolve10, timing.WAIT_FOR_SESSION_INTERVAL_MS));
|
|
90463
90909
|
const updated = manager.getTask(task.id);
|
|
90910
|
+
if (updated && isLaunchTerminalStatus(updated.status)) {
|
|
90911
|
+
if (isQueueSaturated(updated.status, updated.terminalReason)) {
|
|
90912
|
+
return formatSaturatedOutcome(task.id);
|
|
90913
|
+
}
|
|
90914
|
+
return formatLaunchFailure(task.id, updated.status);
|
|
90915
|
+
}
|
|
90464
90916
|
sessionId = updated?.sessionID;
|
|
90465
90917
|
}
|
|
90466
90918
|
if (!sessionId) {
|
|
@@ -92101,7 +92553,7 @@ REQUIRED: You MUST provide EITHER category OR subagent_type (one of them is REQU
|
|
|
92101
92553
|
Available categories:
|
|
92102
92554
|
${categoryList}
|
|
92103
92555
|
- subagent_type: Use specific agent directly (e.g., "oracle", "trinity")
|
|
92104
|
-
- run_in_background: true=async (returns task_id), false=sync (waits for result). Default: false. Use
|
|
92556
|
+
- run_in_background: true=async (returns task_id), false=sync (waits for result). Default: false. Use true for ANY parallel independent work (exploration, fan-out, multi-agent waves); false awaits the result inline.
|
|
92105
92557
|
- session_id: Existing Task session to continue (from previous task output). Continues agent with FULL CONTEXT PRESERVED - saves tokens, maintains continuity.
|
|
92106
92558
|
- command: The command that triggered this task (optional, for slash command tracking).
|
|
92107
92559
|
|
|
@@ -92598,8 +93050,8 @@ Original error: ${createResult.error}`;
|
|
|
92598
93050
|
});
|
|
92599
93051
|
}
|
|
92600
93052
|
// src/tools/task/task-cleanup.ts
|
|
92601
|
-
import { existsSync as
|
|
92602
|
-
import { join as
|
|
93053
|
+
import { existsSync as existsSync74, readdirSync as readdirSync23, readFileSync as readFileSync43, unlinkSync as unlinkSync12 } from "fs";
|
|
93054
|
+
import { join as join74 } from "path";
|
|
92603
93055
|
init_logger();
|
|
92604
93056
|
function parseOlderThan(value) {
|
|
92605
93057
|
const match = value.trim().match(/^(\d+)(d|h|m)$/);
|
|
@@ -92658,17 +93110,17 @@ Returns counts: {deleted, remaining, deletedIds}`,
|
|
|
92658
93110
|
}
|
|
92659
93111
|
const directory = context?.directory ?? ctx?.directory ?? process.cwd();
|
|
92660
93112
|
const taskDir = getTaskDir(config4, directory);
|
|
92661
|
-
if (!
|
|
93113
|
+
if (!existsSync74(taskDir)) {
|
|
92662
93114
|
return JSON.stringify({ deleted: 0, remaining: 0, deletedIds: [] });
|
|
92663
93115
|
}
|
|
92664
|
-
const files =
|
|
93116
|
+
const files = readdirSync23(taskDir).filter((f) => f.endsWith(".json") && f.startsWith("T-"));
|
|
92665
93117
|
const total = files.length;
|
|
92666
93118
|
const deletedIds = [];
|
|
92667
93119
|
for (const file3 of files) {
|
|
92668
|
-
const filePath =
|
|
93120
|
+
const filePath = join74(taskDir, file3);
|
|
92669
93121
|
let raw;
|
|
92670
93122
|
try {
|
|
92671
|
-
const content =
|
|
93123
|
+
const content = readFileSync43(filePath, "utf-8");
|
|
92672
93124
|
raw = JSON.parse(content);
|
|
92673
93125
|
} catch {
|
|
92674
93126
|
continue;
|
|
@@ -92683,7 +93135,7 @@ Returns counts: {deleted, remaining, deletedIds}`,
|
|
|
92683
93135
|
continue;
|
|
92684
93136
|
}
|
|
92685
93137
|
try {
|
|
92686
|
-
|
|
93138
|
+
unlinkSync12(filePath);
|
|
92687
93139
|
deletedIds.push(id);
|
|
92688
93140
|
} catch (err) {
|
|
92689
93141
|
log("[task-cleanup] Failed to delete", { file: file3, error: String(err) });
|
|
@@ -92701,8 +93153,8 @@ Returns counts: {deleted, remaining, deletedIds}`,
|
|
|
92701
93153
|
});
|
|
92702
93154
|
}
|
|
92703
93155
|
// src/tools/task/task-create.ts
|
|
92704
|
-
import { readdirSync as
|
|
92705
|
-
import { join as
|
|
93156
|
+
import { readdirSync as readdirSync24, statSync as statSync17 } from "fs";
|
|
93157
|
+
import { join as join75 } from "path";
|
|
92706
93158
|
init_logger();
|
|
92707
93159
|
function createTaskCreateTool(config4, ctx) {
|
|
92708
93160
|
return tool({
|
|
@@ -92768,7 +93220,7 @@ async function handleCreate2(args, config4, ctx, context) {
|
|
|
92768
93220
|
projectRoot: directory
|
|
92769
93221
|
};
|
|
92770
93222
|
const validatedTask = TaskObjectSchema.parse(task);
|
|
92771
|
-
writeJsonAtomic(
|
|
93223
|
+
writeJsonAtomic(join75(taskDir, `${taskId}.json`), validatedTask);
|
|
92772
93224
|
return JSON.stringify({
|
|
92773
93225
|
task: {
|
|
92774
93226
|
id: validatedTask.id,
|
|
@@ -92791,13 +93243,13 @@ async function handleCreate2(args, config4, ctx, context) {
|
|
|
92791
93243
|
function findDuplicateTask(taskDir, subject, directory) {
|
|
92792
93244
|
let files;
|
|
92793
93245
|
try {
|
|
92794
|
-
files =
|
|
93246
|
+
files = readdirSync24(taskDir).filter((f) => f.endsWith(".json") && f.startsWith("T-"));
|
|
92795
93247
|
} catch (error95) {
|
|
92796
93248
|
log(`[task-create] Failed to list task dir ${taskDir} during dedup scan`, error95);
|
|
92797
93249
|
return null;
|
|
92798
93250
|
}
|
|
92799
93251
|
for (const file3 of files) {
|
|
92800
|
-
const filePath =
|
|
93252
|
+
const filePath = join75(taskDir, file3);
|
|
92801
93253
|
let existing;
|
|
92802
93254
|
try {
|
|
92803
93255
|
existing = readJsonSafe(filePath, TaskObjectSchema);
|
|
@@ -92828,7 +93280,7 @@ function findDuplicateTask(taskDir, subject, directory) {
|
|
|
92828
93280
|
return null;
|
|
92829
93281
|
}
|
|
92830
93282
|
// src/tools/task/task-get.ts
|
|
92831
|
-
import { join as
|
|
93283
|
+
import { join as join76 } from "path";
|
|
92832
93284
|
function parseTaskId(id) {
|
|
92833
93285
|
if (!TASK_ID_PATTERN.test(id))
|
|
92834
93286
|
return null;
|
|
@@ -92853,7 +93305,7 @@ Returns null if the task does not exist or the file is invalid.`,
|
|
|
92853
93305
|
}
|
|
92854
93306
|
const directory = context?.directory ?? ctx?.directory ?? process.cwd();
|
|
92855
93307
|
const taskDir = getTaskDir(config4, directory);
|
|
92856
|
-
const taskPath =
|
|
93308
|
+
const taskPath = join76(taskDir, `${taskId}.json`);
|
|
92857
93309
|
const task = readJsonSafe(taskPath, TaskObjectSchema);
|
|
92858
93310
|
return JSON.stringify({ task: task ?? null });
|
|
92859
93311
|
} catch (error95) {
|
|
@@ -92866,8 +93318,8 @@ Returns null if the task does not exist or the file is invalid.`,
|
|
|
92866
93318
|
});
|
|
92867
93319
|
}
|
|
92868
93320
|
// src/tools/task/task-list.ts
|
|
92869
|
-
import { existsSync as
|
|
92870
|
-
import { join as
|
|
93321
|
+
import { existsSync as existsSync75, readdirSync as readdirSync25 } from "fs";
|
|
93322
|
+
import { join as join77 } from "path";
|
|
92871
93323
|
function createTaskList(config4, ctx) {
|
|
92872
93324
|
return tool({
|
|
92873
93325
|
description: `List all active tasks with summary information.
|
|
@@ -92885,16 +93337,16 @@ Optional \`parentID\` filter: when provided, returns only tasks whose parentID m
|
|
|
92885
93337
|
const parentID = args.parentID;
|
|
92886
93338
|
const directory = context?.directory ?? ctx?.directory ?? process.cwd();
|
|
92887
93339
|
const taskDir = getTaskDir(config4, directory);
|
|
92888
|
-
if (!
|
|
93340
|
+
if (!existsSync75(taskDir)) {
|
|
92889
93341
|
return JSON.stringify({ tasks: [] });
|
|
92890
93342
|
}
|
|
92891
|
-
const files =
|
|
93343
|
+
const files = readdirSync25(taskDir).filter((f) => f.endsWith(".json") && f.startsWith("T-")).map((f) => f.replace(".json", ""));
|
|
92892
93344
|
if (files.length === 0) {
|
|
92893
93345
|
return JSON.stringify({ tasks: [] });
|
|
92894
93346
|
}
|
|
92895
93347
|
const allTasks = [];
|
|
92896
93348
|
for (const fileId of files) {
|
|
92897
|
-
const task = readJsonSafe(
|
|
93349
|
+
const task = readJsonSafe(join77(taskDir, `${fileId}.json`), TaskObjectSchema);
|
|
92898
93350
|
if (task) {
|
|
92899
93351
|
allTasks.push(task);
|
|
92900
93352
|
}
|
|
@@ -92923,7 +93375,7 @@ Optional \`parentID\` filter: when provided, returns only tasks whose parentID m
|
|
|
92923
93375
|
});
|
|
92924
93376
|
}
|
|
92925
93377
|
// src/tools/task/task-update.ts
|
|
92926
|
-
import { join as
|
|
93378
|
+
import { join as join78 } from "path";
|
|
92927
93379
|
function parseTaskId2(id) {
|
|
92928
93380
|
if (!TASK_ID_PATTERN.test(id))
|
|
92929
93381
|
return null;
|
|
@@ -92969,7 +93421,7 @@ async function handleUpdate(args, config4, ctx, context) {
|
|
|
92969
93421
|
return JSON.stringify({ error: "task_lock_unavailable" });
|
|
92970
93422
|
}
|
|
92971
93423
|
try {
|
|
92972
|
-
const taskPath =
|
|
93424
|
+
const taskPath = join78(taskDir, `${taskId}.json`);
|
|
92973
93425
|
const task = readJsonSafe(taskPath, TaskObjectSchema);
|
|
92974
93426
|
if (!task) {
|
|
92975
93427
|
return JSON.stringify({ error: "task_not_found" });
|
|
@@ -93022,12 +93474,13 @@ async function handleUpdate(args, config4, ctx, context) {
|
|
|
93022
93474
|
}
|
|
93023
93475
|
}
|
|
93024
93476
|
// src/tools/index.ts
|
|
93025
|
-
function createBackgroundTools(manager, client2) {
|
|
93477
|
+
function createBackgroundTools(manager, client2, directory) {
|
|
93026
93478
|
const outputManager = manager;
|
|
93027
93479
|
const cancelClient = client2;
|
|
93028
93480
|
return {
|
|
93029
93481
|
background_output: createBackgroundOutput(outputManager, client2),
|
|
93030
|
-
background_cancel: createBackgroundCancel(manager, cancelClient)
|
|
93482
|
+
background_cancel: createBackgroundCancel(manager, cancelClient),
|
|
93483
|
+
background_revive: createBackgroundRevive(manager, directory)
|
|
93031
93484
|
};
|
|
93032
93485
|
}
|
|
93033
93486
|
var builtinTools = {
|
|
@@ -93436,30 +93889,63 @@ class ConcurrencyManager {
|
|
|
93436
93889
|
}
|
|
93437
93890
|
return 5;
|
|
93438
93891
|
}
|
|
93439
|
-
|
|
93892
|
+
tryAcquireSlot(model) {
|
|
93440
93893
|
const limit = this.getConcurrencyLimit(model);
|
|
93441
93894
|
if (limit === Infinity) {
|
|
93442
|
-
return;
|
|
93895
|
+
return true;
|
|
93443
93896
|
}
|
|
93444
93897
|
const current = this.counts.get(model) ?? 0;
|
|
93445
93898
|
if (current < limit) {
|
|
93446
93899
|
this.counts.set(model, current + 1);
|
|
93900
|
+
return true;
|
|
93901
|
+
}
|
|
93902
|
+
return false;
|
|
93903
|
+
}
|
|
93904
|
+
async acquire(model) {
|
|
93905
|
+
if (this.tryAcquireSlot(model))
|
|
93906
|
+
return;
|
|
93907
|
+
const outcome = await this.acquireWithDeadline(model, undefined);
|
|
93908
|
+
if (outcome.granted)
|
|
93447
93909
|
return;
|
|
93910
|
+
if (outcome.reason === "cancelled") {
|
|
93911
|
+
throw new Error(`Concurrency queue cancelled for model: ${model}`);
|
|
93448
93912
|
}
|
|
93449
|
-
|
|
93913
|
+
}
|
|
93914
|
+
async acquireWithDeadline(model, timeoutMs) {
|
|
93915
|
+
if (this.tryAcquireSlot(model)) {
|
|
93916
|
+
return { granted: true };
|
|
93917
|
+
}
|
|
93918
|
+
return new Promise((resolve10) => {
|
|
93450
93919
|
const queue = this.queues.get(model) ?? [];
|
|
93920
|
+
let timer;
|
|
93451
93921
|
const entry = {
|
|
93452
|
-
|
|
93922
|
+
settled: false,
|
|
93923
|
+
enqueuedAt: Date.now(),
|
|
93924
|
+
settle: (outcome) => {
|
|
93453
93925
|
if (entry.settled)
|
|
93454
93926
|
return;
|
|
93455
93927
|
entry.settled = true;
|
|
93456
|
-
|
|
93457
|
-
|
|
93458
|
-
|
|
93459
|
-
|
|
93928
|
+
if (timer !== undefined)
|
|
93929
|
+
clearTimeout(timer);
|
|
93930
|
+
resolve10(outcome);
|
|
93931
|
+
}
|
|
93460
93932
|
};
|
|
93461
93933
|
queue.push(entry);
|
|
93462
93934
|
this.queues.set(model, queue);
|
|
93935
|
+
if (timeoutMs === undefined || timeoutMs <= 0)
|
|
93936
|
+
return;
|
|
93937
|
+
timer = setTimeout(() => {
|
|
93938
|
+
if (entry.settled)
|
|
93939
|
+
return;
|
|
93940
|
+
entry.settled = true;
|
|
93941
|
+
const pending = this.queues.get(model);
|
|
93942
|
+
if (pending) {
|
|
93943
|
+
const index = pending.indexOf(entry);
|
|
93944
|
+
if (index !== -1)
|
|
93945
|
+
pending.splice(index, 1);
|
|
93946
|
+
}
|
|
93947
|
+
resolve10({ granted: false, reason: "timeout", waitedMs: Date.now() - entry.enqueuedAt });
|
|
93948
|
+
}, timeoutMs);
|
|
93463
93949
|
});
|
|
93464
93950
|
}
|
|
93465
93951
|
release(model) {
|
|
@@ -93473,7 +93959,7 @@ class ConcurrencyManager {
|
|
|
93473
93959
|
if (!next)
|
|
93474
93960
|
continue;
|
|
93475
93961
|
if (!next.settled) {
|
|
93476
|
-
next.
|
|
93962
|
+
next.settle({ granted: true });
|
|
93477
93963
|
return;
|
|
93478
93964
|
}
|
|
93479
93965
|
}
|
|
@@ -93485,11 +93971,9 @@ class ConcurrencyManager {
|
|
|
93485
93971
|
cancelWaiters(model) {
|
|
93486
93972
|
const queue = this.queues.get(model);
|
|
93487
93973
|
if (queue) {
|
|
93974
|
+
const now = Date.now();
|
|
93488
93975
|
for (const entry of queue) {
|
|
93489
|
-
|
|
93490
|
-
entry.settled = true;
|
|
93491
|
-
entry.rawReject(new Error(`Concurrency queue cancelled for model: ${model}`));
|
|
93492
|
-
}
|
|
93976
|
+
entry.settle({ granted: false, reason: "cancelled", waitedMs: now - entry.enqueuedAt });
|
|
93493
93977
|
}
|
|
93494
93978
|
this.queues.delete(model);
|
|
93495
93979
|
}
|
|
@@ -93508,144 +93992,55 @@ class ConcurrencyManager {
|
|
|
93508
93992
|
return this.queues.get(model)?.length ?? 0;
|
|
93509
93993
|
}
|
|
93510
93994
|
}
|
|
93511
|
-
// src/
|
|
93512
|
-
|
|
93513
|
-
|
|
93514
|
-
|
|
93515
|
-
|
|
93516
|
-
|
|
93517
|
-
|
|
93518
|
-
|
|
93519
|
-
|
|
93520
|
-
|
|
93521
|
-
|
|
93522
|
-
|
|
93523
|
-
|
|
93524
|
-
|
|
93525
|
-
|
|
93526
|
-
|
|
93527
|
-
|
|
93528
|
-
|
|
93529
|
-
|
|
93530
|
-
|
|
93531
|
-
|
|
93532
|
-
|
|
93533
|
-
|
|
93534
|
-
|
|
93535
|
-
|
|
93536
|
-
|
|
93537
|
-
|
|
93538
|
-
|
|
93539
|
-
|
|
93540
|
-
|
|
93541
|
-
|
|
93542
|
-
|
|
93543
|
-
|
|
93544
|
-
|
|
93545
|
-
|
|
93546
|
-
|
|
93547
|
-
|
|
93548
|
-
|
|
93549
|
-
|
|
93550
|
-
|
|
93551
|
-
|
|
93552
|
-
|
|
93553
|
-
|
|
93554
|
-
|
|
93555
|
-
|
|
93556
|
-
}
|
|
93557
|
-
function ensureDir3(dirPath) {
|
|
93558
|
-
if (!existsSync73(dirPath)) {
|
|
93559
|
-
mkdirSync19(dirPath, { recursive: true });
|
|
93560
|
-
}
|
|
93561
|
-
}
|
|
93562
|
-
function toHandle(task) {
|
|
93563
|
-
return {
|
|
93564
|
-
taskId: task.id,
|
|
93565
|
-
parentSessionID: task.parentSessionID,
|
|
93566
|
-
parentMessageID: task.parentMessageID,
|
|
93567
|
-
description: task.description,
|
|
93568
|
-
agent: task.agent,
|
|
93569
|
-
status: task.status,
|
|
93570
|
-
model: task.model,
|
|
93571
|
-
category: task.category,
|
|
93572
|
-
concurrencyGroup: task.concurrencyGroup,
|
|
93573
|
-
queuedAt: task.queuedAt?.getTime(),
|
|
93574
|
-
startedAt: task.startedAt?.getTime(),
|
|
93575
|
-
completedAt: task.completedAt?.getTime()
|
|
93576
|
-
};
|
|
93577
|
-
}
|
|
93578
|
-
function writeHandle(directory, task) {
|
|
93579
|
-
const filePath = getHandlePath(directory, task.id);
|
|
93580
|
-
ensureDir3(dirname19(filePath));
|
|
93581
|
-
const tempPath = `${filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
93582
|
-
try {
|
|
93583
|
-
writeFileSync26(tempPath, JSON.stringify(toHandle(task), null, 2), "utf-8");
|
|
93584
|
-
renameSync5(tempPath, filePath);
|
|
93585
|
-
} catch (error95) {
|
|
93586
|
-
try {
|
|
93587
|
-
if (existsSync73(tempPath)) {
|
|
93588
|
-
unlinkSync12(tempPath);
|
|
93589
|
-
}
|
|
93590
|
-
} catch {}
|
|
93591
|
-
throw error95;
|
|
93592
|
-
}
|
|
93593
|
-
}
|
|
93594
|
-
function readHandleFile(filePath) {
|
|
93595
|
-
try {
|
|
93596
|
-
if (!existsSync73(filePath)) {
|
|
93597
|
-
return null;
|
|
93598
|
-
}
|
|
93599
|
-
const parsed = JSON.parse(readFileSync43(filePath, "utf-8"));
|
|
93600
|
-
const result = BgHandleSchema.safeParse(parsed);
|
|
93601
|
-
return result.success ? result.data : null;
|
|
93602
|
-
} catch {
|
|
93603
|
-
return null;
|
|
93604
|
-
}
|
|
93605
|
-
}
|
|
93606
|
-
function readHandles(directory) {
|
|
93607
|
-
const dir = getBgHandleDir(directory);
|
|
93608
|
-
if (!existsSync73(dir)) {
|
|
93609
|
-
return [];
|
|
93610
|
-
}
|
|
93611
|
-
const handles = [];
|
|
93612
|
-
for (const name of readdirSync25(dir)) {
|
|
93613
|
-
if (!name.startsWith(BG_HANDLE_FILE_PREFIX) || !name.endsWith(".json")) {
|
|
93614
|
-
continue;
|
|
93615
|
-
}
|
|
93616
|
-
const handle = readHandleFile(join77(dir, name));
|
|
93617
|
-
if (handle) {
|
|
93618
|
-
handles.push(handle);
|
|
93619
|
-
}
|
|
93620
|
-
}
|
|
93621
|
-
return handles;
|
|
93622
|
-
}
|
|
93623
|
-
function sweepStaleHandles(directory, ttlMs = TASK_TTL_MS, now = Date.now()) {
|
|
93624
|
-
const dir = getBgHandleDir(directory);
|
|
93625
|
-
if (!existsSync73(dir)) {
|
|
93626
|
-
return 0;
|
|
93627
|
-
}
|
|
93628
|
-
let removed = 0;
|
|
93629
|
-
for (const name of readdirSync25(dir)) {
|
|
93630
|
-
if (!name.startsWith(BG_HANDLE_FILE_PREFIX) || !name.endsWith(".json")) {
|
|
93631
|
-
continue;
|
|
93632
|
-
}
|
|
93633
|
-
const filePath = join77(dir, name);
|
|
93634
|
-
const handle = readHandleFile(filePath);
|
|
93635
|
-
const timestamp = handle?.completedAt ?? handle?.startedAt ?? handle?.queuedAt;
|
|
93636
|
-
if (handle && timestamp !== undefined && now - timestamp <= ttlMs) {
|
|
93637
|
-
continue;
|
|
93638
|
-
}
|
|
93639
|
-
try {
|
|
93640
|
-
unlinkSync12(filePath);
|
|
93641
|
-
removed++;
|
|
93642
|
-
} catch {}
|
|
93643
|
-
}
|
|
93644
|
-
if (removed > 0) {
|
|
93645
|
-
log(`[bg-handles] Swept ${removed} stale handle file(s) from ${dir}`);
|
|
93995
|
+
// src/shared/wallclock-outcome.ts
|
|
93996
|
+
function formatWallclockTimeout(taskId, elapsedMs, limitMs) {
|
|
93997
|
+
const metadata = JSON.stringify({
|
|
93998
|
+
task_id: taskId,
|
|
93999
|
+
status: "cancelled",
|
|
94000
|
+
reason: "wall-clock-timeout",
|
|
94001
|
+
timedOut: true,
|
|
94002
|
+
elapsedMs,
|
|
94003
|
+
limitMs
|
|
94004
|
+
});
|
|
94005
|
+
return `Background task wall-clock timeout. Task ID ${taskId} exceeded its elapsed-time bound.
|
|
94006
|
+
|
|
94007
|
+
Task ID: ${taskId}
|
|
94008
|
+
Elapsed: ${formatDuration2(elapsedMs)}
|
|
94009
|
+
Limit: ${formatDuration2(limitMs)}
|
|
94010
|
+
Status: cancelled
|
|
94011
|
+
Reason: wall-clock-timeout
|
|
94012
|
+
|
|
94013
|
+
<task_metadata>${metadata}</task_metadata>`;
|
|
94014
|
+
}
|
|
94015
|
+
function formatDuration2(ms) {
|
|
94016
|
+
if (ms < 0)
|
|
94017
|
+
ms = 0;
|
|
94018
|
+
if (ms >= 60000) {
|
|
94019
|
+
const mins = Math.floor(ms / 60000);
|
|
94020
|
+
return `${mins}min`;
|
|
94021
|
+
}
|
|
94022
|
+
return `${Math.floor(ms / 1000)}sec`;
|
|
94023
|
+
}
|
|
94024
|
+
|
|
94025
|
+
// src/features/background-agent/admission.ts
|
|
94026
|
+
function classifyAdmission(parentSessionID, maxDepth) {
|
|
94027
|
+
if (!parentSessionID)
|
|
94028
|
+
return { kind: "root" };
|
|
94029
|
+
const immediateParent = getParentSessionID(parentSessionID);
|
|
94030
|
+
if (!immediateParent)
|
|
94031
|
+
return { kind: "root" };
|
|
94032
|
+
let depth = 1;
|
|
94033
|
+
let current = immediateParent;
|
|
94034
|
+
while (depth < maxDepth) {
|
|
94035
|
+
const parent = getParentSessionID(current);
|
|
94036
|
+
if (!parent)
|
|
94037
|
+
break;
|
|
94038
|
+
depth += 1;
|
|
94039
|
+
current = parent;
|
|
93646
94040
|
}
|
|
93647
|
-
return
|
|
94041
|
+
return { kind: "nested", depth };
|
|
93648
94042
|
}
|
|
94043
|
+
|
|
93649
94044
|
// src/features/background-agent/error-helpers.ts
|
|
93650
94045
|
function getErrorText(error95) {
|
|
93651
94046
|
if (!error95)
|
|
@@ -93748,19 +94143,19 @@ function detectRepetitiveToolUse(window2) {
|
|
|
93748
94143
|
}
|
|
93749
94144
|
|
|
93750
94145
|
// src/features/background-agent/message-dir.ts
|
|
93751
|
-
import { existsSync as
|
|
93752
|
-
import { join as
|
|
94146
|
+
import { existsSync as existsSync76, readdirSync as readdirSync26, readFileSync as readFileSync44 } from "fs";
|
|
94147
|
+
import { join as join79 } from "path";
|
|
93753
94148
|
var MESSAGE_DIR_CACHE_CAP = 1000;
|
|
93754
94149
|
var messageDirCache = new Map;
|
|
93755
94150
|
function computeMessageDir(sessionID) {
|
|
93756
|
-
if (!
|
|
94151
|
+
if (!existsSync76(MESSAGE_STORAGE))
|
|
93757
94152
|
return null;
|
|
93758
|
-
const directPath =
|
|
93759
|
-
if (
|
|
94153
|
+
const directPath = join79(MESSAGE_STORAGE, sessionID);
|
|
94154
|
+
if (existsSync76(directPath))
|
|
93760
94155
|
return directPath;
|
|
93761
94156
|
for (const dir of readdirSync26(MESSAGE_STORAGE)) {
|
|
93762
|
-
const sessionPath =
|
|
93763
|
-
if (
|
|
94157
|
+
const sessionPath = join79(MESSAGE_STORAGE, dir, sessionID);
|
|
94158
|
+
if (existsSync76(sessionPath))
|
|
93764
94159
|
return sessionPath;
|
|
93765
94160
|
}
|
|
93766
94161
|
return null;
|
|
@@ -93798,7 +94193,7 @@ function findNearestMessageExcludingCompaction(messageDir) {
|
|
|
93798
94193
|
const parsedByFile = new Map;
|
|
93799
94194
|
for (const file3 of files) {
|
|
93800
94195
|
try {
|
|
93801
|
-
const content = readFileSync44(
|
|
94196
|
+
const content = readFileSync44(join79(messageDir, file3), "utf-8");
|
|
93802
94197
|
const parsed = JSON.parse(content);
|
|
93803
94198
|
parsedByFile.set(file3, parsed);
|
|
93804
94199
|
if (hasFullAgentAndModel(parsed)) {
|
|
@@ -93818,6 +94213,19 @@ function findNearestMessageExcludingCompaction(messageDir) {
|
|
|
93818
94213
|
}
|
|
93819
94214
|
|
|
93820
94215
|
// src/features/background-agent/notification-builder.ts
|
|
94216
|
+
var STATUS_LABELS = {
|
|
94217
|
+
completed: "COMPLETED",
|
|
94218
|
+
interrupt: "INTERRUPTED",
|
|
94219
|
+
cancelled: "CANCELLED",
|
|
94220
|
+
stopped: "STOPPED",
|
|
94221
|
+
statusUncertain: "STATUS UNCERTAIN",
|
|
94222
|
+
error: "ERROR",
|
|
94223
|
+
pending: "IN PROGRESS",
|
|
94224
|
+
running: "IN PROGRESS"
|
|
94225
|
+
};
|
|
94226
|
+
function statusToLabel(status) {
|
|
94227
|
+
return STATUS_LABELS[status];
|
|
94228
|
+
}
|
|
93821
94229
|
function buildCompletionNotification(task, allComplete, completedTasks, remainingCount, duration5, errorInfo) {
|
|
93822
94230
|
if (allComplete) {
|
|
93823
94231
|
const completedTasksText = completedTasks.map((t) => `- \`${t.id}\`: ${t.description}`).join(`
|
|
@@ -93831,7 +94239,7 @@ ${completedTasksText || `- \`${task.id}\`: ${task.description}`}
|
|
|
93831
94239
|
Use \`background_output(task_id="<id>")\` to retrieve each result.
|
|
93832
94240
|
</system-reminder>`;
|
|
93833
94241
|
}
|
|
93834
|
-
const statusText = task.status
|
|
94242
|
+
const statusText = statusToLabel(task.status);
|
|
93835
94243
|
return `<system-reminder>
|
|
93836
94244
|
[BACKGROUND TASK ${statusText}]
|
|
93837
94245
|
**ID:** \`${task.id}\`
|
|
@@ -93886,6 +94294,164 @@ async function resolveAgentAndModel(client2, task, enableParentSessionNotificati
|
|
|
93886
94294
|
return { agent, model };
|
|
93887
94295
|
}
|
|
93888
94296
|
|
|
94297
|
+
// src/features/background-agent/session-output.ts
|
|
94298
|
+
async function fetchSessionMessages2(client2, sessionID) {
|
|
94299
|
+
const response = await client2.session.messages({ path: { id: sessionID } });
|
|
94300
|
+
return normalizeSDKResponse(response, [], { preferResponseOnMissingData: true });
|
|
94301
|
+
}
|
|
94302
|
+
function messagesHaveAssistantOutput(messages2, sessionID) {
|
|
94303
|
+
const hasAssistantOrToolMessage = messages2.some((m) => m.info?.role === "assistant" || m.info?.role === "tool");
|
|
94304
|
+
if (!hasAssistantOrToolMessage) {
|
|
94305
|
+
if (sessionID)
|
|
94306
|
+
log("[background-agent] No assistant/tool messages found in session:", sessionID);
|
|
94307
|
+
return false;
|
|
94308
|
+
}
|
|
94309
|
+
const hasContent2 = messages2.some((m) => {
|
|
94310
|
+
if (m.info?.role !== "assistant" && m.info?.role !== "tool")
|
|
94311
|
+
return false;
|
|
94312
|
+
const parts = m.parts ?? [];
|
|
94313
|
+
return parts.some((p) => p.type === "text" && p.text && p.text.trim().length > 0 || p.type === "reasoning" && p.text && p.text.trim().length > 0 || p.type === "tool" || p.type === "tool_result" && p.content && (typeof p.content === "string" ? p.content.trim().length > 0 : p.content.length > 0));
|
|
94314
|
+
});
|
|
94315
|
+
if (!hasContent2 && sessionID) {
|
|
94316
|
+
log("[background-agent] Messages exist but no content found in session:", sessionID);
|
|
94317
|
+
}
|
|
94318
|
+
return hasContent2;
|
|
94319
|
+
}
|
|
94320
|
+
function messagesHaveRecordedError(messages2) {
|
|
94321
|
+
return messages2.some((m) => m.info?.role === "assistant" && m.info.error != null);
|
|
94322
|
+
}
|
|
94323
|
+
async function validateSessionHasOutput(client2, sessionID) {
|
|
94324
|
+
try {
|
|
94325
|
+
const messages2 = await fetchSessionMessages2(client2, sessionID);
|
|
94326
|
+
return messagesHaveAssistantOutput(messages2, sessionID);
|
|
94327
|
+
} catch (error95) {
|
|
94328
|
+
log("[background-agent] Error validating session output:", error95);
|
|
94329
|
+
return true;
|
|
94330
|
+
}
|
|
94331
|
+
}
|
|
94332
|
+
|
|
94333
|
+
// src/features/background-agent/reconcile.ts
|
|
94334
|
+
function uncertain() {
|
|
94335
|
+
return { status: "statusUncertain", terminalReason: "uncertain" };
|
|
94336
|
+
}
|
|
94337
|
+
function responseHasError(response) {
|
|
94338
|
+
if (typeof response !== "object" || response === null)
|
|
94339
|
+
return false;
|
|
94340
|
+
return Boolean(response.error);
|
|
94341
|
+
}
|
|
94342
|
+
function isNonIdle(status) {
|
|
94343
|
+
return status !== undefined && status.type !== "idle";
|
|
94344
|
+
}
|
|
94345
|
+
async function statusesOrThrow(client2) {
|
|
94346
|
+
const response = await client2.session.status();
|
|
94347
|
+
if (responseHasError(response))
|
|
94348
|
+
throw new Error("session.status returned an error response");
|
|
94349
|
+
return normalizeSDKResponse(response, {});
|
|
94350
|
+
}
|
|
94351
|
+
async function hasIncompleteTodos2(client2, sessionID) {
|
|
94352
|
+
try {
|
|
94353
|
+
const response = await client2.session.todo({ path: { id: sessionID } });
|
|
94354
|
+
const todos = normalizeSDKResponse(response, [], { preferResponseOnMissingData: true });
|
|
94355
|
+
if (!todos || todos.length === 0)
|
|
94356
|
+
return false;
|
|
94357
|
+
return todos.some((t) => t.status !== "completed" && t.status !== "cancelled");
|
|
94358
|
+
} catch {
|
|
94359
|
+
return false;
|
|
94360
|
+
}
|
|
94361
|
+
}
|
|
94362
|
+
async function reconcileHandle(client2, handle, deps) {
|
|
94363
|
+
const sessionID = handle.sessionID?.trim();
|
|
94364
|
+
if (!sessionID)
|
|
94365
|
+
return uncertain();
|
|
94366
|
+
try {
|
|
94367
|
+
const statuses = await statusesOrThrow(client2);
|
|
94368
|
+
if (isNonIdle(statuses[sessionID]))
|
|
94369
|
+
return { status: "running" };
|
|
94370
|
+
const messages2 = await fetchSessionMessages2(client2, sessionID);
|
|
94371
|
+
if (messagesHaveRecordedError(messages2))
|
|
94372
|
+
return { status: "error" };
|
|
94373
|
+
if (hasPendingQuestionMessage(messages2))
|
|
94374
|
+
return { status: "running" };
|
|
94375
|
+
const hasOutput = deps?.validateOutput ? await deps.validateOutput(sessionID) : messagesHaveAssistantOutput(messages2);
|
|
94376
|
+
if (!hasOutput) {
|
|
94377
|
+
return await confirmStopped(client2, sessionID, deps);
|
|
94378
|
+
}
|
|
94379
|
+
if (await hasIncompleteTodos2(client2, sessionID))
|
|
94380
|
+
return { status: "running" };
|
|
94381
|
+
return { status: "completed" };
|
|
94382
|
+
} catch {
|
|
94383
|
+
return uncertain();
|
|
94384
|
+
}
|
|
94385
|
+
}
|
|
94386
|
+
async function confirmStopped(client2, sessionID, deps) {
|
|
94387
|
+
if (!deps?.sleep)
|
|
94388
|
+
return { status: "stopped", terminalReason: "no-output" };
|
|
94389
|
+
await deps.sleep(RECONCILE_CONFIRMATION_GRACE_MS);
|
|
94390
|
+
const statuses = await statusesOrThrow(client2);
|
|
94391
|
+
if (isNonIdle(statuses[sessionID]))
|
|
94392
|
+
return { status: "running" };
|
|
94393
|
+
const messages2 = await fetchSessionMessages2(client2, sessionID);
|
|
94394
|
+
if (messagesHaveRecordedError(messages2))
|
|
94395
|
+
return { status: "error" };
|
|
94396
|
+
if (messagesHaveAssistantOutput(messages2) || hasPendingQuestionMessage(messages2)) {
|
|
94397
|
+
return { status: "running" };
|
|
94398
|
+
}
|
|
94399
|
+
return { status: "stopped", terminalReason: "no-output" };
|
|
94400
|
+
}
|
|
94401
|
+
|
|
94402
|
+
// src/features/background-agent/revive.ts
|
|
94403
|
+
var REVIVABLE_STATUSES = ["cancelled", "stopped", "interrupt", "error", "completed"];
|
|
94404
|
+
function isRevivableStatus(status) {
|
|
94405
|
+
return REVIVABLE_STATUSES.includes(status);
|
|
94406
|
+
}
|
|
94407
|
+
function classifyRevivable(handle, opts) {
|
|
94408
|
+
if (handle.status === "pending" || handle.status === "running") {
|
|
94409
|
+
return { eligible: false, reason: "active" };
|
|
94410
|
+
}
|
|
94411
|
+
if (handle.sessionID === undefined || handle.sessionID.trim().length === 0) {
|
|
94412
|
+
return { eligible: false, reason: "no-session" };
|
|
94413
|
+
}
|
|
94414
|
+
if (handle.status === "statusUncertain") {
|
|
94415
|
+
if (opts?.force === true) {
|
|
94416
|
+
return { eligible: true };
|
|
94417
|
+
}
|
|
94418
|
+
return { eligible: false, reason: "uncertain" };
|
|
94419
|
+
}
|
|
94420
|
+
if (isRevivableStatus(handle.status)) {
|
|
94421
|
+
return { eligible: true };
|
|
94422
|
+
}
|
|
94423
|
+
return { eligible: false, reason: "unknown-task" };
|
|
94424
|
+
}
|
|
94425
|
+
function findRevivableHandle(handles, opts) {
|
|
94426
|
+
if (opts.taskId !== undefined && opts.taskId !== "") {
|
|
94427
|
+
const byTask = handles.find((handle) => handle.taskId === opts.taskId);
|
|
94428
|
+
if (byTask)
|
|
94429
|
+
return byTask;
|
|
94430
|
+
}
|
|
94431
|
+
if (opts.sessionId !== undefined && opts.sessionId !== "") {
|
|
94432
|
+
return handles.find((handle) => handle.sessionID === opts.sessionId);
|
|
94433
|
+
}
|
|
94434
|
+
return;
|
|
94435
|
+
}
|
|
94436
|
+
function toReviveTask(handle, prompt) {
|
|
94437
|
+
return {
|
|
94438
|
+
id: handle.taskId,
|
|
94439
|
+
parentSessionID: handle.parentSessionID,
|
|
94440
|
+
parentMessageID: handle.parentMessageID,
|
|
94441
|
+
description: handle.description,
|
|
94442
|
+
prompt,
|
|
94443
|
+
agent: handle.agent,
|
|
94444
|
+
status: "pending",
|
|
94445
|
+
sessionID: handle.sessionID,
|
|
94446
|
+
queuedAt: handle.queuedAt !== undefined ? new Date(handle.queuedAt) : undefined,
|
|
94447
|
+
completedAt: undefined,
|
|
94448
|
+
error: undefined,
|
|
94449
|
+
model: handle.model,
|
|
94450
|
+
category: handle.category,
|
|
94451
|
+
concurrencyGroup: handle.concurrencyGroup
|
|
94452
|
+
};
|
|
94453
|
+
}
|
|
94454
|
+
|
|
93889
94455
|
// src/features/background-agent/task-history.ts
|
|
93890
94456
|
var MAX_ENTRIES_PER_PARENT = 100;
|
|
93891
94457
|
|
|
@@ -93943,11 +94509,232 @@ class TaskHistory {
|
|
|
93943
94509
|
return lines.join(`
|
|
93944
94510
|
`);
|
|
93945
94511
|
}
|
|
94512
|
+
getJobBoardEntries(parentSessionID) {
|
|
94513
|
+
return this.getByParentSession(parentSessionID).map((e) => ({
|
|
94514
|
+
agent: e.agent,
|
|
94515
|
+
...e.category !== undefined ? { category: e.category } : {},
|
|
94516
|
+
status: e.status,
|
|
94517
|
+
description: e.description.replace(/[\n\r]+/g, " ").trim(),
|
|
94518
|
+
...e.sessionID !== undefined ? { session: e.sessionID } : {}
|
|
94519
|
+
}));
|
|
94520
|
+
}
|
|
94521
|
+
buildJobBoardSnapshot(parentSessionID, opts) {
|
|
94522
|
+
const entries = this.getJobBoardEntries(parentSessionID);
|
|
94523
|
+
if (entries.length === 0)
|
|
94524
|
+
return null;
|
|
94525
|
+
const strategy = opts?.strategy ?? "latest";
|
|
94526
|
+
const block = formatJobBoardBlock(entries);
|
|
94527
|
+
if (strategy === "latest")
|
|
94528
|
+
return { strategy, block };
|
|
94529
|
+
const maxRetained = clampMaxRetainedSnapshots(opts?.maxRetained);
|
|
94530
|
+
const retained = [...opts?.previous ?? [], block].slice(-maxRetained);
|
|
94531
|
+
return { strategy, block, retained };
|
|
94532
|
+
}
|
|
94533
|
+
}
|
|
94534
|
+
function clampMaxRetainedSnapshots(value) {
|
|
94535
|
+
if (value === undefined || !Number.isFinite(value))
|
|
94536
|
+
return 20;
|
|
94537
|
+
return Math.min(100, Math.max(1, Math.floor(value)));
|
|
94538
|
+
}
|
|
94539
|
+
function formatJobBoardBlock(entries) {
|
|
94540
|
+
const lines = entries.map((row) => {
|
|
94541
|
+
const parts = [
|
|
94542
|
+
`- **${row.agent}**`,
|
|
94543
|
+
row.category ? `[${row.category}]` : null,
|
|
94544
|
+
`(${row.status})`,
|
|
94545
|
+
`: ${row.description}`,
|
|
94546
|
+
row.session ? ` | session: \`${row.session}\`` : null
|
|
94547
|
+
];
|
|
94548
|
+
return parts.filter(Boolean).join("");
|
|
94549
|
+
});
|
|
94550
|
+
return `**Background task board (${entries.length} task${entries.length === 1 ? "" : "s"}):**
|
|
94551
|
+
${lines.join(`
|
|
94552
|
+
`)}`;
|
|
94553
|
+
}
|
|
94554
|
+
|
|
94555
|
+
// src/features/background-agent/wake-scheduler.ts
|
|
94556
|
+
var WAKE_REMINDER_TEXT = `<system-reminder>
|
|
94557
|
+
[BACKGROUND WAKE]
|
|
94558
|
+
|
|
94559
|
+
One or more background tasks for this session are still running or have undelivered results.
|
|
94560
|
+
Use background_output with the task ID to retrieve results when ready.
|
|
94561
|
+
Do NOT poll - continue productive work.
|
|
94562
|
+
</system-reminder>`;
|
|
94563
|
+
function resolveWakeIntervalMs(cfg) {
|
|
94564
|
+
const raw = cfg?.intervalMs ?? DEFAULT_WAKE_INTERVAL_MS;
|
|
94565
|
+
return Math.max(MIN_WAKE_INTERVAL_MS, raw);
|
|
94566
|
+
}
|
|
94567
|
+
function shouldWakeForParent(state3) {
|
|
94568
|
+
if (!state3.hasPendingChildren && !state3.hasUndeliveredNotifications)
|
|
94569
|
+
return false;
|
|
94570
|
+
if (state3.hasUnfinishedTodos === false)
|
|
94571
|
+
return false;
|
|
94572
|
+
return true;
|
|
94573
|
+
}
|
|
94574
|
+
function nextWakeDelay(lastWakeAtMs, intervalMs, nowMs = Date.now()) {
|
|
94575
|
+
const interval = Math.max(MIN_WAKE_INTERVAL_MS, intervalMs);
|
|
94576
|
+
return Math.max(0, lastWakeAtMs + interval - nowMs);
|
|
94577
|
+
}
|
|
94578
|
+
var MAX_STAGNANT_WAKES = 2;
|
|
94579
|
+
|
|
94580
|
+
class WakeTracker {
|
|
94581
|
+
stagnantByParent = new Map;
|
|
94582
|
+
recordWake(parentSessionID, progressed) {
|
|
94583
|
+
if (progressed) {
|
|
94584
|
+
this.stagnantByParent.delete(parentSessionID);
|
|
94585
|
+
return true;
|
|
94586
|
+
}
|
|
94587
|
+
const stagnant = (this.stagnantByParent.get(parentSessionID) ?? 0) + 1;
|
|
94588
|
+
this.stagnantByParent.set(parentSessionID, stagnant);
|
|
94589
|
+
return stagnant <= MAX_STAGNANT_WAKES;
|
|
94590
|
+
}
|
|
94591
|
+
resetOnActivity(parentSessionID) {
|
|
94592
|
+
this.stagnantByParent.delete(parentSessionID);
|
|
94593
|
+
}
|
|
94594
|
+
stagnantCount(parentSessionID) {
|
|
94595
|
+
return this.stagnantByParent.get(parentSessionID) ?? 0;
|
|
94596
|
+
}
|
|
94597
|
+
}
|
|
94598
|
+
function defaultScheduleTimer(callback, delayMs) {
|
|
94599
|
+
return setTimeout(callback, delayMs);
|
|
94600
|
+
}
|
|
94601
|
+
function defaultClearTimer(handle) {
|
|
94602
|
+
clearTimeout(handle);
|
|
94603
|
+
}
|
|
94604
|
+
|
|
94605
|
+
class WakeScheduler {
|
|
94606
|
+
timers = new Map;
|
|
94607
|
+
registrations = new Map;
|
|
94608
|
+
snapshots = new Map;
|
|
94609
|
+
tracker = new WakeTracker;
|
|
94610
|
+
scheduleTimer;
|
|
94611
|
+
clearTimer;
|
|
94612
|
+
constructor(deps) {
|
|
94613
|
+
this.scheduleTimer = deps?.scheduleTimer ?? defaultScheduleTimer;
|
|
94614
|
+
this.clearTimer = deps?.clearTimer ?? defaultClearTimer;
|
|
94615
|
+
}
|
|
94616
|
+
armTimer(parentSessionID, callback, delayMs) {
|
|
94617
|
+
const handle = this.scheduleTimer(callback, delayMs);
|
|
94618
|
+
const maybeUnref = handle;
|
|
94619
|
+
if (typeof maybeUnref.unref === "function")
|
|
94620
|
+
maybeUnref.unref();
|
|
94621
|
+
this.timers.set(parentSessionID, handle);
|
|
94622
|
+
}
|
|
94623
|
+
schedule(parentSessionID, registration) {
|
|
94624
|
+
this.clearTimerFor(parentSessionID);
|
|
94625
|
+
this.registrations.set(parentSessionID, registration);
|
|
94626
|
+
this.snapshots.delete(parentSessionID);
|
|
94627
|
+
this.tracker.resetOnActivity(parentSessionID);
|
|
94628
|
+
this.armTimer(parentSessionID, () => void this.tick(parentSessionID), nextWakeDelay(Date.now(), registration.intervalMs));
|
|
94629
|
+
}
|
|
94630
|
+
clear(parentSessionID) {
|
|
94631
|
+
this.clearTimerFor(parentSessionID);
|
|
94632
|
+
this.registrations.delete(parentSessionID);
|
|
94633
|
+
this.snapshots.delete(parentSessionID);
|
|
94634
|
+
this.tracker.resetOnActivity(parentSessionID);
|
|
94635
|
+
}
|
|
94636
|
+
dispose() {
|
|
94637
|
+
for (const handle of this.timers.values())
|
|
94638
|
+
this.clearTimer(handle);
|
|
94639
|
+
this.timers.clear();
|
|
94640
|
+
this.registrations.clear();
|
|
94641
|
+
this.snapshots.clear();
|
|
94642
|
+
}
|
|
94643
|
+
hasScheduledWake(parentSessionID) {
|
|
94644
|
+
return this.timers.has(parentSessionID);
|
|
94645
|
+
}
|
|
94646
|
+
clearTimerFor(parentSessionID) {
|
|
94647
|
+
const handle = this.timers.get(parentSessionID);
|
|
94648
|
+
if (handle !== undefined) {
|
|
94649
|
+
this.clearTimer(handle);
|
|
94650
|
+
this.timers.delete(parentSessionID);
|
|
94651
|
+
}
|
|
94652
|
+
}
|
|
94653
|
+
async tick(parentSessionID) {
|
|
94654
|
+
this.timers.delete(parentSessionID);
|
|
94655
|
+
const registration = this.registrations.get(parentSessionID);
|
|
94656
|
+
if (!registration)
|
|
94657
|
+
return;
|
|
94658
|
+
const state3 = registration.readState();
|
|
94659
|
+
const previous = this.snapshots.get(parentSessionID);
|
|
94660
|
+
const progressed = previous === undefined || previous.hasPendingChildren !== state3.hasPendingChildren || previous.hasUndeliveredNotifications !== state3.hasUndeliveredNotifications || previous.hasUnfinishedTodos !== state3.hasUnfinishedTodos;
|
|
94661
|
+
this.snapshots.set(parentSessionID, state3);
|
|
94662
|
+
if (!shouldWakeForParent(state3)) {
|
|
94663
|
+
this.registrations.delete(parentSessionID);
|
|
94664
|
+
this.snapshots.delete(parentSessionID);
|
|
94665
|
+
return;
|
|
94666
|
+
}
|
|
94667
|
+
const allowed = this.tracker.recordWake(parentSessionID, progressed);
|
|
94668
|
+
if (allowed) {
|
|
94669
|
+
try {
|
|
94670
|
+
await registration.deliverWake();
|
|
94671
|
+
} catch {}
|
|
94672
|
+
}
|
|
94673
|
+
if (allowed && this.registrations.has(parentSessionID)) {
|
|
94674
|
+
this.armTimer(parentSessionID, () => void this.tick(parentSessionID), nextWakeDelay(Date.now(), registration.intervalMs));
|
|
94675
|
+
} else {
|
|
94676
|
+
this.registrations.delete(parentSessionID);
|
|
94677
|
+
this.snapshots.delete(parentSessionID);
|
|
94678
|
+
}
|
|
94679
|
+
}
|
|
94680
|
+
}
|
|
94681
|
+
|
|
94682
|
+
// src/features/background-agent/wallclock.ts
|
|
94683
|
+
var MAX_WALLCLOCK_TIMEOUT_MS = 2147483647;
|
|
94684
|
+
function isWallclockEnabled(timeoutMs) {
|
|
94685
|
+
return timeoutMs !== undefined && timeoutMs !== 0;
|
|
94686
|
+
}
|
|
94687
|
+
|
|
94688
|
+
class WallclockSupervisor {
|
|
94689
|
+
entries = new Map;
|
|
94690
|
+
arm(taskId, startedAtMs, timeoutMs, onFire) {
|
|
94691
|
+
if (!isWallclockEnabled(timeoutMs))
|
|
94692
|
+
return;
|
|
94693
|
+
const limitMs = Math.min(timeoutMs, MAX_WALLCLOCK_TIMEOUT_MS);
|
|
94694
|
+
this.disarm(taskId);
|
|
94695
|
+
const deadlineAt = startedAtMs + limitMs;
|
|
94696
|
+
const delayMs = Math.max(deadlineAt - Date.now(), 0);
|
|
94697
|
+
let timer;
|
|
94698
|
+
const entry = { deadlineAt, fired: false, timer };
|
|
94699
|
+
timer = setTimeout(() => {
|
|
94700
|
+
if (entry.fired)
|
|
94701
|
+
return;
|
|
94702
|
+
entry.fired = true;
|
|
94703
|
+
this.entries.delete(taskId);
|
|
94704
|
+
onFire({ taskId, elapsedMs: Date.now() - startedAtMs, limitMs });
|
|
94705
|
+
}, delayMs);
|
|
94706
|
+
entry.timer = timer;
|
|
94707
|
+
this.entries.set(taskId, entry);
|
|
94708
|
+
}
|
|
94709
|
+
disarm(taskId) {
|
|
94710
|
+
const entry = this.entries.get(taskId);
|
|
94711
|
+
if (entry === undefined)
|
|
94712
|
+
return false;
|
|
94713
|
+
entry.fired = true;
|
|
94714
|
+
if (entry.timer !== undefined)
|
|
94715
|
+
clearTimeout(entry.timer);
|
|
94716
|
+
this.entries.delete(taskId);
|
|
94717
|
+
return true;
|
|
94718
|
+
}
|
|
94719
|
+
disarmAll() {
|
|
94720
|
+
for (const entry of this.entries.values()) {
|
|
94721
|
+
entry.fired = true;
|
|
94722
|
+
if (entry.timer !== undefined)
|
|
94723
|
+
clearTimeout(entry.timer);
|
|
94724
|
+
}
|
|
94725
|
+
this.entries.clear();
|
|
94726
|
+
}
|
|
94727
|
+
size() {
|
|
94728
|
+
return this.entries.size;
|
|
94729
|
+
}
|
|
94730
|
+
getDeadlineAt(taskId) {
|
|
94731
|
+
return this.entries.get(taskId)?.deadlineAt;
|
|
94732
|
+
}
|
|
93946
94733
|
}
|
|
93947
94734
|
|
|
93948
94735
|
// src/features/background-agent/manager.ts
|
|
94736
|
+
var NESTED_RESERVE_ATTEMPT_TIMEOUT_MS = 100;
|
|
93949
94737
|
function restoreTaskFromHandle(handle) {
|
|
93950
|
-
const interrupted = handle.status === "running" || handle.status === "pending";
|
|
93951
94738
|
return {
|
|
93952
94739
|
id: handle.taskId,
|
|
93953
94740
|
parentSessionID: handle.parentSessionID,
|
|
@@ -93955,7 +94742,9 @@ function restoreTaskFromHandle(handle) {
|
|
|
93955
94742
|
description: handle.description,
|
|
93956
94743
|
prompt: "",
|
|
93957
94744
|
agent: handle.agent,
|
|
93958
|
-
status:
|
|
94745
|
+
status: handle.status,
|
|
94746
|
+
sessionID: handle.sessionID,
|
|
94747
|
+
terminalReason: handle.terminalReason,
|
|
93959
94748
|
queuedAt: handle.queuedAt !== undefined ? new Date(handle.queuedAt) : undefined,
|
|
93960
94749
|
startedAt: handle.startedAt !== undefined ? new Date(handle.startedAt) : undefined,
|
|
93961
94750
|
completedAt: handle.completedAt !== undefined ? new Date(handle.completedAt) : undefined,
|
|
@@ -93987,9 +94776,15 @@ class BackgroundManager {
|
|
|
93987
94776
|
processingKeys = new Set;
|
|
93988
94777
|
completionTimers = new Map;
|
|
93989
94778
|
idleDeferralTimers = new Map;
|
|
94779
|
+
wakeScheduler = new WakeScheduler;
|
|
93990
94780
|
notificationQueueByParent = new Map;
|
|
93991
94781
|
enableParentSessionNotifications;
|
|
93992
94782
|
cachedCircuitBreakerSettings;
|
|
94783
|
+
handoffCompression;
|
|
94784
|
+
nestedActive = new Map;
|
|
94785
|
+
nestedBypassTaskIds = new Set;
|
|
94786
|
+
wallclock = new WallclockSupervisor;
|
|
94787
|
+
wallclockGraceTimers = new Map;
|
|
93993
94788
|
taskHistory = new TaskHistory;
|
|
93994
94789
|
constructor(ctx, config4, options) {
|
|
93995
94790
|
this.tasks = new Map;
|
|
@@ -94003,6 +94798,7 @@ class BackgroundManager {
|
|
|
94003
94798
|
this.onSubagentSessionCreated = options?.onSubagentSessionCreated;
|
|
94004
94799
|
this.onShutdown = options?.onShutdown;
|
|
94005
94800
|
this.enableParentSessionNotifications = options?.enableParentSessionNotifications ?? true;
|
|
94801
|
+
this.handoffCompression = options?.handoffCompression;
|
|
94006
94802
|
this.registerProcessCleanup();
|
|
94007
94803
|
}
|
|
94008
94804
|
persistHandle(task) {
|
|
@@ -94012,6 +94808,66 @@ class BackgroundManager {
|
|
|
94012
94808
|
log("[background-agent] Failed to persist background handle:", { taskId: task.id, error: error95 });
|
|
94013
94809
|
}
|
|
94014
94810
|
}
|
|
94811
|
+
armWallclock(task) {
|
|
94812
|
+
const timeoutMs = this.config?.wallClockTimeoutMs ?? DEFAULT_WALL_CLOCK_TIMEOUT_MS;
|
|
94813
|
+
if (timeoutMs === 0 || timeoutMs === undefined || !task.startedAt)
|
|
94814
|
+
return;
|
|
94815
|
+
const startedAtMs = task.startedAt.getTime();
|
|
94816
|
+
this.wallclock.arm(task.id, startedAtMs, timeoutMs, () => {
|
|
94817
|
+
this.onWallclockFire(task.id);
|
|
94818
|
+
});
|
|
94819
|
+
}
|
|
94820
|
+
disarmWallclock(taskId) {
|
|
94821
|
+
this.wallclock.disarm(taskId);
|
|
94822
|
+
const grace = this.wallclockGraceTimers.get(taskId);
|
|
94823
|
+
if (grace !== undefined) {
|
|
94824
|
+
clearTimeout(grace);
|
|
94825
|
+
this.wallclockGraceTimers.delete(taskId);
|
|
94826
|
+
}
|
|
94827
|
+
}
|
|
94828
|
+
async onWallclockFire(taskId) {
|
|
94829
|
+
const task = this.tasks.get(taskId);
|
|
94830
|
+
if (task?.status !== "running")
|
|
94831
|
+
return;
|
|
94832
|
+
if (this.wallclockGraceTimers.has(taskId))
|
|
94833
|
+
return;
|
|
94834
|
+
if (task.sessionID) {
|
|
94835
|
+
this.abortSessionQuietly(task.sessionID, "wall-clock-timeout");
|
|
94836
|
+
}
|
|
94837
|
+
const graceMs = this.config?.wallClockAbortGraceMs ?? DEFAULT_WALL_CLOCK_ABORT_GRACE_MS;
|
|
94838
|
+
const startedAtMs = task.startedAt?.getTime() ?? Date.now();
|
|
94839
|
+
const limitMs = this.config?.wallClockTimeoutMs ?? DEFAULT_WALL_CLOCK_TIMEOUT_MS;
|
|
94840
|
+
const timer = setTimeout(() => {
|
|
94841
|
+
this.wallclockGraceTimers.delete(taskId);
|
|
94842
|
+
this.completeWallclockTimeout(taskId, startedAtMs, limitMs);
|
|
94843
|
+
}, graceMs);
|
|
94844
|
+
this.wallclockGraceTimers.set(taskId, timer);
|
|
94845
|
+
}
|
|
94846
|
+
async completeWallclockTimeout(taskId, startedAtMs, limitMs) {
|
|
94847
|
+
const task = this.tasks.get(taskId);
|
|
94848
|
+
if (task?.status !== "running")
|
|
94849
|
+
return;
|
|
94850
|
+
const elapsedMs = Date.now() - startedAtMs;
|
|
94851
|
+
const elapsedMin = Math.round(elapsedMs / 60000);
|
|
94852
|
+
const limitMin = Math.round(limitMs / 60000);
|
|
94853
|
+
task.status = "cancelled";
|
|
94854
|
+
task.terminalReason = "wall-clock-timeout";
|
|
94855
|
+
task.error = `Wall-clock timeout (elapsed ${elapsedMin}min, limit ${limitMin}min)
|
|
94856
|
+
|
|
94857
|
+
${formatWallclockTimeout(task.id, elapsedMs, limitMs)}`;
|
|
94858
|
+
task.completedAt = new Date;
|
|
94859
|
+
this.persistHandle(task);
|
|
94860
|
+
if (task.concurrencyKey) {
|
|
94861
|
+
this.concurrencyManager.release(task.concurrencyKey);
|
|
94862
|
+
task.concurrencyKey = undefined;
|
|
94863
|
+
}
|
|
94864
|
+
this.decrementNestedActive(task);
|
|
94865
|
+
try {
|
|
94866
|
+
await this.enqueueNotificationForParent(task.parentSessionID, () => this.notifyParentSession(task));
|
|
94867
|
+
} catch (err) {
|
|
94868
|
+
log("[background-agent] Error in notifyParentSession for wall-clock task:", { taskId: task.id, error: err });
|
|
94869
|
+
}
|
|
94870
|
+
}
|
|
94015
94871
|
async restoreHandles() {
|
|
94016
94872
|
const swept = sweepStaleHandles(this.directory);
|
|
94017
94873
|
const handles = readHandles(this.directory);
|
|
@@ -94020,13 +94876,44 @@ class BackgroundManager {
|
|
|
94020
94876
|
if (this.tasks.has(handle.taskId)) {
|
|
94021
94877
|
continue;
|
|
94022
94878
|
}
|
|
94023
|
-
|
|
94879
|
+
const task = restoreTaskFromHandle(handle);
|
|
94880
|
+
this.tasks.set(handle.taskId, task);
|
|
94024
94881
|
restored++;
|
|
94882
|
+
if (handle.status === "running" || handle.status === "pending") {
|
|
94883
|
+
await this.reconcileRestoredTask(task, handle);
|
|
94884
|
+
}
|
|
94025
94885
|
}
|
|
94026
94886
|
if (restored > 0 || swept > 0) {
|
|
94027
94887
|
log("[background-agent] Restored background handles:", { restored, swept });
|
|
94028
94888
|
}
|
|
94029
94889
|
}
|
|
94890
|
+
async reconcileRestoredTask(task, handle) {
|
|
94891
|
+
let outcome;
|
|
94892
|
+
try {
|
|
94893
|
+
outcome = await reconcileHandle(this.client, handle);
|
|
94894
|
+
} catch (error95) {
|
|
94895
|
+
log("[background-agent] Handle reconciliation failed:", { taskId: handle.taskId, error: error95 });
|
|
94896
|
+
outcome = { status: "statusUncertain", terminalReason: "uncertain" };
|
|
94897
|
+
}
|
|
94898
|
+
if (outcome.status === "running") {
|
|
94899
|
+
task.status = "running";
|
|
94900
|
+
if (task.sessionID) {
|
|
94901
|
+
registerSubagentSession(task.sessionID, task.parentSessionID);
|
|
94902
|
+
}
|
|
94903
|
+
task.concurrencyKey = undefined;
|
|
94904
|
+
const pending = this.pendingByParent.get(task.parentSessionID) ?? new Set;
|
|
94905
|
+
pending.add(task.id);
|
|
94906
|
+
this.pendingByParent.set(task.parentSessionID, pending);
|
|
94907
|
+
this.startPolling();
|
|
94908
|
+
this.armWallclock(task);
|
|
94909
|
+
return;
|
|
94910
|
+
}
|
|
94911
|
+
task.status = outcome.status;
|
|
94912
|
+
task.terminalReason = outcome.terminalReason;
|
|
94913
|
+
task.completedAt = new Date;
|
|
94914
|
+
this.persistHandle(task);
|
|
94915
|
+
this.disarmWallclock(task.id);
|
|
94916
|
+
}
|
|
94030
94917
|
async launch(input) {
|
|
94031
94918
|
log("[background-agent] launch() called with:", {
|
|
94032
94919
|
agent: input.agent,
|
|
@@ -94088,7 +94975,48 @@ class BackgroundManager {
|
|
|
94088
94975
|
const queue = this.queuesByKey.get(key);
|
|
94089
94976
|
while (queue && queue.length > 0) {
|
|
94090
94977
|
const item = queue[0];
|
|
94091
|
-
|
|
94978
|
+
const nestedEnabled = this.config?.nestedAdmission?.enabled ?? DEFAULT_NESTED_ADMISSION_ENABLED;
|
|
94979
|
+
const maxDepth = this.config?.nestedAdmission?.maxDepth ?? DEFAULT_NESTED_MAX_DEPTH;
|
|
94980
|
+
const admission = classifyAdmission(item.input.parentSessionID, maxDepth);
|
|
94981
|
+
if (admission.kind === "nested" && nestedEnabled) {
|
|
94982
|
+
const result = await this.admitNested(key, item, maxDepth);
|
|
94983
|
+
if (result.kind === "skip") {
|
|
94984
|
+
queue.shift();
|
|
94985
|
+
continue;
|
|
94986
|
+
}
|
|
94987
|
+
acquired = result.slotHeld;
|
|
94988
|
+
try {
|
|
94989
|
+
await this.startTask(item);
|
|
94990
|
+
acquired = false;
|
|
94991
|
+
} catch (error95) {
|
|
94992
|
+
log("[background-agent] Error starting nested task:", error95);
|
|
94993
|
+
if (!item.task.concurrencyKey && acquired) {
|
|
94994
|
+
this.concurrencyManager.release(key);
|
|
94995
|
+
acquired = false;
|
|
94996
|
+
}
|
|
94997
|
+
this.decrementNestedActive(item.task);
|
|
94998
|
+
}
|
|
94999
|
+
if (!result.slotHeld) {
|
|
95000
|
+
item.task.concurrencyKey = undefined;
|
|
95001
|
+
}
|
|
95002
|
+
queue.shift();
|
|
95003
|
+
continue;
|
|
95004
|
+
}
|
|
95005
|
+
const admissionTimeoutMs = this.config?.admissionTimeoutMs ?? DEFAULT_ADMISSION_TIMEOUT_MS;
|
|
95006
|
+
const outcome = await this.concurrencyManager.acquireWithDeadline(key, admissionTimeoutMs);
|
|
95007
|
+
if (!outcome.granted) {
|
|
95008
|
+
if (outcome.reason === "cancelled") {
|
|
95009
|
+
item.task.status = "cancelled";
|
|
95010
|
+
item.task.completedAt = new Date;
|
|
95011
|
+
this.persistHandle(item.task);
|
|
95012
|
+
queue.shift();
|
|
95013
|
+
continue;
|
|
95014
|
+
}
|
|
95015
|
+
const limit = this.concurrencyManager.getConcurrencyLimit(key);
|
|
95016
|
+
await this.terminateQueuedTask(item.task, "queue-saturated", `Queue admission timed out after ${outcome.waitedMs}ms (key "${key}", limit ${limit})`);
|
|
95017
|
+
queue.shift();
|
|
95018
|
+
continue;
|
|
95019
|
+
}
|
|
94092
95020
|
acquired = true;
|
|
94093
95021
|
if (item.task.status === "cancelled" || item.task.status === "error") {
|
|
94094
95022
|
this.concurrencyManager.release(key);
|
|
@@ -94115,6 +95043,76 @@ class BackgroundManager {
|
|
|
94115
95043
|
this.processingKeys.delete(key);
|
|
94116
95044
|
}
|
|
94117
95045
|
}
|
|
95046
|
+
async admitNested(key, item, maxDepth) {
|
|
95047
|
+
const mode = this.config?.nestedAdmission?.mode ?? DEFAULT_NESTED_ADMISSION_MODE;
|
|
95048
|
+
if (mode === "reserve") {
|
|
95049
|
+
const outcome = await this.concurrencyManager.acquireWithDeadline(key, NESTED_RESERVE_ATTEMPT_TIMEOUT_MS);
|
|
95050
|
+
if (outcome.granted) {
|
|
95051
|
+
return { kind: "admitted", slotHeld: true };
|
|
95052
|
+
}
|
|
95053
|
+
if (outcome.reason === "cancelled") {
|
|
95054
|
+
item.task.status = "cancelled";
|
|
95055
|
+
item.task.completedAt = new Date;
|
|
95056
|
+
this.persistHandle(item.task);
|
|
95057
|
+
return { kind: "skip" };
|
|
95058
|
+
}
|
|
95059
|
+
}
|
|
95060
|
+
const active = this.nestedActive.get(key) ?? 0;
|
|
95061
|
+
if (active >= maxDepth) {
|
|
95062
|
+
await this.terminateQueuedTask(item.task, "nested-depth-exceeded", `Nested admission depth exceeded (maxDepth ${maxDepth}, key "${key}")`);
|
|
95063
|
+
return { kind: "skip" };
|
|
95064
|
+
}
|
|
95065
|
+
this.nestedActive.set(key, active + 1);
|
|
95066
|
+
this.nestedBypassTaskIds.add(item.task.id);
|
|
95067
|
+
return { kind: "admitted", slotHeld: false };
|
|
95068
|
+
}
|
|
95069
|
+
async terminateQueuedTask(task, terminalReason, errorMessage) {
|
|
95070
|
+
task.status = "stopped";
|
|
95071
|
+
task.terminalReason = terminalReason;
|
|
95072
|
+
task.completedAt = new Date;
|
|
95073
|
+
task.error = errorMessage;
|
|
95074
|
+
this.persistHandle(task);
|
|
95075
|
+
this.disarmWallclock(task.id);
|
|
95076
|
+
this.taskHistory.record(task.parentSessionID, {
|
|
95077
|
+
id: task.id,
|
|
95078
|
+
sessionID: task.sessionID,
|
|
95079
|
+
agent: task.agent,
|
|
95080
|
+
description: task.description,
|
|
95081
|
+
status: "stopped",
|
|
95082
|
+
category: task.category,
|
|
95083
|
+
startedAt: task.startedAt,
|
|
95084
|
+
completedAt: task.completedAt
|
|
95085
|
+
});
|
|
95086
|
+
const existingTimer = this.completionTimers.get(task.id);
|
|
95087
|
+
if (existingTimer) {
|
|
95088
|
+
clearTimeout(existingTimer);
|
|
95089
|
+
this.completionTimers.delete(task.id);
|
|
95090
|
+
}
|
|
95091
|
+
const idleTimer = this.idleDeferralTimers.get(task.id);
|
|
95092
|
+
if (idleTimer) {
|
|
95093
|
+
clearTimeout(idleTimer);
|
|
95094
|
+
this.idleDeferralTimers.delete(task.id);
|
|
95095
|
+
}
|
|
95096
|
+
this.cleanupPendingByParent(task);
|
|
95097
|
+
this.markForNotification(task);
|
|
95098
|
+
try {
|
|
95099
|
+
await this.enqueueNotificationForParent(task.parentSessionID, () => this.notifyParentSession(task));
|
|
95100
|
+
} catch (err) {
|
|
95101
|
+
log("[background-agent] Error in notifyParentSession for stopped task:", { taskId: task.id, error: err });
|
|
95102
|
+
}
|
|
95103
|
+
}
|
|
95104
|
+
decrementNestedActive(task) {
|
|
95105
|
+
if (!this.nestedBypassTaskIds.has(task.id))
|
|
95106
|
+
return;
|
|
95107
|
+
this.nestedBypassTaskIds.delete(task.id);
|
|
95108
|
+
const key = task.concurrencyGroup ?? task.concurrencyKey;
|
|
95109
|
+
if (!key)
|
|
95110
|
+
return;
|
|
95111
|
+
const current = this.nestedActive.get(key) ?? 0;
|
|
95112
|
+
if (current > 0) {
|
|
95113
|
+
this.nestedActive.set(key, current - 1);
|
|
95114
|
+
}
|
|
95115
|
+
}
|
|
94118
95116
|
async startTask(item) {
|
|
94119
95117
|
const { task, input } = item;
|
|
94120
95118
|
log("[background-agent] Starting task:", {
|
|
@@ -94179,6 +95177,7 @@ class BackgroundManager {
|
|
|
94179
95177
|
task.concurrencyKey = concurrencyKey;
|
|
94180
95178
|
task.concurrencyGroup = concurrencyKey;
|
|
94181
95179
|
this.persistHandle(task);
|
|
95180
|
+
this.armWallclock(task);
|
|
94182
95181
|
this.taskHistory.record(input.parentSessionID, { id: task.id, sessionID, agent: input.agent, description: input.description, status: "running", category: input.category, startedAt: task.startedAt });
|
|
94183
95182
|
this.startPolling();
|
|
94184
95183
|
log("[background-agent] Launching task:", { taskId: task.id, sessionID, agent: input.agent });
|
|
@@ -94231,6 +95230,7 @@ class BackgroundManager {
|
|
|
94231
95230
|
}
|
|
94232
95231
|
existingTask.completedAt = new Date;
|
|
94233
95232
|
this.persistHandle(existingTask);
|
|
95233
|
+
this.disarmWallclock(existingTask.id);
|
|
94234
95234
|
if (existingTask.concurrencyKey) {
|
|
94235
95235
|
this.concurrencyManager.release(existingTask.concurrencyKey);
|
|
94236
95236
|
existingTask.concurrencyKey = undefined;
|
|
@@ -94381,6 +95381,7 @@ class BackgroundManager {
|
|
|
94381
95381
|
}
|
|
94382
95382
|
existingTask.startedAt = new Date;
|
|
94383
95383
|
this.persistHandle(existingTask);
|
|
95384
|
+
this.armWallclock(existingTask);
|
|
94384
95385
|
existingTask.progress = {
|
|
94385
95386
|
toolCalls: existingTask.progress?.toolCalls ?? 0,
|
|
94386
95387
|
lastUpdate: new Date
|
|
@@ -94437,6 +95438,7 @@ class BackgroundManager {
|
|
|
94437
95438
|
existingTask.error = errorMessage;
|
|
94438
95439
|
existingTask.completedAt = new Date;
|
|
94439
95440
|
this.persistHandle(existingTask);
|
|
95441
|
+
this.disarmWallclock(existingTask.id);
|
|
94440
95442
|
if (existingTask.concurrencyKey) {
|
|
94441
95443
|
this.concurrencyManager.release(existingTask.concurrencyKey);
|
|
94442
95444
|
existingTask.concurrencyKey = undefined;
|
|
@@ -94452,6 +95454,158 @@ class BackgroundManager {
|
|
|
94452
95454
|
});
|
|
94453
95455
|
return existingTask;
|
|
94454
95456
|
}
|
|
95457
|
+
async revive(input) {
|
|
95458
|
+
const id = input.taskId ?? input.sessionId ?? "";
|
|
95459
|
+
let task = input.taskId !== undefined ? this.tasks.get(input.taskId) : undefined;
|
|
95460
|
+
if (!task && input.sessionId !== undefined)
|
|
95461
|
+
task = this.findBySession(input.sessionId);
|
|
95462
|
+
if (task?.status === "running") {
|
|
95463
|
+
log("[background-agent] Revive skipped - task already running:", { taskId: task.id, sessionID: task.sessionID });
|
|
95464
|
+
return task;
|
|
95465
|
+
}
|
|
95466
|
+
const guidance = { active: `Task ${id} is still active; use background_output to inspect it.`, uncertain: `Task ${id} has unknown liveness; re-run with force: true to acknowledge this.`, "no-session": `Task ${id} has no session to revive (it never produced one, e.g. it was queue-saturated).`, "unknown-task": `Task ${id} is not revivable (unrecognised state).`, expired: `Task ${id} is not revivable (unrecognised state).` };
|
|
95467
|
+
if (!task) {
|
|
95468
|
+
const handle = findRevivableHandle(readHandles(this.directory), { taskId: input.taskId, sessionId: input.sessionId });
|
|
95469
|
+
if (!handle)
|
|
95470
|
+
throw new Error(`Task not found for revive: ${id}. It may have expired (handles are retained 30 minutes) or never existed.`);
|
|
95471
|
+
const handleEligibility = classifyRevivable(handle, { force: input.force });
|
|
95472
|
+
if (!handleEligibility.eligible)
|
|
95473
|
+
throw new Error(guidance[handleEligibility.reason]);
|
|
95474
|
+
task = toReviveTask(handle, input.prompt);
|
|
95475
|
+
} else {
|
|
95476
|
+
task.prompt = input.prompt;
|
|
95477
|
+
const taskEligibility = classifyRevivable(task, { force: input.force });
|
|
95478
|
+
if (!taskEligibility.eligible)
|
|
95479
|
+
throw new Error(guidance[taskEligibility.reason]);
|
|
95480
|
+
}
|
|
95481
|
+
if (!task.sessionID)
|
|
95482
|
+
throw new Error(`Task has no sessionID: ${task.id}`);
|
|
95483
|
+
const sessionID = task.sessionID;
|
|
95484
|
+
const completionTimer = this.completionTimers.get(task.id);
|
|
95485
|
+
if (completionTimer) {
|
|
95486
|
+
clearTimeout(completionTimer);
|
|
95487
|
+
this.completionTimers.delete(task.id);
|
|
95488
|
+
}
|
|
95489
|
+
task.status = "pending";
|
|
95490
|
+
task.parentSessionID = input.parentSessionID;
|
|
95491
|
+
task.parentMessageID = input.parentMessageID;
|
|
95492
|
+
if (input.parentModel !== undefined)
|
|
95493
|
+
task.parentModel = input.parentModel;
|
|
95494
|
+
if (input.parentAgent !== undefined)
|
|
95495
|
+
task.parentAgent = input.parentAgent;
|
|
95496
|
+
if (input.parentTools !== undefined)
|
|
95497
|
+
task.parentTools = input.parentTools;
|
|
95498
|
+
this.tasks.set(task.id, task);
|
|
95499
|
+
this.taskHistory.record(task.parentSessionID, { id: task.id, sessionID, agent: task.agent, description: task.description, status: "pending", startedAt: task.startedAt });
|
|
95500
|
+
const pending = this.pendingByParent.get(task.parentSessionID) ?? new Set;
|
|
95501
|
+
pending.add(task.id);
|
|
95502
|
+
this.pendingByParent.set(task.parentSessionID, pending);
|
|
95503
|
+
const key = task.concurrencyGroup ?? task.agent;
|
|
95504
|
+
const maxDepth = this.config?.nestedAdmission?.maxDepth ?? DEFAULT_NESTED_MAX_DEPTH;
|
|
95505
|
+
const admissionTimeoutMs = this.config?.admissionTimeoutMs ?? DEFAULT_ADMISSION_TIMEOUT_MS;
|
|
95506
|
+
const nestedEnabled = this.config?.nestedAdmission?.enabled ?? DEFAULT_NESTED_ADMISSION_ENABLED;
|
|
95507
|
+
const nestedMode = this.config?.nestedAdmission?.mode ?? DEFAULT_NESTED_ADMISSION_MODE;
|
|
95508
|
+
const admission = classifyAdmission(input.parentSessionID, maxDepth);
|
|
95509
|
+
log("[background-agent] Reviving task:", { taskId: task.id, admission: admission.kind, key });
|
|
95510
|
+
let slotHeld = true;
|
|
95511
|
+
if (admission.kind === "nested" && nestedEnabled) {
|
|
95512
|
+
let bypass = true;
|
|
95513
|
+
if (nestedMode === "reserve") {
|
|
95514
|
+
const reserved = await this.concurrencyManager.acquireWithDeadline(key, NESTED_RESERVE_ATTEMPT_TIMEOUT_MS);
|
|
95515
|
+
if (reserved.granted) {
|
|
95516
|
+
bypass = false;
|
|
95517
|
+
} else if (reserved.reason === "cancelled") {
|
|
95518
|
+
task.status = "cancelled";
|
|
95519
|
+
this.persistHandle(task);
|
|
95520
|
+
throw new Error(`Revive admission cancelled for task ${task.id}.`);
|
|
95521
|
+
}
|
|
95522
|
+
}
|
|
95523
|
+
if (bypass) {
|
|
95524
|
+
const active = this.nestedActive.get(key) ?? 0;
|
|
95525
|
+
if (active >= maxDepth) {
|
|
95526
|
+
const depthMessage = `Nested admission depth exceeded (maxDepth ${maxDepth}, key "${key}")`;
|
|
95527
|
+
await this.terminateQueuedTask(task, "nested-depth-exceeded", depthMessage);
|
|
95528
|
+
throw new Error(depthMessage);
|
|
95529
|
+
}
|
|
95530
|
+
this.nestedActive.set(key, active + 1);
|
|
95531
|
+
this.nestedBypassTaskIds.add(task.id);
|
|
95532
|
+
slotHeld = false;
|
|
95533
|
+
}
|
|
95534
|
+
} else {
|
|
95535
|
+
const outcome = await this.concurrencyManager.acquireWithDeadline(key, admissionTimeoutMs);
|
|
95536
|
+
if (!outcome.granted) {
|
|
95537
|
+
if (outcome.reason === "cancelled") {
|
|
95538
|
+
task.status = "cancelled";
|
|
95539
|
+
this.persistHandle(task);
|
|
95540
|
+
throw new Error(`Revive admission cancelled for task ${task.id}.`);
|
|
95541
|
+
}
|
|
95542
|
+
const errorMessage = `Queue admission timed out after ${outcome.waitedMs}ms (key "${key}", limit ${this.concurrencyManager.getConcurrencyLimit(key)})`;
|
|
95543
|
+
await this.terminateQueuedTask(task, "queue-saturated", errorMessage);
|
|
95544
|
+
throw new Error(errorMessage);
|
|
95545
|
+
}
|
|
95546
|
+
}
|
|
95547
|
+
task.concurrencyGroup = key;
|
|
95548
|
+
task.concurrencyKey = slotHeld ? key : undefined;
|
|
95549
|
+
task.status = "running";
|
|
95550
|
+
task.startedAt = new Date;
|
|
95551
|
+
task.completedAt = undefined;
|
|
95552
|
+
task.error = undefined;
|
|
95553
|
+
task.progress = { toolCalls: task.progress?.toolCalls ?? 0, lastUpdate: new Date };
|
|
95554
|
+
this.persistHandle(task);
|
|
95555
|
+
this.armWallclock(task);
|
|
95556
|
+
registerSubagentSession(sessionID, input.parentSessionID);
|
|
95557
|
+
this.startPolling();
|
|
95558
|
+
const toastManager = getTaskToastManager();
|
|
95559
|
+
if (toastManager)
|
|
95560
|
+
toastManager.addTask({ id: task.id, description: task.description, agent: task.agent, isBackground: true });
|
|
95561
|
+
const tools = { task: false, delegate_agent: true, question: false, ...getAgentToolRestrictions(task.agent) };
|
|
95562
|
+
setSessionTools(sessionID, tools);
|
|
95563
|
+
const reviveModel = task.model ? { providerID: task.model.providerID, modelID: task.model.modelID } : undefined;
|
|
95564
|
+
const reviveVariant = task.model?.variant;
|
|
95565
|
+
this.client.session.promptAsync({
|
|
95566
|
+
path: { id: sessionID },
|
|
95567
|
+
body: { agent: task.agent, ...reviveModel ? { model: reviveModel } : {}, ...reviveVariant ? { variant: reviveVariant } : {}, tools, parts: [{ type: "text", text: input.prompt }] }
|
|
95568
|
+
}).catch((error95) => {
|
|
95569
|
+
log("[background-agent] revive prompt error:", error95);
|
|
95570
|
+
task.status = "interrupt";
|
|
95571
|
+
task.error = error95 instanceof Error ? error95.message : String(error95);
|
|
95572
|
+
task.completedAt = new Date;
|
|
95573
|
+
this.persistHandle(task);
|
|
95574
|
+
this.disarmWallclock(task.id);
|
|
95575
|
+
this.decrementNestedActive(task);
|
|
95576
|
+
if (task.concurrencyKey) {
|
|
95577
|
+
this.concurrencyManager.release(task.concurrencyKey);
|
|
95578
|
+
task.concurrencyKey = undefined;
|
|
95579
|
+
}
|
|
95580
|
+
this.abortSessionQuietly(sessionID, "revive-error");
|
|
95581
|
+
this.markForNotification(task);
|
|
95582
|
+
this.cleanupPendingByParent(task);
|
|
95583
|
+
this.enqueueNotificationForParent(task.parentSessionID, () => this.notifyParentSession(task)).catch((err) => {
|
|
95584
|
+
log("[background-agent] Failed to notify on revive error:", err);
|
|
95585
|
+
});
|
|
95586
|
+
});
|
|
95587
|
+
return task;
|
|
95588
|
+
}
|
|
95589
|
+
listRevivable(parentSessionID) {
|
|
95590
|
+
const seen = new Set;
|
|
95591
|
+
const rows = [];
|
|
95592
|
+
const consider = (id, parent, description, agent, status, sessionID, terminalReason, ts) => {
|
|
95593
|
+
if (seen.has(id) || sessionID === undefined)
|
|
95594
|
+
return;
|
|
95595
|
+
if (parentSessionID !== undefined && parent !== parentSessionID)
|
|
95596
|
+
return;
|
|
95597
|
+
if (!classifyRevivable({ status, sessionID }, {}).eligible)
|
|
95598
|
+
return;
|
|
95599
|
+
seen.add(id);
|
|
95600
|
+
rows.push({ taskId: id, description, agent, status, sessionID, terminalReason, ts });
|
|
95601
|
+
};
|
|
95602
|
+
for (const task of this.tasks.values())
|
|
95603
|
+
consider(task.id, task.parentSessionID, task.description, task.agent, task.status, task.sessionID, task.terminalReason, task.completedAt?.getTime() ?? task.startedAt?.getTime() ?? task.queuedAt?.getTime() ?? 0);
|
|
95604
|
+
for (const handle of readHandles(this.directory))
|
|
95605
|
+
consider(handle.taskId, handle.parentSessionID, handle.description, handle.agent, handle.status, handle.sessionID, handle.terminalReason, handle.completedAt ?? handle.startedAt ?? handle.queuedAt ?? 0);
|
|
95606
|
+
rows.sort((a, b) => b.ts - a.ts);
|
|
95607
|
+
return rows.map((row) => ({ taskId: row.taskId, description: row.description, agent: row.agent, status: row.status, sessionID: row.sessionID, terminalReason: row.terminalReason }));
|
|
95608
|
+
}
|
|
94455
95609
|
async checkSessionTodos(sessionID) {
|
|
94456
95610
|
try {
|
|
94457
95611
|
const response = await this.client.session.todo({
|
|
@@ -94576,12 +95730,12 @@ class BackgroundManager {
|
|
|
94576
95730
|
log("[background-agent] Session.idle but no valid output yet, waiting:", task.id);
|
|
94577
95731
|
return;
|
|
94578
95732
|
}
|
|
94579
|
-
const
|
|
95733
|
+
const hasIncompleteTodos3 = await this.checkSessionTodos(sessionID);
|
|
94580
95734
|
if (task.status !== "running") {
|
|
94581
95735
|
log("[background-agent] Task status changed during todo check, skipping:", { taskId: task.id, status: task.status });
|
|
94582
95736
|
return;
|
|
94583
95737
|
}
|
|
94584
|
-
if (
|
|
95738
|
+
if (hasIncompleteTodos3) {
|
|
94585
95739
|
log("[background-agent] Task has incomplete todos, waiting for todo-continuation:", task.id);
|
|
94586
95740
|
return;
|
|
94587
95741
|
}
|
|
@@ -94602,11 +95756,13 @@ class BackgroundManager {
|
|
|
94602
95756
|
task.error = errorMessage ?? "Session error";
|
|
94603
95757
|
task.completedAt = new Date;
|
|
94604
95758
|
this.persistHandle(task);
|
|
95759
|
+
this.disarmWallclock(task.id);
|
|
94605
95760
|
this.taskHistory.record(task.parentSessionID, { id: task.id, sessionID: task.sessionID, agent: task.agent, description: task.description, status: "error", category: task.category, startedAt: task.startedAt, completedAt: task.completedAt });
|
|
94606
95761
|
if (task.concurrencyKey) {
|
|
94607
95762
|
this.concurrencyManager.release(task.concurrencyKey);
|
|
94608
95763
|
task.concurrencyKey = undefined;
|
|
94609
95764
|
}
|
|
95765
|
+
this.decrementNestedActive(task);
|
|
94610
95766
|
const completionTimer = this.completionTimers.get(task.id);
|
|
94611
95767
|
if (completionTimer) {
|
|
94612
95768
|
clearTimeout(completionTimer);
|
|
@@ -94620,6 +95776,7 @@ class BackgroundManager {
|
|
|
94620
95776
|
this.cleanupPendingByParent(task);
|
|
94621
95777
|
this.tasks.delete(task.id);
|
|
94622
95778
|
this.clearNotificationsForTask(task.id);
|
|
95779
|
+
this.refreshParentWake(task.parentSessionID);
|
|
94623
95780
|
const toastManager = getTaskToastManager();
|
|
94624
95781
|
if (toastManager) {
|
|
94625
95782
|
toastManager.removeTask(task.id);
|
|
@@ -94691,31 +95848,7 @@ class BackgroundManager {
|
|
|
94691
95848
|
this.notifications.delete(sessionID);
|
|
94692
95849
|
}
|
|
94693
95850
|
async validateSessionHasOutput(sessionID) {
|
|
94694
|
-
|
|
94695
|
-
const response = await this.client.session.messages({
|
|
94696
|
-
path: { id: sessionID }
|
|
94697
|
-
});
|
|
94698
|
-
const messages2 = normalizeSDKResponse(response, [], { preferResponseOnMissingData: true });
|
|
94699
|
-
const hasAssistantOrToolMessage = messages2.some((m) => m.info?.role === "assistant" || m.info?.role === "tool");
|
|
94700
|
-
if (!hasAssistantOrToolMessage) {
|
|
94701
|
-
log("[background-agent] No assistant/tool messages found in session:", sessionID);
|
|
94702
|
-
return false;
|
|
94703
|
-
}
|
|
94704
|
-
const hasContent2 = messages2.some((m) => {
|
|
94705
|
-
if (m.info?.role !== "assistant" && m.info?.role !== "tool")
|
|
94706
|
-
return false;
|
|
94707
|
-
const parts = m.parts ?? [];
|
|
94708
|
-
return parts.some((p) => p.type === "text" && p.text && p.text.trim().length > 0 || p.type === "reasoning" && p.text && p.text.trim().length > 0 || p.type === "tool" || p.type === "tool_result" && p.content && (typeof p.content === "string" ? p.content.trim().length > 0 : p.content.length > 0));
|
|
94709
|
-
});
|
|
94710
|
-
if (!hasContent2) {
|
|
94711
|
-
log("[background-agent] Messages exist but no content found in session:", sessionID);
|
|
94712
|
-
return false;
|
|
94713
|
-
}
|
|
94714
|
-
return true;
|
|
94715
|
-
} catch (error95) {
|
|
94716
|
-
log("[background-agent] Error validating session output:", error95);
|
|
94717
|
-
return true;
|
|
94718
|
-
}
|
|
95851
|
+
return validateSessionHasOutput(this.client, sessionID);
|
|
94719
95852
|
}
|
|
94720
95853
|
clearNotificationsForTask(taskId) {
|
|
94721
95854
|
for (const [sessionID, tasks] of this.notifications.entries()) {
|
|
@@ -94738,6 +95871,27 @@ class BackgroundManager {
|
|
|
94738
95871
|
}
|
|
94739
95872
|
}
|
|
94740
95873
|
}
|
|
95874
|
+
refreshParentWake(parentSessionID) {
|
|
95875
|
+
if (this.config?.wakeScheduler?.enabled === false)
|
|
95876
|
+
return;
|
|
95877
|
+
const readState2 = () => ({
|
|
95878
|
+
hasPendingChildren: (this.pendingByParent.get(parentSessionID)?.size ?? 0) > 0,
|
|
95879
|
+
hasUndeliveredNotifications: (this.notifications.get(parentSessionID)?.length ?? 0) > 0
|
|
95880
|
+
});
|
|
95881
|
+
if (!shouldWakeForParent(readState2())) {
|
|
95882
|
+
this.wakeScheduler.clear(parentSessionID);
|
|
95883
|
+
return;
|
|
95884
|
+
}
|
|
95885
|
+
this.wakeScheduler.schedule(parentSessionID, {
|
|
95886
|
+
intervalMs: resolveWakeIntervalMs(this.config?.wakeScheduler),
|
|
95887
|
+
readState: readState2,
|
|
95888
|
+
deliverWake: async () => {
|
|
95889
|
+
await this.enqueueNotificationForParent(parentSessionID, async () => {
|
|
95890
|
+
await this.client.session.promptAsync({ path: { id: parentSessionID }, body: { noReply: true, parts: [{ type: "text", text: WAKE_REMINDER_TEXT }] } });
|
|
95891
|
+
});
|
|
95892
|
+
}
|
|
95893
|
+
});
|
|
95894
|
+
}
|
|
94741
95895
|
abortSessionQuietly(sessionID, context) {
|
|
94742
95896
|
this.client.session.abort({ path: { id: sessionID } }).catch((err) => {
|
|
94743
95897
|
log(`[background-agent] Session abort failed${context ? ` (${context})` : ""}:`, {
|
|
@@ -94771,6 +95925,7 @@ class BackgroundManager {
|
|
|
94771
95925
|
task.status = "cancelled";
|
|
94772
95926
|
task.completedAt = new Date;
|
|
94773
95927
|
this.persistHandle(task);
|
|
95928
|
+
this.disarmWallclock(task.id);
|
|
94774
95929
|
if (reason) {
|
|
94775
95930
|
task.error = reason;
|
|
94776
95931
|
}
|
|
@@ -94779,6 +95934,7 @@ class BackgroundManager {
|
|
|
94779
95934
|
this.concurrencyManager.release(task.concurrencyKey);
|
|
94780
95935
|
task.concurrencyKey = undefined;
|
|
94781
95936
|
}
|
|
95937
|
+
this.decrementNestedActive(task);
|
|
94782
95938
|
const existingTimer = this.completionTimers.get(task.id);
|
|
94783
95939
|
if (existingTimer) {
|
|
94784
95940
|
clearTimeout(existingTimer);
|
|
@@ -94805,6 +95961,7 @@ class BackgroundManager {
|
|
|
94805
95961
|
try {
|
|
94806
95962
|
await this.enqueueNotificationForParent(task.parentSessionID, () => this.notifyParentSession(task));
|
|
94807
95963
|
log(`[background-agent] Task cancelled via ${source}:`, task.id);
|
|
95964
|
+
this.refreshParentWake(task.parentSessionID);
|
|
94808
95965
|
} catch (err) {
|
|
94809
95966
|
log("[background-agent] Error in notifyParentSession for cancelled task:", { taskId: task.id, error: err });
|
|
94810
95967
|
}
|
|
@@ -94893,12 +96050,48 @@ class BackgroundManager {
|
|
|
94893
96050
|
}
|
|
94894
96051
|
task.status = "completed";
|
|
94895
96052
|
task.completedAt = new Date;
|
|
96053
|
+
const hc = this.handoffCompression;
|
|
96054
|
+
if (hc && hc.enabled !== false && task.sessionID) {
|
|
96055
|
+
try {
|
|
96056
|
+
const raw = await this.client.session.messages({ path: { id: task.sessionID } });
|
|
96057
|
+
let msgs = [];
|
|
96058
|
+
if (Array.isArray(raw)) {
|
|
96059
|
+
msgs = raw;
|
|
96060
|
+
} else if (raw && typeof raw === "object" && "data" in raw && Array.isArray(raw.data)) {
|
|
96061
|
+
msgs = raw.data;
|
|
96062
|
+
}
|
|
96063
|
+
const assistantMsgs = msgs.filter((m) => m.info?.role === "assistant");
|
|
96064
|
+
const maxMsgs = hc?.maxMessages ?? 6;
|
|
96065
|
+
const keepFirst = hc?.keepFirst ?? 2;
|
|
96066
|
+
const keepLast = hc?.keepLast ?? 3;
|
|
96067
|
+
if (assistantMsgs.length > maxMsgs) {
|
|
96068
|
+
const first = assistantMsgs.slice(0, keepFirst);
|
|
96069
|
+
const last = assistantMsgs.slice(-keepLast);
|
|
96070
|
+
const extractText = (msgs2) => msgs2.map((m) => m.parts?.filter((p) => p.type === "text" || p.type === "reasoning").map((p) => p.text).filter(Boolean).join(`
|
|
96071
|
+
`) ?? "").filter(Boolean).join(`
|
|
96072
|
+
|
|
96073
|
+
`);
|
|
96074
|
+
const firstText = extractText(first);
|
|
96075
|
+
const lastText = extractText(last);
|
|
96076
|
+
const truncated = assistantMsgs.length - keepFirst - keepLast;
|
|
96077
|
+
task.compactedResult = `${firstText}
|
|
96078
|
+
|
|
96079
|
+
[... ${truncated} messages truncated]
|
|
96080
|
+
|
|
96081
|
+
${lastText}`;
|
|
96082
|
+
}
|
|
96083
|
+
} catch (err) {
|
|
96084
|
+
log("[background-agent] Handoff compression failed:", { taskId: task.id, error: err });
|
|
96085
|
+
}
|
|
96086
|
+
}
|
|
94896
96087
|
this.persistHandle(task);
|
|
96088
|
+
this.disarmWallclock(task.id);
|
|
94897
96089
|
this.taskHistory.record(task.parentSessionID, { id: task.id, sessionID: task.sessionID, agent: task.agent, description: task.description, status: "completed", category: task.category, startedAt: task.startedAt, completedAt: task.completedAt });
|
|
94898
96090
|
if (task.concurrencyKey) {
|
|
94899
96091
|
this.concurrencyManager.release(task.concurrencyKey);
|
|
94900
96092
|
task.concurrencyKey = undefined;
|
|
94901
96093
|
}
|
|
96094
|
+
this.decrementNestedActive(task);
|
|
94902
96095
|
this.markForNotification(task);
|
|
94903
96096
|
this.cleanupPendingByParent(task);
|
|
94904
96097
|
const idleTimer = this.idleDeferralTimers.get(task.id);
|
|
@@ -94912,6 +96105,7 @@ class BackgroundManager {
|
|
|
94912
96105
|
try {
|
|
94913
96106
|
await this.enqueueNotificationForParent(task.parentSessionID, () => this.notifyParentSession(task));
|
|
94914
96107
|
log(`[background-agent] Task completed via ${source}:`, task.id);
|
|
96108
|
+
this.refreshParentWake(task.parentSessionID);
|
|
94915
96109
|
} catch (err) {
|
|
94916
96110
|
log("[background-agent] Error in notifyParentSession:", { taskId: task.id, error: err });
|
|
94917
96111
|
}
|
|
@@ -95027,10 +96221,12 @@ class BackgroundManager {
|
|
|
95027
96221
|
task.error = errorMessage;
|
|
95028
96222
|
task.completedAt = new Date;
|
|
95029
96223
|
this.persistHandle(task);
|
|
96224
|
+
this.disarmWallclock(task.id);
|
|
95030
96225
|
if (task.concurrencyKey) {
|
|
95031
96226
|
this.concurrencyManager.release(task.concurrencyKey);
|
|
95032
96227
|
task.concurrencyKey = undefined;
|
|
95033
96228
|
}
|
|
96229
|
+
this.decrementNestedActive(task);
|
|
95034
96230
|
this.cleanupPendingByParent(task);
|
|
95035
96231
|
if (wasPending) {
|
|
95036
96232
|
const key = task.model ? `${task.model.providerID}/${task.model.modelID}` : task.agent;
|
|
@@ -95073,6 +96269,8 @@ class BackgroundManager {
|
|
|
95073
96269
|
this.notifications.set(sessionID, validNotifications);
|
|
95074
96270
|
}
|
|
95075
96271
|
}
|
|
96272
|
+
this.nestedActive.clear();
|
|
96273
|
+
this.nestedBypassTaskIds.clear();
|
|
95076
96274
|
}
|
|
95077
96275
|
async isSessionAwaitingUser(sessionID) {
|
|
95078
96276
|
try {
|
|
@@ -95112,10 +96310,12 @@ class BackgroundManager {
|
|
|
95112
96310
|
task.error = `Stale timeout (no activity for ${staleMinutes2}min since start)`;
|
|
95113
96311
|
task.completedAt = new Date;
|
|
95114
96312
|
this.persistHandle(task);
|
|
96313
|
+
this.disarmWallclock(task.id);
|
|
95115
96314
|
if (task.concurrencyKey) {
|
|
95116
96315
|
this.concurrencyManager.release(task.concurrencyKey);
|
|
95117
96316
|
task.concurrencyKey = undefined;
|
|
95118
96317
|
}
|
|
96318
|
+
this.decrementNestedActive(task);
|
|
95119
96319
|
this.abortSessionQuietly(sessionID, "stale-no-progress");
|
|
95120
96320
|
log(`[background-agent] Task ${task.id} interrupted: no progress since start`);
|
|
95121
96321
|
try {
|
|
@@ -95141,10 +96341,12 @@ class BackgroundManager {
|
|
|
95141
96341
|
task.error = `Stale timeout (no activity for ${staleMinutes}min)`;
|
|
95142
96342
|
task.completedAt = new Date;
|
|
95143
96343
|
this.persistHandle(task);
|
|
96344
|
+
this.disarmWallclock(task.id);
|
|
95144
96345
|
if (task.concurrencyKey) {
|
|
95145
96346
|
this.concurrencyManager.release(task.concurrencyKey);
|
|
95146
96347
|
task.concurrencyKey = undefined;
|
|
95147
96348
|
}
|
|
96349
|
+
this.decrementNestedActive(task);
|
|
95148
96350
|
this.abortSessionQuietly(sessionID, "stale-timeout");
|
|
95149
96351
|
log(`[background-agent] Task ${task.id} interrupted: stale timeout`);
|
|
95150
96352
|
try {
|
|
@@ -95182,8 +96384,8 @@ class BackgroundManager {
|
|
|
95182
96384
|
}
|
|
95183
96385
|
if (task.status !== "running")
|
|
95184
96386
|
continue;
|
|
95185
|
-
const
|
|
95186
|
-
if (
|
|
96387
|
+
const hasIncompleteTodos3 = await this.checkSessionTodos(sessionID);
|
|
96388
|
+
if (hasIncompleteTodos3) {
|
|
95187
96389
|
log("[background-agent] Task has incomplete todos via polling, waiting:", task.id);
|
|
95188
96390
|
continue;
|
|
95189
96391
|
}
|
|
@@ -95239,6 +96441,12 @@ class BackgroundManager {
|
|
|
95239
96441
|
clearTimeout(timer);
|
|
95240
96442
|
}
|
|
95241
96443
|
this.idleDeferralTimers.clear();
|
|
96444
|
+
this.wakeScheduler.dispose();
|
|
96445
|
+
this.wallclock.disarmAll();
|
|
96446
|
+
for (const timer of this.wallclockGraceTimers.values()) {
|
|
96447
|
+
clearTimeout(timer);
|
|
96448
|
+
}
|
|
96449
|
+
this.wallclockGraceTimers.clear();
|
|
95242
96450
|
this.concurrencyManager.clear();
|
|
95243
96451
|
this.tasks.clear();
|
|
95244
96452
|
this.notifications.clear();
|
|
@@ -95246,6 +96454,8 @@ class BackgroundManager {
|
|
|
95246
96454
|
this.notificationQueueByParent.clear();
|
|
95247
96455
|
this.queuesByKey.clear();
|
|
95248
96456
|
this.processingKeys.clear();
|
|
96457
|
+
this.nestedActive.clear();
|
|
96458
|
+
this.nestedBypassTaskIds.clear();
|
|
95249
96459
|
this.unregisterProcessCleanup();
|
|
95250
96460
|
log("[background-agent] Shutdown complete");
|
|
95251
96461
|
}
|
|
@@ -96032,9 +97242,7 @@ class TmuxSessionManager {
|
|
|
96032
97242
|
var SESSION_TIMEOUT_MS3 = 10 * 60 * 1000;
|
|
96033
97243
|
var MIN_STABILITY_TIME_MS4 = 10 * 1000;
|
|
96034
97244
|
// src/agents/dynamic-agent-prompt-builder.ts
|
|
96035
|
-
import {
|
|
96036
|
-
import { homedir as homedir11 } from "os";
|
|
96037
|
-
import { join as join79 } from "path";
|
|
97245
|
+
import { readFileSync as readFileSync45 } from "fs";
|
|
96038
97246
|
init_logger();
|
|
96039
97247
|
function categorizeTools(toolNames) {
|
|
96040
97248
|
return toolNames.map((name) => {
|
|
@@ -96351,16 +97559,13 @@ function buildAntiPatternsSection() {
|
|
|
96351
97559
|
${patterns2.join(`
|
|
96352
97560
|
`)}`;
|
|
96353
97561
|
}
|
|
96354
|
-
var cachedRuntimeFull =
|
|
96355
|
-
var cachedRuntimeCompact =
|
|
97562
|
+
var cachedRuntimeFull = {};
|
|
97563
|
+
var cachedRuntimeCompact = {};
|
|
96356
97564
|
var cachedFallbackFull = {};
|
|
96357
97565
|
var cachedFallbackCompact = {};
|
|
96358
|
-
function hasGrepGlobToolNames(toolNames) {
|
|
96359
|
-
return toolNames.some((n) => n === "grep" || n === "glob");
|
|
96360
|
-
}
|
|
96361
97566
|
function fallbackFullDiscipline(hasGrepGlob) {
|
|
96362
97567
|
const analysis = hasGrepGlob ? "| Analysis / Processing | Use ctx_* tools \u2014 NEVER raw read/bash/grep/glob for analysis |" : "| Analysis / Processing | Use ctx_* tools \u2014 NEVER raw read/bash for analysis |";
|
|
96363
|
-
const search = hasGrepGlob ? "| Search | ctx_search FIRST -> grep/glob fallback |" : "| Search | ctx_search FIRST (indexed KB) -> LSP/ast_grep fallback |";
|
|
97568
|
+
const search = hasGrepGlob ? "| Search | ctx_search FIRST -> grep/glob fallback |" : "| Search | ctx_search FIRST (indexed KB) -> ctx_batch_execute / ctx_execute (rg) or LSP/ast_grep fallback |";
|
|
96364
97569
|
return `### Context Discipline (ALWAYS)
|
|
96365
97570
|
|
|
96366
97571
|
| Scenario | Tool |
|
|
@@ -96377,7 +97582,7 @@ ${search}
|
|
|
96377
97582
|
}
|
|
96378
97583
|
function fallbackCompactDiscipline(hasGrepGlob) {
|
|
96379
97584
|
const analysis = hasGrepGlob ? "| Analysis / Aggregation / Counting | ctx_batch_execute / ctx_execute(_file) \u2014 NEVER raw read/grep for analysis |" : "| Analysis / Aggregation / Counting | ctx_batch_execute / ctx_execute(_file) \u2014 NEVER raw read for analysis |";
|
|
96380
|
-
const search = hasGrepGlob ? "| Search | ctx_search FIRST (indexed KB) \u2192 grep/glob fallback (raw FS) |" : "| Search | ctx_search FIRST (indexed KB) \u2192 LSP/ast_grep fallback |";
|
|
97585
|
+
const search = hasGrepGlob ? "| Search | ctx_search FIRST (indexed KB) \u2192 grep/glob fallback (raw FS) |" : "| Search | ctx_search FIRST (indexed KB) \u2192 ctx_batch_execute / ctx_execute (rg) or LSP/ast_grep fallback |";
|
|
96381
97586
|
const note = hasGrepGlob ? "Edits need prior read for LINE#ID \u2014 read\u2192edit chain exempt (non-plan paths; .matrixx/plans/*.md must use plan_read/plan_update). MUST use ctx_* when available \u2014 raw grep/read is forbidden for analysis." : "Edits need prior read for LINE#ID \u2014 read\u2192edit chain exempt (non-plan paths; .matrixx/plans/*.md must use plan_read/plan_update). MUST use ctx_* when available \u2014 raw read for analysis is forbidden.";
|
|
96382
97587
|
return `### Context Discipline (when ctx_* available)
|
|
96383
97588
|
|
|
@@ -96391,69 +97596,54 @@ ${search}
|
|
|
96391
97596
|
${note}`;
|
|
96392
97597
|
}
|
|
96393
97598
|
function exploreCtxPart(hasGrepGlob) {
|
|
96394
|
-
return hasGrepGlob ? "MUST use ctx_search for indexed hits \u2192 grep/glob fallback ONLY when ctx_* unavailable; use ctx_batch_execute / ctx_execute for multi-file analysis; use ctx_fetch_and_index for docs/web \u2192 ctx_search. Raw grep/read/glob for analysis is forbidden when ctx_* is available." : "MUST use ctx_search for indexed hits \u2192 LSP/ast_grep fallback ONLY when ctx_* unavailable; use ctx_batch_execute / ctx_execute for multi-file analysis; use ctx_fetch_and_index for docs/web \u2192 ctx_search. Raw read for analysis is forbidden when ctx_* is available.";
|
|
96395
|
-
}
|
|
96396
|
-
function resolveContextModeDisciplinePath() {
|
|
96397
|
-
try {
|
|
96398
|
-
const resolved = __require.resolve("context-mode/configs/opencode/AGENTS.md");
|
|
96399
|
-
if (existsSync75(resolved))
|
|
96400
|
-
return resolved;
|
|
96401
|
-
} catch {}
|
|
96402
|
-
try {
|
|
96403
|
-
const cacheDir = getOpenCodeCacheDir();
|
|
96404
|
-
const p = join79(cacheDir, "packages/context-mode@latest/node_modules/context-mode/configs/opencode/AGENTS.md");
|
|
96405
|
-
if (existsSync75(p))
|
|
96406
|
-
return p;
|
|
96407
|
-
} catch {}
|
|
96408
|
-
try {
|
|
96409
|
-
const base = process.env.XDG_CACHE_HOME ?? join79(homedir11(), ".cache");
|
|
96410
|
-
const p = join79(base, "opencode/packages/context-mode@latest/node_modules/context-mode/configs/opencode/AGENTS.md");
|
|
96411
|
-
if (existsSync75(p))
|
|
96412
|
-
return p;
|
|
96413
|
-
} catch {}
|
|
96414
|
-
try {
|
|
96415
|
-
const p2 = join79(homedir11(), ".cache/opencode/packages/context-mode@latest/node_modules/context-mode/configs/opencode/AGENTS.md");
|
|
96416
|
-
if (existsSync75(p2))
|
|
96417
|
-
return p2;
|
|
96418
|
-
} catch {}
|
|
96419
|
-
return null;
|
|
97599
|
+
return hasGrepGlob ? "MUST use ctx_search for indexed hits \u2192 grep/glob fallback ONLY when ctx_* unavailable; use ctx_batch_execute / ctx_execute for multi-file analysis; use ctx_fetch_and_index for docs/web \u2192 ctx_search. Raw grep/read/glob for analysis is forbidden when ctx_* is available." : "MUST use ctx_search for indexed hits \u2192 ctx_batch_execute / ctx_execute (rg) or LSP/ast_grep fallback ONLY when ctx_* unavailable; use ctx_batch_execute / ctx_execute for multi-file analysis; use ctx_fetch_and_index for docs/web \u2192 ctx_search. Raw read for analysis is forbidden when ctx_* is available.";
|
|
96420
97600
|
}
|
|
96421
97601
|
function stripDisciplineHeader(content) {
|
|
96422
97602
|
return content.trim();
|
|
96423
97603
|
}
|
|
96424
|
-
function
|
|
96425
|
-
|
|
96426
|
-
|
|
96427
|
-
|
|
96428
|
-
|
|
96429
|
-
|
|
97604
|
+
function readDisciplineFile() {
|
|
97605
|
+
const p = resolveContextModeDisciplinePath();
|
|
97606
|
+
if (!p)
|
|
97607
|
+
throw new Error("discipline file not found");
|
|
97608
|
+
const raw = readFileSync45(p, "utf8");
|
|
97609
|
+
let version4 = "1.0.169";
|
|
96430
97610
|
try {
|
|
96431
|
-
const
|
|
96432
|
-
|
|
96433
|
-
|
|
96434
|
-
|
|
96435
|
-
|
|
96436
|
-
|
|
96437
|
-
|
|
96438
|
-
|
|
96439
|
-
const pkg = JSON.parse(pkgRaw);
|
|
96440
|
-
if (pkg.version)
|
|
96441
|
-
version4 = pkg.version;
|
|
96442
|
-
} catch {}
|
|
96443
|
-
const body = stripDisciplineHeader(raw);
|
|
96444
|
-
const withVersion = body.includes("<!-- discipline") ? body : `${body}
|
|
97611
|
+
const pkgResolved = __require.resolve("context-mode/package.json");
|
|
97612
|
+
const pkgRaw = readFileSync45(pkgResolved, "utf8");
|
|
97613
|
+
const pkg = JSON.parse(pkgRaw);
|
|
97614
|
+
if (pkg.version)
|
|
97615
|
+
version4 = pkg.version;
|
|
97616
|
+
} catch {}
|
|
97617
|
+
const body = stripDisciplineHeader(raw);
|
|
97618
|
+
const text = body.includes("<!-- discipline") ? body : `${body}
|
|
96445
97619
|
|
|
96446
97620
|
<!-- discipline v${version4} Elastic-2.0 -->`;
|
|
96447
|
-
|
|
96448
|
-
|
|
96449
|
-
|
|
97621
|
+
return { text, version: version4, path: p };
|
|
97622
|
+
}
|
|
97623
|
+
function loadDiscipline(kind, hasGrepGlob = true) {
|
|
97624
|
+
const fallbackKey = hasGrepGlob ? "1" : "0";
|
|
97625
|
+
const runtimeCache = kind === "full" ? cachedRuntimeFull : cachedRuntimeCompact;
|
|
97626
|
+
const fallbackCache = kind === "full" ? cachedFallbackFull : cachedFallbackCompact;
|
|
97627
|
+
const label = kind === "full" ? "context-discipline" : "compact-discipline";
|
|
97628
|
+
if (runtimeCache[fallbackKey])
|
|
97629
|
+
return runtimeCache[fallbackKey];
|
|
97630
|
+
if (fallbackCache[fallbackKey])
|
|
97631
|
+
return fallbackCache[fallbackKey];
|
|
97632
|
+
try {
|
|
97633
|
+
const { text, version: version4, path: path13 } = readDisciplineFile();
|
|
97634
|
+
runtimeCache[fallbackKey] = text;
|
|
97635
|
+
log(`${label} loaded`, { path: path13, version: version4 });
|
|
97636
|
+
return text;
|
|
96450
97637
|
} catch (e) {
|
|
96451
|
-
log(
|
|
96452
|
-
const fallback = fallbackFullDiscipline(hasGrepGlob);
|
|
96453
|
-
|
|
97638
|
+
log(`${label} fallback`, { error: String(e) });
|
|
97639
|
+
const fallback = kind === "full" ? fallbackFullDiscipline(hasGrepGlob) : fallbackCompactDiscipline(hasGrepGlob);
|
|
97640
|
+
fallbackCache[fallbackKey] = fallback;
|
|
96454
97641
|
return fallback;
|
|
96455
97642
|
}
|
|
96456
97643
|
}
|
|
97644
|
+
function loadContextModeDiscipline(hasGrepGlob = true) {
|
|
97645
|
+
return loadDiscipline("full", hasGrepGlob);
|
|
97646
|
+
}
|
|
96457
97647
|
function buildContextDisciplineSection(hasContextMode = false, hasGrepGlob = true) {
|
|
96458
97648
|
if (!hasContextMode)
|
|
96459
97649
|
return "";
|
|
@@ -96473,37 +97663,7 @@ function buildHeadroomSection(hasHeadroom = false) {
|
|
|
96473
97663
|
**Headroom L4 is transport-level (CacheAligner->ContentRouter->CCR). It complements L1 RTK, L2 context-mode, L3 DCP \u2014 do not duplicate their discipline.**`;
|
|
96474
97664
|
}
|
|
96475
97665
|
function loadCompactContextDiscipline(hasGrepGlob = true) {
|
|
96476
|
-
|
|
96477
|
-
return cachedRuntimeCompact;
|
|
96478
|
-
const fallbackKey = hasGrepGlob ? "1" : "0";
|
|
96479
|
-
if (cachedFallbackCompact[fallbackKey])
|
|
96480
|
-
return cachedFallbackCompact[fallbackKey];
|
|
96481
|
-
try {
|
|
96482
|
-
const p = resolveContextModeDisciplinePath();
|
|
96483
|
-
if (!p)
|
|
96484
|
-
throw new Error("discipline file not found");
|
|
96485
|
-
const raw = readFileSync45(p, "utf8");
|
|
96486
|
-
let version4 = "1.0.169";
|
|
96487
|
-
try {
|
|
96488
|
-
const pkgResolved = __require.resolve("context-mode/package.json");
|
|
96489
|
-
const pkgRaw = readFileSync45(pkgResolved, "utf8");
|
|
96490
|
-
const pkg = JSON.parse(pkgRaw);
|
|
96491
|
-
if (pkg.version)
|
|
96492
|
-
version4 = pkg.version;
|
|
96493
|
-
} catch {}
|
|
96494
|
-
const body = stripDisciplineHeader(raw);
|
|
96495
|
-
const withVersion = body.includes("<!-- discipline") ? body : `${body}
|
|
96496
|
-
|
|
96497
|
-
<!-- discipline v${version4} Elastic-2.0 -->`;
|
|
96498
|
-
cachedRuntimeCompact = withVersion;
|
|
96499
|
-
log("compact-discipline loaded", { path: p, version: version4 });
|
|
96500
|
-
return withVersion;
|
|
96501
|
-
} catch (e) {
|
|
96502
|
-
log("compact-discipline fallback", { error: String(e) });
|
|
96503
|
-
const fallback = fallbackCompactDiscipline(hasGrepGlob);
|
|
96504
|
-
cachedFallbackCompact[fallbackKey] = fallback;
|
|
96505
|
-
return fallback;
|
|
96506
|
-
}
|
|
97666
|
+
return loadDiscipline("compact", hasGrepGlob);
|
|
96507
97667
|
}
|
|
96508
97668
|
function buildCompactContextDisciplineSection(hasContextMode = false, hasGrepGlob = true) {
|
|
96509
97669
|
if (!hasContextMode)
|
|
@@ -97555,7 +98715,7 @@ function createBddContractAgent(model) {
|
|
|
97555
98715
|
createBddContractAgent.mode = MODE2;
|
|
97556
98716
|
|
|
97557
98717
|
// src/agents/builtin-agents/resolve-file-uri.ts
|
|
97558
|
-
import { existsSync as
|
|
98718
|
+
import { existsSync as existsSync77, readFileSync as readFileSync46 } from "fs";
|
|
97559
98719
|
import { homedir as homedir12 } from "os";
|
|
97560
98720
|
import { isAbsolute as isAbsolute6, resolve as resolve10 } from "path";
|
|
97561
98721
|
function resolvePromptAppend(promptAppend, configDir) {
|
|
@@ -97570,7 +98730,7 @@ function resolvePromptAppend(promptAppend, configDir) {
|
|
|
97570
98730
|
} catch {
|
|
97571
98731
|
return `[WARNING: Malformed file URI (invalid percent-encoding): ${promptAppend}]`;
|
|
97572
98732
|
}
|
|
97573
|
-
if (!
|
|
98733
|
+
if (!existsSync77(filePath)) {
|
|
97574
98734
|
return `[WARNING: Could not resolve file URI: ${promptAppend}]`;
|
|
97575
98735
|
}
|
|
97576
98736
|
try {
|
|
@@ -97924,7 +99084,7 @@ function buildKeymakerPrompt(availableAgents = [], availableTools = [], availabl
|
|
|
97924
99084
|
const hardBlocks = buildHardBlocksSection();
|
|
97925
99085
|
const antiPatterns = buildAntiPatternsSection();
|
|
97926
99086
|
const hasContextMode = availableTools.some((t) => t.name.startsWith("ctx_"));
|
|
97927
|
-
const hasGrepGlob =
|
|
99087
|
+
const hasGrepGlob = resolveGrepGlobUsable(availableTools.map((t) => t.name));
|
|
97928
99088
|
const contextDiscipline = buildContextDisciplineSection(hasContextMode, hasGrepGlob);
|
|
97929
99089
|
const hasHeadroom = availableTools.some((t) => t.name.startsWith("headroom_"));
|
|
97930
99090
|
const headroomDiscipline = buildHeadroomSection(hasHeadroom);
|
|
@@ -98113,6 +99273,11 @@ result = task(..., run_in_background=false) // Never wait synchronously for exp
|
|
|
98113
99273
|
- Collect results with \`background_output(task_id="...")\` when needed
|
|
98114
99274
|
- BEFORE final answer: \`background_cancel(all=true)\` to clean up
|
|
98115
99275
|
|
|
99276
|
+
**Interpreting terminal task states** \u2014 \`background_output\` and the completion \`<system-reminder>\` report one of:
|
|
99277
|
+
- \`completed\` / \`error\` / \`cancelled\` / \`interrupt\` \u2014 as named.
|
|
99278
|
+
- \`stopped\` (notification label \`STOPPED\`) \u2014 ended without a terminal result (queue-saturated, no output, or nested depth cap); partial output may exist.
|
|
99279
|
+
- \`statusUncertain\` (label \`STATUS UNCERTAIN\`) \u2014 liveness could not be determined after a restart; re-check with \`background_output\`, never assume success or failure.
|
|
99280
|
+
|
|
98116
99281
|
### Search Stop Conditions
|
|
98117
99282
|
|
|
98118
99283
|
STOP searching when:
|
|
@@ -98601,7 +99766,7 @@ function buildDynamicMorpheusPrompt(availableAgents, availableTools = [], availa
|
|
|
98601
99766
|
const hardBlocks = buildHardBlocksSection();
|
|
98602
99767
|
const antiPatterns = buildAntiPatternsSection();
|
|
98603
99768
|
const hasContextMode = availableTools.some((t) => t.name.startsWith("ctx_"));
|
|
98604
|
-
const hasGrepGlob =
|
|
99769
|
+
const hasGrepGlob = resolveGrepGlobUsable(availableTools.map((t) => t.name));
|
|
98605
99770
|
const contextDiscipline = buildContextDisciplineSection(hasContextMode, hasGrepGlob);
|
|
98606
99771
|
const hasHeadroom = availableTools.some((t) => t.name.startsWith("headroom_"));
|
|
98607
99772
|
const headroomDiscipline = buildHeadroomSection(hasHeadroom);
|
|
@@ -98746,6 +99911,11 @@ result = task(..., run_in_background=false) // Never wait synchronously for exp
|
|
|
98746
99911
|
3. When results needed: \`background_output(task_id="...")\`
|
|
98747
99912
|
4. BEFORE final answer: \`background_cancel(all=true)\`
|
|
98748
99913
|
|
|
99914
|
+
**Interpreting terminal task states** \u2014 \`background_output\` and the completion \`<system-reminder>\` report one of:
|
|
99915
|
+
- \`completed\` / \`error\` / \`cancelled\` / \`interrupt\` \u2014 as named.
|
|
99916
|
+
- \`stopped\` (notification label \`STOPPED\`) \u2014 ended without a terminal result (queue-saturated, no output, or nested depth cap); partial output may exist.
|
|
99917
|
+
- \`statusUncertain\` (label \`STATUS UNCERTAIN\`) \u2014 liveness could not be determined after a restart; re-check with \`background_output\`, never assume success or failure.
|
|
99918
|
+
|
|
98749
99919
|
### Search Stop Conditions
|
|
98750
99920
|
|
|
98751
99921
|
STOP searching when:
|
|
@@ -102999,12 +104169,12 @@ function setAvailableToolNames(names) {
|
|
|
102999
104169
|
function getAvailableToolNames() {
|
|
103000
104170
|
return _availableToolNames;
|
|
103001
104171
|
}
|
|
103002
|
-
function injectContextDiscipline(availableToolNames, agents) {
|
|
104172
|
+
function injectContextDiscipline(availableToolNames, agents, contextMode) {
|
|
103003
104173
|
const hasContextMode = availableToolNames.some((n) => n.startsWith("ctx_"));
|
|
103004
104174
|
const hasHeadroom = availableToolNames.some((n) => n.startsWith("headroom_"));
|
|
103005
104175
|
if (!(hasContextMode || hasHeadroom))
|
|
103006
104176
|
return;
|
|
103007
|
-
const hasGrepGlob =
|
|
104177
|
+
const hasGrepGlob = resolveGrepGlobUsable(availableToolNames, contextMode ?? getContextModeForPrompts());
|
|
103008
104178
|
const exploreAgents = new Set(["trinity", "operator", "seraph", "smith", "merovingian", "construct", "bdd-contract"]);
|
|
103009
104179
|
for (const [name, cfg] of Object.entries(agents)) {
|
|
103010
104180
|
if (name === "morpheus" || name === "keymaker")
|
|
@@ -103045,6 +104215,7 @@ async function applyAgentConfig(params) {
|
|
|
103045
104215
|
disabledSkills.add("tdd-enforcer");
|
|
103046
104216
|
}
|
|
103047
104217
|
const useTaskSystem = isTaskSystemEnabled(params.pluginConfig);
|
|
104218
|
+
setContextModeForPrompts(params.pluginConfig.context_mode);
|
|
103048
104219
|
const availableToolNames = getAvailableToolNames();
|
|
103049
104220
|
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);
|
|
103050
104221
|
const rawPluginAgents = params.pluginComponents.agents;
|
|
@@ -103120,7 +104291,7 @@ async function applyAgentConfig(params) {
|
|
|
103120
104291
|
params.config.agent = reorderAgentsByPriority(params.config.agent);
|
|
103121
104292
|
}
|
|
103122
104293
|
if (params.config.agent) {
|
|
103123
|
-
injectContextDiscipline(availableToolNames, params.config.agent);
|
|
104294
|
+
injectContextDiscipline(availableToolNames, params.config.agent, params.pluginConfig.context_mode);
|
|
103124
104295
|
}
|
|
103125
104296
|
const agentResult = params.config.agent;
|
|
103126
104297
|
log("[config-handler] agents loaded", { agentKeys: Object.keys(agentResult) });
|
|
@@ -103457,6 +104628,7 @@ function createManagers(args) {
|
|
|
103457
104628
|
const tmuxSessionManager = new TmuxSessionManager(ctx, tmuxConfig);
|
|
103458
104629
|
const backgroundManager = new BackgroundManager(ctx, pluginConfig.background_task, {
|
|
103459
104630
|
tmuxConfig,
|
|
104631
|
+
handoffCompression: pluginConfig.dcp?.handoffCompression,
|
|
103460
104632
|
onSubagentSessionCreated: async (event) => {
|
|
103461
104633
|
log("[index] onSubagentSessionCreated callback received", {
|
|
103462
104634
|
sessionID: event.sessionID,
|
|
@@ -103554,7 +104726,7 @@ function filterDisabledTools(tools, disabledTools) {
|
|
|
103554
104726
|
// src/plugin/tool-registry.ts
|
|
103555
104727
|
function createToolRegistry(args) {
|
|
103556
104728
|
const { ctx, pluginConfig, managers, skillContext, availableCategories } = args;
|
|
103557
|
-
const backgroundTools = createBackgroundTools(managers.backgroundManager, ctx.client);
|
|
104729
|
+
const backgroundTools = createBackgroundTools(managers.backgroundManager, ctx.client, ctx.directory);
|
|
103558
104730
|
const delegateAgent = createDelegateAgent(ctx, managers.backgroundManager, pluginConfig.disabled_agents ?? []);
|
|
103559
104731
|
const isMultimodalLookerEnabled = !(pluginConfig.disabled_agents ?? []).some((agent) => agent.toLowerCase() === "construct");
|
|
103560
104732
|
const lookAt = isMultimodalLookerEnabled ? createLookAt(ctx) : null;
|
|
@@ -103834,6 +105006,20 @@ var CircuitBreakerConfigSchema = exports_external.object({
|
|
|
103834
105006
|
maxToolCalls: exports_external.number().int().min(10).optional(),
|
|
103835
105007
|
consecutiveThreshold: exports_external.number().int().min(5).optional()
|
|
103836
105008
|
});
|
|
105009
|
+
var NestedAdmissionConfigSchema = exports_external.object({
|
|
105010
|
+
enabled: exports_external.boolean().optional(),
|
|
105011
|
+
mode: exports_external.enum(["bypass", "reserve"]).optional(),
|
|
105012
|
+
maxDepth: exports_external.number().int().min(1).max(5).optional()
|
|
105013
|
+
});
|
|
105014
|
+
var WakeSchedulerConfigSchema = exports_external.object({
|
|
105015
|
+
enabled: exports_external.boolean().optional(),
|
|
105016
|
+
intervalMs: exports_external.number().min(60000).optional()
|
|
105017
|
+
});
|
|
105018
|
+
var JobBoardConfigSchema = exports_external.object({
|
|
105019
|
+
enabled: exports_external.boolean().optional(),
|
|
105020
|
+
strategy: exports_external.enum(["latest", "checkpoint-compatible"]).optional(),
|
|
105021
|
+
maxRetainedSnapshots: exports_external.number().int().min(1).max(100).optional()
|
|
105022
|
+
});
|
|
103837
105023
|
var BackgroundTaskConfigSchema = exports_external.object({
|
|
103838
105024
|
defaultConcurrency: exports_external.number().min(1).optional(),
|
|
103839
105025
|
providerConcurrency: exports_external.record(exports_external.string(), exports_external.number().min(0)).optional(),
|
|
@@ -103841,7 +105027,13 @@ var BackgroundTaskConfigSchema = exports_external.object({
|
|
|
103841
105027
|
staleTimeoutMs: exports_external.number().min(60000).optional(),
|
|
103842
105028
|
messageStalenessTimeoutMs: exports_external.number().min(60000).optional(),
|
|
103843
105029
|
maxToolCalls: exports_external.number().int().min(10).optional(),
|
|
103844
|
-
circuitBreaker: CircuitBreakerConfigSchema.optional()
|
|
105030
|
+
circuitBreaker: CircuitBreakerConfigSchema.optional(),
|
|
105031
|
+
admissionTimeoutMs: exports_external.union([exports_external.literal(0), exports_external.number().min(60000)]).optional(),
|
|
105032
|
+
nestedAdmission: NestedAdmissionConfigSchema.optional(),
|
|
105033
|
+
wakeScheduler: WakeSchedulerConfigSchema.optional(),
|
|
105034
|
+
jobBoard: JobBoardConfigSchema.optional(),
|
|
105035
|
+
wallClockTimeoutMs: exports_external.union([exports_external.literal(0), exports_external.number().int().min(60000).max(2147483647)]).optional(),
|
|
105036
|
+
wallClockAbortGraceMs: exports_external.number().int().min(1000).max(60000).optional()
|
|
103845
105037
|
});
|
|
103846
105038
|
// src/config/schema/browser-automation.ts
|
|
103847
105039
|
var BrowserAutomationProviderSchema = exports_external.enum([
|
|
@@ -103915,7 +105107,7 @@ var CommentCheckerConfigSchema = exports_external.object({
|
|
|
103915
105107
|
var ContextModeConfigSchema = exports_external.object({
|
|
103916
105108
|
enabled: exports_external.boolean().default(true),
|
|
103917
105109
|
enforce: exports_external.boolean().default(false),
|
|
103918
|
-
blocked_tools: exports_external.array(exports_external.string()).default(["
|
|
105110
|
+
blocked_tools: exports_external.array(exports_external.string()).default(["grep", "glob"])
|
|
103919
105111
|
});
|
|
103920
105112
|
// src/config/schema/evolution.ts
|
|
103921
105113
|
var EvolutionWatcherConfigSchema = exports_external.object({
|
|
@@ -105210,6 +106402,7 @@ var MatrixxPlugin = async (ctx) => {
|
|
|
105210
106402
|
injectServerAuthIntoClient(ctx.client);
|
|
105211
106403
|
startBackgroundCheck();
|
|
105212
106404
|
const pluginConfig = await loadPluginConfig(ctx.directory, ctx);
|
|
106405
|
+
setContextModeForPrompts(pluginConfig.context_mode);
|
|
105213
106406
|
const disabledHooks = new Set(pluginConfig.disabled_hooks ?? []);
|
|
105214
106407
|
const isHookEnabled = (hookName) => !disabledHooks.has(hookName);
|
|
105215
106408
|
const safeHookEnabled = pluginConfig.experimental?.safe_hook_creation ?? true;
|