neko-harness 0.5.2 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/dist/client/assets/index-10n4trvc.css +2 -0
- package/dist/client/assets/index-CYjEttGJ.js +398 -0
- package/dist/client/index.html +2 -2
- package/dist/gateway.js +607 -108
- package/dist/neko.js +824 -128
- package/package.json +2 -1
- package/dist/client/assets/index-DCZeTvcw.css +0 -2
- package/dist/client/assets/index-PcV-h5ox.js +0 -396
package/dist/gateway.js
CHANGED
|
@@ -16604,6 +16604,42 @@ import { basename as basename4, dirname as dirname9, join as join13 } from "path
|
|
|
16604
16604
|
import { createHost as createHost2 } from "@earendil-works/chord";
|
|
16605
16605
|
import { BACKGROUND_CONTEXT as BACKGROUND_CONTEXT4 } from "@earendil-works/chord/context";
|
|
16606
16606
|
|
|
16607
|
+
// packages/core/src/codemode-mode.ts
|
|
16608
|
+
var CODEMODE_NAME = "codemode";
|
|
16609
|
+
var CODEMODE_MODES = ["off", "on", "only"];
|
|
16610
|
+
var DEFAULT_CODEMODE_MODE = "off";
|
|
16611
|
+
var isCodemodeMode = (value) => typeof value === "string" && CODEMODE_MODES.includes(value);
|
|
16612
|
+
var CODEMODE_LIMITS = {
|
|
16613
|
+
memoryLimitBytes: 256 * 1024 * 1024,
|
|
16614
|
+
maxWorkers: 2,
|
|
16615
|
+
waitMs: 60000
|
|
16616
|
+
};
|
|
16617
|
+
var CODEMODE_VIEW_LIMITS = {
|
|
16618
|
+
maxCalls: 100,
|
|
16619
|
+
argsChars: 200,
|
|
16620
|
+
errorChars: 200
|
|
16621
|
+
};
|
|
16622
|
+
function parseCodemodeMode(value, field = "codemode.mode") {
|
|
16623
|
+
if (!isCodemodeMode(value))
|
|
16624
|
+
throw new Error(`"${field}" must be one of ${CODEMODE_MODES.join(", ")}.`);
|
|
16625
|
+
return value;
|
|
16626
|
+
}
|
|
16627
|
+
function parseCodemodeSettings(value, prefix = "codemode") {
|
|
16628
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
16629
|
+
throw new Error(`"${prefix}" must be an object.`);
|
|
16630
|
+
const { mode, ...rest } = value;
|
|
16631
|
+
const unknown = Object.keys(rest)[0];
|
|
16632
|
+
if (unknown !== undefined)
|
|
16633
|
+
throw new Error(`"${prefix}.${unknown}" is not a known setting.`);
|
|
16634
|
+
return mode === undefined ? {} : { mode: parseCodemodeMode(mode, `${prefix}.mode`) };
|
|
16635
|
+
}
|
|
16636
|
+
function resolveCodemodeSettings(config, saved) {
|
|
16637
|
+
if (isCodemodeMode(saved))
|
|
16638
|
+
return { effective: { mode: saved }, source: "saved" };
|
|
16639
|
+
if (config?.mode !== undefined)
|
|
16640
|
+
return { effective: { mode: config.mode }, source: "config" };
|
|
16641
|
+
return { effective: { mode: DEFAULT_CODEMODE_MODE }, source: "default" };
|
|
16642
|
+
}
|
|
16607
16643
|
// packages/core/src/config.ts
|
|
16608
16644
|
import { existsSync, readFileSync } from "fs";
|
|
16609
16645
|
import { homedir } from "os";
|
|
@@ -16934,6 +16970,13 @@ function validate(config, source) {
|
|
|
16934
16970
|
throw new Error(`neko config: ${source}: ${error instanceof Error ? error.message : String(error)}`);
|
|
16935
16971
|
}
|
|
16936
16972
|
}
|
|
16973
|
+
if (config.codemode !== undefined) {
|
|
16974
|
+
try {
|
|
16975
|
+
parseCodemodeSettings(config.codemode);
|
|
16976
|
+
} catch (error) {
|
|
16977
|
+
throw new Error(`neko config: ${source}: ${error instanceof Error ? error.message : String(error)}`);
|
|
16978
|
+
}
|
|
16979
|
+
}
|
|
16937
16980
|
}
|
|
16938
16981
|
// packages/core/src/credentials.ts
|
|
16939
16982
|
import { chmod, mkdir, open, readFile, stat, unlink } from "fs/promises";
|
|
@@ -17146,6 +17189,36 @@ class FileCredentialStore {
|
|
|
17146
17189
|
// packages/core/src/file-changes.ts
|
|
17147
17190
|
import { isAbsolute as isAbsolute2, relative, resolve as resolve3, sep } from "path";
|
|
17148
17191
|
|
|
17192
|
+
// packages/core/src/change-limits.ts
|
|
17193
|
+
var CHANGE_LIMITS = {
|
|
17194
|
+
files: 500,
|
|
17195
|
+
opsPerFile: 50,
|
|
17196
|
+
diffBytes: 2 * 1024 * 1024,
|
|
17197
|
+
opDiffBytes: 64 * 1024,
|
|
17198
|
+
conversations: 64,
|
|
17199
|
+
nestedRecords: 1000
|
|
17200
|
+
};
|
|
17201
|
+
function clipDiff(diff) {
|
|
17202
|
+
if (Buffer.byteLength(diff, "utf8") <= CHANGE_LIMITS.opDiffBytes)
|
|
17203
|
+
return { diff, truncated: false };
|
|
17204
|
+
const lines = diff.split(`
|
|
17205
|
+
`);
|
|
17206
|
+
const kept = [];
|
|
17207
|
+
let bytes = 0;
|
|
17208
|
+
for (const line of lines) {
|
|
17209
|
+
bytes += Buffer.byteLength(line, "utf8") + 1;
|
|
17210
|
+
if (bytes > CHANGE_LIMITS.opDiffBytes)
|
|
17211
|
+
break;
|
|
17212
|
+
kept.push(line);
|
|
17213
|
+
}
|
|
17214
|
+
return { diff: kept.join(`
|
|
17215
|
+
`), truncated: true };
|
|
17216
|
+
}
|
|
17217
|
+
|
|
17218
|
+
// packages/core/src/nested-changes.ts
|
|
17219
|
+
import { defineDoc, wrapTool } from "@earendil-works/pi-durable";
|
|
17220
|
+
import { createEditTool, createWriteTool } from "@earendil-works/pi-durable/tools";
|
|
17221
|
+
|
|
17149
17222
|
// packages/core/src/image-prompt.ts
|
|
17150
17223
|
var DEFAULT_IMAGE_PROMPT = {
|
|
17151
17224
|
version: 2,
|
|
@@ -17310,6 +17383,47 @@ var interactionField = (name, details) => {
|
|
|
17310
17383
|
const interaction = toolInteraction(name, details);
|
|
17311
17384
|
return interaction ? { interaction } : {};
|
|
17312
17385
|
};
|
|
17386
|
+
var clip2 = (text, max) => text.length <= max ? text : `${text.slice(0, max - 1)}\u2026`;
|
|
17387
|
+
function codemodeView(calls, fullOutputPath) {
|
|
17388
|
+
const limit = CODEMODE_VIEW_LIMITS.maxCalls;
|
|
17389
|
+
return { calls: calls.slice(0, limit), omitted: Math.max(0, calls.length - limit), fullOutputPath };
|
|
17390
|
+
}
|
|
17391
|
+
function liveCodemodeView(live, callId) {
|
|
17392
|
+
const slots = (live.nestedTools ?? []).filter((slot) => slot.parentCallId === callId);
|
|
17393
|
+
return codemodeView(slots.map((slot, index) => {
|
|
17394
|
+
const key = /\/c(\d+)$/.exec(slot.callId)?.[1];
|
|
17395
|
+
const summary = slot.summary;
|
|
17396
|
+
return {
|
|
17397
|
+
id: key === undefined ? index + 1 : Number(key),
|
|
17398
|
+
name: slot.name,
|
|
17399
|
+
args: clip2(JSON.stringify(slot.arguments) ?? "", CODEMODE_VIEW_LIMITS.argsChars),
|
|
17400
|
+
status: slot.status !== "done" ? "running" : summary === undefined ? "cancelled" : summary.isError ? "error" : "ok",
|
|
17401
|
+
durationMs: summary?.durationMs ?? null,
|
|
17402
|
+
error: summary?.error === undefined ? null : clip2(summary.error, CODEMODE_VIEW_LIMITS.errorChars)
|
|
17403
|
+
};
|
|
17404
|
+
}), null);
|
|
17405
|
+
}
|
|
17406
|
+
function codemodeResultView(details) {
|
|
17407
|
+
if (!isRecord(details) || !Array.isArray(details.calls))
|
|
17408
|
+
return;
|
|
17409
|
+
const calls = details.calls.flatMap((call) => {
|
|
17410
|
+
if (!isRecord(call) || typeof call.name !== "string")
|
|
17411
|
+
return [];
|
|
17412
|
+
const { id, args, status, durationMs, error } = call;
|
|
17413
|
+
return [
|
|
17414
|
+
{
|
|
17415
|
+
id: typeof id === "number" ? id : 0,
|
|
17416
|
+
name: call.name,
|
|
17417
|
+
args: typeof args === "string" ? clip2(args, CODEMODE_VIEW_LIMITS.argsChars) : "",
|
|
17418
|
+
status: status === "ok" || status === "error" || status === "cancelled" ? status : "running",
|
|
17419
|
+
durationMs: typeof durationMs === "number" ? durationMs : null,
|
|
17420
|
+
error: typeof error === "string" ? clip2(error, CODEMODE_VIEW_LIMITS.errorChars) : null
|
|
17421
|
+
}
|
|
17422
|
+
];
|
|
17423
|
+
});
|
|
17424
|
+
return codemodeView(calls, typeof details.fullOutputPath === "string" ? details.fullOutputPath : null);
|
|
17425
|
+
}
|
|
17426
|
+
var codemodeField = (name, view) => name === CODEMODE_NAME && view ? { codemode: view } : {};
|
|
17313
17427
|
function summarizeToolCall(name, args) {
|
|
17314
17428
|
const record = args ?? {};
|
|
17315
17429
|
switch (name) {
|
|
@@ -17342,6 +17456,12 @@ function summarizeToolCall(name, args) {
|
|
|
17342
17456
|
`)
|
|
17343
17457
|
};
|
|
17344
17458
|
}
|
|
17459
|
+
case "codemode": {
|
|
17460
|
+
const code = typeof record.code === "string" ? record.code : "";
|
|
17461
|
+
const first = code.split(`
|
|
17462
|
+
`).find((line) => line.trim() !== "" && !line.trim().startsWith("// @options")) ?? "";
|
|
17463
|
+
return { args: truncateMiddle(first.trim(), 100), detail: code };
|
|
17464
|
+
}
|
|
17345
17465
|
case "subagent":
|
|
17346
17466
|
return {
|
|
17347
17467
|
args: record.action === "list" ? "List agents" : `${record.agentId ?? record.type}: ${record.title}`,
|
|
@@ -17487,7 +17607,8 @@ function projectChatView(view, modelLabel, options = {}) {
|
|
|
17487
17607
|
diff,
|
|
17488
17608
|
diffAdded: counts?.added ?? null,
|
|
17489
17609
|
diffRemoved: counts?.removed ?? null,
|
|
17490
|
-
...interactionField(message.toolName, message.details)
|
|
17610
|
+
...interactionField(message.toolName, message.details),
|
|
17611
|
+
...codemodeField(message.toolName, codemodeResultView(message.details))
|
|
17491
17612
|
});
|
|
17492
17613
|
}
|
|
17493
17614
|
}
|
|
@@ -17585,7 +17706,8 @@ function projectChatView(view, modelLabel, options = {}) {
|
|
|
17585
17706
|
diffAdded: synthesized?.added ?? null,
|
|
17586
17707
|
diffRemoved: synthesized?.removed ?? null,
|
|
17587
17708
|
diffTruncated: synthesized?.truncated ?? false,
|
|
17588
|
-
...interactionField(block.name, slot?.details)
|
|
17709
|
+
...interactionField(block.name, slot?.details),
|
|
17710
|
+
...codemodeField(block.name, slot?.status === "running" ? liveCodemodeView(live, block.id) : undefined)
|
|
17589
17711
|
});
|
|
17590
17712
|
}
|
|
17591
17713
|
}
|
|
@@ -17708,7 +17830,8 @@ ${block.text}`;
|
|
|
17708
17830
|
diff: synthesized?.diff ?? null,
|
|
17709
17831
|
diffAdded: synthesized?.added ?? null,
|
|
17710
17832
|
diffRemoved: synthesized?.removed ?? null,
|
|
17711
|
-
diffTruncated: synthesized?.truncated ?? false
|
|
17833
|
+
diffTruncated: synthesized?.truncated ?? false,
|
|
17834
|
+
...codemodeField(block.name, slot?.status === "running" ? liveCodemodeView(live, block.id) : undefined)
|
|
17712
17835
|
});
|
|
17713
17836
|
}
|
|
17714
17837
|
}
|
|
@@ -17729,11 +17852,74 @@ function contextTokens(entries) {
|
|
|
17729
17852
|
return null;
|
|
17730
17853
|
}
|
|
17731
17854
|
|
|
17855
|
+
// packages/core/src/nested-changes.ts
|
|
17856
|
+
var NestedChangesDoc = defineDoc({
|
|
17857
|
+
kind: "app.neko.nested-changes",
|
|
17858
|
+
checkpointWhen: () => false,
|
|
17859
|
+
version: 1,
|
|
17860
|
+
scope: "conversation",
|
|
17861
|
+
history: "rewindable",
|
|
17862
|
+
fork: "asOf",
|
|
17863
|
+
initial: () => ({ total: 0, dropped: false, items: [] })
|
|
17864
|
+
});
|
|
17865
|
+
var bytesOf = (items) => items.reduce((sum, item) => sum + Buffer.byteLength(item.diff, "utf8"), 0);
|
|
17866
|
+
function addNestedChange(state, change) {
|
|
17867
|
+
const existing = state.items.findIndex((item) => item.id === change.id);
|
|
17868
|
+
if (existing !== -1)
|
|
17869
|
+
state.items.splice(existing, 1);
|
|
17870
|
+
else
|
|
17871
|
+
state.total += 1;
|
|
17872
|
+
state.items.push(change);
|
|
17873
|
+
let bytes = bytesOf(state.items);
|
|
17874
|
+
while (state.items.length > 1 && (state.items.length > CHANGE_LIMITS.nestedRecords || bytes > CHANGE_LIMITS.diffBytes)) {
|
|
17875
|
+
const evicted = state.items.shift();
|
|
17876
|
+
bytes -= Buffer.byteLength(evicted.diff, "utf8");
|
|
17877
|
+
state.dropped = true;
|
|
17878
|
+
}
|
|
17879
|
+
}
|
|
17880
|
+
function describe2(tool, args, details) {
|
|
17881
|
+
if (tool === "edit") {
|
|
17882
|
+
const raw = editResultDiff(details);
|
|
17883
|
+
if (raw === null)
|
|
17884
|
+
return { diff: "", added: 0, removed: 0, truncated: false };
|
|
17885
|
+
return { ...clipDiff(raw), ...countDiffLines(raw) };
|
|
17886
|
+
}
|
|
17887
|
+
return synthesizedWriteDiff(args) ?? { diff: "", added: 0, removed: 0, truncated: false };
|
|
17888
|
+
}
|
|
17889
|
+
function nestedChangeWraps() {
|
|
17890
|
+
const wrap = (reference, tool) => wrapTool(reference, (inner) => ({
|
|
17891
|
+
...inner,
|
|
17892
|
+
execute: async (args, api, context) => {
|
|
17893
|
+
const result = await inner.execute(args, api, context);
|
|
17894
|
+
const path = args?.path;
|
|
17895
|
+
if (result.isError || typeof path !== "string" || path === "")
|
|
17896
|
+
return result;
|
|
17897
|
+
const task = await api.getTask(api.taskId, context);
|
|
17898
|
+
const input = task?.input;
|
|
17899
|
+
if (input?.kind !== "nested" || typeof input.parentCallId !== "string")
|
|
17900
|
+
return result;
|
|
17901
|
+
const parentCallId = input.parentCallId;
|
|
17902
|
+
const change = {
|
|
17903
|
+
id: api.callId,
|
|
17904
|
+
parentCallId,
|
|
17905
|
+
tool,
|
|
17906
|
+
path,
|
|
17907
|
+
...describe2(tool, args, result.details)
|
|
17908
|
+
};
|
|
17909
|
+
await api.commit(async (tx) => {
|
|
17910
|
+
addNestedChange(await tx.doc(NestedChangesDoc, api.conversationId), change);
|
|
17911
|
+
}, context);
|
|
17912
|
+
return result;
|
|
17913
|
+
}
|
|
17914
|
+
}));
|
|
17915
|
+
return [wrap(createEditTool(), "edit"), wrap(createWriteTool(), "write")];
|
|
17916
|
+
}
|
|
17917
|
+
|
|
17732
17918
|
// packages/core/src/subagents.ts
|
|
17733
17919
|
import {
|
|
17734
17920
|
AssistantEntry,
|
|
17735
17921
|
configure,
|
|
17736
|
-
defineDoc as
|
|
17922
|
+
defineDoc as defineDoc4,
|
|
17737
17923
|
defineExtension,
|
|
17738
17924
|
defineTask,
|
|
17739
17925
|
defineTool,
|
|
@@ -17744,9 +17930,9 @@ import { Type } from "typebox";
|
|
|
17744
17930
|
import { Value } from "typebox/value";
|
|
17745
17931
|
|
|
17746
17932
|
// packages/core/src/plan-doc.ts
|
|
17747
|
-
import { defineDoc } from "@earendil-works/pi-durable";
|
|
17933
|
+
import { defineDoc as defineDoc2 } from "@earendil-works/pi-durable";
|
|
17748
17934
|
var PLAN_HISTORY = 5;
|
|
17749
|
-
var PlanDoc =
|
|
17935
|
+
var PlanDoc = defineDoc2({
|
|
17750
17936
|
kind: "app.neko.plan",
|
|
17751
17937
|
checkpointWhen: () => false,
|
|
17752
17938
|
version: 1,
|
|
@@ -17756,6 +17942,198 @@ var PlanDoc = defineDoc({
|
|
|
17756
17942
|
initial: () => ({ mode: "normal", revision: 0, items: [] })
|
|
17757
17943
|
});
|
|
17758
17944
|
var PLAN_CODER_BLOCKED = "Plan mode is read-only: the coder subagent is unavailable. Use explore or plan subagents, or finish the plan first.";
|
|
17945
|
+
var PLAN_REMOVED_TOOLS = ["edit", "write", "job"];
|
|
17946
|
+
var PLAN_INSTRUCTIONS = [
|
|
17947
|
+
"You are in plan mode: research and plan only, and change nothing.",
|
|
17948
|
+
"- Allowed: read, find_files, search_text, web_search, web_extract, todo, ask_user, read-only subagents (explore, plan) and bash limited to read-only commands (listing, searching, git log/diff/status, version checks). Other commands are rejected.",
|
|
17949
|
+
"- Not available: edit, write, job and any command that modifies files, installs packages or starts processes. Do not try to work around this.",
|
|
17950
|
+
"- When you understand the task, call submit_plan once, alone in its turn, with a short title, ordered concrete steps and an optional note (risks, assumptions, verification). The user then approves it or asks for changes.",
|
|
17951
|
+
"- After submit_plan do nothing else. If the user asks for changes, research what is needed and submit a revised plan.",
|
|
17952
|
+
"- Use ask_user only for decisions you cannot settle by reading the code."
|
|
17953
|
+
].join(`
|
|
17954
|
+
`);
|
|
17955
|
+
|
|
17956
|
+
// packages/core/src/agent-change.ts
|
|
17957
|
+
var ONLY_MODE_MAIN_TOOLS = ["todo", "ask_user", "subagent"];
|
|
17958
|
+
var named = (name) => ({ name });
|
|
17959
|
+
function onlyModeTools(plan, role) {
|
|
17960
|
+
return [
|
|
17961
|
+
CODEMODE_NAME,
|
|
17962
|
+
...role === "main" ? [...ONLY_MODE_MAIN_TOOLS, ...plan === "plan" ? ["submit_plan"] : []] : []
|
|
17963
|
+
];
|
|
17964
|
+
}
|
|
17965
|
+
function agentChangeFor({ plan, codemode, role }) {
|
|
17966
|
+
const planning = plan === "plan";
|
|
17967
|
+
return {
|
|
17968
|
+
tools: planning ? { remove: PLAN_REMOVED_TOOLS.map(named) } : null,
|
|
17969
|
+
modelTools: codemode === "only" ? onlyModeTools(plan, role).map(named) : planning ? { add: [named("submit_plan")] } : null,
|
|
17970
|
+
extensions: codemode === "off" ? { remove: [{ name: CODEMODE_NAME }] } : { add: [{ name: CODEMODE_NAME }] },
|
|
17971
|
+
instructions: planning ? PLAN_INSTRUCTIONS : null
|
|
17972
|
+
};
|
|
17973
|
+
}
|
|
17974
|
+
function enabledTools(agent) {
|
|
17975
|
+
const offered = new Set(agent.tools.map((tool) => tool.name));
|
|
17976
|
+
return [...agent.tools, ...agent.callable.filter((tool) => !offered.has(tool.name))];
|
|
17977
|
+
}
|
|
17978
|
+
var agentInPlanMode = (agent) => agent.instructions === PLAN_INSTRUCTIONS;
|
|
17979
|
+
function agentSyncChange(agent, state) {
|
|
17980
|
+
const desired = agentChangeFor(state);
|
|
17981
|
+
const planMismatch = agentInPlanMode(agent) !== (state.plan === "plan");
|
|
17982
|
+
const hasExtension = agent.extensions.some((extension) => extension.name === CODEMODE_NAME);
|
|
17983
|
+
const callable = new Set(agent.callable.map((tool) => tool.name));
|
|
17984
|
+
const offered = agent.tools.map((tool) => tool.name);
|
|
17985
|
+
let codemodeMismatch;
|
|
17986
|
+
if (state.codemode === "off")
|
|
17987
|
+
codemodeMismatch = hasExtension || callable.has(CODEMODE_NAME);
|
|
17988
|
+
else {
|
|
17989
|
+
codemodeMismatch = !hasExtension || !offered.includes(CODEMODE_NAME);
|
|
17990
|
+
if (state.codemode === "only") {
|
|
17991
|
+
const allowed = new Set(onlyModeTools(state.plan, state.role));
|
|
17992
|
+
codemodeMismatch ||= offered.some((name) => !allowed.has(name));
|
|
17993
|
+
}
|
|
17994
|
+
}
|
|
17995
|
+
if (!planMismatch && !codemodeMismatch)
|
|
17996
|
+
return;
|
|
17997
|
+
return {
|
|
17998
|
+
...planMismatch ? { tools: desired.tools, instructions: desired.instructions } : {},
|
|
17999
|
+
modelTools: desired.modelTools,
|
|
18000
|
+
...codemodeMismatch ? { extensions: desired.extensions } : {}
|
|
18001
|
+
};
|
|
18002
|
+
}
|
|
18003
|
+
|
|
18004
|
+
// packages/core/src/codemode.ts
|
|
18005
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
18006
|
+
import { CodemodeSandbox } from "@earendil-works/pi-codemode";
|
|
18007
|
+
import { defineDoc as defineDoc3, wrapTool as wrapTool2 } from "@earendil-works/pi-durable";
|
|
18008
|
+
import { createCodemodeExtension, createCodemodeTool } from "@earendil-works/pi-durable/codemode";
|
|
18009
|
+
var CodemodeSettingsDoc = defineDoc3({
|
|
18010
|
+
kind: "app.neko.codemode-settings",
|
|
18011
|
+
checkpointWhen: () => false,
|
|
18012
|
+
version: 1,
|
|
18013
|
+
scope: "conversation",
|
|
18014
|
+
history: "latest",
|
|
18015
|
+
fork: "initial",
|
|
18016
|
+
initial: () => ({ mode: null })
|
|
18017
|
+
});
|
|
18018
|
+
var CodemodeDoc = defineDoc3({
|
|
18019
|
+
kind: "app.neko.codemode",
|
|
18020
|
+
checkpointWhen: () => false,
|
|
18021
|
+
version: 1,
|
|
18022
|
+
scope: "conversation",
|
|
18023
|
+
history: "rewindable",
|
|
18024
|
+
fork: "asOf",
|
|
18025
|
+
initial: () => ({ mode: "off" })
|
|
18026
|
+
});
|
|
18027
|
+
var SANDBOX_BUSY = "Codemode sandbox is busy: too many scripts are running. Try again shortly.";
|
|
18028
|
+
|
|
18029
|
+
class WorkerLimiter {
|
|
18030
|
+
#max;
|
|
18031
|
+
#waitMs;
|
|
18032
|
+
#tickets = new AsyncLocalStorage;
|
|
18033
|
+
#waiters = [];
|
|
18034
|
+
#running = 0;
|
|
18035
|
+
#reserved = 0;
|
|
18036
|
+
constructor(limits = CODEMODE_LIMITS) {
|
|
18037
|
+
this.#max = limits.maxWorkers;
|
|
18038
|
+
this.#waitMs = limits.waitMs;
|
|
18039
|
+
}
|
|
18040
|
+
get running() {
|
|
18041
|
+
return this.#running;
|
|
18042
|
+
}
|
|
18043
|
+
get waiting() {
|
|
18044
|
+
return this.#waiters.length;
|
|
18045
|
+
}
|
|
18046
|
+
#free() {
|
|
18047
|
+
return this.#running + this.#reserved < this.#max;
|
|
18048
|
+
}
|
|
18049
|
+
#wake() {
|
|
18050
|
+
while (this.#waiters.length > 0 && this.#free()) {
|
|
18051
|
+
this.#reserved += 1;
|
|
18052
|
+
this.#waiters.shift()?.();
|
|
18053
|
+
}
|
|
18054
|
+
}
|
|
18055
|
+
acquireWorker = () => {
|
|
18056
|
+
const ticket = this.#tickets.getStore();
|
|
18057
|
+
if (ticket && !ticket.claimed) {
|
|
18058
|
+
ticket.claimed = true;
|
|
18059
|
+
this.#reserved -= 1;
|
|
18060
|
+
} else if (!this.#free())
|
|
18061
|
+
throw new Error(SANDBOX_BUSY);
|
|
18062
|
+
this.#running += 1;
|
|
18063
|
+
let released = false;
|
|
18064
|
+
return () => {
|
|
18065
|
+
if (released)
|
|
18066
|
+
return;
|
|
18067
|
+
released = true;
|
|
18068
|
+
this.#running -= 1;
|
|
18069
|
+
this.#wake();
|
|
18070
|
+
};
|
|
18071
|
+
};
|
|
18072
|
+
async#admit(signal) {
|
|
18073
|
+
signal?.throwIfAborted();
|
|
18074
|
+
if (this.#waiters.length === 0 && this.#free()) {
|
|
18075
|
+
this.#reserved += 1;
|
|
18076
|
+
return;
|
|
18077
|
+
}
|
|
18078
|
+
await new Promise((resolve, reject) => {
|
|
18079
|
+
let timer;
|
|
18080
|
+
const leave = () => {
|
|
18081
|
+
clearTimeout(timer);
|
|
18082
|
+
signal?.removeEventListener("abort", onAbort);
|
|
18083
|
+
};
|
|
18084
|
+
const grant = () => {
|
|
18085
|
+
leave();
|
|
18086
|
+
resolve();
|
|
18087
|
+
};
|
|
18088
|
+
const drop = (error) => {
|
|
18089
|
+
const index = this.#waiters.indexOf(grant);
|
|
18090
|
+
if (index === -1)
|
|
18091
|
+
return;
|
|
18092
|
+
this.#waiters.splice(index, 1);
|
|
18093
|
+
leave();
|
|
18094
|
+
reject(error);
|
|
18095
|
+
};
|
|
18096
|
+
const onAbort = () => drop(signal?.reason instanceof Error ? signal.reason : new Error("Aborted"));
|
|
18097
|
+
this.#waiters.push(grant);
|
|
18098
|
+
timer = setTimeout(() => drop(new Error(SANDBOX_BUSY)), this.#waitMs);
|
|
18099
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
18100
|
+
});
|
|
18101
|
+
}
|
|
18102
|
+
async run(signal, work) {
|
|
18103
|
+
await this.#admit(signal);
|
|
18104
|
+
const ticket = { claimed: false };
|
|
18105
|
+
try {
|
|
18106
|
+
return await this.#tickets.run(ticket, work);
|
|
18107
|
+
} finally {
|
|
18108
|
+
if (!ticket.claimed) {
|
|
18109
|
+
this.#reserved -= 1;
|
|
18110
|
+
this.#wake();
|
|
18111
|
+
}
|
|
18112
|
+
}
|
|
18113
|
+
}
|
|
18114
|
+
}
|
|
18115
|
+
function createCodemodeRuntime(limits = CODEMODE_LIMITS) {
|
|
18116
|
+
const limiter = new WorkerLimiter(limits);
|
|
18117
|
+
const sandbox = new CodemodeSandbox({
|
|
18118
|
+
memoryLimitBytes: CODEMODE_LIMITS.memoryLimitBytes,
|
|
18119
|
+
acquireWorker: limiter.acquireWorker
|
|
18120
|
+
});
|
|
18121
|
+
const options = { sandbox, models: false };
|
|
18122
|
+
const base = createCodemodeExtension(options);
|
|
18123
|
+
const reference = createCodemodeTool(options);
|
|
18124
|
+
const extension = {
|
|
18125
|
+
...base,
|
|
18126
|
+
wraps: [
|
|
18127
|
+
...base.wraps ?? [],
|
|
18128
|
+
...nestedChangeWraps(),
|
|
18129
|
+
wrapTool2(reference, (tool) => ({
|
|
18130
|
+
...tool,
|
|
18131
|
+
execute: (args, api, context) => limiter.run(context.abortSignal, () => tool.execute(args, api, context))
|
|
18132
|
+
}))
|
|
18133
|
+
]
|
|
18134
|
+
};
|
|
18135
|
+
return { extension, limiter, close: () => sandbox.close() };
|
|
18136
|
+
}
|
|
17759
18137
|
|
|
17760
18138
|
// packages/core/src/subagent-usage.ts
|
|
17761
18139
|
var emptySubagentUsage = () => ({ models: {}, tools: {} });
|
|
@@ -17801,7 +18179,7 @@ function subagentUsageTotals(state) {
|
|
|
17801
18179
|
|
|
17802
18180
|
// packages/core/src/subagents.ts
|
|
17803
18181
|
var agentIdFor = (childId) => `agent-${childId}`;
|
|
17804
|
-
var DelegationsDoc =
|
|
18182
|
+
var DelegationsDoc = defineDoc4({
|
|
17805
18183
|
kind: "app.neko.delegations",
|
|
17806
18184
|
checkpointWhen: () => false,
|
|
17807
18185
|
version: 1,
|
|
@@ -17810,7 +18188,7 @@ var DelegationsDoc = defineDoc2({
|
|
|
17810
18188
|
fork: "initial",
|
|
17811
18189
|
initial: () => ({ items: [] })
|
|
17812
18190
|
});
|
|
17813
|
-
var SubagentsDoc =
|
|
18191
|
+
var SubagentsDoc = defineDoc4({
|
|
17814
18192
|
kind: "app.neko.subagents",
|
|
17815
18193
|
checkpointWhen: () => false,
|
|
17816
18194
|
version: 1,
|
|
@@ -17819,7 +18197,7 @@ var SubagentsDoc = defineDoc2({
|
|
|
17819
18197
|
fork: "initial",
|
|
17820
18198
|
initial: () => ({ items: [] })
|
|
17821
18199
|
});
|
|
17822
|
-
var SubagentDoc =
|
|
18200
|
+
var SubagentDoc = defineDoc4({
|
|
17823
18201
|
kind: "app.neko.subagent",
|
|
17824
18202
|
checkpointWhen: () => false,
|
|
17825
18203
|
version: 1,
|
|
@@ -17828,7 +18206,7 @@ var SubagentDoc = defineDoc2({
|
|
|
17828
18206
|
fork: "initial",
|
|
17829
18207
|
initial: () => ({ role: null, parentId: null })
|
|
17830
18208
|
});
|
|
17831
|
-
var SlotsDoc =
|
|
18209
|
+
var SlotsDoc = defineDoc4({
|
|
17832
18210
|
kind: "app.neko.subagent-slots",
|
|
17833
18211
|
checkpointWhen: () => false,
|
|
17834
18212
|
version: 1,
|
|
@@ -17843,7 +18221,7 @@ var ROLE_PROMPTS = {
|
|
|
17843
18221
|
var SUBAGENT_DELIVERY = " Work from the assigned task, your own prior conversation context and project resources; you cannot see the main conversation. Do not delegate or ask the user directly. Your final message must be a self-contained delivery to the main agent, including evidence, results and limitations. Skills never expand your allowed tools.";
|
|
17844
18222
|
var MAIN_ONLY_TOOLS = new Set(["todo", "ask_user", "submit_plan"]);
|
|
17845
18223
|
var MAIN_ONLY_EXTENSIONS = new Set(["neko-todos", "neko-questions", "neko-plan"]);
|
|
17846
|
-
var READ_TOOLS = new Set(["read", "find_files", "search_text", "web_search", "web_extract"]);
|
|
18224
|
+
var READ_TOOLS = new Set(["read", "find_files", "search_text", "web_search", "web_extract", CODEMODE_NAME]);
|
|
17847
18225
|
async function refreshDelegations(tx, id) {
|
|
17848
18226
|
const doc = await tx.doc(DelegationsDoc, id);
|
|
17849
18227
|
const identities = await tx.doc(SubagentsDoc, id);
|
|
@@ -18131,6 +18509,7 @@ function createSubagentExtension(prepare, discard, guard) {
|
|
|
18131
18509
|
parameters,
|
|
18132
18510
|
structuredOutputSchema,
|
|
18133
18511
|
replay: "safe",
|
|
18512
|
+
callers: ["model"],
|
|
18134
18513
|
execute: async (input, api, context) => {
|
|
18135
18514
|
if (!Value.Check(actionParameters, input))
|
|
18136
18515
|
throw new Error("Invalid subagent arguments. create requires type/title/task; continue requires agentId/title/task; list accepts only cursor/limit. Do not mix fields from different actions.");
|
|
@@ -18181,12 +18560,16 @@ function createSubagentExtension(prepare, discard, guard) {
|
|
|
18181
18560
|
if (!identity) {
|
|
18182
18561
|
const created = child ?? await tx.createConversation({ ownership: { kind: "task", taskId: api.taskId } });
|
|
18183
18562
|
const role = args.action === "continue" ? "explore" : args.type;
|
|
18563
|
+
const codemode = (await tx.doc(CodemodeDoc, api.conversationId)).mode;
|
|
18564
|
+
(await tx.doc(CodemodeDoc, created.id)).mode = codemode;
|
|
18565
|
+
const enabled = enabledTools(agent);
|
|
18184
18566
|
await configure(tx, created.id, {
|
|
18185
18567
|
model: agent.model,
|
|
18186
18568
|
thinkingLevel: agent.thinkingLevel,
|
|
18187
18569
|
cwd: agent.cwd,
|
|
18188
18570
|
extensions: agent.extensions.filter((e) => e.name !== extension.name && !MAIN_ONLY_EXTENSIONS.has(e.name)),
|
|
18189
|
-
|
|
18571
|
+
modelTools: agentChangeFor({ plan: "normal", codemode, role: "subagent" }).modelTools,
|
|
18572
|
+
tools: enabled.filter((t) => t.name !== "subagent" && t.name !== "job" && !MAIN_ONLY_TOOLS.has(t.name) && (role === "coder" || READ_TOOLS.has(t.name) || role === "explore" && t.name === "bash"))
|
|
18190
18573
|
});
|
|
18191
18574
|
Object.assign(await tx.doc(SubagentDoc, created.id), { role, parentId: api.conversationId });
|
|
18192
18575
|
identity = {
|
|
@@ -18289,9 +18672,10 @@ async function removeMainOnlyTools(harness, ids, context) {
|
|
|
18289
18672
|
if (!(await harness.snapshot(SubagentDoc, id, context))?.role)
|
|
18290
18673
|
continue;
|
|
18291
18674
|
const agent = await (await harness.conversation(id, context)).agent(context);
|
|
18292
|
-
|
|
18675
|
+
const enabled = enabledTools(agent);
|
|
18676
|
+
if (!enabled.some((t) => MAIN_ONLY_TOOLS.has(t.name)))
|
|
18293
18677
|
continue;
|
|
18294
|
-
await harness.commit((tx) => configure(tx, id, { tools:
|
|
18678
|
+
await harness.commit((tx) => configure(tx, id, { tools: enabled.filter((t) => !MAIN_ONLY_TOOLS.has(t.name)) }), context);
|
|
18295
18679
|
}
|
|
18296
18680
|
}
|
|
18297
18681
|
async function installSubagentCoordinator(harness, parents, context) {
|
|
@@ -18338,13 +18722,6 @@ async function installSubagentCoordinator(harness, parents, context) {
|
|
|
18338
18722
|
}
|
|
18339
18723
|
|
|
18340
18724
|
// packages/core/src/file-changes.ts
|
|
18341
|
-
var CHANGE_LIMITS = {
|
|
18342
|
-
files: 500,
|
|
18343
|
-
opsPerFile: 50,
|
|
18344
|
-
diffBytes: 2 * 1024 * 1024,
|
|
18345
|
-
opDiffBytes: 64 * 1024,
|
|
18346
|
-
conversations: 64
|
|
18347
|
-
};
|
|
18348
18725
|
var MAIN_AGENT = "main";
|
|
18349
18726
|
function normalizeChangePath(raw, cwd, workspace) {
|
|
18350
18727
|
const resolved = raw.startsWith("~") ? raw : resolve3(cwd, raw);
|
|
@@ -18355,22 +18732,6 @@ function normalizeChangePath(raw, cwd, workspace) {
|
|
|
18355
18732
|
}
|
|
18356
18733
|
return { path: resolved.split(sep).join("/"), outside: true };
|
|
18357
18734
|
}
|
|
18358
|
-
function clipDiff(diff) {
|
|
18359
|
-
if (Buffer.byteLength(diff, "utf8") <= CHANGE_LIMITS.opDiffBytes)
|
|
18360
|
-
return { diff, truncated: false };
|
|
18361
|
-
const lines = diff.split(`
|
|
18362
|
-
`);
|
|
18363
|
-
const kept = [];
|
|
18364
|
-
let bytes = 0;
|
|
18365
|
-
for (const line of lines) {
|
|
18366
|
-
bytes += Buffer.byteLength(line, "utf8") + 1;
|
|
18367
|
-
if (bytes > CHANGE_LIMITS.opDiffBytes)
|
|
18368
|
-
break;
|
|
18369
|
-
kept.push(line);
|
|
18370
|
-
}
|
|
18371
|
-
return { diff: kept.join(`
|
|
18372
|
-
`), truncated: true };
|
|
18373
|
-
}
|
|
18374
18735
|
|
|
18375
18736
|
class ConversationChanges {
|
|
18376
18737
|
last = 0;
|
|
@@ -18378,6 +18739,7 @@ class ConversationChanges {
|
|
|
18378
18739
|
truncated = false;
|
|
18379
18740
|
bytes = 0;
|
|
18380
18741
|
files = new Map;
|
|
18742
|
+
#scripts = new Map;
|
|
18381
18743
|
#pending = new Map;
|
|
18382
18744
|
#agent;
|
|
18383
18745
|
#cwd;
|
|
@@ -18391,6 +18753,8 @@ class ConversationChanges {
|
|
|
18391
18753
|
for (const message of entry.model ?? []) {
|
|
18392
18754
|
if (message.role === "assistant") {
|
|
18393
18755
|
for (const block of message.content) {
|
|
18756
|
+
if (block.type === "toolCall" && block.name === CODEMODE_NAME)
|
|
18757
|
+
this.#scripts.set(block.id, entry.id);
|
|
18394
18758
|
if (block.type !== "toolCall" || block.name !== "edit" && block.name !== "write")
|
|
18395
18759
|
continue;
|
|
18396
18760
|
const path = block.arguments?.path;
|
|
@@ -18398,6 +18762,8 @@ class ConversationChanges {
|
|
|
18398
18762
|
this.#pending.set(block.id, { tool: block.name, path, args: block.arguments });
|
|
18399
18763
|
}
|
|
18400
18764
|
} else if (message.role === "toolResult") {
|
|
18765
|
+
if (this.#scripts.has(message.toolCallId))
|
|
18766
|
+
this.#scripts.set(message.toolCallId, entry.id);
|
|
18401
18767
|
const call = this.#pending.get(message.toolCallId);
|
|
18402
18768
|
if (!call)
|
|
18403
18769
|
continue;
|
|
@@ -18409,8 +18775,20 @@ class ConversationChanges {
|
|
|
18409
18775
|
}
|
|
18410
18776
|
this.last = entry.id;
|
|
18411
18777
|
}
|
|
18778
|
+
withNested(nested) {
|
|
18779
|
+
const visible = nested.items.filter((item) => this.#scripts.has(item.parentCallId));
|
|
18780
|
+
const copy = new ConversationChanges(this.#agent, this.#cwd, this.#workspace);
|
|
18781
|
+
copy.last = this.last;
|
|
18782
|
+
copy.total = this.total;
|
|
18783
|
+
copy.truncated = this.truncated || nested.dropped && visible.length > 0;
|
|
18784
|
+
copy.bytes = this.bytes;
|
|
18785
|
+
for (const [path, file] of this.files)
|
|
18786
|
+
copy.files.set(path, { ...file, agents: new Set(file.agents), items: file.items.map((item) => ({ ...item })) });
|
|
18787
|
+
for (const item of visible)
|
|
18788
|
+
copy.#record({ tool: item.tool, path: item.path }, { diff: item.diff, added: item.added, removed: item.removed, truncated: item.truncated }, this.#scripts.get(item.parentCallId));
|
|
18789
|
+
return copy;
|
|
18790
|
+
}
|
|
18412
18791
|
#add(call, details, seq) {
|
|
18413
|
-
const { path, outside } = normalizeChangePath(call.path, this.#cwd, this.#workspace);
|
|
18414
18792
|
let diff = "";
|
|
18415
18793
|
let added = 0;
|
|
18416
18794
|
let removed = 0;
|
|
@@ -18430,6 +18808,11 @@ class ConversationChanges {
|
|
|
18430
18808
|
({ diff, added, removed, truncated } = synthesized);
|
|
18431
18809
|
}
|
|
18432
18810
|
}
|
|
18811
|
+
this.#record(call, { diff, added, removed, truncated }, seq);
|
|
18812
|
+
}
|
|
18813
|
+
#record(call, shown, seq) {
|
|
18814
|
+
const { path, outside } = normalizeChangePath(call.path, this.#cwd, this.#workspace);
|
|
18815
|
+
const { diff, added, removed, truncated } = shown;
|
|
18433
18816
|
this.total++;
|
|
18434
18817
|
let file = this.files.get(path);
|
|
18435
18818
|
if (!file) {
|
|
@@ -18450,7 +18833,7 @@ class ConversationChanges {
|
|
|
18450
18833
|
file.added += added;
|
|
18451
18834
|
file.removed += removed;
|
|
18452
18835
|
file.agents.add(this.#agent);
|
|
18453
|
-
file.lastSeq = seq;
|
|
18836
|
+
file.lastSeq = Math.max(file.lastSeq, seq);
|
|
18454
18837
|
file.items.push({ seq, agent: this.#agent, tool: call.tool, diff, added, removed, truncated });
|
|
18455
18838
|
this.bytes += Buffer.byteLength(diff, "utf8");
|
|
18456
18839
|
if (file.items.length > CHANGE_LIMITS.opsPerFile) {
|
|
@@ -18552,10 +18935,12 @@ class ChangeTracker {
|
|
|
18552
18935
|
let revision = 0;
|
|
18553
18936
|
let truncated = false;
|
|
18554
18937
|
for (const member of await this.#members(rootId)) {
|
|
18555
|
-
const
|
|
18556
|
-
if (!
|
|
18938
|
+
const synced = await this.#sync(member, workspace);
|
|
18939
|
+
if (!synced)
|
|
18557
18940
|
continue;
|
|
18558
|
-
|
|
18941
|
+
const nested = await this.#harness.snapshot(NestedChangesDoc, member.id, this.#context);
|
|
18942
|
+
const state = nested && nested.items.length > 0 ? synced.withNested(nested) : synced;
|
|
18943
|
+
revision += synced.total + (nested?.total ?? 0);
|
|
18559
18944
|
truncated ||= state.truncated;
|
|
18560
18945
|
for (const file of state.files.values()) {
|
|
18561
18946
|
const existing = merged.get(file.path);
|
|
@@ -18960,7 +19345,7 @@ function validatePlanDecision(input) {
|
|
|
18960
19345
|
return { kind: "revise", feedback: text(input.feedback, "feedback", PLAN_LIMITS.feedbackChars) };
|
|
18961
19346
|
}
|
|
18962
19347
|
// packages/core/src/jobs.ts
|
|
18963
|
-
import { defineDoc as
|
|
19348
|
+
import { defineDoc as defineDoc5 } from "@earendil-works/pi-durable";
|
|
18964
19349
|
var JOB_LIMITS = {
|
|
18965
19350
|
maxRunning: 4,
|
|
18966
19351
|
keepTerminal: 20,
|
|
@@ -18974,7 +19359,7 @@ var JOB_LIMITS = {
|
|
|
18974
19359
|
commandBytes: 32 * 1024,
|
|
18975
19360
|
maxTimeoutSeconds: 7 * 24 * 3600
|
|
18976
19361
|
};
|
|
18977
|
-
var JobsDoc =
|
|
19362
|
+
var JobsDoc = defineDoc5({
|
|
18978
19363
|
kind: "app.neko.jobs",
|
|
18979
19364
|
checkpointWhen: () => false,
|
|
18980
19365
|
version: 1,
|
|
@@ -19115,7 +19500,7 @@ import { PI_ATTRIBUTION } from "@earendil-works/pi-ai/utils/identity";
|
|
|
19115
19500
|
import {
|
|
19116
19501
|
AssistantEntry as AssistantEntry2,
|
|
19117
19502
|
createRegistry,
|
|
19118
|
-
defineDoc as
|
|
19503
|
+
defineDoc as defineDoc10,
|
|
19119
19504
|
Harness,
|
|
19120
19505
|
InboxDoc as InboxDoc3,
|
|
19121
19506
|
LiveDoc as importLiveDoc,
|
|
@@ -19547,7 +19932,7 @@ var statusWord = {
|
|
|
19547
19932
|
stopped: "stopped",
|
|
19548
19933
|
lost: "lost"
|
|
19549
19934
|
};
|
|
19550
|
-
var
|
|
19935
|
+
var describe3 = (job) => {
|
|
19551
19936
|
const parts = [statusWord[job.status]];
|
|
19552
19937
|
if (job.exitCode !== null)
|
|
19553
19938
|
parts.push(`exit code ${job.exitCode}`);
|
|
@@ -19562,7 +19947,7 @@ var describe2 = (job) => {
|
|
|
19562
19947
|
function reportText(job, tail) {
|
|
19563
19948
|
const body = reportTail(tail);
|
|
19564
19949
|
return [
|
|
19565
|
-
`[Background job ${job.jobId} "${job.title}" ${
|
|
19950
|
+
`[Background job ${job.jobId} "${job.title}" ${describe3(job)}]`,
|
|
19566
19951
|
`Command: ${job.command}`,
|
|
19567
19952
|
body ? `Last output (use the job tool's output action with jobId "${job.jobId}" for more):
|
|
19568
19953
|
${body}` : "It produced no output."
|
|
@@ -19772,7 +20157,7 @@ class JobManager {
|
|
|
19772
20157
|
if (kind === "stop") {
|
|
19773
20158
|
const jobId = stringField(args.jobId, "jobId");
|
|
19774
20159
|
const job = await this.stop(io, api.conversationId, jobId, "model", context);
|
|
19775
|
-
return this.#text(`Job ${job.jobId} ${
|
|
20160
|
+
return this.#text(`Job ${job.jobId} ${describe3(job)}.`);
|
|
19776
20161
|
}
|
|
19777
20162
|
if (kind !== "start")
|
|
19778
20163
|
throw new Error("Invalid job arguments: action must be start, output, stop or list.");
|
|
@@ -19800,7 +20185,7 @@ class JobManager {
|
|
|
19800
20185
|
#listText(items) {
|
|
19801
20186
|
if (!items.length)
|
|
19802
20187
|
return "No jobs.";
|
|
19803
|
-
return items.map((job) => `${job.jobId} [${
|
|
20188
|
+
return items.map((job) => `${job.jobId} [${describe3(job)}] "${job.title}" \u2014 ${job.command}`).join(`
|
|
19804
20189
|
`);
|
|
19805
20190
|
}
|
|
19806
20191
|
async start(io, conversationId, cwd, args, context) {
|
|
@@ -19852,7 +20237,7 @@ class JobManager {
|
|
|
19852
20237
|
return this.#render(job, read.text, read.offset, read.dropped);
|
|
19853
20238
|
}
|
|
19854
20239
|
#render(job, text, offset, dropped) {
|
|
19855
|
-
const lines = [`Job ${job.jobId} ${
|
|
20240
|
+
const lines = [`Job ${job.jobId} ${describe3(job)}.`, `Title: ${job.title}`, `Command: ${job.command}`];
|
|
19856
20241
|
if (dropped > 0)
|
|
19857
20242
|
lines.push(`(${dropped} bytes of earlier output were dropped)`);
|
|
19858
20243
|
lines.push(text ? `Output (next offset ${offset}):
|
|
@@ -19926,7 +20311,7 @@ import {
|
|
|
19926
20311
|
hook as hook2,
|
|
19927
20312
|
InboxDoc,
|
|
19928
20313
|
ToolTask as ToolTask2,
|
|
19929
|
-
wrapTool
|
|
20314
|
+
wrapTool as wrapTool3
|
|
19930
20315
|
} from "@earendil-works/pi-durable";
|
|
19931
20316
|
import { createBashTool } from "@earendil-works/pi-durable/tools";
|
|
19932
20317
|
import { Type as Type5 } from "typebox";
|
|
@@ -20039,7 +20424,7 @@ function isSafeCommand(command) {
|
|
|
20039
20424
|
// packages/core/src/user-questions.ts
|
|
20040
20425
|
import { randomUUID } from "crypto";
|
|
20041
20426
|
import {
|
|
20042
|
-
defineDoc as
|
|
20427
|
+
defineDoc as defineDoc6,
|
|
20043
20428
|
defineDocFamily,
|
|
20044
20429
|
defineExtension as defineExtension5,
|
|
20045
20430
|
defineTool as defineTool4
|
|
@@ -20055,7 +20440,7 @@ var QuestionDoc = defineDocFamily({
|
|
|
20055
20440
|
family: true,
|
|
20056
20441
|
initial: (seed) => seed
|
|
20057
20442
|
});
|
|
20058
|
-
var QuestionsDoc =
|
|
20443
|
+
var QuestionsDoc = defineDoc6({
|
|
20059
20444
|
kind: "app.neko.questions",
|
|
20060
20445
|
checkpointWhen: () => false,
|
|
20061
20446
|
version: 1,
|
|
@@ -20377,28 +20762,7 @@ function createAskUserTool(service) {
|
|
|
20377
20762
|
var createQuestionExtension = (service) => defineExtension5({ name: "neko-questions", tools: [createAskUserTool(service)] });
|
|
20378
20763
|
|
|
20379
20764
|
// packages/core/src/plan.ts
|
|
20380
|
-
var PLAN_REMOVED_TOOLS = ["edit", "write", "job"];
|
|
20381
|
-
var PLAN_INSTRUCTIONS = [
|
|
20382
|
-
"You are in plan mode: research and plan only, and change nothing.",
|
|
20383
|
-
"- Allowed: read, find_files, search_text, web_search, web_extract, todo, ask_user, read-only subagents (explore, plan) and bash limited to read-only commands (listing, searching, git log/diff/status, version checks). Other commands are rejected.",
|
|
20384
|
-
"- Not available: edit, write, job and any command that modifies files, installs packages or starts processes. Do not try to work around this.",
|
|
20385
|
-
"- When you understand the task, call submit_plan once, alone in its turn, with a short title, ordered concrete steps and an optional note (risks, assumptions, verification). The user then approves it or asks for changes.",
|
|
20386
|
-
"- After submit_plan do nothing else. If the user asks for changes, research what is needed and submit a revised plan.",
|
|
20387
|
-
"- Use ask_user only for decisions you cannot settle by reading the code."
|
|
20388
|
-
].join(`
|
|
20389
|
-
`);
|
|
20390
20765
|
var PLAN_BUSY = "PLAN_BUSY: Wait for the current run to finish or stop it first.";
|
|
20391
|
-
function planAgentChange(mode) {
|
|
20392
|
-
if (mode === "normal")
|
|
20393
|
-
return { tools: null, modelTools: null, instructions: null };
|
|
20394
|
-
const named = (name) => ({ name });
|
|
20395
|
-
return {
|
|
20396
|
-
tools: { remove: PLAN_REMOVED_TOOLS.map(named) },
|
|
20397
|
-
modelTools: { add: [named("submit_plan")] },
|
|
20398
|
-
instructions: PLAN_INSTRUCTIONS
|
|
20399
|
-
};
|
|
20400
|
-
}
|
|
20401
|
-
var agentInPlanMode = (agent) => agent.instructions === PLAN_INSTRUCTIONS;
|
|
20402
20766
|
var clone2 = (value) => JSON.parse(JSON.stringify(value));
|
|
20403
20767
|
var toRecord = ({ taskId: _taskId, dispatched: _dispatched, ...record }) => clone2(record);
|
|
20404
20768
|
function settle2(state, item, status) {
|
|
@@ -20634,7 +20998,7 @@ var createPlanExtension = (service) => defineExtension6({
|
|
|
20634
20998
|
})
|
|
20635
20999
|
],
|
|
20636
21000
|
wraps: [
|
|
20637
|
-
|
|
21001
|
+
wrapTool3(createBashTool(), (bash) => ({
|
|
20638
21002
|
...bash,
|
|
20639
21003
|
execute: async (args, api, context) => {
|
|
20640
21004
|
if ((await api.snapshot(PlanDoc, api.conversationId, context))?.mode === "plan" && !isSafeCommand(args.command))
|
|
@@ -20786,6 +21150,9 @@ import { defineExtension as defineExtension7, section } from "@earendil-works/pi
|
|
|
20786
21150
|
var KEYS = ["preamble", "rules", "project_context", "skills", "cwd"];
|
|
20787
21151
|
var PREAMBLE = "You are an expert coding assistant operating inside neko, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.";
|
|
20788
21152
|
var TOOL_GUIDELINES = {
|
|
21153
|
+
codemode: [
|
|
21154
|
+
"Use codemode to batch independent tool calls (Promise.allSettled), chain them, or filter large output, instead of many separate calls."
|
|
21155
|
+
],
|
|
20789
21156
|
subagent: [
|
|
20790
21157
|
"Delegate only independent, sufficiently scoped work when isolation or parallelism helps. Handle simple tasks directly. Pass necessary context and user constraints; research-only requests must not become coding tasks. For related follow-up work, list and continue a suitable idle subagent using its stable agentId; busy means no task was accepted. Do not change its role. Verify and integrate reports. Do not modify files concurrently with a coder assigned those files."
|
|
20791
21158
|
],
|
|
@@ -20850,7 +21217,7 @@ var createNekoPrompt = (loadResources, platform = process.platform) => {
|
|
|
20850
21217
|
const resources = cwd ? await loadResources(input.conversationId, cwd) : { contextFiles: [], skills: [] };
|
|
20851
21218
|
const sections = {
|
|
20852
21219
|
preamble: role ? ROLE_PROMPTS[role] + SUBAGENT_DELIVERY : PREAMBLE,
|
|
20853
|
-
rules: buildRules(input.agent.
|
|
21220
|
+
rules: buildRules(enabledTools(input.agent).map((tool) => tool.name), platform),
|
|
20854
21221
|
project_context: renderProjectContext(resources.contextFiles),
|
|
20855
21222
|
skills: formatSkillsForPrompt(resources.skills) || undefined,
|
|
20856
21223
|
cwd: cwd?.replace(/\\/g, "/")
|
|
@@ -21017,13 +21384,13 @@ var createResourceSnapshots = (load, submission) => {
|
|
|
21017
21384
|
import { stripVTControlCharacters } from "util";
|
|
21018
21385
|
import { getSupportedThinkingLevels } from "@earendil-works/pi-ai";
|
|
21019
21386
|
import {
|
|
21020
|
-
defineDoc as
|
|
21387
|
+
defineDoc as defineDoc7,
|
|
21021
21388
|
defineExtension as defineExtension8,
|
|
21022
21389
|
defineTask as defineTask3,
|
|
21023
21390
|
InboxDoc as InboxDoc2,
|
|
21024
21391
|
LiveDoc as LiveDoc2
|
|
21025
21392
|
} from "@earendil-works/pi-durable";
|
|
21026
|
-
var SessionMetadataDoc =
|
|
21393
|
+
var SessionMetadataDoc = defineDoc7({
|
|
21027
21394
|
kind: "app.neko.session-metadata",
|
|
21028
21395
|
checkpointWhen: () => false,
|
|
21029
21396
|
version: 1,
|
|
@@ -21360,9 +21727,9 @@ class SessionMetadataManager {
|
|
|
21360
21727
|
}
|
|
21361
21728
|
|
|
21362
21729
|
// packages/core/src/todos.ts
|
|
21363
|
-
import { defineDoc as
|
|
21730
|
+
import { defineDoc as defineDoc8, defineExtension as defineExtension9, defineTool as defineTool6 } from "@earendil-works/pi-durable";
|
|
21364
21731
|
import { Type as Type6 } from "typebox";
|
|
21365
|
-
var TodosDoc =
|
|
21732
|
+
var TodosDoc = defineDoc8({
|
|
21366
21733
|
kind: "app.neko.todos",
|
|
21367
21734
|
checkpointWhen: () => false,
|
|
21368
21735
|
version: 1,
|
|
@@ -21371,7 +21738,7 @@ var TodosDoc = defineDoc6({
|
|
|
21371
21738
|
fork: "asOf",
|
|
21372
21739
|
initial: () => ({ revision: 0, items: [] })
|
|
21373
21740
|
});
|
|
21374
|
-
var TodoReceiptDoc =
|
|
21741
|
+
var TodoReceiptDoc = defineDoc8({
|
|
21375
21742
|
kind: "app.neko.todo-receipt",
|
|
21376
21743
|
checkpointWhen: () => false,
|
|
21377
21744
|
version: 1,
|
|
@@ -21463,14 +21830,14 @@ import { randomUUID as randomUUID3 } from "crypto";
|
|
|
21463
21830
|
import { realpath } from "fs/promises";
|
|
21464
21831
|
import {
|
|
21465
21832
|
configure as configure2,
|
|
21466
|
-
defineDoc as
|
|
21833
|
+
defineDoc as defineDoc9,
|
|
21467
21834
|
defineExtension as defineExtension10,
|
|
21468
21835
|
hook as hook3,
|
|
21469
21836
|
ToolTask as ToolTask3,
|
|
21470
|
-
wrapTool as
|
|
21837
|
+
wrapTool as wrapTool4
|
|
21471
21838
|
} from "@earendil-works/pi-durable";
|
|
21472
21839
|
import { createBashTool as createBashTool2 } from "@earendil-works/pi-durable/tools";
|
|
21473
|
-
var ApprovalsDoc =
|
|
21840
|
+
var ApprovalsDoc = defineDoc9({
|
|
21474
21841
|
kind: "app.neko.approvals",
|
|
21475
21842
|
checkpointWhen: () => false,
|
|
21476
21843
|
version: 1,
|
|
@@ -21612,7 +21979,7 @@ function createApprovalExtension(service) {
|
|
|
21612
21979
|
})
|
|
21613
21980
|
],
|
|
21614
21981
|
wraps: [
|
|
21615
|
-
|
|
21982
|
+
wrapTool4(createBashTool2(), (bash) => ({
|
|
21616
21983
|
...bash,
|
|
21617
21984
|
execute: async (args, api, context) => {
|
|
21618
21985
|
const role = await api.snapshot(SubagentDoc, api.conversationId, context);
|
|
@@ -21636,7 +22003,7 @@ async function prepareApprovalConversations(harness, ids, extension, context) {
|
|
|
21636
22003
|
const child = await harness.conversation(id, context);
|
|
21637
22004
|
const agent = await child.agent(context);
|
|
21638
22005
|
await harness.commit((tx) => configure2(tx, id, {
|
|
21639
|
-
tools: [...agent.
|
|
22006
|
+
tools: [...enabledTools(agent).filter((t) => t.name !== "bash"), createBashTool2()],
|
|
21640
22007
|
extensions: { add: [extension] }
|
|
21641
22008
|
}), context);
|
|
21642
22009
|
}
|
|
@@ -22645,7 +23012,7 @@ function cancellable(work, signal) {
|
|
|
22645
23012
|
var DEFAULT_MODEL = { provider: "deepseek", modelId: "deepseek-flash" };
|
|
22646
23013
|
var DEFAULT_STORAGE_PATH = join5(homedir4(), ".neko", "neko.sqlite");
|
|
22647
23014
|
var neverAborted = () => new AbortController().signal;
|
|
22648
|
-
var ArchivedDoc =
|
|
23015
|
+
var ArchivedDoc = defineDoc10({
|
|
22649
23016
|
kind: "app.neko.conversation",
|
|
22650
23017
|
checkpointWhen: () => false,
|
|
22651
23018
|
version: 1,
|
|
@@ -22654,7 +23021,7 @@ var ArchivedDoc = defineDoc8({
|
|
|
22654
23021
|
fork: "initial",
|
|
22655
23022
|
initial: () => ({ archived: false })
|
|
22656
23023
|
});
|
|
22657
|
-
var WorkspacesDoc =
|
|
23024
|
+
var WorkspacesDoc = defineDoc10({
|
|
22658
23025
|
kind: "app.neko.workspaces",
|
|
22659
23026
|
checkpointWhen: () => false,
|
|
22660
23027
|
version: 1,
|
|
@@ -22663,7 +23030,7 @@ var WorkspacesDoc = defineDoc8({
|
|
|
22663
23030
|
fork: "initial",
|
|
22664
23031
|
initial: () => ({ items: [] })
|
|
22665
23032
|
});
|
|
22666
|
-
var RemovedWorkspacesDoc =
|
|
23033
|
+
var RemovedWorkspacesDoc = defineDoc10({
|
|
22667
23034
|
kind: "app.neko.workspaces-removed",
|
|
22668
23035
|
checkpointWhen: () => false,
|
|
22669
23036
|
version: 1,
|
|
@@ -22672,7 +23039,7 @@ var RemovedWorkspacesDoc = defineDoc8({
|
|
|
22672
23039
|
fork: "initial",
|
|
22673
23040
|
initial: () => ({ paths: [] })
|
|
22674
23041
|
});
|
|
22675
|
-
var SearchSettingsDoc =
|
|
23042
|
+
var SearchSettingsDoc = defineDoc10({
|
|
22676
23043
|
kind: "app.neko.search-settings",
|
|
22677
23044
|
checkpointWhen: () => false,
|
|
22678
23045
|
version: 1,
|
|
@@ -22681,7 +23048,7 @@ var SearchSettingsDoc = defineDoc8({
|
|
|
22681
23048
|
fork: "initial",
|
|
22682
23049
|
initial: () => ({ provider: null })
|
|
22683
23050
|
});
|
|
22684
|
-
var ProviderSettingsDoc =
|
|
23051
|
+
var ProviderSettingsDoc = defineDoc10({
|
|
22685
23052
|
kind: "app.neko.provider-settings",
|
|
22686
23053
|
checkpointWhen: () => false,
|
|
22687
23054
|
version: 1,
|
|
@@ -22690,7 +23057,7 @@ var ProviderSettingsDoc = defineDoc8({
|
|
|
22690
23057
|
fork: "initial",
|
|
22691
23058
|
initial: () => ({ enabled: null })
|
|
22692
23059
|
});
|
|
22693
|
-
var DefaultModelDoc =
|
|
23060
|
+
var DefaultModelDoc = defineDoc10({
|
|
22694
23061
|
kind: "app.neko.default-model",
|
|
22695
23062
|
checkpointWhen: () => false,
|
|
22696
23063
|
version: 1,
|
|
@@ -22699,7 +23066,7 @@ var DefaultModelDoc = defineDoc8({
|
|
|
22699
23066
|
fork: "initial",
|
|
22700
23067
|
initial: () => ({ model: null })
|
|
22701
23068
|
});
|
|
22702
|
-
var ContextSettingsDoc =
|
|
23069
|
+
var ContextSettingsDoc = defineDoc10({
|
|
22703
23070
|
kind: "app.neko.context-settings",
|
|
22704
23071
|
checkpointWhen: () => false,
|
|
22705
23072
|
version: 1,
|
|
@@ -22708,7 +23075,7 @@ var ContextSettingsDoc = defineDoc8({
|
|
|
22708
23075
|
fork: "initial",
|
|
22709
23076
|
initial: emptySavedContextSettings
|
|
22710
23077
|
});
|
|
22711
|
-
var ActivityDoc =
|
|
23078
|
+
var ActivityDoc = defineDoc10({
|
|
22712
23079
|
kind: "app.neko.activity",
|
|
22713
23080
|
checkpointWhen: () => false,
|
|
22714
23081
|
version: 1,
|
|
@@ -22815,6 +23182,11 @@ async function createNekoRuntime(options = {}) {
|
|
|
22815
23182
|
registry.install(createQuestionExtension(() => questions));
|
|
22816
23183
|
let plans;
|
|
22817
23184
|
registry.install(createPlanExtension(() => plans));
|
|
23185
|
+
const codemode = createCodemodeRuntime({
|
|
23186
|
+
maxWorkers: options.codemodeLimits?.maxWorkers ?? CODEMODE_LIMITS.maxWorkers,
|
|
23187
|
+
waitMs: options.codemodeLimits?.waitMs ?? CODEMODE_LIMITS.waitMs
|
|
23188
|
+
});
|
|
23189
|
+
registry.install(codemode.extension);
|
|
22818
23190
|
for (const extension of options.extensions ?? [])
|
|
22819
23191
|
registry.install(extension);
|
|
22820
23192
|
let approvals;
|
|
@@ -22868,6 +23240,7 @@ async function createNekoRuntime(options = {}) {
|
|
|
22868
23240
|
}
|
|
22869
23241
|
}
|
|
22870
23242
|
} catch (error) {
|
|
23243
|
+
await codemode.close();
|
|
22871
23244
|
await workspaceFiles.close();
|
|
22872
23245
|
await harness.close(context);
|
|
22873
23246
|
throw error;
|
|
@@ -22882,6 +23255,13 @@ async function createNekoRuntime(options = {}) {
|
|
|
22882
23255
|
}
|
|
22883
23256
|
});
|
|
22884
23257
|
enabled = new Set(normalizeEnabledProviders((await harness.snapshot(ProviderSettingsDoc, conversation.id, context))?.enabled));
|
|
23258
|
+
const codemodeState = async () => resolveCodemodeSettings(options.codemodeSettings, (await harness.snapshot(CodemodeSettingsDoc, conversation.id, context))?.mode);
|
|
23259
|
+
if (existing.length === 0) {
|
|
23260
|
+
const initial = (await codemodeState()).effective.mode;
|
|
23261
|
+
await harness.commit(async (tx) => {
|
|
23262
|
+
(await tx.doc(CodemodeDoc, conversation.id)).mode = initial;
|
|
23263
|
+
}, context);
|
|
23264
|
+
}
|
|
22885
23265
|
const savedSearch = (await harness.snapshot(SearchSettingsDoc, conversation.id, context))?.provider;
|
|
22886
23266
|
if (isSearchProvider(savedSearch))
|
|
22887
23267
|
searchProvider = savedSearch;
|
|
@@ -23008,13 +23388,17 @@ async function createNekoRuntime(options = {}) {
|
|
|
23008
23388
|
admissions.delete(id);
|
|
23009
23389
|
}
|
|
23010
23390
|
};
|
|
23011
|
-
const
|
|
23012
|
-
const
|
|
23013
|
-
|
|
23014
|
-
await
|
|
23391
|
+
const syncAgent = async (target) => {
|
|
23392
|
+
const change = agentSyncChange(await target.agent(context), {
|
|
23393
|
+
plan: await plans.mode(target.id),
|
|
23394
|
+
codemode: (await harness.snapshot(CodemodeDoc, target.id, context))?.mode ?? "off",
|
|
23395
|
+
role: "main"
|
|
23396
|
+
});
|
|
23397
|
+
if (change)
|
|
23398
|
+
await target.configure(change, context);
|
|
23015
23399
|
};
|
|
23016
23400
|
const submitTo = async (target, content, submitOptions, extra) => admit(target.id, async () => {
|
|
23017
|
-
await
|
|
23401
|
+
await syncAgent(target);
|
|
23018
23402
|
const agent = await target.agent(context);
|
|
23019
23403
|
const workspace = executionWorkspace(agent.cwd);
|
|
23020
23404
|
const duplicate = submitOptions?.requestId !== undefined && await storage.submissionByRequest(target.id, submitOptions.requestId, context);
|
|
@@ -23178,7 +23562,7 @@ async function createNekoRuntime(options = {}) {
|
|
|
23178
23562
|
if (await isRunning(target.id))
|
|
23179
23563
|
throw new Error(PLAN_BUSY);
|
|
23180
23564
|
await plans.setMode(target.id, mode);
|
|
23181
|
-
await
|
|
23565
|
+
await syncAgent(target);
|
|
23182
23566
|
return plans.view(target.id);
|
|
23183
23567
|
});
|
|
23184
23568
|
},
|
|
@@ -23300,7 +23684,7 @@ async function createNekoRuntime(options = {}) {
|
|
|
23300
23684
|
const session = await getConversation(id);
|
|
23301
23685
|
if (!session)
|
|
23302
23686
|
continue;
|
|
23303
|
-
await admit(id, () =>
|
|
23687
|
+
await admit(id, () => syncAgent(session.conversation));
|
|
23304
23688
|
const pending = await plans.undispatched(id);
|
|
23305
23689
|
if (pending)
|
|
23306
23690
|
await dispatchPlan(session.conversation, pending);
|
|
@@ -23386,6 +23770,8 @@ async function createNekoRuntime(options = {}) {
|
|
|
23386
23770
|
}
|
|
23387
23771
|
const child = at === undefined ? await tx.createConversation({ ownership: { kind: "ownerless" } }) : await tx.forkConversation(id, at, { ownership: { kind: "ownerless" } });
|
|
23388
23772
|
(await tx.doc(DelegationsDoc, child.id)).items = inherited;
|
|
23773
|
+
if (at === undefined)
|
|
23774
|
+
(await tx.doc(CodemodeDoc, child.id)).mode = (await tx.doc(CodemodeDoc, id)).mode;
|
|
23389
23775
|
if (sourceMetadata) {
|
|
23390
23776
|
const doc = await tx.doc(SessionMetadataDoc, child.id);
|
|
23391
23777
|
doc.title = sourceMetadata.title || sourceMetadata.firstPromptPreview;
|
|
@@ -23466,12 +23852,17 @@ async function createNekoRuntime(options = {}) {
|
|
|
23466
23852
|
const workspace = await addWorkspace(createOptions?.cwd ?? defaultCwd);
|
|
23467
23853
|
const savedDefault = (await harness.snapshot(DefaultModelDoc, conversation.id, context))?.model ?? null;
|
|
23468
23854
|
const effective = createOptions?.model ?? modelOverride ?? savedDefault ?? model;
|
|
23855
|
+
const mode = (await codemodeState()).effective.mode;
|
|
23469
23856
|
const created = await harness.createConversation({
|
|
23470
23857
|
ownership: { kind: "ownerless" },
|
|
23471
23858
|
agent: {
|
|
23472
23859
|
model: effective,
|
|
23473
23860
|
thinkingLevel: resolveLevel(effective, createOptions?.thinkingLevel ?? options.thinkingLevel ?? "high"),
|
|
23474
|
-
cwd: workspace.path
|
|
23861
|
+
cwd: workspace.path,
|
|
23862
|
+
...agentChangeFor({ plan: "normal", codemode: mode, role: "main" })
|
|
23863
|
+
},
|
|
23864
|
+
init: async (tx, id) => {
|
|
23865
|
+
(await tx.doc(CodemodeDoc, id)).mode = mode;
|
|
23475
23866
|
}
|
|
23476
23867
|
}, context);
|
|
23477
23868
|
await metadata.initialize(created.id);
|
|
@@ -23526,6 +23917,20 @@ async function createNekoRuntime(options = {}) {
|
|
|
23526
23917
|
async resetContextSettings() {
|
|
23527
23918
|
return persistContext(emptySavedContextSettings());
|
|
23528
23919
|
},
|
|
23920
|
+
codemodeSettings: codemodeState,
|
|
23921
|
+
async setCodemodeMode(mode) {
|
|
23922
|
+
const parsed = parseCodemodeMode(mode);
|
|
23923
|
+
await harness.commit(async (tx) => {
|
|
23924
|
+
(await tx.doc(CodemodeSettingsDoc, conversation.id)).mode = parsed;
|
|
23925
|
+
}, context);
|
|
23926
|
+
return codemodeState();
|
|
23927
|
+
},
|
|
23928
|
+
async resetCodemodeMode() {
|
|
23929
|
+
await harness.commit(async (tx) => {
|
|
23930
|
+
(await tx.doc(CodemodeSettingsDoc, conversation.id)).mode = null;
|
|
23931
|
+
}, context);
|
|
23932
|
+
return codemodeState();
|
|
23933
|
+
},
|
|
23529
23934
|
async searchSettings() {
|
|
23530
23935
|
return { selected: searchProvider };
|
|
23531
23936
|
},
|
|
@@ -23647,6 +24052,7 @@ async function createNekoRuntime(options = {}) {
|
|
|
23647
24052
|
registry.install(extension);
|
|
23648
24053
|
},
|
|
23649
24054
|
async close() {
|
|
24055
|
+
await codemode.close();
|
|
23650
24056
|
await cleanup();
|
|
23651
24057
|
return harness.close(context);
|
|
23652
24058
|
}
|
|
@@ -23689,6 +24095,18 @@ var image = Type8.Object({
|
|
|
23689
24095
|
contentIndex: integer
|
|
23690
24096
|
});
|
|
23691
24097
|
var skill = Type8.Object({ name: str, location: str, body: str, args: str });
|
|
24098
|
+
var CodemodeViewSchema = Type8.Object({
|
|
24099
|
+
calls: Type8.Array(Type8.Object({
|
|
24100
|
+
id: integer,
|
|
24101
|
+
name: str,
|
|
24102
|
+
args: str,
|
|
24103
|
+
status: Type8.Enum(["running", "ok", "error", "cancelled"]),
|
|
24104
|
+
durationMs: Type8.Union([num, nil]),
|
|
24105
|
+
error: nullableString
|
|
24106
|
+
})),
|
|
24107
|
+
omitted: integer,
|
|
24108
|
+
fullOutputPath: nullableString
|
|
24109
|
+
});
|
|
23692
24110
|
var call = Type8.Object({
|
|
23693
24111
|
callId: str,
|
|
23694
24112
|
name: str,
|
|
@@ -23699,7 +24117,8 @@ var call = Type8.Object({
|
|
|
23699
24117
|
diff: nullableString,
|
|
23700
24118
|
diffAdded: Type8.Union([num, nil]),
|
|
23701
24119
|
diffRemoved: Type8.Union([num, nil]),
|
|
23702
|
-
diffTruncated: Type8.Optional(bool)
|
|
24120
|
+
diffTruncated: Type8.Optional(bool),
|
|
24121
|
+
codemode: Type8.Optional(CodemodeViewSchema)
|
|
23703
24122
|
});
|
|
23704
24123
|
var DelegationSchema = Type8.Object({
|
|
23705
24124
|
parentId: integer,
|
|
@@ -24007,7 +24426,8 @@ var ChatViewSchema = Type8.Object({
|
|
|
24007
24426
|
diffAdded: Type8.Union([num, nil]),
|
|
24008
24427
|
diffRemoved: Type8.Union([num, nil]),
|
|
24009
24428
|
diffTruncated: Type8.Optional(bool),
|
|
24010
|
-
interaction: Type8.Optional(ToolInteractionSchema)
|
|
24429
|
+
interaction: Type8.Optional(ToolInteractionSchema),
|
|
24430
|
+
codemode: Type8.Optional(CodemodeViewSchema)
|
|
24011
24431
|
}),
|
|
24012
24432
|
Type8.Object({
|
|
24013
24433
|
type: Type8.Literal("system"),
|
|
@@ -24284,6 +24704,24 @@ var NekoContextSettings = contract("neko.context-settings", {
|
|
|
24284
24704
|
}
|
|
24285
24705
|
}
|
|
24286
24706
|
});
|
|
24707
|
+
var CodemodeSettingsStateSchema = Type8.Object({
|
|
24708
|
+
revision: integer,
|
|
24709
|
+
effective: Type8.Object({ mode: Type8.Enum([...CODEMODE_MODES]) }),
|
|
24710
|
+
source: Type8.Enum(["default", "config", "saved"])
|
|
24711
|
+
});
|
|
24712
|
+
var NekoCodemodeSettings = contract("neko.codemode-settings", {
|
|
24713
|
+
version: 1,
|
|
24714
|
+
input: Type8.Object({}, { additionalProperties: false }),
|
|
24715
|
+
variants: {
|
|
24716
|
+
ready: {
|
|
24717
|
+
state: { state: CodemodeSettingsStateSchema },
|
|
24718
|
+
methods: {
|
|
24719
|
+
set: rpc(Type8.Object({ mode: Type8.Enum([...CODEMODE_MODES]) }, { additionalProperties: false }), nil),
|
|
24720
|
+
reset: rpc(Type8.Object({}, { additionalProperties: false }), nil)
|
|
24721
|
+
}
|
|
24722
|
+
}
|
|
24723
|
+
}
|
|
24724
|
+
});
|
|
24287
24725
|
var defaultModelSource = Type8.Enum(["cli", "saved", "config", "default"]);
|
|
24288
24726
|
var DefaultModelStateSchema = Type8.Object({
|
|
24289
24727
|
effective: model2,
|
|
@@ -25090,8 +25528,8 @@ function subagentClient(connect) {
|
|
|
25090
25528
|
import { createHash as createHash2 } from "crypto";
|
|
25091
25529
|
|
|
25092
25530
|
// packages/core/src/application-version.ts
|
|
25093
|
-
var RUNNING_VERSION = "0.5.
|
|
25094
|
-
var APPLICATION_PROTOCOL =
|
|
25531
|
+
var RUNNING_VERSION = "0.5.3";
|
|
25532
|
+
var APPLICATION_PROTOCOL = 10;
|
|
25095
25533
|
function stableVersion(value) {
|
|
25096
25534
|
return typeof value === "string" && /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(value) && value.split(".").every((n) => Number.isSafeInteger(Number(n)));
|
|
25097
25535
|
}
|
|
@@ -25228,6 +25666,23 @@ var MobileToolInteractionSchema = Type10.Union([
|
|
|
25228
25666
|
note: str2
|
|
25229
25667
|
})
|
|
25230
25668
|
]);
|
|
25669
|
+
var MobileCodemodeSchema = Type10.Object({
|
|
25670
|
+
calls: Type10.Array(Type10.Object({
|
|
25671
|
+
id: int,
|
|
25672
|
+
name: str2,
|
|
25673
|
+
args: Type10.String({ maxLength: CODEMODE_VIEW_LIMITS.argsChars }),
|
|
25674
|
+
status: Type10.Union([
|
|
25675
|
+
Type10.Literal("running"),
|
|
25676
|
+
Type10.Literal("ok"),
|
|
25677
|
+
Type10.Literal("error"),
|
|
25678
|
+
Type10.Literal("cancelled")
|
|
25679
|
+
]),
|
|
25680
|
+
durationMs: opt(Type10.Number()),
|
|
25681
|
+
error: opt(Type10.String({ maxLength: CODEMODE_VIEW_LIMITS.errorChars }))
|
|
25682
|
+
}), { maxItems: CODEMODE_VIEW_LIMITS.maxCalls }),
|
|
25683
|
+
omitted: int,
|
|
25684
|
+
fullOutputPath: opt(str2)
|
|
25685
|
+
});
|
|
25231
25686
|
var MobileToolItemSchema = Type10.Object({
|
|
25232
25687
|
callId: str2,
|
|
25233
25688
|
name: str2,
|
|
@@ -25242,7 +25697,8 @@ var MobileToolItemSchema = Type10.Object({
|
|
|
25242
25697
|
diffTruncated: opt(Type10.Boolean()),
|
|
25243
25698
|
durationMs: opt(Type10.Number()),
|
|
25244
25699
|
note: opt(str2),
|
|
25245
|
-
interaction: opt(MobileToolInteractionSchema)
|
|
25700
|
+
interaction: opt(MobileToolInteractionSchema),
|
|
25701
|
+
codemode: opt(MobileCodemodeSchema)
|
|
25246
25702
|
});
|
|
25247
25703
|
var MobileChatItemSchema = Type10.Object({
|
|
25248
25704
|
key: str2,
|
|
@@ -25773,6 +26229,17 @@ function mapChatItem(raw, owner) {
|
|
|
25773
26229
|
tool.note = raw.note;
|
|
25774
26230
|
if (raw.interaction !== undefined)
|
|
25775
26231
|
tool.interaction = raw.interaction;
|
|
26232
|
+
if (raw.codemode !== undefined) {
|
|
26233
|
+
tool.codemode = {
|
|
26234
|
+
calls: raw.codemode.calls.map(({ durationMs, error, ...call }) => ({
|
|
26235
|
+
...call,
|
|
26236
|
+
...durationMs === null ? {} : { durationMs },
|
|
26237
|
+
...error === null ? {} : { error }
|
|
26238
|
+
})),
|
|
26239
|
+
omitted: raw.codemode.omitted,
|
|
26240
|
+
...raw.codemode.fullOutputPath === null ? {} : { fullOutputPath: raw.codemode.fullOutputPath }
|
|
26241
|
+
};
|
|
26242
|
+
}
|
|
25776
26243
|
item = { key: raw.key, type: "tool", tool, ...images(raw.images) };
|
|
25777
26244
|
break;
|
|
25778
26245
|
}
|
|
@@ -30044,10 +30511,12 @@ var ServerState = local3("neko.server-state", {
|
|
|
30044
30511
|
credentials: CredentialStateSchema,
|
|
30045
30512
|
searchSettings: SearchSettingsStateSchema,
|
|
30046
30513
|
contextSettings: ContextSettingsStateSchema,
|
|
30514
|
+
codemodeSettings: CodemodeSettingsStateSchema,
|
|
30047
30515
|
defaultModel: DefaultModelStateSchema,
|
|
30048
30516
|
refreshCredentials: method2(Type13.Null(), Type13.Null()),
|
|
30049
30517
|
refreshSearchSettings: method2(Type13.Null(), Type13.Null()),
|
|
30050
30518
|
refreshContextSettings: method2(Type13.Null(), Type13.Null()),
|
|
30519
|
+
refreshCodemodeSettings: method2(Type13.Null(), Type13.Null()),
|
|
30051
30520
|
refreshDefaultModel: method2(Type13.Null(), Type13.Null())
|
|
30052
30521
|
}
|
|
30053
30522
|
});
|
|
@@ -30138,12 +30607,13 @@ async function createNekoServerServices(options) {
|
|
|
30138
30607
|
return [spec.id, spec.kind === "model" ? { ...status, enabled: enabled.has(spec.id) } : status];
|
|
30139
30608
|
})));
|
|
30140
30609
|
};
|
|
30141
|
-
const [sessions, workspaces, statuses, search, contextSettings0, defaultModel0] = await Promise.all([
|
|
30610
|
+
const [sessions, workspaces, statuses, search, contextSettings0, codemodeSettings0, defaultModel0] = await Promise.all([
|
|
30142
30611
|
list(),
|
|
30143
30612
|
runtime.listWorkspaces(),
|
|
30144
30613
|
readStatuses(),
|
|
30145
30614
|
runtime.searchSettings(),
|
|
30146
30615
|
runtime.contextSettings(),
|
|
30616
|
+
runtime.codemodeSettings(),
|
|
30147
30617
|
runtime.defaultModel()
|
|
30148
30618
|
]);
|
|
30149
30619
|
let mutationTail = Promise.resolve();
|
|
@@ -30161,6 +30631,10 @@ async function createNekoServerServices(options) {
|
|
|
30161
30631
|
revision: 1,
|
|
30162
30632
|
...contextSettings0
|
|
30163
30633
|
});
|
|
30634
|
+
const codemodeSettings = self2.state("codemodeSettings", CodemodeSettingsStateSchema, {
|
|
30635
|
+
revision: 1,
|
|
30636
|
+
...codemodeSettings0
|
|
30637
|
+
});
|
|
30164
30638
|
const defaultModel = self2.state("defaultModel", DefaultModelStateSchema, defaultModel0);
|
|
30165
30639
|
const refreshContextSettings = async (_input, ctx) => {
|
|
30166
30640
|
const next = await runtime.contextSettings();
|
|
@@ -30169,6 +30643,13 @@ async function createNekoServerServices(options) {
|
|
|
30169
30643
|
contextSettings.replace(ctx, { revision: revision + 1, ...next });
|
|
30170
30644
|
return null;
|
|
30171
30645
|
};
|
|
30646
|
+
const refreshCodemodeSettings = async (_input, ctx) => {
|
|
30647
|
+
const next = await runtime.codemodeSettings();
|
|
30648
|
+
const { revision, ...current } = codemodeSettings.value;
|
|
30649
|
+
if (JSON.stringify(next) !== JSON.stringify(current))
|
|
30650
|
+
codemodeSettings.replace(ctx, { revision: revision + 1, ...next });
|
|
30651
|
+
return null;
|
|
30652
|
+
};
|
|
30172
30653
|
const refreshDefaultModel = async (_input, ctx) => {
|
|
30173
30654
|
const next = await runtime.defaultModel();
|
|
30174
30655
|
if (JSON.stringify(next) !== JSON.stringify(defaultModel.value))
|
|
@@ -30204,6 +30685,7 @@ async function createNekoServerServices(options) {
|
|
|
30204
30685
|
credentials,
|
|
30205
30686
|
searchSettings,
|
|
30206
30687
|
contextSettings,
|
|
30688
|
+
codemodeSettings,
|
|
30207
30689
|
defaultModel,
|
|
30208
30690
|
async refreshCredentials(_input, ctx) {
|
|
30209
30691
|
credentials.replace(ctx, {
|
|
@@ -30214,6 +30696,7 @@ async function createNekoServerServices(options) {
|
|
|
30214
30696
|
},
|
|
30215
30697
|
refreshSearchSettings,
|
|
30216
30698
|
refreshContextSettings,
|
|
30699
|
+
refreshCodemodeSettings,
|
|
30217
30700
|
refreshDefaultModel
|
|
30218
30701
|
};
|
|
30219
30702
|
}, { lifetime: "host" });
|
|
@@ -30280,6 +30763,21 @@ async function createNekoServerServices(options) {
|
|
|
30280
30763
|
})
|
|
30281
30764
|
};
|
|
30282
30765
|
});
|
|
30766
|
+
host.serve(NekoCodemodeSettings, (_input, _principal, self2) => {
|
|
30767
|
+
const hub = shared(self2);
|
|
30768
|
+
return {
|
|
30769
|
+
variant: "ready",
|
|
30770
|
+
state: hub.codemodeSettings,
|
|
30771
|
+
set: (input, ctx) => serialize(async () => {
|
|
30772
|
+
await runtime.setCodemodeMode(input.mode);
|
|
30773
|
+
return hub.refreshCodemodeSettings(null, ctx);
|
|
30774
|
+
}),
|
|
30775
|
+
reset: (_input, ctx) => serialize(async () => {
|
|
30776
|
+
await runtime.resetCodemodeMode();
|
|
30777
|
+
return hub.refreshCodemodeSettings(null, ctx);
|
|
30778
|
+
})
|
|
30779
|
+
};
|
|
30780
|
+
});
|
|
30283
30781
|
host.serve(NekoDefaultModel, (_input, _principal, self2) => {
|
|
30284
30782
|
const hub = shared(self2);
|
|
30285
30783
|
return {
|
|
@@ -30608,6 +31106,7 @@ async function startGateway(options = {}) {
|
|
|
30608
31106
|
...options.apiKey !== undefined ? { apiKey: options.apiKey } : {},
|
|
30609
31107
|
...searchProvider !== undefined ? { searchProvider } : {},
|
|
30610
31108
|
...fileConfig.context !== undefined ? { contextSettings: fileConfig.context } : {},
|
|
31109
|
+
...fileConfig.codemode !== undefined ? { codemodeSettings: fileConfig.codemode } : {},
|
|
30611
31110
|
...fileConfig.defaultModel !== undefined ? { model: fileConfig.defaultModel } : {},
|
|
30612
31111
|
...options.model !== undefined ? { modelOverride: options.model } : {},
|
|
30613
31112
|
thinkingLevel: options.thinkingLevel ?? fileConfig.thinkingLevel ?? "high",
|