@mrciphersmith/keryx 0.2.109 → 0.2.110
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 +300 -14
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -28143,7 +28143,7 @@ async function runAgentTurnCore(io, deps, history, userLine, options = {}) {
|
|
|
28143
28143
|
continue;
|
|
28144
28144
|
}
|
|
28145
28145
|
executedAny = true;
|
|
28146
|
-
const result = concurrentSpawnResults?.get(call.id) ?? await executeCall(call, toolByName, io.requestApproval, io.permissionMode, io.onAutoApproved, hasInvocationCapacity, reserveInvocation, invocationBudget.maxCalls);
|
|
28146
|
+
const result = concurrentSpawnResults?.get(call.id) ?? await executeCall(call, toolByName, io.requestApproval, io.permissionMode, io.onAutoApproved, hasInvocationCapacity, reserveInvocation, invocationBudget.maxCalls, signal);
|
|
28147
28147
|
io.onToolResult?.(call.name, result);
|
|
28148
28148
|
const modelOutput = redactSensitiveText(result.output);
|
|
28149
28149
|
const untrusted = result.untrusted === true;
|
|
@@ -28417,7 +28417,7 @@ async function runConcurrentSpawnBatch(spawnCalls, toolByName, io, deps, hasInvo
|
|
|
28417
28417
|
return results;
|
|
28418
28418
|
}
|
|
28419
28419
|
}
|
|
28420
|
-
async function executeCall(call, toolByName, requestApproval, permissionMode, onAutoApproved, hasInvocationCapacity, reserveInvocation, maxToolCalls) {
|
|
28420
|
+
async function executeCall(call, toolByName, requestApproval, permissionMode, onAutoApproved, hasInvocationCapacity, reserveInvocation, maxToolCalls, signal) {
|
|
28421
28421
|
const tool = toolByName.get(call.name);
|
|
28422
28422
|
if (tool === undefined) {
|
|
28423
28423
|
return { output: `unknown tool: ${call.name}`, isError: true };
|
|
@@ -28489,7 +28489,7 @@ async function executeCall(call, toolByName, requestApproval, permissionMode, on
|
|
|
28489
28489
|
if (!reserveInvocation()) {
|
|
28490
28490
|
return toolCallBudgetResult(maxToolCalls ?? 0, maxToolCalls ?? 0);
|
|
28491
28491
|
}
|
|
28492
|
-
return tool.invoke(input);
|
|
28492
|
+
return tool.invoke(input, { ...signal !== undefined ? { signal } : {} });
|
|
28493
28493
|
}
|
|
28494
28494
|
function validateDirectBudget(name, value, min) {
|
|
28495
28495
|
if (value === undefined)
|
|
@@ -28519,7 +28519,11 @@ var init_agent = __esm(() => {
|
|
|
28519
28519
|
init_slate_lifecycle();
|
|
28520
28520
|
init_slate_terminal_state();
|
|
28521
28521
|
DURABLE_READ_TOOL_NAMES = new Set(["workspace_create", "workspace_propose", "slate_write_seed"]);
|
|
28522
|
-
REPEATABLE_TOOL_NAMES = new Set([
|
|
28522
|
+
REPEATABLE_TOOL_NAMES = new Set([
|
|
28523
|
+
"shell_job_output",
|
|
28524
|
+
"shell_task_output",
|
|
28525
|
+
"shell_task_wait"
|
|
28526
|
+
]);
|
|
28523
28527
|
INSTRUCTION_METAPROJECT_TOOLS = [
|
|
28524
28528
|
"search_code",
|
|
28525
28529
|
"graph_affected",
|
|
@@ -48683,7 +48687,7 @@ import path47 from "path";
|
|
|
48683
48687
|
// package.json
|
|
48684
48688
|
var package_default = {
|
|
48685
48689
|
name: "@mrciphersmith/keryx",
|
|
48686
|
-
version: "0.2.
|
|
48690
|
+
version: "0.2.110",
|
|
48687
48691
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
48688
48692
|
private: false,
|
|
48689
48693
|
publishConfig: {
|
|
@@ -80719,6 +80723,7 @@ function createJobRegistry(options) {
|
|
|
80719
80723
|
const dropped = job.outputBuffer.length - TERMINATED_OUTPUT_TAIL_BYTES;
|
|
80720
80724
|
job.outputBuffer = job.outputBuffer.slice(dropped);
|
|
80721
80725
|
job.readCursor = Math.max(0, job.readCursor - dropped);
|
|
80726
|
+
job.droppedBytes += dropped;
|
|
80722
80727
|
}
|
|
80723
80728
|
function appendOutput(job, chunk, stream) {
|
|
80724
80729
|
job.outputBuffer += chunk;
|
|
@@ -80732,6 +80737,7 @@ function createJobRegistry(options) {
|
|
|
80732
80737
|
const dropped = job.outputBuffer.length - MAX_BACKGROUND_OUTPUT_BYTES;
|
|
80733
80738
|
job.outputBuffer = job.outputBuffer.slice(dropped);
|
|
80734
80739
|
job.readCursor = Math.max(0, job.readCursor - dropped);
|
|
80740
|
+
job.droppedBytes += dropped;
|
|
80735
80741
|
requestKill(job, "output-cap");
|
|
80736
80742
|
}
|
|
80737
80743
|
}
|
|
@@ -80797,6 +80803,7 @@ function createJobRegistry(options) {
|
|
|
80797
80803
|
outputBuffer: "",
|
|
80798
80804
|
outputHead: "",
|
|
80799
80805
|
readCursor: 0,
|
|
80806
|
+
droppedBytes: 0,
|
|
80800
80807
|
exited: false,
|
|
80801
80808
|
killRequested: false,
|
|
80802
80809
|
phaseEmitted: false,
|
|
@@ -80874,6 +80881,27 @@ function createJobRegistry(options) {
|
|
|
80874
80881
|
}
|
|
80875
80882
|
return { ok: true, output };
|
|
80876
80883
|
},
|
|
80884
|
+
readOutputSince(jobId, since) {
|
|
80885
|
+
const job = jobs.get(jobId);
|
|
80886
|
+
if (job === undefined) {
|
|
80887
|
+
return { ok: false, error: `unknown job_id: ${jobId}` };
|
|
80888
|
+
}
|
|
80889
|
+
const requested = Number.isFinite(since) && since > 0 ? Math.trunc(since) : 0;
|
|
80890
|
+
const missed = Math.max(0, job.droppedBytes - requested);
|
|
80891
|
+
const index = Math.min(job.outputBuffer.length, Math.max(0, requested - job.droppedBytes));
|
|
80892
|
+
return {
|
|
80893
|
+
ok: true,
|
|
80894
|
+
output: job.outputBuffer.slice(index),
|
|
80895
|
+
nextCursor: job.droppedBytes + job.outputBuffer.length,
|
|
80896
|
+
missed,
|
|
80897
|
+
status: job.info.status
|
|
80898
|
+
};
|
|
80899
|
+
},
|
|
80900
|
+
markObserved(jobId) {
|
|
80901
|
+
const job = jobs.get(jobId);
|
|
80902
|
+
if (job !== undefined)
|
|
80903
|
+
job.info.observed = true;
|
|
80904
|
+
},
|
|
80877
80905
|
async waitForExit(jobId, ms) {
|
|
80878
80906
|
const job = jobs.get(jobId);
|
|
80879
80907
|
if (job === undefined)
|
|
@@ -80959,7 +80987,7 @@ function shellJobOutputTool(registry) {
|
|
|
80959
80987
|
return {
|
|
80960
80988
|
definition: {
|
|
80961
80989
|
name: "shell_job_output",
|
|
80962
|
-
description: "Return output produced by a shell task SINCE the previous call for that id \u2014 never the full transcript " + "again. Input: { job_id: string } \u2014 pass the task_id shell_exec returned for a command that outlived its " + "yield (or a background:true job). Poll this instead of re-running shell_exec to check on a long command.",
|
|
80990
|
+
description: "DEPRECATED \u2014 use shell_task_output, which takes an explicit cursor; this name is kept for one release. " + "Return output produced by a shell task SINCE the previous call for that id \u2014 never the full transcript " + "again. Input: { job_id: string } \u2014 pass the task_id shell_exec returned for a command that outlived its " + "yield (or a background:true job). Poll this instead of re-running shell_exec to check on a long command.",
|
|
80963
80991
|
inputSchema: {
|
|
80964
80992
|
type: "object",
|
|
80965
80993
|
properties: { job_id: { type: "string" } },
|
|
@@ -80973,7 +81001,7 @@ function shellJobOutputTool(registry) {
|
|
|
80973
81001
|
if (jobId.length === 0) {
|
|
80974
81002
|
return { output: "shell_job_output requires a non-empty 'job_id'", isError: true };
|
|
80975
81003
|
}
|
|
80976
|
-
const result = registry.readOutput(jobId);
|
|
81004
|
+
const result = registry.readOutput(resolveTaskId(registry, jobId));
|
|
80977
81005
|
if (!result.ok) {
|
|
80978
81006
|
return { output: result.error, isError: true };
|
|
80979
81007
|
}
|
|
@@ -80981,11 +81009,175 @@ function shellJobOutputTool(registry) {
|
|
|
80981
81009
|
}
|
|
80982
81010
|
};
|
|
80983
81011
|
}
|
|
81012
|
+
function shellTaskOutputTool(registry, options) {
|
|
81013
|
+
return {
|
|
81014
|
+
definition: {
|
|
81015
|
+
name: "shell_task_output",
|
|
81016
|
+
description: "Read a shell task's output from an EXPLICIT cursor. Input: { task_id: string, since?: integer } \u2014 " + "omit `since` (or pass 0) for everything retained, then pass back the next_cursor from the previous " + "result to get only what is new. Two calls with the same `since` return the same bytes, so this is safe " + "to call again after a failure. The result states the task's status and its next_cursor, and says so " + "when older output was dropped before your cursor.",
|
|
81017
|
+
inputSchema: {
|
|
81018
|
+
type: "object",
|
|
81019
|
+
properties: {
|
|
81020
|
+
task_id: { type: "string" },
|
|
81021
|
+
since: { type: "integer" }
|
|
81022
|
+
},
|
|
81023
|
+
required: ["task_id"],
|
|
81024
|
+
additionalProperties: false
|
|
81025
|
+
},
|
|
81026
|
+
risk: "read"
|
|
81027
|
+
},
|
|
81028
|
+
invoke: async (input) => {
|
|
81029
|
+
const taskId = typeof input.task_id === "string" ? input.task_id : "";
|
|
81030
|
+
if (taskId.length === 0) {
|
|
81031
|
+
return { output: "shell_task_output requires a non-empty 'task_id'", isError: true };
|
|
81032
|
+
}
|
|
81033
|
+
const since = typeof input.since === "number" ? input.since : 0;
|
|
81034
|
+
const result = registry.readOutputSince(taskId, since);
|
|
81035
|
+
if (!result.ok) {
|
|
81036
|
+
return { output: result.error, isError: true };
|
|
81037
|
+
}
|
|
81038
|
+
if (options.observer === "main" && result.status !== "running") {
|
|
81039
|
+
registry.markObserved(taskId);
|
|
81040
|
+
}
|
|
81041
|
+
const missed = result.missed > 0 ? `
|
|
81042
|
+
[${result.missed} byte(s) older than your cursor were dropped and cannot be re-read]` : "";
|
|
81043
|
+
const trailer = `
|
|
81044
|
+
[task ${taskId} status=${result.status} next_cursor=${result.nextCursor}]`;
|
|
81045
|
+
return { output: `${result.output}${missed}${trailer}`, isError: false };
|
|
81046
|
+
}
|
|
81047
|
+
};
|
|
81048
|
+
}
|
|
81049
|
+
function demoteTask(registry, taskId) {
|
|
81050
|
+
const resolved = taskId;
|
|
81051
|
+
const info = registry.get(resolved);
|
|
81052
|
+
if (info === undefined) {
|
|
81053
|
+
return { ok: false, error: `unknown task_id: ${taskId}` };
|
|
81054
|
+
}
|
|
81055
|
+
if (info.status !== "running") {
|
|
81056
|
+
return { ok: false, error: `task ${resolved} is not running (status: ${info.status})` };
|
|
81057
|
+
}
|
|
81058
|
+
if (info.phase === "background") {
|
|
81059
|
+
return { ok: false, error: `task ${resolved} is already running in the background` };
|
|
81060
|
+
}
|
|
81061
|
+
return registry.promote(resolved);
|
|
81062
|
+
}
|
|
81063
|
+
var MAX_TASK_WAIT_MS = 300000;
|
|
81064
|
+
function clampTaskWaitMs(ms) {
|
|
81065
|
+
if (!Number.isFinite(ms))
|
|
81066
|
+
return MAX_TASK_WAIT_MS;
|
|
81067
|
+
if (ms <= 0)
|
|
81068
|
+
return 0;
|
|
81069
|
+
return Math.min(MAX_TASK_WAIT_MS, Math.trunc(ms));
|
|
81070
|
+
}
|
|
81071
|
+
function describeWaitedTask(registry, taskId) {
|
|
81072
|
+
const info = registry.get(taskId);
|
|
81073
|
+
if (info === undefined)
|
|
81074
|
+
return `${taskId}: unknown task_id (not tracked by this session)`;
|
|
81075
|
+
const exit = info.exitCode !== undefined ? ` exit=${info.exitCode}` : "";
|
|
81076
|
+
const reason = info.killReason !== undefined ? ` killReason=${info.killReason}` : "";
|
|
81077
|
+
return `${taskId}: ${info.status}${exit}${reason}`;
|
|
81078
|
+
}
|
|
81079
|
+
function shellTaskWaitTool(registry) {
|
|
81080
|
+
return {
|
|
81081
|
+
definition: {
|
|
81082
|
+
name: "shell_task_wait",
|
|
81083
|
+
description: 'Wait for shell tasks to finish instead of polling. Input: { task_ids: string[], mode: "any" | "all", ' + "timeout_ms?: integer } \u2014 `any` returns as soon as one of them ends, `all` when every one has. The wait is " + "bounded (timeout_ms is clamped to at most 300000) and NEVER kills: if the bound is reached you get each " + "task's current status and the tasks keep running. Read their output with shell_task_output(task_id).",
|
|
81084
|
+
inputSchema: {
|
|
81085
|
+
type: "object",
|
|
81086
|
+
properties: {
|
|
81087
|
+
task_ids: { type: "array", items: { type: "string" } },
|
|
81088
|
+
mode: { type: "string", enum: ["any", "all"] },
|
|
81089
|
+
timeout_ms: { type: "integer" }
|
|
81090
|
+
},
|
|
81091
|
+
required: ["task_ids", "mode"],
|
|
81092
|
+
additionalProperties: false
|
|
81093
|
+
},
|
|
81094
|
+
risk: "read"
|
|
81095
|
+
},
|
|
81096
|
+
invoke: async (input, ctx) => {
|
|
81097
|
+
const rawIds = Array.isArray(input.task_ids) ? input.task_ids : [];
|
|
81098
|
+
const taskIds = rawIds.filter((id) => typeof id === "string" && id.length > 0);
|
|
81099
|
+
if (taskIds.length === 0) {
|
|
81100
|
+
return { output: "shell_task_wait requires a non-empty 'task_ids'", isError: true };
|
|
81101
|
+
}
|
|
81102
|
+
const mode = input.mode === "any" ? "any" : "all";
|
|
81103
|
+
const timeoutMs = clampTaskWaitMs(typeof input.timeout_ms === "number" ? input.timeout_ms : MAX_TASK_WAIT_MS);
|
|
81104
|
+
const known = taskIds.filter((id) => registry.get(id) !== undefined);
|
|
81105
|
+
const signal = ctx?.signal;
|
|
81106
|
+
let onAbort;
|
|
81107
|
+
const aborted = new Promise((resolve) => {
|
|
81108
|
+
if (signal === undefined)
|
|
81109
|
+
return;
|
|
81110
|
+
if (signal.aborted) {
|
|
81111
|
+
resolve("aborted");
|
|
81112
|
+
return;
|
|
81113
|
+
}
|
|
81114
|
+
onAbort = () => resolve("aborted");
|
|
81115
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
81116
|
+
});
|
|
81117
|
+
try {
|
|
81118
|
+
if (known.length > 0) {
|
|
81119
|
+
const waits = known.map((id) => registry.waitForExit(id, timeoutMs).then(() => id));
|
|
81120
|
+
const settled = mode === "any" ? await Promise.race([...waits, aborted]) : await Promise.race([Promise.all(waits).then(() => "all"), aborted]);
|
|
81121
|
+
if (settled === "aborted") {
|
|
81122
|
+
const lines = taskIds.map((id) => describeWaitedTask(registry, id));
|
|
81123
|
+
return {
|
|
81124
|
+
output: `interrupted: the wait was stopped and every task is STILL RUNNING \u2014 none was killed.
|
|
81125
|
+
` + lines.join(`
|
|
81126
|
+
`),
|
|
81127
|
+
isError: false
|
|
81128
|
+
};
|
|
81129
|
+
}
|
|
81130
|
+
}
|
|
81131
|
+
const lines = taskIds.map((id) => describeWaitedTask(registry, id));
|
|
81132
|
+
return { output: lines.join(`
|
|
81133
|
+
`), isError: false };
|
|
81134
|
+
} finally {
|
|
81135
|
+
if (onAbort !== undefined)
|
|
81136
|
+
signal?.removeEventListener("abort", onAbort);
|
|
81137
|
+
}
|
|
81138
|
+
}
|
|
81139
|
+
};
|
|
81140
|
+
}
|
|
81141
|
+
function resolveTaskId(registry, id) {
|
|
81142
|
+
if (registry.get(id) !== undefined)
|
|
81143
|
+
return id;
|
|
81144
|
+
const swapped = id.startsWith("job-") ? `task-${id.slice("job-".length)}` : id.startsWith("task-") ? `job-${id.slice("task-".length)}` : undefined;
|
|
81145
|
+
if (swapped !== undefined && registry.get(swapped) !== undefined)
|
|
81146
|
+
return swapped;
|
|
81147
|
+
return id;
|
|
81148
|
+
}
|
|
81149
|
+
function shellTaskKillTool(registry) {
|
|
81150
|
+
return {
|
|
81151
|
+
definition: {
|
|
81152
|
+
name: "shell_task_kill",
|
|
81153
|
+
description: "Stop a running shell task \u2014 its entire process group, including any descendant it backgrounded. Input: " + "{ task_id: string }. Killing a task that has already finished is refused with its status rather than " + "signalling anything, so calling this twice is safe. Only tasks from this session can be targeted.",
|
|
81154
|
+
inputSchema: {
|
|
81155
|
+
type: "object",
|
|
81156
|
+
properties: { task_id: { type: "string" } },
|
|
81157
|
+
required: ["task_id"],
|
|
81158
|
+
additionalProperties: false
|
|
81159
|
+
},
|
|
81160
|
+
risk: "read"
|
|
81161
|
+
},
|
|
81162
|
+
invoke: async (input) => {
|
|
81163
|
+
const raw = typeof input.task_id === "string" ? input.task_id : "";
|
|
81164
|
+
if (raw.length === 0) {
|
|
81165
|
+
return { output: "shell_task_kill requires a non-empty 'task_id'", isError: true };
|
|
81166
|
+
}
|
|
81167
|
+
const taskId = raw;
|
|
81168
|
+
const result = await registry.kill(taskId);
|
|
81169
|
+
if (!result.ok) {
|
|
81170
|
+
return { output: result.error, isError: true };
|
|
81171
|
+
}
|
|
81172
|
+
return { output: `task ${taskId} killed`, isError: false };
|
|
81173
|
+
}
|
|
81174
|
+
};
|
|
81175
|
+
}
|
|
80984
81176
|
function shellJobKillTool(registry) {
|
|
80985
81177
|
return {
|
|
80986
81178
|
definition: {
|
|
80987
81179
|
name: "shell_job_kill",
|
|
80988
|
-
description: "Stop a running shell task \u2014 its entire process group, including any descendant it backgrounded. Input: " + "{ job_id: string } \u2014 the task_id shell_exec returned for a command still running after its yield (or a " + "background:true job). Only tasks in this session's own registry can be targeted.",
|
|
81180
|
+
description: "DEPRECATED \u2014 use shell_task_kill; this name is kept for one release. " + "Stop a running shell task \u2014 its entire process group, including any descendant it backgrounded. Input: " + "{ job_id: string } \u2014 the task_id shell_exec returned for a command still running after its yield (or a " + "background:true job). Only tasks in this session's own registry can be targeted.",
|
|
80989
81181
|
inputSchema: {
|
|
80990
81182
|
type: "object",
|
|
80991
81183
|
properties: { job_id: { type: "string" } },
|
|
@@ -81332,7 +81524,7 @@ function shellExecTool(root, run = makeCommandRunner(root), jobRegistry, options
|
|
|
81332
81524
|
},
|
|
81333
81525
|
risk: "shell"
|
|
81334
81526
|
},
|
|
81335
|
-
invoke: async (input) => {
|
|
81527
|
+
invoke: async (input, ctx) => {
|
|
81336
81528
|
const command = typeof input.command === "string" ? input.command : "";
|
|
81337
81529
|
if (command.length === 0) {
|
|
81338
81530
|
return { output: "shell_exec requires a non-empty 'command'", isError: true };
|
|
@@ -81365,21 +81557,34 @@ function shellExecTool(root, run = makeCommandRunner(root), jobRegistry, options
|
|
|
81365
81557
|
});
|
|
81366
81558
|
}
|
|
81367
81559
|
const yieldMs = options?.yieldMs ?? resolveShellYieldMs();
|
|
81368
|
-
const
|
|
81560
|
+
const signal = ctx?.signal;
|
|
81561
|
+
let onAbort;
|
|
81562
|
+
const outcome = await (signal === undefined || signal.aborted === false ? Promise.race([
|
|
81563
|
+
jobRegistry.waitForExit(taskId, yieldMs),
|
|
81564
|
+
new Promise((resolve) => {
|
|
81565
|
+
if (signal === undefined)
|
|
81566
|
+
return;
|
|
81567
|
+
onAbort = () => resolve("aborted");
|
|
81568
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
81569
|
+
})
|
|
81570
|
+
]) : Promise.resolve("aborted"));
|
|
81571
|
+
if (onAbort !== undefined)
|
|
81572
|
+
signal?.removeEventListener("abort", onAbort);
|
|
81369
81573
|
let collected = started.output;
|
|
81370
81574
|
const pending = jobRegistry.readOutput(taskId);
|
|
81371
81575
|
if (pending.ok)
|
|
81372
81576
|
collected += pending.output;
|
|
81373
|
-
if (outcome === "timeout") {
|
|
81577
|
+
if (outcome === "timeout" || outcome === "aborted") {
|
|
81374
81578
|
const promoted = jobRegistry.promote(taskId);
|
|
81375
81579
|
if (!promoted.ok) {
|
|
81376
81580
|
return { output: promoted.error, isError: true };
|
|
81377
81581
|
}
|
|
81582
|
+
const notice = outcome === "aborted" ? `interrupted: the wait was stopped, so this is now a background task and it is STILL RUNNING \u2014 it was not killed; read new output with shell_job_output("${taskId}") and stop it with shell_job_kill("${taskId}")` : `still running after ${yieldMs}ms, so it is now a background task; read new output with shell_job_output("${taskId}") and stop it with shell_job_kill("${taskId}")`;
|
|
81378
81583
|
return taskHandleResult({
|
|
81379
81584
|
taskId,
|
|
81380
81585
|
pid: started.pid,
|
|
81381
81586
|
output: collected,
|
|
81382
|
-
notice
|
|
81587
|
+
notice,
|
|
81383
81588
|
...promoted.overCap !== undefined ? { overCap: promoted.overCap } : {}
|
|
81384
81589
|
});
|
|
81385
81590
|
}
|
|
@@ -82239,7 +82444,13 @@ function buildInteractiveAgentTools(input) {
|
|
|
82239
82444
|
webFetchTool(),
|
|
82240
82445
|
webSearchTool(input.searchController),
|
|
82241
82446
|
shellExecTool(input.cwd, undefined, jobRegistry),
|
|
82242
|
-
...jobRegistry !== undefined ? [
|
|
82447
|
+
...jobRegistry !== undefined ? [
|
|
82448
|
+
shellTaskOutputTool(jobRegistry, { observer: "main" }),
|
|
82449
|
+
shellTaskWaitTool(jobRegistry),
|
|
82450
|
+
shellTaskKillTool(jobRegistry),
|
|
82451
|
+
shellJobOutputTool(jobRegistry),
|
|
82452
|
+
shellJobKillTool(jobRegistry)
|
|
82453
|
+
] : [],
|
|
82243
82454
|
applyPatchTool(input.cwd),
|
|
82244
82455
|
workspaceOverviewTool(input.cwd),
|
|
82245
82456
|
workspaceReadTool(input.cwd),
|
|
@@ -88278,6 +88489,8 @@ function classifyBusyDispatch(params) {
|
|
|
88278
88489
|
return "copy";
|
|
88279
88490
|
if (commandName === "/mode")
|
|
88280
88491
|
return "mode";
|
|
88492
|
+
if (commandName === "/demote")
|
|
88493
|
+
return "demote";
|
|
88281
88494
|
if (commandName === "/game")
|
|
88282
88495
|
return "game";
|
|
88283
88496
|
const isBusyReadonlyCommand = isSessionInfo || isFlows || isWorkspace || isReview || isMcp || isMcpConsumer;
|
|
@@ -90432,6 +90645,11 @@ var AGENT_SLASH_COMMANDS = [
|
|
|
90432
90645
|
description: "Hand a task to an external agent CLI \u2014 /delegate <agent> <task>",
|
|
90433
90646
|
modes: AGENT_ONLY
|
|
90434
90647
|
},
|
|
90648
|
+
{
|
|
90649
|
+
name: "/demote",
|
|
90650
|
+
description: "Move a running foreground task to the background \u2014 /demote <task_id>",
|
|
90651
|
+
modes: AGENT_ONLY
|
|
90652
|
+
},
|
|
90435
90653
|
{
|
|
90436
90654
|
name: "/exit",
|
|
90437
90655
|
description: "Leave the shell (/quit works too)",
|
|
@@ -90462,6 +90680,18 @@ function parseDelegateCommand(args) {
|
|
|
90462
90680
|
}
|
|
90463
90681
|
return { ok: true, agentId, task };
|
|
90464
90682
|
}
|
|
90683
|
+
var DEMOTE_USAGE = "/demote <task_id> (the task_id shell_exec returned)";
|
|
90684
|
+
function parseDemoteCommand(args) {
|
|
90685
|
+
const trimmed = args.trim();
|
|
90686
|
+
if (trimmed.length === 0) {
|
|
90687
|
+
return { ok: false, reason: `usage: ${DEMOTE_USAGE}` };
|
|
90688
|
+
}
|
|
90689
|
+
const parts = trimmed.split(/\s+/);
|
|
90690
|
+
if (parts.length > 1) {
|
|
90691
|
+
return { ok: false, reason: `\`/demote\` takes ONE task_id \u2014 usage: ${DEMOTE_USAGE}` };
|
|
90692
|
+
}
|
|
90693
|
+
return { ok: true, taskId: parts[0] ?? "" };
|
|
90694
|
+
}
|
|
90465
90695
|
function isCommandInMode(command, mode) {
|
|
90466
90696
|
return command.modes.includes(mode);
|
|
90467
90697
|
}
|
|
@@ -95116,7 +95346,12 @@ function paintBackgroundJobSidebar(otui, renderer, parent, entries, options) {
|
|
|
95116
95346
|
|
|
95117
95347
|
// src/tui/tui-shell.ts
|
|
95118
95348
|
var SESSION_PREVIEW_MESSAGE_COUNT = 200;
|
|
95119
|
-
var SIDE_WORKER_DENIED_TOOL_NAMES = new Set([
|
|
95349
|
+
var SIDE_WORKER_DENIED_TOOL_NAMES = new Set([
|
|
95350
|
+
"shell_job_kill",
|
|
95351
|
+
"shell_task_kill",
|
|
95352
|
+
"shell_task_wait",
|
|
95353
|
+
"shell_job_output"
|
|
95354
|
+
]);
|
|
95120
95355
|
function parseGitHubRemote(remote) {
|
|
95121
95356
|
const trimmed = remote.trim();
|
|
95122
95357
|
if (trimmed.length === 0) {
|
|
@@ -97989,6 +98224,22 @@ Staying in the current session.
|
|
|
97989
98224
|
isMcpConsumer: isMcpConsumerCommand(line)
|
|
97990
98225
|
});
|
|
97991
98226
|
switch (decision) {
|
|
98227
|
+
case "demote": {
|
|
98228
|
+
const parsed = parseDemoteCommand(line.trim().replace(/^\/\S+\s*/, ""));
|
|
98229
|
+
if (!parsed.ok) {
|
|
98230
|
+
io.onSystem?.(`${parsed.reason}
|
|
98231
|
+
`);
|
|
98232
|
+
} else if (deps.jobRegistry === undefined) {
|
|
98233
|
+
io.onSystem?.(`This session tracks no shell tasks, so there is nothing to demote.
|
|
98234
|
+
`);
|
|
98235
|
+
} else {
|
|
98236
|
+
const demoted = demoteTask(deps.jobRegistry, parsed.taskId);
|
|
98237
|
+
io.onSystem?.(demoted.ok ? `Task ${parsed.taskId} keeps running, now in the background \u2014 it was NOT stopped.
|
|
98238
|
+
` : `${demoted.error}
|
|
98239
|
+
`);
|
|
98240
|
+
}
|
|
98241
|
+
return;
|
|
98242
|
+
}
|
|
97992
98243
|
case "exit": {
|
|
97993
98244
|
foregroundOperation.cancel("shell exit");
|
|
97994
98245
|
foregroundOperation.dispose();
|
|
@@ -98184,6 +98435,22 @@ Staying in the current session.
|
|
|
98184
98435
|
})();
|
|
98185
98436
|
return;
|
|
98186
98437
|
}
|
|
98438
|
+
if (command.name === "/demote") {
|
|
98439
|
+
const parsed = parseDemoteCommand(line.trim().replace(/^\/\S+\s*/, ""));
|
|
98440
|
+
if (!parsed.ok) {
|
|
98441
|
+
io.onSystem?.(`${parsed.reason}
|
|
98442
|
+
`);
|
|
98443
|
+
} else if (deps.jobRegistry === undefined) {
|
|
98444
|
+
io.onSystem?.(`This session tracks no shell tasks, so there is nothing to demote.
|
|
98445
|
+
`);
|
|
98446
|
+
} else {
|
|
98447
|
+
const demoted = demoteTask(deps.jobRegistry, parsed.taskId);
|
|
98448
|
+
io.onSystem?.(demoted.ok ? `Task ${parsed.taskId} keeps running, now in the background \u2014 it was NOT stopped.
|
|
98449
|
+
` : `${demoted.error}
|
|
98450
|
+
`);
|
|
98451
|
+
}
|
|
98452
|
+
return;
|
|
98453
|
+
}
|
|
98187
98454
|
if (command.name === "/clear" || command.name === "/new") {
|
|
98188
98455
|
(async () => {
|
|
98189
98456
|
await closeSlateSession(slateSession, mintTimestampAttemptId);
|
|
@@ -100223,6 +100490,25 @@ ${GUTTER}${turnSeparator()}
|
|
|
100223
100490
|
`);
|
|
100224
100491
|
} else {
|
|
100225
100492
|
agentIo.onSystem?.(`Compacted \u2212${packed.result.removed} context msgs \xB7 archive ${live.summary.archiveMessageCount} \xB7 compact\xD7${live.summary.compactCount}
|
|
100493
|
+
`);
|
|
100494
|
+
}
|
|
100495
|
+
}
|
|
100496
|
+
} else if (command === "/demote") {
|
|
100497
|
+
const parsed = parseDemoteCommand(rest);
|
|
100498
|
+
if (!parsed.ok) {
|
|
100499
|
+
agentIo.onSystem?.(`${parsed.reason}
|
|
100500
|
+
`);
|
|
100501
|
+
} else if (deps.jobRegistry === undefined) {
|
|
100502
|
+
agentIo.onSystem?.(`This session tracks no shell tasks, so there is nothing to demote.
|
|
100503
|
+
`);
|
|
100504
|
+
} else {
|
|
100505
|
+
const demoted = demoteTask(deps.jobRegistry, parsed.taskId);
|
|
100506
|
+
if (!demoted.ok) {
|
|
100507
|
+
agentIo.onSystem?.(`${demoted.error}
|
|
100508
|
+
`);
|
|
100509
|
+
} else {
|
|
100510
|
+
const overCap = demoted.overCap !== undefined ? ` Background tasks are over the cap; also running: ${demoted.overCap}.` : "";
|
|
100511
|
+
agentIo.onSystem?.(`Task ${parsed.taskId} keeps running, now in the background \u2014 it was NOT stopped.${overCap}
|
|
100226
100512
|
`);
|
|
100227
100513
|
}
|
|
100228
100514
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrciphersmith/keryx",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.110",
|
|
4
4
|
"description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|