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/dist/neko.js CHANGED
@@ -80,7 +80,7 @@ function updateAction(input) {
80
80
  return "install";
81
81
  return "none";
82
82
  }
83
- var RUNNING_VERSION = "0.5.2", APPLICATION_PROTOCOL = 9, ApplicationProtocolError;
83
+ var RUNNING_VERSION = "0.5.3", APPLICATION_PROTOCOL = 10, ApplicationProtocolError;
84
84
  var init_application_version = __esm(() => {
85
85
  ApplicationProtocolError = class ApplicationProtocolError extends Error {
86
86
  constructor() {
@@ -403,6 +403,43 @@ var init_update = __esm(() => {
403
403
  init_application_version();
404
404
  });
405
405
 
406
+ // packages/core/src/codemode-mode.ts
407
+ function parseCodemodeMode(value, field = "codemode.mode") {
408
+ if (!isCodemodeMode(value))
409
+ throw new Error(`"${field}" must be one of ${CODEMODE_MODES.join(", ")}.`);
410
+ return value;
411
+ }
412
+ function parseCodemodeSettings(value, prefix = "codemode") {
413
+ if (value === null || typeof value !== "object" || Array.isArray(value))
414
+ throw new Error(`"${prefix}" must be an object.`);
415
+ const { mode, ...rest } = value;
416
+ const unknown = Object.keys(rest)[0];
417
+ if (unknown !== undefined)
418
+ throw new Error(`"${prefix}.${unknown}" is not a known setting.`);
419
+ return mode === undefined ? {} : { mode: parseCodemodeMode(mode, `${prefix}.mode`) };
420
+ }
421
+ function resolveCodemodeSettings(config, saved) {
422
+ if (isCodemodeMode(saved))
423
+ return { effective: { mode: saved }, source: "saved" };
424
+ if (config?.mode !== undefined)
425
+ return { effective: { mode: config.mode }, source: "config" };
426
+ return { effective: { mode: DEFAULT_CODEMODE_MODE }, source: "default" };
427
+ }
428
+ var CODEMODE_NAME = "codemode", CODEMODE_MODES, DEFAULT_CODEMODE_MODE = "off", isCodemodeMode = (value) => typeof value === "string" && CODEMODE_MODES.includes(value), CODEMODE_LIMITS, CODEMODE_VIEW_LIMITS;
429
+ var init_codemode_mode = __esm(() => {
430
+ CODEMODE_MODES = ["off", "on", "only"];
431
+ CODEMODE_LIMITS = {
432
+ memoryLimitBytes: 256 * 1024 * 1024,
433
+ maxWorkers: 2,
434
+ waitMs: 60000
435
+ };
436
+ CODEMODE_VIEW_LIMITS = {
437
+ maxCalls: 100,
438
+ argsChars: 200,
439
+ errorChars: 200
440
+ };
441
+ });
442
+
406
443
  // packages/core/src/context-limits.ts
407
444
  var PERCENT, CONTEXT_FIELD_LIMITS, PRUNING_DEFAULTS;
408
445
  var init_context_limits = __esm(() => {
@@ -733,9 +770,17 @@ function validate(config, source) {
733
770
  throw new Error(`neko config: ${source}: ${error instanceof Error ? error.message : String(error)}`);
734
771
  }
735
772
  }
773
+ if (config.codemode !== undefined) {
774
+ try {
775
+ parseCodemodeSettings(config.codemode);
776
+ } catch (error) {
777
+ throw new Error(`neko config: ${source}: ${error instanceof Error ? error.message : String(error)}`);
778
+ }
779
+ }
736
780
  }
737
781
  var DEFAULT_SKILL_MARKET_URL = "https://skill.kirineko.tech", NEKO_CONFIG_BASENAME = "neko.config.json", ENV_REFERENCE;
738
782
  var init_config = __esm(() => {
783
+ init_codemode_mode();
739
784
  init_context_settings();
740
785
  init_search_providers();
741
786
  ENV_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g;
@@ -926,6 +971,35 @@ var init_credentials = __esm(() => {
926
971
  DEFAULT_CREDENTIALS_PATH = join4(homedir3(), ".neko", "credentials.json");
927
972
  });
928
973
 
974
+ // packages/core/src/change-limits.ts
975
+ function clipDiff(diff) {
976
+ if (Buffer.byteLength(diff, "utf8") <= CHANGE_LIMITS.opDiffBytes)
977
+ return { diff, truncated: false };
978
+ const lines = diff.split(`
979
+ `);
980
+ const kept = [];
981
+ let bytes = 0;
982
+ for (const line of lines) {
983
+ bytes += Buffer.byteLength(line, "utf8") + 1;
984
+ if (bytes > CHANGE_LIMITS.opDiffBytes)
985
+ break;
986
+ kept.push(line);
987
+ }
988
+ return { diff: kept.join(`
989
+ `), truncated: true };
990
+ }
991
+ var CHANGE_LIMITS;
992
+ var init_change_limits = __esm(() => {
993
+ CHANGE_LIMITS = {
994
+ files: 500,
995
+ opsPerFile: 50,
996
+ diffBytes: 2 * 1024 * 1024,
997
+ opDiffBytes: 64 * 1024,
998
+ conversations: 64,
999
+ nestedRecords: 1000
1000
+ };
1001
+ });
1002
+
929
1003
  // packages/core/src/image-prompt.ts
930
1004
  function addDefaultImagePrompt(content) {
931
1005
  if (typeof content === "string" || !content.some((block) => block.type === "image") || content.some((block) => block.type === "text" && block.text.trim() !== ""))
@@ -1091,6 +1165,45 @@ function toolInteraction(name, details) {
1091
1165
  ...typeof details.text === "string" || details.text === null ? { text: details.text } : {}
1092
1166
  };
1093
1167
  }
1168
+ function codemodeView(calls, fullOutputPath) {
1169
+ const limit = CODEMODE_VIEW_LIMITS.maxCalls;
1170
+ return { calls: calls.slice(0, limit), omitted: Math.max(0, calls.length - limit), fullOutputPath };
1171
+ }
1172
+ function liveCodemodeView(live, callId) {
1173
+ const slots = (live.nestedTools ?? []).filter((slot) => slot.parentCallId === callId);
1174
+ return codemodeView(slots.map((slot, index) => {
1175
+ const key = /\/c(\d+)$/.exec(slot.callId)?.[1];
1176
+ const summary = slot.summary;
1177
+ return {
1178
+ id: key === undefined ? index + 1 : Number(key),
1179
+ name: slot.name,
1180
+ args: clip2(JSON.stringify(slot.arguments) ?? "", CODEMODE_VIEW_LIMITS.argsChars),
1181
+ status: slot.status !== "done" ? "running" : summary === undefined ? "cancelled" : summary.isError ? "error" : "ok",
1182
+ durationMs: summary?.durationMs ?? null,
1183
+ error: summary?.error === undefined ? null : clip2(summary.error, CODEMODE_VIEW_LIMITS.errorChars)
1184
+ };
1185
+ }), null);
1186
+ }
1187
+ function codemodeResultView(details) {
1188
+ if (!isRecord(details) || !Array.isArray(details.calls))
1189
+ return;
1190
+ const calls = details.calls.flatMap((call) => {
1191
+ if (!isRecord(call) || typeof call.name !== "string")
1192
+ return [];
1193
+ const { id, args, status, durationMs, error } = call;
1194
+ return [
1195
+ {
1196
+ id: typeof id === "number" ? id : 0,
1197
+ name: call.name,
1198
+ args: typeof args === "string" ? clip2(args, CODEMODE_VIEW_LIMITS.argsChars) : "",
1199
+ status: status === "ok" || status === "error" || status === "cancelled" ? status : "running",
1200
+ durationMs: typeof durationMs === "number" ? durationMs : null,
1201
+ error: typeof error === "string" ? clip2(error, CODEMODE_VIEW_LIMITS.errorChars) : null
1202
+ }
1203
+ ];
1204
+ });
1205
+ return codemodeView(calls, typeof details.fullOutputPath === "string" ? details.fullOutputPath : null);
1206
+ }
1094
1207
  function summarizeToolCall(name, args) {
1095
1208
  const record = args ?? {};
1096
1209
  switch (name) {
@@ -1123,6 +1236,12 @@ function summarizeToolCall(name, args) {
1123
1236
  `)
1124
1237
  };
1125
1238
  }
1239
+ case "codemode": {
1240
+ const code = typeof record.code === "string" ? record.code : "";
1241
+ const first = code.split(`
1242
+ `).find((line) => line.trim() !== "" && !line.trim().startsWith("// @options")) ?? "";
1243
+ return { args: truncateMiddle(first.trim(), 100), detail: code };
1244
+ }
1126
1245
  case "subagent":
1127
1246
  return {
1128
1247
  args: record.action === "list" ? "List agents" : `${record.agentId ?? record.type}: ${record.title}`,
@@ -1266,7 +1385,8 @@ function projectChatView(view, modelLabel, options = {}) {
1266
1385
  diff,
1267
1386
  diffAdded: counts?.added ?? null,
1268
1387
  diffRemoved: counts?.removed ?? null,
1269
- ...interactionField(message.toolName, message.details)
1388
+ ...interactionField(message.toolName, message.details),
1389
+ ...codemodeField(message.toolName, codemodeResultView(message.details))
1270
1390
  });
1271
1391
  }
1272
1392
  }
@@ -1364,7 +1484,8 @@ function projectChatView(view, modelLabel, options = {}) {
1364
1484
  diffAdded: synthesized?.added ?? null,
1365
1485
  diffRemoved: synthesized?.removed ?? null,
1366
1486
  diffTruncated: synthesized?.truncated ?? false,
1367
- ...interactionField(block.name, slot?.details)
1487
+ ...interactionField(block.name, slot?.details),
1488
+ ...codemodeField(block.name, slot?.status === "running" ? liveCodemodeView(live, block.id) : undefined)
1368
1489
  });
1369
1490
  }
1370
1491
  }
@@ -1487,7 +1608,8 @@ ${block.text}`;
1487
1608
  diff: synthesized?.diff ?? null,
1488
1609
  diffAdded: synthesized?.added ?? null,
1489
1610
  diffRemoved: synthesized?.removed ?? null,
1490
- diffTruncated: synthesized?.truncated ?? false
1611
+ diffTruncated: synthesized?.truncated ?? false,
1612
+ ...codemodeField(block.name, slot?.status === "running" ? liveCodemodeView(live, block.id) : undefined)
1491
1613
  });
1492
1614
  }
1493
1615
  }
@@ -1510,8 +1632,9 @@ function contextTokens(entries) {
1510
1632
  var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value), strings = (value) => Array.isArray(value) && value.every((v) => typeof v === "string") ? value : undefined, interactionField = (name, details) => {
1511
1633
  const interaction = toolInteraction(name, details);
1512
1634
  return interaction ? { interaction } : {};
1513
- }, WRITE_DIFF_MAX_LINES = 2000, WRITE_DIFF_MAX_BYTES;
1635
+ }, clip2 = (text, max) => text.length <= max ? text : `${text.slice(0, max - 1)}\u2026`, codemodeField = (name, view) => name === CODEMODE_NAME && view ? { codemode: view } : {}, WRITE_DIFF_MAX_LINES = 2000, WRITE_DIFF_MAX_BYTES;
1514
1636
  var init_projection = __esm(() => {
1637
+ init_codemode_mode();
1515
1638
  init_image_prompt();
1516
1639
  init_input();
1517
1640
  init_queued();
@@ -1519,11 +1642,80 @@ var init_projection = __esm(() => {
1519
1642
  WRITE_DIFF_MAX_BYTES = 64 * 1024;
1520
1643
  });
1521
1644
 
1645
+ // packages/core/src/nested-changes.ts
1646
+ import { defineDoc, wrapTool } from "@earendil-works/pi-durable";
1647
+ import { createEditTool, createWriteTool } from "@earendil-works/pi-durable/tools";
1648
+ function addNestedChange(state, change) {
1649
+ const existing = state.items.findIndex((item) => item.id === change.id);
1650
+ if (existing !== -1)
1651
+ state.items.splice(existing, 1);
1652
+ else
1653
+ state.total += 1;
1654
+ state.items.push(change);
1655
+ let bytes = bytesOf(state.items);
1656
+ while (state.items.length > 1 && (state.items.length > CHANGE_LIMITS.nestedRecords || bytes > CHANGE_LIMITS.diffBytes)) {
1657
+ const evicted = state.items.shift();
1658
+ bytes -= Buffer.byteLength(evicted.diff, "utf8");
1659
+ state.dropped = true;
1660
+ }
1661
+ }
1662
+ function describe2(tool, args, details) {
1663
+ if (tool === "edit") {
1664
+ const raw = editResultDiff(details);
1665
+ if (raw === null)
1666
+ return { diff: "", added: 0, removed: 0, truncated: false };
1667
+ return { ...clipDiff(raw), ...countDiffLines(raw) };
1668
+ }
1669
+ return synthesizedWriteDiff(args) ?? { diff: "", added: 0, removed: 0, truncated: false };
1670
+ }
1671
+ function nestedChangeWraps() {
1672
+ const wrap = (reference, tool) => wrapTool(reference, (inner) => ({
1673
+ ...inner,
1674
+ execute: async (args, api, context) => {
1675
+ const result = await inner.execute(args, api, context);
1676
+ const path = args?.path;
1677
+ if (result.isError || typeof path !== "string" || path === "")
1678
+ return result;
1679
+ const task = await api.getTask(api.taskId, context);
1680
+ const input = task?.input;
1681
+ if (input?.kind !== "nested" || typeof input.parentCallId !== "string")
1682
+ return result;
1683
+ const parentCallId = input.parentCallId;
1684
+ const change = {
1685
+ id: api.callId,
1686
+ parentCallId,
1687
+ tool,
1688
+ path,
1689
+ ...describe2(tool, args, result.details)
1690
+ };
1691
+ await api.commit(async (tx) => {
1692
+ addNestedChange(await tx.doc(NestedChangesDoc, api.conversationId), change);
1693
+ }, context);
1694
+ return result;
1695
+ }
1696
+ }));
1697
+ return [wrap(createEditTool(), "edit"), wrap(createWriteTool(), "write")];
1698
+ }
1699
+ var NestedChangesDoc, bytesOf = (items) => items.reduce((sum, item) => sum + Buffer.byteLength(item.diff, "utf8"), 0);
1700
+ var init_nested_changes = __esm(() => {
1701
+ init_change_limits();
1702
+ init_projection();
1703
+ NestedChangesDoc = defineDoc({
1704
+ kind: "app.neko.nested-changes",
1705
+ checkpointWhen: () => false,
1706
+ version: 1,
1707
+ scope: "conversation",
1708
+ history: "rewindable",
1709
+ fork: "asOf",
1710
+ initial: () => ({ total: 0, dropped: false, items: [] })
1711
+ });
1712
+ });
1713
+
1522
1714
  // packages/core/src/plan-doc.ts
1523
- import { defineDoc } from "@earendil-works/pi-durable";
1524
- var PLAN_HISTORY = 5, PlanDoc, PLAN_CODER_BLOCKED = "Plan mode is read-only: the coder subagent is unavailable. Use explore or plan subagents, or finish the plan first.";
1715
+ import { defineDoc as defineDoc2 } from "@earendil-works/pi-durable";
1716
+ var PLAN_HISTORY = 5, PlanDoc, PLAN_CODER_BLOCKED = "Plan mode is read-only: the coder subagent is unavailable. Use explore or plan subagents, or finish the plan first.", PLAN_REMOVED_TOOLS, PLAN_INSTRUCTIONS;
1525
1717
  var init_plan_doc = __esm(() => {
1526
- PlanDoc = defineDoc({
1718
+ PlanDoc = defineDoc2({
1527
1719
  kind: "app.neko.plan",
1528
1720
  checkpointWhen: () => false,
1529
1721
  version: 1,
@@ -1532,6 +1724,205 @@ var init_plan_doc = __esm(() => {
1532
1724
  fork: "asOf",
1533
1725
  initial: () => ({ mode: "normal", revision: 0, items: [] })
1534
1726
  });
1727
+ PLAN_REMOVED_TOOLS = ["edit", "write", "job"];
1728
+ PLAN_INSTRUCTIONS = [
1729
+ "You are in plan mode: research and plan only, and change nothing.",
1730
+ "- 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.",
1731
+ "- Not available: edit, write, job and any command that modifies files, installs packages or starts processes. Do not try to work around this.",
1732
+ "- 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.",
1733
+ "- After submit_plan do nothing else. If the user asks for changes, research what is needed and submit a revised plan.",
1734
+ "- Use ask_user only for decisions you cannot settle by reading the code."
1735
+ ].join(`
1736
+ `);
1737
+ });
1738
+
1739
+ // packages/core/src/agent-change.ts
1740
+ function onlyModeTools(plan, role) {
1741
+ return [
1742
+ CODEMODE_NAME,
1743
+ ...role === "main" ? [...ONLY_MODE_MAIN_TOOLS, ...plan === "plan" ? ["submit_plan"] : []] : []
1744
+ ];
1745
+ }
1746
+ function agentChangeFor({ plan, codemode, role }) {
1747
+ const planning = plan === "plan";
1748
+ return {
1749
+ tools: planning ? { remove: PLAN_REMOVED_TOOLS.map(named) } : null,
1750
+ modelTools: codemode === "only" ? onlyModeTools(plan, role).map(named) : planning ? { add: [named("submit_plan")] } : null,
1751
+ extensions: codemode === "off" ? { remove: [{ name: CODEMODE_NAME }] } : { add: [{ name: CODEMODE_NAME }] },
1752
+ instructions: planning ? PLAN_INSTRUCTIONS : null
1753
+ };
1754
+ }
1755
+ function enabledTools(agent) {
1756
+ const offered = new Set(agent.tools.map((tool) => tool.name));
1757
+ return [...agent.tools, ...agent.callable.filter((tool) => !offered.has(tool.name))];
1758
+ }
1759
+ function agentSyncChange(agent, state) {
1760
+ const desired = agentChangeFor(state);
1761
+ const planMismatch = agentInPlanMode(agent) !== (state.plan === "plan");
1762
+ const hasExtension = agent.extensions.some((extension) => extension.name === CODEMODE_NAME);
1763
+ const callable = new Set(agent.callable.map((tool) => tool.name));
1764
+ const offered = agent.tools.map((tool) => tool.name);
1765
+ let codemodeMismatch;
1766
+ if (state.codemode === "off")
1767
+ codemodeMismatch = hasExtension || callable.has(CODEMODE_NAME);
1768
+ else {
1769
+ codemodeMismatch = !hasExtension || !offered.includes(CODEMODE_NAME);
1770
+ if (state.codemode === "only") {
1771
+ const allowed = new Set(onlyModeTools(state.plan, state.role));
1772
+ codemodeMismatch ||= offered.some((name) => !allowed.has(name));
1773
+ }
1774
+ }
1775
+ if (!planMismatch && !codemodeMismatch)
1776
+ return;
1777
+ return {
1778
+ ...planMismatch ? { tools: desired.tools, instructions: desired.instructions } : {},
1779
+ modelTools: desired.modelTools,
1780
+ ...codemodeMismatch ? { extensions: desired.extensions } : {}
1781
+ };
1782
+ }
1783
+ var ONLY_MODE_MAIN_TOOLS, named = (name) => ({ name }), agentInPlanMode = (agent) => agent.instructions === PLAN_INSTRUCTIONS;
1784
+ var init_agent_change = __esm(() => {
1785
+ init_codemode_mode();
1786
+ init_plan_doc();
1787
+ ONLY_MODE_MAIN_TOOLS = ["todo", "ask_user", "subagent"];
1788
+ });
1789
+
1790
+ // packages/core/src/codemode.ts
1791
+ import { AsyncLocalStorage } from "async_hooks";
1792
+ import { CodemodeSandbox } from "@earendil-works/pi-codemode";
1793
+ import { defineDoc as defineDoc3, wrapTool as wrapTool2 } from "@earendil-works/pi-durable";
1794
+ import { createCodemodeExtension, createCodemodeTool } from "@earendil-works/pi-durable/codemode";
1795
+
1796
+ class WorkerLimiter {
1797
+ #max;
1798
+ #waitMs;
1799
+ #tickets = new AsyncLocalStorage;
1800
+ #waiters = [];
1801
+ #running = 0;
1802
+ #reserved = 0;
1803
+ constructor(limits = CODEMODE_LIMITS) {
1804
+ this.#max = limits.maxWorkers;
1805
+ this.#waitMs = limits.waitMs;
1806
+ }
1807
+ get running() {
1808
+ return this.#running;
1809
+ }
1810
+ get waiting() {
1811
+ return this.#waiters.length;
1812
+ }
1813
+ #free() {
1814
+ return this.#running + this.#reserved < this.#max;
1815
+ }
1816
+ #wake() {
1817
+ while (this.#waiters.length > 0 && this.#free()) {
1818
+ this.#reserved += 1;
1819
+ this.#waiters.shift()?.();
1820
+ }
1821
+ }
1822
+ acquireWorker = () => {
1823
+ const ticket = this.#tickets.getStore();
1824
+ if (ticket && !ticket.claimed) {
1825
+ ticket.claimed = true;
1826
+ this.#reserved -= 1;
1827
+ } else if (!this.#free())
1828
+ throw new Error(SANDBOX_BUSY);
1829
+ this.#running += 1;
1830
+ let released = false;
1831
+ return () => {
1832
+ if (released)
1833
+ return;
1834
+ released = true;
1835
+ this.#running -= 1;
1836
+ this.#wake();
1837
+ };
1838
+ };
1839
+ async#admit(signal) {
1840
+ signal?.throwIfAborted();
1841
+ if (this.#waiters.length === 0 && this.#free()) {
1842
+ this.#reserved += 1;
1843
+ return;
1844
+ }
1845
+ await new Promise((resolve, reject) => {
1846
+ let timer;
1847
+ const leave = () => {
1848
+ clearTimeout(timer);
1849
+ signal?.removeEventListener("abort", onAbort);
1850
+ };
1851
+ const grant = () => {
1852
+ leave();
1853
+ resolve();
1854
+ };
1855
+ const drop = (error) => {
1856
+ const index = this.#waiters.indexOf(grant);
1857
+ if (index === -1)
1858
+ return;
1859
+ this.#waiters.splice(index, 1);
1860
+ leave();
1861
+ reject(error);
1862
+ };
1863
+ const onAbort = () => drop(signal?.reason instanceof Error ? signal.reason : new Error("Aborted"));
1864
+ this.#waiters.push(grant);
1865
+ timer = setTimeout(() => drop(new Error(SANDBOX_BUSY)), this.#waitMs);
1866
+ signal?.addEventListener("abort", onAbort, { once: true });
1867
+ });
1868
+ }
1869
+ async run(signal, work) {
1870
+ await this.#admit(signal);
1871
+ const ticket = { claimed: false };
1872
+ try {
1873
+ return await this.#tickets.run(ticket, work);
1874
+ } finally {
1875
+ if (!ticket.claimed) {
1876
+ this.#reserved -= 1;
1877
+ this.#wake();
1878
+ }
1879
+ }
1880
+ }
1881
+ }
1882
+ function createCodemodeRuntime(limits = CODEMODE_LIMITS) {
1883
+ const limiter = new WorkerLimiter(limits);
1884
+ const sandbox = new CodemodeSandbox({
1885
+ memoryLimitBytes: CODEMODE_LIMITS.memoryLimitBytes,
1886
+ acquireWorker: limiter.acquireWorker
1887
+ });
1888
+ const options = { sandbox, models: false };
1889
+ const base = createCodemodeExtension(options);
1890
+ const reference = createCodemodeTool(options);
1891
+ const extension = {
1892
+ ...base,
1893
+ wraps: [
1894
+ ...base.wraps ?? [],
1895
+ ...nestedChangeWraps(),
1896
+ wrapTool2(reference, (tool) => ({
1897
+ ...tool,
1898
+ execute: (args, api, context) => limiter.run(context.abortSignal, () => tool.execute(args, api, context))
1899
+ }))
1900
+ ]
1901
+ };
1902
+ return { extension, limiter, close: () => sandbox.close() };
1903
+ }
1904
+ var CodemodeSettingsDoc, CodemodeDoc, SANDBOX_BUSY = "Codemode sandbox is busy: too many scripts are running. Try again shortly.";
1905
+ var init_codemode = __esm(() => {
1906
+ init_codemode_mode();
1907
+ init_nested_changes();
1908
+ CodemodeSettingsDoc = defineDoc3({
1909
+ kind: "app.neko.codemode-settings",
1910
+ checkpointWhen: () => false,
1911
+ version: 1,
1912
+ scope: "conversation",
1913
+ history: "latest",
1914
+ fork: "initial",
1915
+ initial: () => ({ mode: null })
1916
+ });
1917
+ CodemodeDoc = defineDoc3({
1918
+ kind: "app.neko.codemode",
1919
+ checkpointWhen: () => false,
1920
+ version: 1,
1921
+ scope: "conversation",
1922
+ history: "rewindable",
1923
+ fork: "asOf",
1924
+ initial: () => ({ mode: "off" })
1925
+ });
1535
1926
  });
1536
1927
 
1537
1928
  // packages/core/src/subagent-usage.ts
@@ -1583,7 +1974,7 @@ var init_subagent_usage = __esm(() => {
1583
1974
  import {
1584
1975
  AssistantEntry,
1585
1976
  configure,
1586
- defineDoc as defineDoc2,
1977
+ defineDoc as defineDoc4,
1587
1978
  defineExtension,
1588
1979
  defineTask,
1589
1980
  defineTool,
@@ -1824,6 +2215,7 @@ function createSubagentExtension(prepare, discard, guard) {
1824
2215
  parameters,
1825
2216
  structuredOutputSchema,
1826
2217
  replay: "safe",
2218
+ callers: ["model"],
1827
2219
  execute: async (input, api, context) => {
1828
2220
  if (!Value.Check(actionParameters, input))
1829
2221
  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.");
@@ -1874,12 +2266,16 @@ function createSubagentExtension(prepare, discard, guard) {
1874
2266
  if (!identity) {
1875
2267
  const created = child ?? await tx.createConversation({ ownership: { kind: "task", taskId: api.taskId } });
1876
2268
  const role = args.action === "continue" ? "explore" : args.type;
2269
+ const codemode = (await tx.doc(CodemodeDoc, api.conversationId)).mode;
2270
+ (await tx.doc(CodemodeDoc, created.id)).mode = codemode;
2271
+ const enabled = enabledTools(agent);
1877
2272
  await configure(tx, created.id, {
1878
2273
  model: agent.model,
1879
2274
  thinkingLevel: agent.thinkingLevel,
1880
2275
  cwd: agent.cwd,
1881
2276
  extensions: agent.extensions.filter((e) => e.name !== extension.name && !MAIN_ONLY_EXTENSIONS.has(e.name)),
1882
- tools: agent.tools.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"))
2277
+ modelTools: agentChangeFor({ plan: "normal", codemode, role: "subagent" }).modelTools,
2278
+ 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"))
1883
2279
  });
1884
2280
  Object.assign(await tx.doc(SubagentDoc, created.id), { role, parentId: api.conversationId });
1885
2281
  identity = {
@@ -1982,9 +2378,10 @@ async function removeMainOnlyTools(harness, ids, context) {
1982
2378
  if (!(await harness.snapshot(SubagentDoc, id, context))?.role)
1983
2379
  continue;
1984
2380
  const agent = await (await harness.conversation(id, context)).agent(context);
1985
- if (!agent.tools.some((t) => MAIN_ONLY_TOOLS.has(t.name)))
2381
+ const enabled = enabledTools(agent);
2382
+ if (!enabled.some((t) => MAIN_ONLY_TOOLS.has(t.name)))
1986
2383
  continue;
1987
- await harness.commit((tx) => configure(tx, id, { tools: agent.tools.filter((t) => !MAIN_ONLY_TOOLS.has(t.name)) }), context);
2384
+ await harness.commit((tx) => configure(tx, id, { tools: enabled.filter((t) => !MAIN_ONLY_TOOLS.has(t.name)) }), context);
1988
2385
  }
1989
2386
  }
1990
2387
  async function installSubagentCoordinator(harness, parents, context) {
@@ -2031,10 +2428,13 @@ async function installSubagentCoordinator(harness, parents, context) {
2031
2428
  }
2032
2429
  var agentIdFor = (childId) => `agent-${childId}`, DelegationsDoc, SubagentsDoc, SubagentDoc, SlotsDoc, ROLE_PROMPTS, 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.", MAIN_ONLY_TOOLS, MAIN_ONLY_EXTENSIONS, READ_TOOLS, actionParameters, parameters, structuredOutputSchema, ADMISSION_POLL_MS = 100;
2033
2430
  var init_subagents = __esm(() => {
2431
+ init_agent_change();
2432
+ init_codemode();
2433
+ init_codemode_mode();
2034
2434
  init_input();
2035
2435
  init_plan_doc();
2036
2436
  init_subagent_usage();
2037
- DelegationsDoc = defineDoc2({
2437
+ DelegationsDoc = defineDoc4({
2038
2438
  kind: "app.neko.delegations",
2039
2439
  checkpointWhen: () => false,
2040
2440
  version: 1,
@@ -2043,7 +2443,7 @@ var init_subagents = __esm(() => {
2043
2443
  fork: "initial",
2044
2444
  initial: () => ({ items: [] })
2045
2445
  });
2046
- SubagentsDoc = defineDoc2({
2446
+ SubagentsDoc = defineDoc4({
2047
2447
  kind: "app.neko.subagents",
2048
2448
  checkpointWhen: () => false,
2049
2449
  version: 1,
@@ -2052,7 +2452,7 @@ var init_subagents = __esm(() => {
2052
2452
  fork: "initial",
2053
2453
  initial: () => ({ items: [] })
2054
2454
  });
2055
- SubagentDoc = defineDoc2({
2455
+ SubagentDoc = defineDoc4({
2056
2456
  kind: "app.neko.subagent",
2057
2457
  checkpointWhen: () => false,
2058
2458
  version: 1,
@@ -2061,7 +2461,7 @@ var init_subagents = __esm(() => {
2061
2461
  fork: "initial",
2062
2462
  initial: () => ({ role: null, parentId: null })
2063
2463
  });
2064
- SlotsDoc = defineDoc2({
2464
+ SlotsDoc = defineDoc4({
2065
2465
  kind: "app.neko.subagent-slots",
2066
2466
  checkpointWhen: () => false,
2067
2467
  version: 1,
@@ -2075,7 +2475,7 @@ var init_subagents = __esm(() => {
2075
2475
  };
2076
2476
  MAIN_ONLY_TOOLS = new Set(["todo", "ask_user", "submit_plan"]);
2077
2477
  MAIN_ONLY_EXTENSIONS = new Set(["neko-todos", "neko-questions", "neko-plan"]);
2078
- READ_TOOLS = new Set(["read", "find_files", "search_text", "web_search", "web_extract"]);
2478
+ READ_TOOLS = new Set(["read", "find_files", "search_text", "web_search", "web_extract", CODEMODE_NAME]);
2079
2479
  actionParameters = Type.Union([
2080
2480
  Type.Object({
2081
2481
  action: Type.Optional(Type.Literal("create")),
@@ -2143,22 +2543,6 @@ function normalizeChangePath(raw, cwd, workspace) {
2143
2543
  }
2144
2544
  return { path: resolved.split(sep).join("/"), outside: true };
2145
2545
  }
2146
- function clipDiff(diff) {
2147
- if (Buffer.byteLength(diff, "utf8") <= CHANGE_LIMITS.opDiffBytes)
2148
- return { diff, truncated: false };
2149
- const lines = diff.split(`
2150
- `);
2151
- const kept = [];
2152
- let bytes = 0;
2153
- for (const line of lines) {
2154
- bytes += Buffer.byteLength(line, "utf8") + 1;
2155
- if (bytes > CHANGE_LIMITS.opDiffBytes)
2156
- break;
2157
- kept.push(line);
2158
- }
2159
- return { diff: kept.join(`
2160
- `), truncated: true };
2161
- }
2162
2546
 
2163
2547
  class ConversationChanges {
2164
2548
  last = 0;
@@ -2166,6 +2550,7 @@ class ConversationChanges {
2166
2550
  truncated = false;
2167
2551
  bytes = 0;
2168
2552
  files = new Map;
2553
+ #scripts = new Map;
2169
2554
  #pending = new Map;
2170
2555
  #agent;
2171
2556
  #cwd;
@@ -2179,6 +2564,8 @@ class ConversationChanges {
2179
2564
  for (const message of entry.model ?? []) {
2180
2565
  if (message.role === "assistant") {
2181
2566
  for (const block of message.content) {
2567
+ if (block.type === "toolCall" && block.name === CODEMODE_NAME)
2568
+ this.#scripts.set(block.id, entry.id);
2182
2569
  if (block.type !== "toolCall" || block.name !== "edit" && block.name !== "write")
2183
2570
  continue;
2184
2571
  const path = block.arguments?.path;
@@ -2186,6 +2573,8 @@ class ConversationChanges {
2186
2573
  this.#pending.set(block.id, { tool: block.name, path, args: block.arguments });
2187
2574
  }
2188
2575
  } else if (message.role === "toolResult") {
2576
+ if (this.#scripts.has(message.toolCallId))
2577
+ this.#scripts.set(message.toolCallId, entry.id);
2189
2578
  const call = this.#pending.get(message.toolCallId);
2190
2579
  if (!call)
2191
2580
  continue;
@@ -2197,8 +2586,20 @@ class ConversationChanges {
2197
2586
  }
2198
2587
  this.last = entry.id;
2199
2588
  }
2589
+ withNested(nested) {
2590
+ const visible = nested.items.filter((item) => this.#scripts.has(item.parentCallId));
2591
+ const copy = new ConversationChanges(this.#agent, this.#cwd, this.#workspace);
2592
+ copy.last = this.last;
2593
+ copy.total = this.total;
2594
+ copy.truncated = this.truncated || nested.dropped && visible.length > 0;
2595
+ copy.bytes = this.bytes;
2596
+ for (const [path, file] of this.files)
2597
+ copy.files.set(path, { ...file, agents: new Set(file.agents), items: file.items.map((item) => ({ ...item })) });
2598
+ for (const item of visible)
2599
+ 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));
2600
+ return copy;
2601
+ }
2200
2602
  #add(call, details, seq) {
2201
- const { path, outside } = normalizeChangePath(call.path, this.#cwd, this.#workspace);
2202
2603
  let diff = "";
2203
2604
  let added = 0;
2204
2605
  let removed = 0;
@@ -2218,6 +2619,11 @@ class ConversationChanges {
2218
2619
  ({ diff, added, removed, truncated } = synthesized);
2219
2620
  }
2220
2621
  }
2622
+ this.#record(call, { diff, added, removed, truncated }, seq);
2623
+ }
2624
+ #record(call, shown, seq) {
2625
+ const { path, outside } = normalizeChangePath(call.path, this.#cwd, this.#workspace);
2626
+ const { diff, added, removed, truncated } = shown;
2221
2627
  this.total++;
2222
2628
  let file = this.files.get(path);
2223
2629
  if (!file) {
@@ -2238,7 +2644,7 @@ class ConversationChanges {
2238
2644
  file.added += added;
2239
2645
  file.removed += removed;
2240
2646
  file.agents.add(this.#agent);
2241
- file.lastSeq = seq;
2647
+ file.lastSeq = Math.max(file.lastSeq, seq);
2242
2648
  file.items.push({ seq, agent: this.#agent, tool: call.tool, diff, added, removed, truncated });
2243
2649
  this.bytes += Buffer.byteLength(diff, "utf8");
2244
2650
  if (file.items.length > CHANGE_LIMITS.opsPerFile) {
@@ -2340,10 +2746,12 @@ class ChangeTracker {
2340
2746
  let revision = 0;
2341
2747
  let truncated = false;
2342
2748
  for (const member of await this.#members(rootId)) {
2343
- const state = await this.#sync(member, workspace);
2344
- if (!state)
2749
+ const synced = await this.#sync(member, workspace);
2750
+ if (!synced)
2345
2751
  continue;
2346
- revision += state.total;
2752
+ const nested = await this.#harness.snapshot(NestedChangesDoc, member.id, this.#context);
2753
+ const state = nested && nested.items.length > 0 ? synced.withNested(nested) : synced;
2754
+ revision += synced.total + (nested?.total ?? 0);
2347
2755
  truncated ||= state.truncated;
2348
2756
  for (const file of state.files.values()) {
2349
2757
  const existing = merged.get(file.path);
@@ -2403,17 +2811,13 @@ class ChangeTracker {
2403
2811
  };
2404
2812
  }
2405
2813
  }
2406
- var CHANGE_LIMITS, MAIN_AGENT = "main";
2814
+ var MAIN_AGENT = "main";
2407
2815
  var init_file_changes = __esm(() => {
2816
+ init_change_limits();
2817
+ init_codemode_mode();
2818
+ init_nested_changes();
2408
2819
  init_projection();
2409
2820
  init_subagents();
2410
- CHANGE_LIMITS = {
2411
- files: 500,
2412
- opsPerFile: 50,
2413
- diffBytes: 2 * 1024 * 1024,
2414
- opDiffBytes: 64 * 1024,
2415
- conversations: 64
2416
- };
2417
2821
  });
2418
2822
 
2419
2823
  // packages/core/src/images.ts
@@ -2804,7 +3208,7 @@ var init_interactions = __esm(() => {
2804
3208
  });
2805
3209
 
2806
3210
  // packages/core/src/jobs.ts
2807
- import { defineDoc as defineDoc3 } from "@earendil-works/pi-durable";
3211
+ import { defineDoc as defineDoc5 } from "@earendil-works/pi-durable";
2808
3212
  function tailOf(text, maxBytes, maxLines) {
2809
3213
  let bytes = encoder2.encode(text);
2810
3214
  if (bytes.length > maxBytes) {
@@ -2910,7 +3314,7 @@ var init_jobs = __esm(() => {
2910
3314
  commandBytes: 32 * 1024,
2911
3315
  maxTimeoutSeconds: 7 * 24 * 3600
2912
3316
  };
2913
- JobsDoc = defineDoc3({
3317
+ JobsDoc = defineDoc5({
2914
3318
  kind: "app.neko.jobs",
2915
3319
  checkpointWhen: () => false,
2916
3320
  version: 1,
@@ -3367,7 +3771,7 @@ class JobRegistry {
3367
3771
  function reportText(job, tail) {
3368
3772
  const body = reportTail(tail);
3369
3773
  return [
3370
- `[Background job ${job.jobId} "${job.title}" ${describe2(job)}]`,
3774
+ `[Background job ${job.jobId} "${job.title}" ${describe3(job)}]`,
3371
3775
  `Command: ${job.command}`,
3372
3776
  body ? `Last output (use the job tool's output action with jobId "${job.jobId}" for more):
3373
3777
  ${body}` : "It produced no output."
@@ -3551,7 +3955,7 @@ class JobManager {
3551
3955
  if (kind === "stop") {
3552
3956
  const jobId = stringField(args.jobId, "jobId");
3553
3957
  const job = await this.stop(io, api.conversationId, jobId, "model", context);
3554
- return this.#text(`Job ${job.jobId} ${describe2(job)}.`);
3958
+ return this.#text(`Job ${job.jobId} ${describe3(job)}.`);
3555
3959
  }
3556
3960
  if (kind !== "start")
3557
3961
  throw new Error("Invalid job arguments: action must be start, output, stop or list.");
@@ -3579,7 +3983,7 @@ class JobManager {
3579
3983
  #listText(items) {
3580
3984
  if (!items.length)
3581
3985
  return "No jobs.";
3582
- return items.map((job) => `${job.jobId} [${describe2(job)}] "${job.title}" \u2014 ${job.command}`).join(`
3986
+ return items.map((job) => `${job.jobId} [${describe3(job)}] "${job.title}" \u2014 ${job.command}`).join(`
3583
3987
  `);
3584
3988
  }
3585
3989
  async start(io, conversationId, cwd, args, context) {
@@ -3631,7 +4035,7 @@ class JobManager {
3631
4035
  return this.#render(job, read.text, read.offset, read.dropped);
3632
4036
  }
3633
4037
  #render(job, text, offset, dropped) {
3634
- const lines = [`Job ${job.jobId} ${describe2(job)}.`, `Title: ${job.title}`, `Command: ${job.command}`];
4038
+ const lines = [`Job ${job.jobId} ${describe3(job)}.`, `Title: ${job.title}`, `Command: ${job.command}`];
3635
4039
  if (dropped > 0)
3636
4040
  lines.push(`(${dropped} bytes of earlier output were dropped)`);
3637
4041
  lines.push(text ? `Output (next offset ${offset}):
@@ -3696,7 +4100,7 @@ ${text}` : `No new output (next offset ${offset}).`);
3696
4100
  this.registry.abortAll();
3697
4101
  }
3698
4102
  }
3699
- var plain = (value) => JSON.parse(JSON.stringify(value)), statusWord, describe2 = (job) => {
4103
+ var plain = (value) => JSON.parse(JSON.stringify(value)), statusWord, describe3 = (job) => {
3700
4104
  const parts = [statusWord[job.status]];
3701
4105
  if (job.exitCode !== null)
3702
4106
  parts.push(`exit code ${job.exitCode}`);
@@ -3855,7 +4259,7 @@ var init_plan_bash = __esm(() => {
3855
4259
  // packages/core/src/user-questions.ts
3856
4260
  import { randomUUID } from "crypto";
3857
4261
  import {
3858
- defineDoc as defineDoc4,
4262
+ defineDoc as defineDoc6,
3859
4263
  defineDocFamily,
3860
4264
  defineExtension as defineExtension5,
3861
4265
  defineTool as defineTool4
@@ -4146,7 +4550,7 @@ var init_user_questions = __esm(() => {
4146
4550
  family: true,
4147
4551
  initial: (seed) => seed
4148
4552
  });
4149
- QuestionsDoc = defineDoc4({
4553
+ QuestionsDoc = defineDoc6({
4150
4554
  kind: "app.neko.questions",
4151
4555
  checkpointWhen: () => false,
4152
4556
  version: 1,
@@ -4202,20 +4606,10 @@ import {
4202
4606
  hook as hook2,
4203
4607
  InboxDoc,
4204
4608
  ToolTask as ToolTask2,
4205
- wrapTool
4609
+ wrapTool as wrapTool3
4206
4610
  } from "@earendil-works/pi-durable";
4207
4611
  import { createBashTool } from "@earendil-works/pi-durable/tools";
4208
4612
  import { Type as Type5 } from "typebox";
4209
- function planAgentChange(mode) {
4210
- if (mode === "normal")
4211
- return { tools: null, modelTools: null, instructions: null };
4212
- const named = (name) => ({ name });
4213
- return {
4214
- tools: { remove: PLAN_REMOVED_TOOLS.map(named) },
4215
- modelTools: { add: [named("submit_plan")] },
4216
- instructions: PLAN_INSTRUCTIONS
4217
- };
4218
- }
4219
4613
  function settle2(state, item, status) {
4220
4614
  item.status = status;
4221
4615
  item.settledAt = Date.now();
@@ -4418,7 +4812,7 @@ function submitPlanTool(service) {
4418
4812
  }
4419
4813
  });
4420
4814
  }
4421
- var PLAN_REMOVED_TOOLS, PLAN_INSTRUCTIONS, PLAN_BUSY = "PLAN_BUSY: Wait for the current run to finish or stop it first.", agentInPlanMode = (agent) => agent.instructions === PLAN_INSTRUCTIONS, clone2 = (value) => JSON.parse(JSON.stringify(value)), toRecord = ({ taskId: _taskId, dispatched: _dispatched, ...record }) => clone2(record), parameters4, unsafeBash = (command) => `Plan mode is read-only, so this command was not run (it is not on the read-only allowlist): ${command}
4815
+ var PLAN_BUSY = "PLAN_BUSY: Wait for the current run to finish or stop it first.", clone2 = (value) => JSON.parse(JSON.stringify(value)), toRecord = ({ taskId: _taskId, dispatched: _dispatched, ...record }) => clone2(record), parameters4, unsafeBash = (command) => `Plan mode is read-only, so this command was not run (it is not on the read-only allowlist): ${command}
4422
4816
  Use read, find_files or search_text, run a read-only command, or ask the user. If the command is needed, put it in the plan.`, blockedTool = (name) => `Plan mode is read-only: ${name} is unavailable. Research with read-only tools, then call submit_plan.`, createPlanExtension = (service) => defineExtension6({
4423
4817
  name: "neko-plan",
4424
4818
  tools: [submitPlanTool(service)],
@@ -4438,7 +4832,7 @@ Use read, find_files or search_text, run a read-only command, or ask the user. I
4438
4832
  })
4439
4833
  ],
4440
4834
  wraps: [
4441
- wrapTool(createBashTool(), (bash) => ({
4835
+ wrapTool3(createBashTool(), (bash) => ({
4442
4836
  ...bash,
4443
4837
  execute: async (args, api, context) => {
4444
4838
  if ((await api.snapshot(PlanDoc, api.conversationId, context))?.mode === "plan" && !isSafeCommand(args.command))
@@ -4456,16 +4850,6 @@ var init_plan = __esm(() => {
4456
4850
  init_subagents();
4457
4851
  init_user_questions();
4458
4852
  init_plan_doc();
4459
- PLAN_REMOVED_TOOLS = ["edit", "write", "job"];
4460
- PLAN_INSTRUCTIONS = [
4461
- "You are in plan mode: research and plan only, and change nothing.",
4462
- "- 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.",
4463
- "- Not available: edit, write, job and any command that modifies files, installs packages or starts processes. Do not try to work around this.",
4464
- "- 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.",
4465
- "- After submit_plan do nothing else. If the user asks for changes, research what is needed and submit a revised plan.",
4466
- "- Use ask_user only for decisions you cannot settle by reading the code."
4467
- ].join(`
4468
- `);
4469
4853
  parameters4 = Type5.Object({
4470
4854
  title: Type5.String({ minLength: 1, maxLength: PLAN_LIMITS.titleChars, description: "Short name of the plan." }),
4471
4855
  steps: Type5.Array(Type5.String({ minLength: 1, maxLength: PLAN_LIMITS.stepChars }), {
@@ -4641,7 +5025,7 @@ ${content}
4641
5025
  const resources = cwd ? await loadResources(input.conversationId, cwd) : { contextFiles: [], skills: [] };
4642
5026
  const sections = {
4643
5027
  preamble: role ? ROLE_PROMPTS[role] + SUBAGENT_DELIVERY : PREAMBLE,
4644
- rules: buildRules(input.agent.tools.map((tool) => tool.name), platform),
5028
+ rules: buildRules(enabledTools(input.agent).map((tool) => tool.name), platform),
4645
5029
  project_context: renderProjectContext(resources.contextFiles),
4646
5030
  skills: formatSkillsForPrompt(resources.skills) || undefined,
4647
5031
  cwd: cwd?.replace(/\\/g, "/")
@@ -4666,10 +5050,14 @@ ${text}
4666
5050
  });
4667
5051
  };
4668
5052
  var init_prompt = __esm(() => {
5053
+ init_agent_change();
4669
5054
  init_subagents();
4670
5055
  init_skills();
4671
5056
  KEYS = ["preamble", "rules", "project_context", "skills", "cwd"];
4672
5057
  TOOL_GUIDELINES = {
5058
+ codemode: [
5059
+ "Use codemode to batch independent tool calls (Promise.allSettled), chain them, or filter large output, instead of many separate calls."
5060
+ ],
4673
5061
  subagent: [
4674
5062
  "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."
4675
5063
  ],
@@ -4841,7 +5229,7 @@ var createResourceSnapshots = (load, submission) => {
4841
5229
  import { stripVTControlCharacters } from "util";
4842
5230
  import { getSupportedThinkingLevels } from "@earendil-works/pi-ai";
4843
5231
  import {
4844
- defineDoc as defineDoc5,
5232
+ defineDoc as defineDoc7,
4845
5233
  defineExtension as defineExtension8,
4846
5234
  defineTask as defineTask3,
4847
5235
  InboxDoc as InboxDoc2,
@@ -5161,7 +5549,7 @@ var init_session_metadata = __esm(() => {
5161
5549
  init_input();
5162
5550
  init_plan_doc();
5163
5551
  init_user_questions();
5164
- SessionMetadataDoc = defineDoc5({
5552
+ SessionMetadataDoc = defineDoc7({
5165
5553
  kind: "app.neko.session-metadata",
5166
5554
  checkpointWhen: () => false,
5167
5555
  version: 1,
@@ -5188,7 +5576,7 @@ var init_session_metadata = __esm(() => {
5188
5576
  });
5189
5577
 
5190
5578
  // packages/core/src/todos.ts
5191
- import { defineDoc as defineDoc6, defineExtension as defineExtension9, defineTool as defineTool6 } from "@earendil-works/pi-durable";
5579
+ import { defineDoc as defineDoc8, defineExtension as defineExtension9, defineTool as defineTool6 } from "@earendil-works/pi-durable";
5192
5580
  import { Type as Type6 } from "typebox";
5193
5581
  function renderTodos(list) {
5194
5582
  const { completed, total } = todoProgress(list.items);
@@ -5254,7 +5642,7 @@ var TodosDoc, TodoReceiptDoc, itemSchema, parameters5, structuredOutputSchema3,
5254
5642
  var init_todos = __esm(() => {
5255
5643
  init_interactions();
5256
5644
  init_subagents();
5257
- TodosDoc = defineDoc6({
5645
+ TodosDoc = defineDoc8({
5258
5646
  kind: "app.neko.todos",
5259
5647
  checkpointWhen: () => false,
5260
5648
  version: 1,
@@ -5263,7 +5651,7 @@ var init_todos = __esm(() => {
5263
5651
  fork: "asOf",
5264
5652
  initial: () => ({ revision: 0, items: [] })
5265
5653
  });
5266
- TodoReceiptDoc = defineDoc6({
5654
+ TodoReceiptDoc = defineDoc8({
5267
5655
  kind: "app.neko.todo-receipt",
5268
5656
  checkpointWhen: () => false,
5269
5657
  version: 1,
@@ -5294,11 +5682,11 @@ import { randomUUID as randomUUID3 } from "crypto";
5294
5682
  import { realpath as realpath2 } from "fs/promises";
5295
5683
  import {
5296
5684
  configure as configure2,
5297
- defineDoc as defineDoc7,
5685
+ defineDoc as defineDoc9,
5298
5686
  defineExtension as defineExtension10,
5299
5687
  hook as hook3,
5300
5688
  ToolTask as ToolTask3,
5301
- wrapTool as wrapTool2
5689
+ wrapTool as wrapTool4
5302
5690
  } from "@earendil-works/pi-durable";
5303
5691
  import { createBashTool as createBashTool2 } from "@earendil-works/pi-durable/tools";
5304
5692
  async function live2(tx, id) {
@@ -5434,7 +5822,7 @@ function createApprovalExtension(service) {
5434
5822
  })
5435
5823
  ],
5436
5824
  wraps: [
5437
- wrapTool2(createBashTool2(), (bash) => ({
5825
+ wrapTool4(createBashTool2(), (bash) => ({
5438
5826
  ...bash,
5439
5827
  execute: async (args, api, context) => {
5440
5828
  const role = await api.snapshot(SubagentDoc, api.conversationId, context);
@@ -5458,15 +5846,16 @@ async function prepareApprovalConversations(harness, ids, extension, context) {
5458
5846
  const child = await harness.conversation(id, context);
5459
5847
  const agent = await child.agent(context);
5460
5848
  await harness.commit((tx) => configure2(tx, id, {
5461
- tools: [...agent.tools.filter((t) => t.name !== "bash"), createBashTool2()],
5849
+ tools: [...enabledTools(agent).filter((t) => t.name !== "bash"), createBashTool2()],
5462
5850
  extensions: { add: [extension] }
5463
5851
  }), context);
5464
5852
  }
5465
5853
  }
5466
5854
  var ApprovalsDoc;
5467
5855
  var init_tool_approvals = __esm(() => {
5856
+ init_agent_change();
5468
5857
  init_subagents();
5469
- ApprovalsDoc = defineDoc7({
5858
+ ApprovalsDoc = defineDoc9({
5470
5859
  kind: "app.neko.approvals",
5471
5860
  checkpointWhen: () => false,
5472
5861
  version: 1,
@@ -6489,7 +6878,7 @@ import { PI_ATTRIBUTION } from "@earendil-works/pi-ai/utils/identity";
6489
6878
  import {
6490
6879
  AssistantEntry as AssistantEntry2,
6491
6880
  createRegistry,
6492
- defineDoc as defineDoc8,
6881
+ defineDoc as defineDoc10,
6493
6882
  Harness,
6494
6883
  InboxDoc as InboxDoc3,
6495
6884
  LiveDoc as importLiveDoc,
@@ -6598,6 +6987,11 @@ async function createNekoRuntime(options = {}) {
6598
6987
  registry.install(createQuestionExtension(() => questions));
6599
6988
  let plans;
6600
6989
  registry.install(createPlanExtension(() => plans));
6990
+ const codemode = createCodemodeRuntime({
6991
+ maxWorkers: options.codemodeLimits?.maxWorkers ?? CODEMODE_LIMITS.maxWorkers,
6992
+ waitMs: options.codemodeLimits?.waitMs ?? CODEMODE_LIMITS.waitMs
6993
+ });
6994
+ registry.install(codemode.extension);
6601
6995
  for (const extension of options.extensions ?? [])
6602
6996
  registry.install(extension);
6603
6997
  let approvals;
@@ -6651,6 +7045,7 @@ async function createNekoRuntime(options = {}) {
6651
7045
  }
6652
7046
  }
6653
7047
  } catch (error) {
7048
+ await codemode.close();
6654
7049
  await workspaceFiles.close();
6655
7050
  await harness.close(context);
6656
7051
  throw error;
@@ -6665,6 +7060,13 @@ async function createNekoRuntime(options = {}) {
6665
7060
  }
6666
7061
  });
6667
7062
  enabled = new Set(normalizeEnabledProviders((await harness.snapshot(ProviderSettingsDoc, conversation.id, context))?.enabled));
7063
+ const codemodeState = async () => resolveCodemodeSettings(options.codemodeSettings, (await harness.snapshot(CodemodeSettingsDoc, conversation.id, context))?.mode);
7064
+ if (existing.length === 0) {
7065
+ const initial = (await codemodeState()).effective.mode;
7066
+ await harness.commit(async (tx) => {
7067
+ (await tx.doc(CodemodeDoc, conversation.id)).mode = initial;
7068
+ }, context);
7069
+ }
6668
7070
  const savedSearch = (await harness.snapshot(SearchSettingsDoc, conversation.id, context))?.provider;
6669
7071
  if (isSearchProvider(savedSearch))
6670
7072
  searchProvider = savedSearch;
@@ -6791,13 +7193,17 @@ async function createNekoRuntime(options = {}) {
6791
7193
  admissions.delete(id);
6792
7194
  }
6793
7195
  };
6794
- const syncPlanAgent = async (target) => {
6795
- const mode = await plans.mode(target.id);
6796
- if (mode === "plan" !== agentInPlanMode(await target.agent(context)))
6797
- await target.configure(planAgentChange(mode), context);
7196
+ const syncAgent = async (target) => {
7197
+ const change = agentSyncChange(await target.agent(context), {
7198
+ plan: await plans.mode(target.id),
7199
+ codemode: (await harness.snapshot(CodemodeDoc, target.id, context))?.mode ?? "off",
7200
+ role: "main"
7201
+ });
7202
+ if (change)
7203
+ await target.configure(change, context);
6798
7204
  };
6799
7205
  const submitTo = async (target, content, submitOptions, extra) => admit(target.id, async () => {
6800
- await syncPlanAgent(target);
7206
+ await syncAgent(target);
6801
7207
  const agent = await target.agent(context);
6802
7208
  const workspace = executionWorkspace(agent.cwd);
6803
7209
  const duplicate = submitOptions?.requestId !== undefined && await storage.submissionByRequest(target.id, submitOptions.requestId, context);
@@ -6961,7 +7367,7 @@ async function createNekoRuntime(options = {}) {
6961
7367
  if (await isRunning(target.id))
6962
7368
  throw new Error(PLAN_BUSY);
6963
7369
  await plans.setMode(target.id, mode);
6964
- await syncPlanAgent(target);
7370
+ await syncAgent(target);
6965
7371
  return plans.view(target.id);
6966
7372
  });
6967
7373
  },
@@ -7083,7 +7489,7 @@ async function createNekoRuntime(options = {}) {
7083
7489
  const session = await getConversation(id);
7084
7490
  if (!session)
7085
7491
  continue;
7086
- await admit(id, () => syncPlanAgent(session.conversation));
7492
+ await admit(id, () => syncAgent(session.conversation));
7087
7493
  const pending = await plans.undispatched(id);
7088
7494
  if (pending)
7089
7495
  await dispatchPlan(session.conversation, pending);
@@ -7169,6 +7575,8 @@ async function createNekoRuntime(options = {}) {
7169
7575
  }
7170
7576
  const child = at === undefined ? await tx.createConversation({ ownership: { kind: "ownerless" } }) : await tx.forkConversation(id, at, { ownership: { kind: "ownerless" } });
7171
7577
  (await tx.doc(DelegationsDoc, child.id)).items = inherited;
7578
+ if (at === undefined)
7579
+ (await tx.doc(CodemodeDoc, child.id)).mode = (await tx.doc(CodemodeDoc, id)).mode;
7172
7580
  if (sourceMetadata) {
7173
7581
  const doc = await tx.doc(SessionMetadataDoc, child.id);
7174
7582
  doc.title = sourceMetadata.title || sourceMetadata.firstPromptPreview;
@@ -7249,12 +7657,17 @@ async function createNekoRuntime(options = {}) {
7249
7657
  const workspace = await addWorkspace(createOptions?.cwd ?? defaultCwd);
7250
7658
  const savedDefault = (await harness.snapshot(DefaultModelDoc, conversation.id, context))?.model ?? null;
7251
7659
  const effective = createOptions?.model ?? modelOverride ?? savedDefault ?? model;
7660
+ const mode = (await codemodeState()).effective.mode;
7252
7661
  const created = await harness.createConversation({
7253
7662
  ownership: { kind: "ownerless" },
7254
7663
  agent: {
7255
7664
  model: effective,
7256
7665
  thinkingLevel: resolveLevel(effective, createOptions?.thinkingLevel ?? options.thinkingLevel ?? "high"),
7257
- cwd: workspace.path
7666
+ cwd: workspace.path,
7667
+ ...agentChangeFor({ plan: "normal", codemode: mode, role: "main" })
7668
+ },
7669
+ init: async (tx, id) => {
7670
+ (await tx.doc(CodemodeDoc, id)).mode = mode;
7258
7671
  }
7259
7672
  }, context);
7260
7673
  await metadata.initialize(created.id);
@@ -7309,6 +7722,20 @@ async function createNekoRuntime(options = {}) {
7309
7722
  async resetContextSettings() {
7310
7723
  return persistContext(emptySavedContextSettings());
7311
7724
  },
7725
+ codemodeSettings: codemodeState,
7726
+ async setCodemodeMode(mode) {
7727
+ const parsed = parseCodemodeMode(mode);
7728
+ await harness.commit(async (tx) => {
7729
+ (await tx.doc(CodemodeSettingsDoc, conversation.id)).mode = parsed;
7730
+ }, context);
7731
+ return codemodeState();
7732
+ },
7733
+ async resetCodemodeMode() {
7734
+ await harness.commit(async (tx) => {
7735
+ (await tx.doc(CodemodeSettingsDoc, conversation.id)).mode = null;
7736
+ }, context);
7737
+ return codemodeState();
7738
+ },
7312
7739
  async searchSettings() {
7313
7740
  return { selected: searchProvider };
7314
7741
  },
@@ -7430,6 +7857,7 @@ async function createNekoRuntime(options = {}) {
7430
7857
  registry.install(extension);
7431
7858
  },
7432
7859
  async close() {
7860
+ await codemode.close();
7433
7861
  await cleanup();
7434
7862
  return harness.close(context);
7435
7863
  }
@@ -7437,7 +7865,10 @@ async function createNekoRuntime(options = {}) {
7437
7865
  }
7438
7866
  var DEFAULT_MODEL, DEFAULT_STORAGE_PATH, neverAborted = () => new AbortController().signal, ArchivedDoc, WorkspacesDoc, RemovedWorkspacesDoc, SearchSettingsDoc, ProviderSettingsDoc, DefaultModelDoc, ContextSettingsDoc, ActivityDoc;
7439
7867
  var init_runtime = __esm(() => {
7868
+ init_agent_change();
7440
7869
  init_agent_file_search();
7870
+ init_codemode();
7871
+ init_codemode_mode();
7441
7872
  init_compaction();
7442
7873
  init_context_pruning();
7443
7874
  init_context_settings();
@@ -7471,7 +7902,7 @@ var init_runtime = __esm(() => {
7471
7902
  init_workspace_files();
7472
7903
  DEFAULT_MODEL = { provider: "deepseek", modelId: "deepseek-flash" };
7473
7904
  DEFAULT_STORAGE_PATH = join7(homedir5(), ".neko", "neko.sqlite");
7474
- ArchivedDoc = defineDoc8({
7905
+ ArchivedDoc = defineDoc10({
7475
7906
  kind: "app.neko.conversation",
7476
7907
  checkpointWhen: () => false,
7477
7908
  version: 1,
@@ -7480,7 +7911,7 @@ var init_runtime = __esm(() => {
7480
7911
  fork: "initial",
7481
7912
  initial: () => ({ archived: false })
7482
7913
  });
7483
- WorkspacesDoc = defineDoc8({
7914
+ WorkspacesDoc = defineDoc10({
7484
7915
  kind: "app.neko.workspaces",
7485
7916
  checkpointWhen: () => false,
7486
7917
  version: 1,
@@ -7489,7 +7920,7 @@ var init_runtime = __esm(() => {
7489
7920
  fork: "initial",
7490
7921
  initial: () => ({ items: [] })
7491
7922
  });
7492
- RemovedWorkspacesDoc = defineDoc8({
7923
+ RemovedWorkspacesDoc = defineDoc10({
7493
7924
  kind: "app.neko.workspaces-removed",
7494
7925
  checkpointWhen: () => false,
7495
7926
  version: 1,
@@ -7498,7 +7929,7 @@ var init_runtime = __esm(() => {
7498
7929
  fork: "initial",
7499
7930
  initial: () => ({ paths: [] })
7500
7931
  });
7501
- SearchSettingsDoc = defineDoc8({
7932
+ SearchSettingsDoc = defineDoc10({
7502
7933
  kind: "app.neko.search-settings",
7503
7934
  checkpointWhen: () => false,
7504
7935
  version: 1,
@@ -7507,7 +7938,7 @@ var init_runtime = __esm(() => {
7507
7938
  fork: "initial",
7508
7939
  initial: () => ({ provider: null })
7509
7940
  });
7510
- ProviderSettingsDoc = defineDoc8({
7941
+ ProviderSettingsDoc = defineDoc10({
7511
7942
  kind: "app.neko.provider-settings",
7512
7943
  checkpointWhen: () => false,
7513
7944
  version: 1,
@@ -7516,7 +7947,7 @@ var init_runtime = __esm(() => {
7516
7947
  fork: "initial",
7517
7948
  initial: () => ({ enabled: null })
7518
7949
  });
7519
- DefaultModelDoc = defineDoc8({
7950
+ DefaultModelDoc = defineDoc10({
7520
7951
  kind: "app.neko.default-model",
7521
7952
  checkpointWhen: () => false,
7522
7953
  version: 1,
@@ -7525,7 +7956,7 @@ var init_runtime = __esm(() => {
7525
7956
  fork: "initial",
7526
7957
  initial: () => ({ model: null })
7527
7958
  });
7528
- ContextSettingsDoc = defineDoc8({
7959
+ ContextSettingsDoc = defineDoc10({
7529
7960
  kind: "app.neko.context-settings",
7530
7961
  checkpointWhen: () => false,
7531
7962
  version: 1,
@@ -7534,7 +7965,7 @@ var init_runtime = __esm(() => {
7534
7965
  fork: "initial",
7535
7966
  initial: emptySavedContextSettings
7536
7967
  });
7537
- ActivityDoc = defineDoc8({
7968
+ ActivityDoc = defineDoc10({
7538
7969
  kind: "app.neko.activity",
7539
7970
  checkpointWhen: () => false,
7540
7971
  version: 1,
@@ -7559,6 +7990,7 @@ function mergeTranscriptItems(previous, incoming) {
7559
7990
 
7560
7991
  // packages/core/src/index.ts
7561
7992
  var init_src = __esm(() => {
7993
+ init_codemode_mode();
7562
7994
  init_config();
7563
7995
  init_context_settings();
7564
7996
  init_credentials();
@@ -7596,8 +8028,9 @@ function contract(name, def) {
7596
8028
  ]));
7597
8029
  return remote(name, { ...def, host: hostNamed(GATEWAY_HOST), variants });
7598
8030
  }
7599
- var GATEWAY_HOST = "neko", rpc = (args, result) => [args, result], str, num, bool, nil, integer, nullableString, phase, level, thinking, model2, change, usage, image, skill, call, DelegationSchema, SubagentSummarySchema, jobStatus, JobSummarySchema, JobOutputSchema, QueuedInputSchema, WithdrawResultSchema, SteerResultSchema, TodoItemSchema, TodoListSchema, QuestionOptionSchema, questionStatus, QuestionSummarySchema, WorkspaceEntrySchema, WorkspaceDirSchema, WorkspaceFileSchema, ChangeSummarySchema, ChangedFileSchema, ChangeListSchema, ChangeOpSchema, ChangeDetailSchema, QuestionListSchema, QuestionRecordSchema, QuestionResponseSchema, PlanRecordSchema, PlanViewSchema, PlanDecisionSchema, ToolInteractionSchema, ChatViewSchema, SessionSummarySchema, DirectoryStateSchema, WorkspaceSummarySchema, WorkspaceStateSchema, credentialStatus, CredentialStateSchema, AttachmentSchema, chatInput, NekoSessionDirectory, NekoSessionManagement, resize, inputLimits, modelInfo, NekoModels, imageChunk, NekoSubagent, NekoChat, browse, page, NekoWorkspaces, provider, NekoCredentials, SearchSettingsStateSchema, NekoSearchSettings, contextSource, ContextSettingsStateSchema, nullable = (schema) => Type8.Union([schema, nil]), ContextSettingsPatchSchema, NekoContextSettings, defaultModelSource, DefaultModelStateSchema, NekoDefaultModel, FileCandidatesSchema, NekoWorkspaceFiles, marketSkill, preview, scope, installResult, NekoSkillMarket, ShellStatusSchema, ApplicationInfoSchema, NekoApplicationInfo;
8031
+ var GATEWAY_HOST = "neko", rpc = (args, result) => [args, result], str, num, bool, nil, integer, nullableString, phase, level, thinking, model2, change, usage, image, skill, CodemodeViewSchema, call, DelegationSchema, SubagentSummarySchema, jobStatus, JobSummarySchema, JobOutputSchema, QueuedInputSchema, WithdrawResultSchema, SteerResultSchema, TodoItemSchema, TodoListSchema, QuestionOptionSchema, questionStatus, QuestionSummarySchema, WorkspaceEntrySchema, WorkspaceDirSchema, WorkspaceFileSchema, ChangeSummarySchema, ChangedFileSchema, ChangeListSchema, ChangeOpSchema, ChangeDetailSchema, QuestionListSchema, QuestionRecordSchema, QuestionResponseSchema, PlanRecordSchema, PlanViewSchema, PlanDecisionSchema, ToolInteractionSchema, ChatViewSchema, SessionSummarySchema, DirectoryStateSchema, WorkspaceSummarySchema, WorkspaceStateSchema, credentialStatus, CredentialStateSchema, AttachmentSchema, chatInput, NekoSessionDirectory, NekoSessionManagement, resize, inputLimits, modelInfo, NekoModels, imageChunk, NekoSubagent, NekoChat, browse, page, NekoWorkspaces, provider, NekoCredentials, SearchSettingsStateSchema, NekoSearchSettings, contextSource, ContextSettingsStateSchema, nullable = (schema) => Type8.Union([schema, nil]), ContextSettingsPatchSchema, NekoContextSettings, CodemodeSettingsStateSchema, NekoCodemodeSettings, defaultModelSource, DefaultModelStateSchema, NekoDefaultModel, FileCandidatesSchema, NekoWorkspaceFiles, marketSkill, preview, scope, installResult, NekoSkillMarket, ShellStatusSchema, ApplicationInfoSchema, NekoApplicationInfo;
7600
8032
  var init_contract = __esm(() => {
8033
+ init_codemode_mode();
7601
8034
  init_interactions();
7602
8035
  str = Type8.String();
7603
8036
  num = Type8.Number();
@@ -7621,6 +8054,18 @@ var init_contract = __esm(() => {
7621
8054
  contentIndex: integer
7622
8055
  });
7623
8056
  skill = Type8.Object({ name: str, location: str, body: str, args: str });
8057
+ CodemodeViewSchema = Type8.Object({
8058
+ calls: Type8.Array(Type8.Object({
8059
+ id: integer,
8060
+ name: str,
8061
+ args: str,
8062
+ status: Type8.Enum(["running", "ok", "error", "cancelled"]),
8063
+ durationMs: Type8.Union([num, nil]),
8064
+ error: nullableString
8065
+ })),
8066
+ omitted: integer,
8067
+ fullOutputPath: nullableString
8068
+ });
7624
8069
  call = Type8.Object({
7625
8070
  callId: str,
7626
8071
  name: str,
@@ -7631,7 +8076,8 @@ var init_contract = __esm(() => {
7631
8076
  diff: nullableString,
7632
8077
  diffAdded: Type8.Union([num, nil]),
7633
8078
  diffRemoved: Type8.Union([num, nil]),
7634
- diffTruncated: Type8.Optional(bool)
8079
+ diffTruncated: Type8.Optional(bool),
8080
+ codemode: Type8.Optional(CodemodeViewSchema)
7635
8081
  });
7636
8082
  DelegationSchema = Type8.Object({
7637
8083
  parentId: integer,
@@ -7939,7 +8385,8 @@ var init_contract = __esm(() => {
7939
8385
  diffAdded: Type8.Union([num, nil]),
7940
8386
  diffRemoved: Type8.Union([num, nil]),
7941
8387
  diffTruncated: Type8.Optional(bool),
7942
- interaction: Type8.Optional(ToolInteractionSchema)
8388
+ interaction: Type8.Optional(ToolInteractionSchema),
8389
+ codemode: Type8.Optional(CodemodeViewSchema)
7943
8390
  }),
7944
8391
  Type8.Object({
7945
8392
  type: Type8.Literal("system"),
@@ -8215,6 +8662,24 @@ var init_contract = __esm(() => {
8215
8662
  }
8216
8663
  }
8217
8664
  });
8665
+ CodemodeSettingsStateSchema = Type8.Object({
8666
+ revision: integer,
8667
+ effective: Type8.Object({ mode: Type8.Enum([...CODEMODE_MODES]) }),
8668
+ source: Type8.Enum(["default", "config", "saved"])
8669
+ });
8670
+ NekoCodemodeSettings = contract("neko.codemode-settings", {
8671
+ version: 1,
8672
+ input: Type8.Object({}, { additionalProperties: false }),
8673
+ variants: {
8674
+ ready: {
8675
+ state: { state: CodemodeSettingsStateSchema },
8676
+ methods: {
8677
+ set: rpc(Type8.Object({ mode: Type8.Enum([...CODEMODE_MODES]) }, { additionalProperties: false }), nil),
8678
+ reset: rpc(Type8.Object({}, { additionalProperties: false }), nil)
8679
+ }
8680
+ }
8681
+ }
8682
+ });
8218
8683
  defaultModelSource = Type8.Enum(["cli", "saved", "config", "default"]);
8219
8684
  DefaultModelStateSchema = Type8.Object({
8220
8685
  effective: model2,
@@ -8702,8 +9167,8 @@ function createGatewayConnection(link, serverId, options = {}) {
8702
9167
  let business;
8703
9168
  let protocolChecked = false;
8704
9169
  function bindBusiness() {
8705
- const directory = get(NekoSessionDirectory), management = get(NekoSessionManagement), models = get(NekoModels), workspaces = get(NekoWorkspaces), workspaceFiles = get(NekoWorkspaceFiles), credentials = get(NekoCredentials), searchSettings = get(NekoSearchSettings), contextSettings = get(NekoContextSettings), defaultModel = get(NekoDefaultModel), skillMarket = get(NekoSkillMarket);
8706
- keep.push(directory.state.subscribe(() => {}), workspaces.state.subscribe(() => {}), credentials.state.subscribe(() => {}), searchSettings.state.subscribe(() => {}), contextSettings.state.subscribe(() => {}), defaultModel.state.subscribe(() => {}));
9170
+ const directory = get(NekoSessionDirectory), management = get(NekoSessionManagement), models = get(NekoModels), workspaces = get(NekoWorkspaces), workspaceFiles = get(NekoWorkspaceFiles), credentials = get(NekoCredentials), searchSettings = get(NekoSearchSettings), contextSettings = get(NekoContextSettings), codemodeSettings = get(NekoCodemodeSettings), defaultModel = get(NekoDefaultModel), skillMarket = get(NekoSkillMarket);
9171
+ keep.push(directory.state.subscribe(() => {}), workspaces.state.subscribe(() => {}), credentials.state.subscribe(() => {}), searchSettings.state.subscribe(() => {}), contextSettings.state.subscribe(() => {}), codemodeSettings.state.subscribe(() => {}), defaultModel.state.subscribe(() => {}));
8707
9172
  return {
8708
9173
  directory,
8709
9174
  management,
@@ -8713,6 +9178,7 @@ function createGatewayConnection(link, serverId, options = {}) {
8713
9178
  credentials,
8714
9179
  searchSettings,
8715
9180
  contextSettings,
9181
+ codemodeSettings,
8716
9182
  defaultModel,
8717
9183
  skillMarket
8718
9184
  };
@@ -8740,6 +9206,7 @@ function createGatewayConnection(link, serverId, options = {}) {
8740
9206
  credentials,
8741
9207
  searchSettings,
8742
9208
  contextSettings,
9209
+ codemodeSettings,
8743
9210
  defaultModel,
8744
9211
  workspaceFiles,
8745
9212
  skillMarket
@@ -8752,6 +9219,7 @@ function createGatewayConnection(link, serverId, options = {}) {
8752
9219
  credentials.ready,
8753
9220
  searchSettings.ready,
8754
9221
  contextSettings.ready,
9222
+ codemodeSettings.ready,
8755
9223
  defaultModel.ready,
8756
9224
  workspaceFiles.ready,
8757
9225
  skillMarket.ready
@@ -8796,6 +9264,9 @@ function createGatewayConnection(link, serverId, options = {}) {
8796
9264
  get contextSettings() {
8797
9265
  return services().contextSettings;
8798
9266
  },
9267
+ get codemodeSettings() {
9268
+ return services().codemodeSettings;
9269
+ },
8799
9270
  get defaultModel() {
8800
9271
  return services().defaultModel;
8801
9272
  },
@@ -9710,9 +10181,10 @@ function buildHelloResultSchema() {
9710
10181
  })
9711
10182
  });
9712
10183
  }
9713
- var MOBILE_LIMITS, MOBILE_API_VERSIONS, MOBILE_CAPABILITIES, MOBILE_CLOSE_REVOKED = 4401, MOBILE_CLOSE_INCOMPATIBLE = 4402, MOBILE_CLOSE_SLOW_CONSUMER = 4408, MobileHelloParams, opt, str2, int, nullable2 = (schema) => Type10.Union([schema, Type10.Null()]), MobileContentRefSchema, MobileImageRefSchema, planStatus, questionStatus2, MobileToolInteractionSchema, MobileToolItemSchema, MobileChatItemSchema, MobileStreamingSchema, MobileUsageSchema, MobileSubagentRoundSchema, MobileAgentSchema, MobileQueuedSchema, MobileChatStatusSchema, MobileChatSnapshotSchema, MobileWorkspaceSchema, MobileSessionSummarySchema, MobileApprovalSchema, MobileTodoItemSchema, MobileTodoListSchema, MobileQuestionOptionSchema, MobileQuestionSummarySchema, MobileQuestionListSchema, MobileQuestionSchema, MobileQuestionResponseSchema, MobilePlanSchema, MobilePlanDecisionSchema, sessionListParams, sessionListResult, subscription, pageResult, pageParams, chunkResult, MOBILE_API_V1_METHODS, patchBody, MOBILE_API_V1_NOTIFICATIONS;
10184
+ var MOBILE_LIMITS, MOBILE_API_VERSIONS, MOBILE_CAPABILITIES, MOBILE_CLOSE_REVOKED = 4401, MOBILE_CLOSE_INCOMPATIBLE = 4402, MOBILE_CLOSE_SLOW_CONSUMER = 4408, MobileHelloParams, opt, str2, int, nullable2 = (schema) => Type10.Union([schema, Type10.Null()]), MobileContentRefSchema, MobileImageRefSchema, planStatus, questionStatus2, MobileToolInteractionSchema, MobileCodemodeSchema, MobileToolItemSchema, MobileChatItemSchema, MobileStreamingSchema, MobileUsageSchema, MobileSubagentRoundSchema, MobileAgentSchema, MobileQueuedSchema, MobileChatStatusSchema, MobileChatSnapshotSchema, MobileWorkspaceSchema, MobileSessionSummarySchema, MobileApprovalSchema, MobileTodoItemSchema, MobileTodoListSchema, MobileQuestionOptionSchema, MobileQuestionSummarySchema, MobileQuestionListSchema, MobileQuestionSchema, MobileQuestionResponseSchema, MobilePlanSchema, MobilePlanDecisionSchema, sessionListParams, sessionListResult, subscription, pageResult, pageParams, chunkResult, MOBILE_API_V1_METHODS, patchBody, MOBILE_API_V1_NOTIFICATIONS;
9714
10185
  var init_protocol = __esm(() => {
9715
10186
  init_application_version();
10187
+ init_codemode_mode();
9716
10188
  init_interactions();
9717
10189
  MOBILE_LIMITS = {
9718
10190
  maxFrameBytes: 262144,
@@ -9802,6 +10274,23 @@ var init_protocol = __esm(() => {
9802
10274
  note: str2
9803
10275
  })
9804
10276
  ]);
10277
+ MobileCodemodeSchema = Type10.Object({
10278
+ calls: Type10.Array(Type10.Object({
10279
+ id: int,
10280
+ name: str2,
10281
+ args: Type10.String({ maxLength: CODEMODE_VIEW_LIMITS.argsChars }),
10282
+ status: Type10.Union([
10283
+ Type10.Literal("running"),
10284
+ Type10.Literal("ok"),
10285
+ Type10.Literal("error"),
10286
+ Type10.Literal("cancelled")
10287
+ ]),
10288
+ durationMs: opt(Type10.Number()),
10289
+ error: opt(Type10.String({ maxLength: CODEMODE_VIEW_LIMITS.errorChars }))
10290
+ }), { maxItems: CODEMODE_VIEW_LIMITS.maxCalls }),
10291
+ omitted: int,
10292
+ fullOutputPath: opt(str2)
10293
+ });
9805
10294
  MobileToolItemSchema = Type10.Object({
9806
10295
  callId: str2,
9807
10296
  name: str2,
@@ -9816,7 +10305,8 @@ var init_protocol = __esm(() => {
9816
10305
  diffTruncated: opt(Type10.Boolean()),
9817
10306
  durationMs: opt(Type10.Number()),
9818
10307
  note: opt(str2),
9819
- interaction: opt(MobileToolInteractionSchema)
10308
+ interaction: opt(MobileToolInteractionSchema),
10309
+ codemode: opt(MobileCodemodeSchema)
9820
10310
  });
9821
10311
  MobileChatItemSchema = Type10.Object({
9822
10312
  key: str2,
@@ -10319,6 +10809,17 @@ function mapChatItem(raw, owner) {
10319
10809
  tool.note = raw.note;
10320
10810
  if (raw.interaction !== undefined)
10321
10811
  tool.interaction = raw.interaction;
10812
+ if (raw.codemode !== undefined) {
10813
+ tool.codemode = {
10814
+ calls: raw.codemode.calls.map(({ durationMs, error, ...call }) => ({
10815
+ ...call,
10816
+ ...durationMs === null ? {} : { durationMs },
10817
+ ...error === null ? {} : { error }
10818
+ })),
10819
+ omitted: raw.codemode.omitted,
10820
+ ...raw.codemode.fullOutputPath === null ? {} : { fullOutputPath: raw.codemode.fullOutputPath }
10821
+ };
10822
+ }
10322
10823
  item = { key: raw.key, type: "tool", tool, ...images(raw.images) };
10323
10824
  break;
10324
10825
  }
@@ -31009,12 +31510,13 @@ async function createNekoServerServices(options) {
31009
31510
  return [spec.id, spec.kind === "model" ? { ...status, enabled: enabled.has(spec.id) } : status];
31010
31511
  })));
31011
31512
  };
31012
- const [sessions, workspaces, statuses, search, contextSettings0, defaultModel0] = await Promise.all([
31513
+ const [sessions, workspaces, statuses, search, contextSettings0, codemodeSettings0, defaultModel0] = await Promise.all([
31013
31514
  list(),
31014
31515
  runtime.listWorkspaces(),
31015
31516
  readStatuses(),
31016
31517
  runtime.searchSettings(),
31017
31518
  runtime.contextSettings(),
31519
+ runtime.codemodeSettings(),
31018
31520
  runtime.defaultModel()
31019
31521
  ]);
31020
31522
  let mutationTail = Promise.resolve();
@@ -31032,6 +31534,10 @@ async function createNekoServerServices(options) {
31032
31534
  revision: 1,
31033
31535
  ...contextSettings0
31034
31536
  });
31537
+ const codemodeSettings = self2.state("codemodeSettings", CodemodeSettingsStateSchema, {
31538
+ revision: 1,
31539
+ ...codemodeSettings0
31540
+ });
31035
31541
  const defaultModel = self2.state("defaultModel", DefaultModelStateSchema, defaultModel0);
31036
31542
  const refreshContextSettings = async (_input, ctx) => {
31037
31543
  const next = await runtime.contextSettings();
@@ -31040,6 +31546,13 @@ async function createNekoServerServices(options) {
31040
31546
  contextSettings.replace(ctx, { revision: revision + 1, ...next });
31041
31547
  return null;
31042
31548
  };
31549
+ const refreshCodemodeSettings = async (_input, ctx) => {
31550
+ const next = await runtime.codemodeSettings();
31551
+ const { revision, ...current } = codemodeSettings.value;
31552
+ if (JSON.stringify(next) !== JSON.stringify(current))
31553
+ codemodeSettings.replace(ctx, { revision: revision + 1, ...next });
31554
+ return null;
31555
+ };
31043
31556
  const refreshDefaultModel = async (_input, ctx) => {
31044
31557
  const next = await runtime.defaultModel();
31045
31558
  if (JSON.stringify(next) !== JSON.stringify(defaultModel.value))
@@ -31075,6 +31588,7 @@ async function createNekoServerServices(options) {
31075
31588
  credentials,
31076
31589
  searchSettings,
31077
31590
  contextSettings,
31591
+ codemodeSettings,
31078
31592
  defaultModel,
31079
31593
  async refreshCredentials(_input, ctx) {
31080
31594
  credentials.replace(ctx, {
@@ -31085,6 +31599,7 @@ async function createNekoServerServices(options) {
31085
31599
  },
31086
31600
  refreshSearchSettings,
31087
31601
  refreshContextSettings,
31602
+ refreshCodemodeSettings,
31088
31603
  refreshDefaultModel
31089
31604
  };
31090
31605
  }, { lifetime: "host" });
@@ -31151,6 +31666,21 @@ async function createNekoServerServices(options) {
31151
31666
  })
31152
31667
  };
31153
31668
  });
31669
+ host.serve(NekoCodemodeSettings, (_input, _principal, self2) => {
31670
+ const hub = shared(self2);
31671
+ return {
31672
+ variant: "ready",
31673
+ state: hub.codemodeSettings,
31674
+ set: (input, ctx) => serialize(async () => {
31675
+ await runtime.setCodemodeMode(input.mode);
31676
+ return hub.refreshCodemodeSettings(null, ctx);
31677
+ }),
31678
+ reset: (_input, ctx) => serialize(async () => {
31679
+ await runtime.resetCodemodeMode();
31680
+ return hub.refreshCodemodeSettings(null, ctx);
31681
+ })
31682
+ };
31683
+ });
31154
31684
  host.serve(NekoDefaultModel, (_input, _principal, self2) => {
31155
31685
  const hub = shared(self2);
31156
31686
  return {
@@ -31260,10 +31790,12 @@ var init_server_services = __esm(() => {
31260
31790
  credentials: CredentialStateSchema,
31261
31791
  searchSettings: SearchSettingsStateSchema,
31262
31792
  contextSettings: ContextSettingsStateSchema,
31793
+ codemodeSettings: CodemodeSettingsStateSchema,
31263
31794
  defaultModel: DefaultModelStateSchema,
31264
31795
  refreshCredentials: method2(Type13.Null(), Type13.Null()),
31265
31796
  refreshSearchSettings: method2(Type13.Null(), Type13.Null()),
31266
31797
  refreshContextSettings: method2(Type13.Null(), Type13.Null()),
31798
+ refreshCodemodeSettings: method2(Type13.Null(), Type13.Null()),
31267
31799
  refreshDefaultModel: method2(Type13.Null(), Type13.Null())
31268
31800
  }
31269
31801
  });
@@ -31295,6 +31827,7 @@ async function startGateway(options = {}) {
31295
31827
  ...options.apiKey !== undefined ? { apiKey: options.apiKey } : {},
31296
31828
  ...searchProvider !== undefined ? { searchProvider } : {},
31297
31829
  ...fileConfig.context !== undefined ? { contextSettings: fileConfig.context } : {},
31830
+ ...fileConfig.codemode !== undefined ? { codemodeSettings: fileConfig.codemode } : {},
31298
31831
  ...fileConfig.defaultModel !== undefined ? { model: fileConfig.defaultModel } : {},
31299
31832
  ...options.model !== undefined ? { modelOverride: options.model } : {},
31300
31833
  thinkingLevel: options.thinkingLevel ?? fileConfig.thinkingLevel ?? "high",
@@ -31874,6 +32407,7 @@ var init_en = __esm(() => {
31874
32407
  "command.plan": "Toggle plan mode, or review the plan awaiting approval (Shift+Tab)",
31875
32408
  "command.search": "Choose the web search provider (Tavily/DeepSeek); K sets its key",
31876
32409
  "command.context": "Tune context compaction and tool result pruning",
32410
+ "command.codemode": "Choose the codemode mode (off/on/only) for new sessions",
31877
32411
  "command.theme": "Choose color theme (auto/light/dark)",
31878
32412
  "command.language": "Choose interface language (zh/en)",
31879
32413
  "command.think": "Set thinking level (/think high)",
@@ -31999,6 +32533,10 @@ var init_en = __esm(() => {
31999
32533
  "tool.failed": "Failed",
32000
32534
  "tool.argumentsHeader": "---- Arguments ----",
32001
32535
  "tool.moreLines": "... {count} more lines",
32536
+ "tool.callsHeader": "---- Tool calls ({count}) ----",
32537
+ "tool.callsMore": "{count} more",
32538
+ "tool.scriptHeader": "---- Script ----",
32539
+ "tool.fullOutput": "Full output: {path}",
32002
32540
  "tool.diffHeader": "---- Diff ----",
32003
32541
  "tool.diffTruncated": "\u2026 Diff preview truncated (line or byte limit)",
32004
32542
  "tool.diffMore": "\u2026 +{count} more lines",
@@ -32012,6 +32550,7 @@ var init_en = __esm(() => {
32012
32550
  "help.section.plan": "Plan mode",
32013
32551
  "help.section.search": "Web search",
32014
32552
  "help.section.context": "Context",
32553
+ "help.section.codemode": "Codemode",
32015
32554
  "help.section.language": "Language",
32016
32555
  "help.section.skills": "Skills",
32017
32556
  "help.section.credentials": "Credentials",
@@ -32091,6 +32630,9 @@ var init_en = __esm(() => {
32091
32630
  "help.context.tune": "Tune compaction and tool result pruning",
32092
32631
  "help.context.choose": "Choose a setting, then a value to save",
32093
32632
  "help.context.back": "Back",
32633
+ "help.codemode.choose": "Choose a mode; applies to sessions created afterwards",
32634
+ "help.codemode.apply": "Save the selected mode",
32635
+ "help.codemode.back": "Close the panel",
32094
32636
  "help.language.choose": "Choose the interface language (saved as the default)",
32095
32637
  "help.skills.user": "Install for user",
32096
32638
  "help.skills.project": "Install for project",
@@ -32328,6 +32870,18 @@ var init_en = __esm(() => {
32328
32870
  "context.setTo": "{label} set to {value}.",
32329
32871
  "context.title": "Context settings",
32330
32872
  "context.resetDone": "Context settings reset to defaults.",
32873
+ "codemode.title": "Codemode settings",
32874
+ "codemode.empty": "Codemode settings are unavailable.",
32875
+ "codemode.mode.off": "Off",
32876
+ "codemode.mode.offDescription": "The model calls tools one by one, as before.",
32877
+ "codemode.mode.on": "On",
32878
+ "codemode.mode.onDescription": "Adds codemode next to the usual tools; the model picks what fits.",
32879
+ "codemode.mode.only": "Only codemode",
32880
+ "codemode.mode.onlyDescription": "File and command tools are reachable only through scripts; todo, questions and sub-agents stay direct.",
32881
+ "codemode.reset": "Reset to default",
32882
+ "codemode.resetDescription": "Clear the saved mode. A config file value still applies.",
32883
+ "codemode.setTo": "Codemode set to {mode}. Applies to sessions created afterwards.",
32884
+ "codemode.resetDone": "Codemode reset to default. Applies to sessions created afterwards.",
32331
32885
  "subagent.queued": "Queued",
32332
32886
  "subagent.running": "Running",
32333
32887
  "subagent.done": "Done",
@@ -32495,6 +33049,7 @@ var init_zh = __esm(() => {
32495
33049
  "command.plan": "\u5207\u6362\u8BA1\u5212\u6A21\u5F0F\uFF0C\u6216\u67E5\u770B\u5F85\u6279\u51C6\u7684\u8BA1\u5212\uFF08Shift+Tab\uFF09",
32496
33050
  "command.search": "\u9009\u62E9\u8054\u7F51\u641C\u7D22\u670D\u52A1\uFF08Tavily/DeepSeek\uFF09\uFF1B\u6309 K \u8BBE\u7F6E\u5176 Key",
32497
33051
  "command.context": "\u8C03\u6574\u4E0A\u4E0B\u6587\u538B\u7F29\u4E0E\u5DE5\u5177\u7ED3\u679C\u88C1\u526A",
33052
+ "command.codemode": "\u9009\u62E9 codemode \u6A21\u5F0F\uFF08\u5173\u95ED/\u5F00\u542F/\u4EC5 codemode\uFF09\uFF0C\u5BF9\u65B0\u4F1A\u8BDD\u751F\u6548",
32498
33053
  "command.theme": "\u9009\u62E9\u914D\u8272\u4E3B\u9898\uFF08auto/light/dark\uFF09",
32499
33054
  "command.language": "\u9009\u62E9\u754C\u9762\u8BED\u8A00\uFF08zh/en\uFF09",
32500
33055
  "command.think": "\u8BBE\u7F6E\u601D\u8003\u5F3A\u5EA6\uFF08/think high\uFF09",
@@ -32620,6 +33175,10 @@ var init_zh = __esm(() => {
32620
33175
  "tool.failed": "\u5931\u8D25",
32621
33176
  "tool.argumentsHeader": "---- \u53C2\u6570 ----",
32622
33177
  "tool.moreLines": "... \u8FD8\u6709 {count} \u884C",
33178
+ "tool.callsHeader": "---- \u5DE5\u5177\u8C03\u7528\uFF08{count}\uFF09----",
33179
+ "tool.callsMore": "\u8FD8\u6709 {count} \u6761",
33180
+ "tool.scriptHeader": "---- \u811A\u672C ----",
33181
+ "tool.fullOutput": "\u5B8C\u6574\u8F93\u51FA\uFF1A{path}",
32623
33182
  "tool.diffHeader": "---- \u5DEE\u5F02 ----",
32624
33183
  "tool.diffTruncated": "\u2026 \u5DEE\u5F02\u9884\u89C8\u5DF2\u622A\u65AD\uFF08\u884C\u6570\u6216\u5B57\u8282\u6570\u4E0A\u9650\uFF09",
32625
33184
  "tool.diffMore": "\u2026 \u8FD8\u6709 {count} \u884C",
@@ -32633,6 +33192,7 @@ var init_zh = __esm(() => {
32633
33192
  "help.section.plan": "\u8BA1\u5212\u6A21\u5F0F",
32634
33193
  "help.section.search": "\u8054\u7F51\u641C\u7D22",
32635
33194
  "help.section.context": "\u4E0A\u4E0B\u6587",
33195
+ "help.section.codemode": "Codemode",
32636
33196
  "help.section.language": "\u8BED\u8A00",
32637
33197
  "help.section.skills": "\u6280\u80FD",
32638
33198
  "help.section.credentials": "\u51ED\u636E",
@@ -32712,6 +33272,9 @@ var init_zh = __esm(() => {
32712
33272
  "help.context.tune": "\u8C03\u6574\u538B\u7F29\u4E0E\u5DE5\u5177\u7ED3\u679C\u88C1\u526A",
32713
33273
  "help.context.choose": "\u9009\u62E9\u8BBE\u7F6E\u9879\uFF0C\u518D\u9009\u62E9\u8981\u4FDD\u5B58\u7684\u503C",
32714
33274
  "help.context.back": "\u8FD4\u56DE",
33275
+ "help.codemode.choose": "\u9009\u62E9\u6A21\u5F0F\uFF1B\u53EA\u5BF9\u4E4B\u540E\u65B0\u5EFA\u7684\u4F1A\u8BDD\u751F\u6548",
33276
+ "help.codemode.apply": "\u4FDD\u5B58\u6240\u9009\u6A21\u5F0F",
33277
+ "help.codemode.back": "\u5173\u95ED\u9762\u677F",
32715
33278
  "help.language.choose": "\u9009\u62E9\u754C\u9762\u8BED\u8A00\uFF08\u4FDD\u5B58\u4E3A\u9ED8\u8BA4\uFF09",
32716
33279
  "help.skills.user": "\u5B89\u88C5\u5230\u7528\u6237\u7EA7",
32717
33280
  "help.skills.project": "\u5B89\u88C5\u5230\u9879\u76EE\u7EA7",
@@ -32949,6 +33512,18 @@ var init_zh = __esm(() => {
32949
33512
  "context.setTo": "{label} \u5DF2\u8BBE\u4E3A {value}\u3002",
32950
33513
  "context.title": "\u4E0A\u4E0B\u6587\u8BBE\u7F6E",
32951
33514
  "context.resetDone": "\u4E0A\u4E0B\u6587\u8BBE\u7F6E\u5DF2\u6062\u590D\u9ED8\u8BA4\u3002",
33515
+ "codemode.title": "Codemode \u8BBE\u7F6E",
33516
+ "codemode.empty": "Codemode \u8BBE\u7F6E\u4E0D\u53EF\u7528\u3002",
33517
+ "codemode.mode.off": "\u5173\u95ED",
33518
+ "codemode.mode.offDescription": "\u6A21\u578B\u9010\u4E2A\u8C03\u7528\u5DE5\u5177\uFF0C\u4E0E\u4EE5\u524D\u4E00\u81F4\u3002",
33519
+ "codemode.mode.on": "\u5F00\u542F",
33520
+ "codemode.mode.onDescription": "\u5728\u539F\u6709\u5DE5\u5177\u4E4B\u5916\u589E\u52A0 codemode\uFF0C\u6A21\u578B\u6309\u9700\u9009\u7528\u3002",
33521
+ "codemode.mode.only": "\u4EC5 codemode",
33522
+ "codemode.mode.onlyDescription": "\u6587\u4EF6\u548C\u547D\u4EE4\u7C7B\u5DE5\u5177\u53EA\u80FD\u901A\u8FC7\u811A\u672C\u4F7F\u7528\uFF0Ctodo\u3001\u63D0\u95EE\u4E0E\u5B50 agent \u4ECD\u53EF\u76F4\u63A5\u8C03\u7528\u3002",
33523
+ "codemode.reset": "\u6062\u590D\u9ED8\u8BA4",
33524
+ "codemode.resetDescription": "\u6E05\u9664\u5DF2\u4FDD\u5B58\u7684\u6A21\u5F0F\u3002\u914D\u7F6E\u6587\u4EF6\u4E2D\u7684\u503C\u4ECD\u7136\u6709\u6548\u3002",
33525
+ "codemode.setTo": "Codemode \u5DF2\u8BBE\u4E3A{mode}\uFF0C\u5BF9\u4E4B\u540E\u65B0\u5EFA\u7684\u4F1A\u8BDD\u751F\u6548\u3002",
33526
+ "codemode.resetDone": "Codemode \u5DF2\u6062\u590D\u9ED8\u8BA4\uFF0C\u5BF9\u4E4B\u540E\u65B0\u5EFA\u7684\u4F1A\u8BDD\u751F\u6548\u3002",
32952
33527
  "subagent.queued": "\u6392\u961F\u4E2D",
32953
33528
  "subagent.running": "\u8FD0\u884C\u4E2D",
32954
33529
  "subagent.done": "\u5DF2\u5B8C\u6210",
@@ -33368,13 +33943,21 @@ function imageLabel(image) {
33368
33943
  const size = image.width > 0 && image.height > 0 ? ` \xB7 ${image.width}\xD7${image.height}` : "";
33369
33944
  return `${image.name} \xB7 ${image.mimeType}${size}`;
33370
33945
  }
33946
+ function codemodeCallLine(call) {
33947
+ return [
33948
+ `${call.name}${call.args === "" ? "" : ` ${oneLine(call.args)}`}`,
33949
+ call.status,
33950
+ call.durationMs === null ? "" : formatDuration(call.durationMs),
33951
+ call.error === null ? "" : oneLine(call.error)
33952
+ ].filter(Boolean).join(" \xB7 ");
33953
+ }
33371
33954
  function formatDuration(ms) {
33372
33955
  return ms < 1000 ? `${Math.round(ms)}ms` : `${(ms / 1000).toFixed(1)}s`;
33373
33956
  }
33374
33957
  function oneLine(text) {
33375
33958
  return text.replace(/\s+/g, " ").trim();
33376
33959
  }
33377
- var FALLBACK_EXPORT_TITLE = "neko-session";
33960
+ var FALLBACK_EXPORT_TITLE = "neko-session", codemodeOmittedLine = (omitted) => `\u2026 ${omitted} more calls not listed`;
33378
33961
 
33379
33962
  // packages/core/src/input-history.ts
33380
33963
  function promptHistory(items) {
@@ -44990,7 +45573,12 @@ async function exportHtml(source, options) {
44990
45573
  status === "ok" ? "" : `<span class="status">[${status}]</span>`,
44991
45574
  item.durationMs === null ? "" : `<span class="duration">(${formatDuration(item.durationMs)})</span>`
44992
45575
  ].filter(Boolean).join(" ");
44993
- body.push(`<details class="tool ${status}"><summary>${heading}</summary>${item.detail === null || item.detail === "" ? "" : `<pre>${escapeHtml(item.detail)}</pre>`}${output === "" ? "" : `<div class="label">${status === "running" ? "Output so far" : "Result"}</div><pre>${escapeHtml(output)}</pre>`}${item.note === null ? "" : `<div class="note">${escapeHtml(oneLine(item.note))}</div>`}${renderImages(item.images, embedded)}</details>`);
45576
+ const calls = item.codemode === undefined || item.codemode.calls.length === 0 ? "" : `<div class="label">Calls</div><pre>${escapeHtml([
45577
+ ...item.codemode.calls.map(codemodeCallLine),
45578
+ ...item.codemode.omitted > 0 ? [codemodeOmittedLine(item.codemode.omitted)] : []
45579
+ ].join(`
45580
+ `))}</pre>`;
45581
+ body.push(`<details class="tool ${status}"><summary>${heading}</summary>${item.detail === null || item.detail === "" ? "" : `<pre${item.codemode === undefined ? "" : ' class="language-javascript"'}>${escapeHtml(item.detail)}</pre>`}${calls}${output === "" ? "" : `<div class="label">${status === "running" ? "Output so far" : "Result"}</div><pre>${escapeHtml(output)}</pre>`}${item.note === null ? "" : `<div class="note">${escapeHtml(oneLine(item.note))}</div>`}${renderImages(item.images, embedded)}</details>`);
44994
45582
  }
44995
45583
  }
44996
45584
  const content = `<!doctype html>
@@ -45106,12 +45694,12 @@ async function exportConversation(format, source, options) {
45106
45694
  const content = format === "markdown" ? exportMarkdown(source, options) : exportJson(source, options);
45107
45695
  return { content, mimeType, extension, skippedImages: 0 };
45108
45696
  }
45109
- function block(content) {
45697
+ function block(content, language = "") {
45110
45698
  let longest = 0;
45111
45699
  for (const run of content.matchAll(/`+/g))
45112
45700
  longest = Math.max(longest, run[0].length);
45113
45701
  const marks = "`".repeat(Math.max(3, longest + 1));
45114
- return `${marks}
45702
+ return `${marks}${language}
45115
45703
  ${content}
45116
45704
  ${marks}`;
45117
45705
  }
@@ -45169,7 +45757,14 @@ function markdownTool(item) {
45169
45757
  ].filter(Boolean).join(" ");
45170
45758
  const lines = ["<details>", `<summary>${escapeInline(label)}</summary>`, ""];
45171
45759
  if (item.detail !== null && item.detail !== "")
45172
- lines.push(block(item.detail), "");
45760
+ lines.push(block(item.detail, item.codemode === undefined ? "" : "javascript"), "");
45761
+ if (item.codemode !== undefined && item.codemode.calls.length > 0) {
45762
+ const calls = item.codemode.calls.map(codemodeCallLine);
45763
+ if (item.codemode.omitted > 0)
45764
+ calls.push(codemodeOmittedLine(item.codemode.omitted));
45765
+ lines.push("Calls:", "", block(calls.join(`
45766
+ `)), "");
45767
+ }
45173
45768
  const output = toolOutput(item);
45174
45769
  if (output !== "")
45175
45770
  lines.push(status === "running" ? "Output so far:" : "Result:", "", block(output), "");
@@ -45212,6 +45807,7 @@ function exportJson(source, options) {
45212
45807
  status: item.phase,
45213
45808
  isError: item.isError,
45214
45809
  result: toolOutput(item),
45810
+ ...item.codemode === undefined ? {} : { codemode: item.codemode },
45215
45811
  durationMs: item.durationMs,
45216
45812
  note: item.note,
45217
45813
  images: (item.images ?? []).map(({ name, mimeType, width, height, bytes }) => ({
@@ -45348,6 +45944,56 @@ var init_clipboard_write = __esm(() => {
45348
45944
  init_i18n();
45349
45945
  });
45350
45946
 
45947
+ // packages/tui/src/codemode-ui.ts
45948
+ function codemodeItems(state) {
45949
+ if (!state)
45950
+ return [];
45951
+ const items = CODEMODE_MODES.map((mode) => ({
45952
+ value: mode,
45953
+ label: t(MODE_TEXT[mode].label),
45954
+ ...state.effective.mode === mode ? { rightLabel: `${t("common.currentSuffix").trim()} ${t(SOURCES[state.source])}` } : {},
45955
+ description: t(MODE_TEXT[mode].description)
45956
+ }));
45957
+ items.push({
45958
+ value: CODEMODE_RESET_VALUE,
45959
+ label: t("codemode.reset"),
45960
+ description: t("codemode.resetDescription")
45961
+ });
45962
+ return items;
45963
+ }
45964
+ function openCodemodePanel(panel, client, notify) {
45965
+ const items = () => codemodeItems(client.codemodeSettings.state.value);
45966
+ const stop = client.codemodeSettings.state.subscribe(() => panel.update(items()));
45967
+ panel.emptyText = codemodeEmpty();
45968
+ panel.show(t("codemode.title"), async () => items(), async (value) => {
45969
+ if (value === CODEMODE_RESET_VALUE) {
45970
+ await client.codemodeSettings.reset({});
45971
+ notify(t("codemode.resetDone"));
45972
+ return;
45973
+ }
45974
+ if (!isCodemodeMode(value))
45975
+ return;
45976
+ await client.codemodeSettings.set({ mode: value });
45977
+ notify(t("codemode.setTo", { mode: t(MODE_TEXT[value].label) }));
45978
+ });
45979
+ return stop;
45980
+ }
45981
+ var codemodeEmpty = () => t("codemode.empty"), CODEMODE_RESET_VALUE = "reset", MODE_TEXT, SOURCES;
45982
+ var init_codemode_ui = __esm(() => {
45983
+ init_codemode_mode();
45984
+ init_i18n();
45985
+ MODE_TEXT = {
45986
+ off: { label: "codemode.mode.off", description: "codemode.mode.offDescription" },
45987
+ on: { label: "codemode.mode.on", description: "codemode.mode.onDescription" },
45988
+ only: { label: "codemode.mode.only", description: "codemode.mode.onlyDescription" }
45989
+ };
45990
+ SOURCES = {
45991
+ default: "context.source.default",
45992
+ config: "context.source.config",
45993
+ saved: "context.source.saved"
45994
+ };
45995
+ });
45996
+
45351
45997
  // packages/tui/src/i18n/core-text.ts
45352
45998
  function localizeStatus(text) {
45353
45999
  for (const { pattern, show } of STATUS) {
@@ -45686,6 +46332,7 @@ var commands = () => [
45686
46332
  { name: "plan", description: t("command.plan") },
45687
46333
  { name: "search", description: t("command.search") },
45688
46334
  { name: "context", description: t("command.context") },
46335
+ { name: "codemode", description: t("command.codemode") },
45689
46336
  { name: "theme", description: t("command.theme") },
45690
46337
  { name: "language", description: t("command.language") },
45691
46338
  { name: "think", description: t("command.think") },
@@ -47266,6 +47913,12 @@ function answerSummary(interaction) {
47266
47913
  const parts = [...interaction.labels ?? [], ...interaction.text ? [interaction.text] : []];
47267
47914
  return plainLine(parts.join(", "), 80);
47268
47915
  }
47916
+ function codemodeCallLine2(call) {
47917
+ const glyph = call.status === "running" ? theme.accent(currentToolFrame()) : call.status === "ok" ? theme.ok("\u2713") : call.status === "error" ? theme.error("\u2717") : theme.dim("\u21B7");
47918
+ const duration = call.durationMs === null ? "" : theme.dim(` ${(call.durationMs / 1000).toFixed(1)}s`);
47919
+ const error = call.error === null ? "" : ` ${theme.error(plainLine(call.error, 120))}`;
47920
+ return ` ${glyph} ${plainLine(call.name, 40)} ${theme.dim(plainLine(call.args, 100))}${duration}${error}`;
47921
+ }
47269
47922
  function diffCountSuffix(state) {
47270
47923
  if (state.diffAdded === null)
47271
47924
  return "";
@@ -47278,7 +47931,7 @@ function clipLines(text, maxLines) {
47278
47931
  return { visible: lines.slice(0, maxLines).join(`
47279
47932
  `), rest: Math.max(0, lines.length - maxLines) };
47280
47933
  }
47281
- var SPINNER_FRAMES, spinnerFrame = 0, DIFF_MAX_LINES = 50, ToolExecutionComponent;
47934
+ var SPINNER_FRAMES, spinnerFrame = 0, CODEMODE_COLLAPSED_CALLS = 8, DIFF_MAX_LINES = 50, ToolExecutionComponent;
47282
47935
  var init_tool_execution = __esm(() => {
47283
47936
  init_core_text();
47284
47937
  init_i18n();
@@ -47388,19 +48041,33 @@ var init_tool_execution = __esm(() => {
47388
48041
  }
47389
48042
  rebuild() {
47390
48043
  const state = this.#state;
47391
- const plain = state.name === "ask_user" || state.name === "todo" || state.name === "submit_plan";
48044
+ const plain = state.name === "ask_user" || state.name === "todo" || state.name === "submit_plan" || state.codemode !== undefined;
47392
48045
  const parts = [
47393
48046
  `${theme.toolTitle(`\u26A1 ${state.name}`)}`,
47394
48047
  theme.dim((plain ? plainLine(state.summary) : state.summary) + diffCountSuffix(state)),
47395
48048
  this.#statusLine(state.phase)
47396
48049
  ];
47397
48050
  let text = parts.join(" ");
48051
+ const codemode = state.codemode;
48052
+ if (codemode !== undefined) {
48053
+ const shown = this.#expanded ? codemode.calls : codemode.calls.slice(0, CODEMODE_COLLAPSED_CALLS);
48054
+ if (this.#expanded && shown.length + codemode.omitted > 0)
48055
+ text += `
48056
+ ${theme.dim(t("tool.callsHeader", { count: shown.length + codemode.omitted }))}`;
48057
+ for (const call of shown)
48058
+ text += `
48059
+ ${codemodeCallLine2(call)}`;
48060
+ const rest = codemode.calls.length - shown.length + codemode.omitted;
48061
+ if (rest > 0)
48062
+ text += `
48063
+ ${theme.dim(t("tool.callsMore", { count: rest }))}`;
48064
+ }
47398
48065
  if (this.#expanded) {
47399
48066
  const detail = state.detail !== null && plain ? plainText(state.detail) : state.detail;
47400
48067
  if (detail !== null && !(state.diff !== null && state.name === "write" && !state.diffTruncated)) {
47401
48068
  const clipped = clipLines(detail, 20);
47402
48069
  text += `
47403
- ${theme.dim(t("tool.argumentsHeader"))}
48070
+ ${theme.dim(t(codemode === undefined ? "tool.argumentsHeader" : "tool.scriptHeader"))}
47404
48071
  ${clipped.visible}`;
47405
48072
  if (clipped.rest > 0)
47406
48073
  text += `
@@ -47431,6 +48098,10 @@ ${theme.dim(t("tool.diffMore", { count: clipped.rest }))}`;
47431
48098
  if (this.#expanded && body !== null && body !== "") {
47432
48099
  text += `
47433
48100
  ${theme.toolOutput(body)}`;
48101
+ }
48102
+ if (this.#expanded && codemode?.fullOutputPath != null) {
48103
+ text += `
48104
+ ${theme.dim(t("tool.fullOutput", { path: plainLine(codemode.fullOutputPath, 300) }))}`;
47434
48105
  }
47435
48106
  if (state.note !== null) {
47436
48107
  text += `
@@ -47584,7 +48255,7 @@ function contextItems(state) {
47584
48255
  items.push({
47585
48256
  value: row.key,
47586
48257
  label: t(row.label),
47587
- rightLabel: `${format(row.kind, value)} ${t(SOURCES[source])}${custom ? ` \xB7 ${t("context.custom")}` : ""}`,
48258
+ rightLabel: `${format(row.kind, value)} ${t(SOURCES2[source])}${custom ? ` \xB7 ${t("context.custom")}` : ""}`,
47588
48259
  description: t(row.description)
47589
48260
  });
47590
48261
  }
@@ -47634,7 +48305,7 @@ class PresetDetail {
47634
48305
  head.push(theme.error(t("common.error", { message: this.#error })));
47635
48306
  const options = this.#options.map((option, i) => {
47636
48307
  const custom = option !== null && !this.#row.presets.includes(option);
47637
- const text = `${format(this.#row.kind, option)}${custom ? ` (${t("context.custom")})` : ""}${option === null && current ? ` - ${t("context.now", { source: t(SOURCES[current.source]) })}` : ""}`;
48308
+ const text = `${format(this.#row.kind, option)}${custom ? ` (${t("context.custom")})` : ""}${option === null && current ? ` - ${t("context.now", { source: t(SOURCES2[current.source]) })}` : ""}`;
47638
48309
  return i === this.#index ? theme.selection(`\u276F ${text}`) : ` ${text}`;
47639
48310
  });
47640
48311
  const count = Math.max(1, height - head.length);
@@ -47684,7 +48355,7 @@ function openContextPanel(panel, client, notify) {
47684
48355
  });
47685
48356
  return stop;
47686
48357
  }
47687
- var contextEmpty = () => t("context.empty"), CONTEXT_ROWS, SOURCES, RESET_VALUE = "reset", message = (error) => error instanceof Error ? error.message : String(error);
48358
+ var contextEmpty = () => t("context.empty"), CONTEXT_ROWS, SOURCES2, RESET_VALUE = "reset", message = (error) => error instanceof Error ? error.message : String(error);
47688
48359
  var init_context_ui = __esm(() => {
47689
48360
  init_shortcut_hints();
47690
48361
  init_i18n();
@@ -47763,7 +48434,7 @@ var init_context_ui = __esm(() => {
47763
48434
  presets: [2, 5, 10, 15]
47764
48435
  }
47765
48436
  ];
47766
- SOURCES = {
48437
+ SOURCES2 = {
47767
48438
  default: "context.source.default",
47768
48439
  config: "context.source.config",
47769
48440
  saved: "context.source.saved"
@@ -48872,6 +49543,11 @@ function helpLines(platform = process.platform, wsl = runningInWsl()) {
48872
49543
  ["Enter", t("help.context.choose")],
48873
49544
  ["Esc", t("help.context.back")]
48874
49545
  ]);
49546
+ section(t("help.section.codemode"), [
49547
+ ["/codemode", t("help.codemode.choose")],
49548
+ ["Enter", t("help.codemode.apply")],
49549
+ ["Esc", t("help.codemode.back")]
49550
+ ]);
48875
49551
  section(t("help.section.language"), [["/language", t("help.language.choose")]]);
48876
49552
  section(t("help.section.skills"), [
48877
49553
  ["U", t("help.skills.user")],
@@ -50890,6 +51566,7 @@ class NekoTuiApp {
50890
51566
  #queueRow = new QueueRow;
50891
51567
  #searchStop;
50892
51568
  #contextStop;
51569
+ #codemodeStop;
50893
51570
  #modelStop;
50894
51571
  #agentDetails = new Map;
50895
51572
  #sessionId = "";
@@ -50962,6 +51639,8 @@ class NekoTuiApp {
50962
51639
  this.#searchStop = undefined;
50963
51640
  this.#contextStop?.();
50964
51641
  this.#contextStop = undefined;
51642
+ this.#codemodeStop?.();
51643
+ this.#codemodeStop = undefined;
50965
51644
  this.#modelStop?.();
50966
51645
  this.#modelStop = undefined;
50967
51646
  clearInterval(this.#sessionPanelTimer);
@@ -51213,6 +51892,10 @@ class NekoTuiApp {
51213
51892
  this.#showContext();
51214
51893
  return;
51215
51894
  }
51895
+ if (trimmed === "/codemode") {
51896
+ this.#showCodemode();
51897
+ return;
51898
+ }
51216
51899
  if (trimmed === "/language") {
51217
51900
  this.openLanguageOverlay();
51218
51901
  return;
@@ -51863,6 +52546,12 @@ class NekoTuiApp {
51863
52546
  this.#contextStop = openContextPanel(this.#panel, this.#gateway.client, (message) => this.addNotice(message));
51864
52547
  this.#ui.setFocus(this.#panel);
51865
52548
  }
52549
+ #showCodemode() {
52550
+ if (this.#exitStarted || this.#panel.open || this.#login.open)
52551
+ return;
52552
+ this.#codemodeStop = openCodemodePanel(this.#panel, this.#gateway.client, (message) => this.addNotice(message));
52553
+ this.#ui.setFocus(this.#panel);
52554
+ }
51866
52555
  #agentDetail(target) {
51867
52556
  const round = typeof target === "number" ? this.#lastView?.subagents?.find((r) => r.taskId === target) : undefined;
51868
52557
  const id = typeof target === "string" ? target : round?.agentId ?? `agent-${round?.childId}`;
@@ -52218,7 +52907,12 @@ function assistantContent(item) {
52218
52907
  }
52219
52908
  function mergeStream(item, streamCall) {
52220
52909
  if (streamCall !== undefined && streamCall.phase !== "done") {
52221
- return { ...item, phase: streamCall.phase, output: streamCall.output };
52910
+ return {
52911
+ ...item,
52912
+ phase: streamCall.phase,
52913
+ output: streamCall.output,
52914
+ codemode: streamCall.codemode ?? item.codemode
52915
+ };
52222
52916
  }
52223
52917
  return item;
52224
52918
  }
@@ -52236,7 +52930,8 @@ function streamingCardState(call) {
52236
52930
  diff: call.diff,
52237
52931
  diffAdded: call.diffAdded,
52238
52932
  diffRemoved: call.diffRemoved,
52239
- diffTruncated: call.diffTruncated
52933
+ diffTruncated: call.diffTruncated,
52934
+ codemode: call.codemode
52240
52935
  };
52241
52936
  }
52242
52937
  function rawMessageOf(error) {
@@ -52299,6 +52994,7 @@ var init_tui = __esm(() => {
52299
52994
  init_client_session();
52300
52995
  init_approval_dock();
52301
52996
  init_clipboard_write();
52997
+ init_codemode_ui();
52302
52998
  init_compaction_status();
52303
52999
  init_assistant_message();
52304
53000
  init_editor();