neko-harness 0.4.7 → 0.4.9

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/gateway.js CHANGED
@@ -16598,7 +16598,7 @@ function parseGatewayArgs(argv) {
16598
16598
  }
16599
16599
 
16600
16600
  // packages/gateway/src/gateway.ts
16601
- import { randomUUID as randomUUID6 } from "crypto";
16601
+ import { randomUUID as randomUUID7 } from "crypto";
16602
16602
  import { mkdir as mkdir6, readFile as readFile7, writeFile as writeFile5 } from "fs/promises";
16603
16603
  import { basename as basename4, dirname as dirname9, join as join13 } from "path";
16604
16604
  import { createHost as createHost2 } from "@earendil-works/chord";
@@ -17173,7 +17173,7 @@ function queuedUserInput(raw) {
17173
17173
  if (item.entry?.kind !== NekoInput.kind)
17174
17174
  return;
17175
17175
  const data = item.entry.data;
17176
- if (!data || data.jobReport || data.subagentRoundId)
17176
+ if (!data || data.jobReport || data.planDecision || data.subagentRoundId)
17177
17177
  return;
17178
17178
  const defaultPrompt = Array.isArray(item.content) ? savedDefaultImagePrompt(item.content, data.defaultImagePrompt) : undefined;
17179
17179
  const content = defaultPrompt && Array.isArray(item.content) ? item.content.slice(1) : item.content;
@@ -17259,6 +17259,11 @@ function toolInteraction(name, details) {
17259
17259
  const { action, revision, completed, total } = details;
17260
17260
  return (action === "list" || action === "replace") && typeof revision === "number" && typeof completed === "number" && typeof total === "number" ? { kind: "todo", action, revision, completed, total } : undefined;
17261
17261
  }
17262
+ if (name === "submit_plan") {
17263
+ const { version, title, note } = details;
17264
+ const steps = strings(details.steps);
17265
+ return typeof version === "string" && typeof title === "string" && typeof note === "string" && steps ? { kind: "submit_plan", version, title, steps, note } : undefined;
17266
+ }
17262
17267
  if (name !== "ask_user")
17263
17268
  return;
17264
17269
  const { id, question, multiple, options, status } = details;
@@ -17304,6 +17309,14 @@ function summarizeToolCall(name, args) {
17304
17309
  return {
17305
17310
  args: truncateMiddle(question.replace(/\s+/g, " "), 100),
17306
17311
  detail: [question, ...lines.length ? ["", ...lines] : []].join(`
17312
+ `)
17313
+ };
17314
+ }
17315
+ case "submit_plan": {
17316
+ const steps = strings(record.steps) ?? [];
17317
+ return {
17318
+ args: truncateMiddle(String(record.title ?? "").replace(/\s+/g, " "), 100),
17319
+ detail: steps.map((step, index) => `${index + 1}. ${step}`).join(`
17307
17320
  `)
17308
17321
  };
17309
17322
  }
@@ -17484,6 +17497,17 @@ function projectChatView(view, modelLabel, options = {}) {
17484
17497
  for (const entry of view.entries) {
17485
17498
  const message = entry.model?.[0];
17486
17499
  const jobReport = entry.kind === NekoInput.kind ? entry.data?.jobReport : undefined;
17500
+ const planDecision = entry.kind === NekoInput.kind ? entry.data?.planDecision : undefined;
17501
+ if (message?.role === "user" && planDecision) {
17502
+ items.push({
17503
+ type: "system",
17504
+ key: `e${entry.id}`,
17505
+ id: entry.id,
17506
+ text: planDecision.kind === "approve" ? `Plan approved: ${planDecision.title}` : `Changes requested for plan "${planDecision.title}": ${planDecision.feedback ?? ""}`,
17507
+ planDecision: { version: planDecision.version, kind: planDecision.kind }
17508
+ });
17509
+ continue;
17510
+ }
17487
17511
  if (message?.role === "user" && jobReport) {
17488
17512
  items.push({
17489
17513
  type: "system",
@@ -17687,7 +17711,7 @@ function contextTokens(entries) {
17687
17711
  import {
17688
17712
  AssistantEntry,
17689
17713
  configure,
17690
- defineDoc,
17714
+ defineDoc as defineDoc2,
17691
17715
  defineExtension,
17692
17716
  defineTask,
17693
17717
  defineTool,
@@ -17697,6 +17721,19 @@ import {
17697
17721
  import { Type } from "typebox";
17698
17722
  import { Value } from "typebox/value";
17699
17723
 
17724
+ // packages/core/src/plan-doc.ts
17725
+ import { defineDoc } from "@earendil-works/pi-durable";
17726
+ var PLAN_HISTORY = 5;
17727
+ var PlanDoc = defineDoc({
17728
+ kind: "app.neko.plan",
17729
+ version: 1,
17730
+ scope: "conversation",
17731
+ history: "rewindable",
17732
+ fork: "asOf",
17733
+ initial: () => ({ mode: "normal", revision: 0, items: [] })
17734
+ });
17735
+ var PLAN_CODER_BLOCKED = "Plan mode is read-only: the coder subagent is unavailable. Use explore or plan subagents, or finish the plan first.";
17736
+
17700
17737
  // packages/core/src/subagent-usage.ts
17701
17738
  var emptySubagentUsage = () => ({ models: {}, tools: {} });
17702
17739
  function subtractSubagentUsage(end, start) {
@@ -17741,7 +17778,7 @@ function subagentUsageTotals(state) {
17741
17778
 
17742
17779
  // packages/core/src/subagents.ts
17743
17780
  var agentIdFor = (childId) => `agent-${childId}`;
17744
- var DelegationsDoc = defineDoc({
17781
+ var DelegationsDoc = defineDoc2({
17745
17782
  kind: "app.neko.delegations",
17746
17783
  version: 1,
17747
17784
  scope: "conversation",
@@ -17749,7 +17786,7 @@ var DelegationsDoc = defineDoc({
17749
17786
  fork: "initial",
17750
17787
  initial: () => ({ items: [] })
17751
17788
  });
17752
- var SubagentsDoc = defineDoc({
17789
+ var SubagentsDoc = defineDoc2({
17753
17790
  kind: "app.neko.subagents",
17754
17791
  version: 1,
17755
17792
  scope: "conversation",
@@ -17757,7 +17794,7 @@ var SubagentsDoc = defineDoc({
17757
17794
  fork: "initial",
17758
17795
  initial: () => ({ items: [] })
17759
17796
  });
17760
- var SubagentDoc = defineDoc({
17797
+ var SubagentDoc = defineDoc2({
17761
17798
  kind: "app.neko.subagent",
17762
17799
  version: 1,
17763
17800
  scope: "conversation",
@@ -17765,7 +17802,7 @@ var SubagentDoc = defineDoc({
17765
17802
  fork: "initial",
17766
17803
  initial: () => ({ role: null, parentId: null })
17767
17804
  });
17768
- var SlotsDoc = defineDoc({
17805
+ var SlotsDoc = defineDoc2({
17769
17806
  kind: "app.neko.subagent-slots",
17770
17807
  version: 1,
17771
17808
  scope: "session",
@@ -17777,8 +17814,8 @@ var ROLE_PROMPTS = {
17777
17814
  coder: "You are neko's coding subagent. Implement only the assigned scope and verify it. Coordinate through the main agent; do not overwrite unrelated changes. Report changed files and verification results."
17778
17815
  };
17779
17816
  var SUBAGENT_DELIVERY = " Work from the assigned task, your own prior conversation context and project resources; you cannot see the main conversation. Do not delegate or ask the user directly. Your final message must be a self-contained delivery to the main agent, including evidence, results and limitations. Skills never expand your allowed tools.";
17780
- var MAIN_ONLY_TOOLS = new Set(["todo", "ask_user"]);
17781
- var MAIN_ONLY_EXTENSIONS = new Set(["neko-todos", "neko-questions"]);
17817
+ var MAIN_ONLY_TOOLS = new Set(["todo", "ask_user", "submit_plan"]);
17818
+ var MAIN_ONLY_EXTENSIONS = new Set(["neko-todos", "neko-questions", "neko-plan"]);
17782
17819
  var READ_TOOLS = new Set(["read", "find_files", "search_text", "web_search", "web_extract"]);
17783
17820
  async function refreshDelegations(tx, id) {
17784
17821
  const doc = await tx.doc(DelegationsDoc, id);
@@ -18078,6 +18115,9 @@ function createSubagentExtension(prepare, discard, guard) {
18078
18115
  return receipt;
18079
18116
  const identities = await tx.doc(SubagentsDoc, api.conversationId);
18080
18117
  let identity;
18118
+ const planning = !receipt && (await tx.doc(PlanDoc, api.conversationId)).mode === "plan";
18119
+ if (planning && args.action !== "continue" && args.type === "coder")
18120
+ throw new Error(PLAN_CODER_BLOCKED);
18081
18121
  if (args.action === "continue") {
18082
18122
  identity = identities.items.find((a) => a.agentId === args.agentId);
18083
18123
  if (!identity)
@@ -18085,6 +18125,8 @@ function createSubagentExtension(prepare, discard, guard) {
18085
18125
  const info = await tx.doc(SubagentDoc, identity.childId);
18086
18126
  if (info.parentId !== api.conversationId || info.role !== identity.role)
18087
18127
  throw new Error("Subagent ownership mismatch.");
18128
+ if (planning && identity.role === "coder")
18129
+ throw new Error(PLAN_CODER_BLOCKED);
18088
18130
  if (identity.activeRoundId !== null)
18089
18131
  throw new Error("Subagent is busy; continue only when idle.");
18090
18132
  } else if (receipt) {
@@ -18646,6 +18688,13 @@ var QUESTION_LIMITS = {
18646
18688
  previewChars: 256,
18647
18689
  recordBytes: 65536
18648
18690
  };
18691
+ var PLAN_LIMITS = {
18692
+ titleChars: 120,
18693
+ maxSteps: 30,
18694
+ stepChars: 500,
18695
+ noteChars: 4000,
18696
+ feedbackChars: 2000
18697
+ };
18649
18698
  var TODO_STATUSES = ["pending", "in_progress", "completed"];
18650
18699
 
18651
18700
  class InteractionInputError extends Error {
@@ -18830,8 +18879,44 @@ var todoProgress = (items) => ({
18830
18879
  completed: items.filter((i) => i.status === "completed").length,
18831
18880
  total: items.length
18832
18881
  });
18882
+ function validatePlanArgs(input) {
18883
+ if (!isRecord2(input))
18884
+ return fail("Plan arguments must be an object.");
18885
+ exactKeys(input, ["title", "steps", "note"], "Plan");
18886
+ const title = text(input.title, "title", PLAN_LIMITS.titleChars);
18887
+ if (!Array.isArray(input.steps))
18888
+ return fail("steps must be an array.");
18889
+ if (input.steps.length < 1)
18890
+ fail("A plan needs at least one step.");
18891
+ if (input.steps.length > PLAN_LIMITS.maxSteps)
18892
+ fail(`Too many steps (limit ${PLAN_LIMITS.maxSteps}).`);
18893
+ const steps = input.steps.map((step, index) => text(step, `steps[${index}]`, PLAN_LIMITS.stepChars));
18894
+ let note = "";
18895
+ if (input.note !== undefined) {
18896
+ if (typeof input.note !== "string")
18897
+ fail("note must be a string.");
18898
+ const value = input.note;
18899
+ if (value.length > PLAN_LIMITS.noteChars)
18900
+ fail(`note is too long (limit ${PLAN_LIMITS.noteChars} characters).`);
18901
+ if (!blank(value))
18902
+ note = value;
18903
+ }
18904
+ return { title, steps, note };
18905
+ }
18906
+ function validatePlanDecision(input) {
18907
+ if (!isRecord2(input))
18908
+ return fail("Decision must be an object.");
18909
+ if (input.kind === "approve") {
18910
+ exactKeys(input, ["kind"], "Decision");
18911
+ return { kind: "approve" };
18912
+ }
18913
+ if (input.kind !== "revise")
18914
+ return fail('Decision kind must be "approve" or "revise".');
18915
+ exactKeys(input, ["kind", "feedback"], "Decision");
18916
+ return { kind: "revise", feedback: text(input.feedback, "feedback", PLAN_LIMITS.feedbackChars) };
18917
+ }
18833
18918
  // packages/core/src/jobs.ts
18834
- import { defineDoc as defineDoc2 } from "@earendil-works/pi-durable";
18919
+ import { defineDoc as defineDoc3 } from "@earendil-works/pi-durable";
18835
18920
  var JOB_LIMITS = {
18836
18921
  maxRunning: 4,
18837
18922
  keepTerminal: 20,
@@ -18845,7 +18930,7 @@ var JOB_LIMITS = {
18845
18930
  commandBytes: 32 * 1024,
18846
18931
  maxTimeoutSeconds: 7 * 24 * 3600
18847
18932
  };
18848
- var JobsDoc = defineDoc2({
18933
+ var JobsDoc = defineDoc3({
18849
18934
  kind: "app.neko.jobs",
18850
18935
  version: 1,
18851
18936
  scope: "conversation",
@@ -18984,9 +19069,9 @@ import { builtinProviders } from "@earendil-works/pi-ai/providers/all";
18984
19069
  import {
18985
19070
  AssistantEntry as AssistantEntry2,
18986
19071
  createRegistry,
18987
- defineDoc as defineDoc7,
19072
+ defineDoc as defineDoc8,
18988
19073
  Harness,
18989
- InboxDoc as InboxDoc2,
19074
+ InboxDoc as InboxDoc3,
18990
19075
  LiveDoc as importLiveDoc,
18991
19076
  MemoryStorage,
18992
19077
  UsageDoc as UsageDoc2,
@@ -19520,7 +19605,9 @@ class JobManager {
19520
19605
  #reporter = createReporter();
19521
19606
  #runner = this.#createRunner();
19522
19607
  extension;
19523
- constructor() {
19608
+ #startWindowMs;
19609
+ constructor(options = {}) {
19610
+ this.#startWindowMs = options.startWindowMs ?? JOB_LIMITS.startWindowMs;
19524
19611
  this.extension = this.#createExtension();
19525
19612
  }
19526
19613
  #closing = false;
@@ -19710,7 +19797,7 @@ class JobManager {
19710
19797
  throw error;
19711
19798
  }
19712
19799
  const live = entry;
19713
- await Promise.race([live.exited, delay(JOB_LIMITS.startWindowMs, context.abortSignal)]);
19800
+ await Promise.race([live.exited, delay(this.#startWindowMs, context.abortSignal)]);
19714
19801
  live.windowOpen = false;
19715
19802
  if (live.suppressReport)
19716
19803
  await live.exited;
@@ -19785,499 +19872,238 @@ ${text}` : `No new output (next offset ${offset}).`);
19785
19872
  }
19786
19873
  }
19787
19874
 
19788
- // packages/core/src/prompt/invoke.ts
19789
- import { dirname as dirname4 } from "path";
19790
-
19791
- // packages/core/src/prompt/diagnostics.ts
19792
- var ignorePromptDiagnostic = () => {};
19793
- var warnPromptDiagnostic = ({ path, message }) => {
19794
- console.warn(`[neko-prompt] ${JSON.stringify(path)}: ${message}`);
19795
- };
19796
- var isMissing = (error) => typeof error === "object" && error !== null && ("code" in error) && (error.code === "ENOENT" || error.code === "ENOTDIR");
19875
+ // packages/core/src/plan.ts
19876
+ import { randomUUID as randomUUID2 } from "crypto";
19877
+ import {
19878
+ defineExtension as defineExtension6,
19879
+ defineTool as defineTool5,
19880
+ hook as hook2,
19881
+ InboxDoc,
19882
+ ToolTask as ToolTask2,
19883
+ wrapTool
19884
+ } from "@earendil-works/pi-durable";
19885
+ import { createBashTool } from "@earendil-works/pi-durable/tools";
19886
+ import { Type as Type5 } from "typebox";
19797
19887
 
19798
- // packages/core/src/prompt/skills.ts
19799
- import { readdir, readFile as readFile2 } from "fs/promises";
19800
- import { basename, dirname as dirname3, join as join3 } from "path";
19801
- var MAX_NAME_LENGTH = 64;
19802
- var MAX_DESCRIPTION_LENGTH = 1024;
19803
- var parseSkillFile = (content, filePath, report = ignorePromptDiagnostic) => {
19804
- const skip = (message) => {
19805
- report({ path: filePath, message });
19806
- return null;
19807
- };
19808
- try {
19809
- const { frontmatter } = parseFrontmatter(content);
19810
- const description = frontmatter.description;
19811
- if (typeof description !== "string" || description.trim() === "")
19812
- return skip("Skill description is missing or empty; skipped.");
19813
- if (description.length > MAX_DESCRIPTION_LENGTH)
19814
- return skip("Skill description is too long; skipped.");
19815
- const name = typeof frontmatter.name === "string" && frontmatter.name.trim() !== "" ? frontmatter.name : basename(dirname3(filePath));
19816
- if (name.length > MAX_NAME_LENGTH || /[\s\p{Cc}]/u.test(name))
19817
- return skip("Skill name is too long or contains whitespace/control characters; skipped.");
19818
- return {
19819
- name,
19820
- description,
19821
- filePath,
19822
- disableModelInvocation: frontmatter["disable-model-invocation"] === true
19823
- };
19824
- } catch {
19825
- return skip("Invalid skill frontmatter; skipped.");
19826
- }
19827
- };
19828
- var scanSkillsDir = async (dir, report = ignorePromptDiagnostic, capture) => {
19829
- let entries;
19830
- try {
19831
- entries = await readdir(dir, { withFileTypes: true });
19832
- } catch (error) {
19833
- if (!isMissing(error))
19834
- report({ path: dir, message: "Cannot scan skills directory; skipped." });
19835
- return [];
19836
- }
19837
- const skills = [];
19838
- for (const entry of entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
19839
- if (!entry.isDirectory())
19888
+ // packages/core/src/plan-bash.ts
19889
+ var SAFE_PATTERNS = [
19890
+ /^\s*(cat|head|tail|nl|tac|rev|cut|tr|paste|column|fold|comm|wc|sort|uniq|diff|file|stat|du|df|tree)\b/,
19891
+ /^\s*(grep|egrep|fgrep|rg|fd|find|jq|bat|eza|ls|pwd|echo|printf|true|cd)\b/,
19892
+ /^\s*(basename|dirname|realpath|readlink|which|whereis|type|sha256sum|shasum|md5sum)\b/,
19893
+ /^\s*(uname|whoami|id|date|cal|uptime|ps|free)\b/,
19894
+ /^\s*env\s*$/,
19895
+ /^\s*printenv\b/,
19896
+ /^\s*sed\s+-n\b/,
19897
+ /^\s*awk\b/,
19898
+ /^\s*curl\b/,
19899
+ /^\s*wget\s+-O\s*-/,
19900
+ /^\s*git\s+(status|log|diff|show|blame|rev-parse|describe|shortlog|grep|cat-file|merge-base|name-rev|reflog|show-ref|for-each-ref|ls-[\w-]+)\b/,
19901
+ /^\s*git\s+config\s+--get\b/,
19902
+ /^\s*git\s+stash\s+list\b/,
19903
+ /^\s*git\s+branch(\s+(-a|-r|-v|-vv|-l|--all|--remotes|--list|--show-current))*\s*$/,
19904
+ /^\s*git\s+remote(\s+(-v|show\b.*|get-url\b.*))?\s*$/,
19905
+ /^\s*npm\s+(list|ls|view|info|search|outdated|audit)\b/,
19906
+ /^\s*(bun|pnpm)\s+(pm\s+ls|list|ls|why|outdated|info|view)\b/,
19907
+ /^\s*yarn\s+(list|info|why|audit)\b/,
19908
+ /^\s*(node|python3?|bun|deno)\s+(-v|--version)\s*$/
19909
+ ];
19910
+ var DANGEROUS_FLAGS = [
19911
+ /--output\b/,
19912
+ /\bfind\b[^|;&]*\s-(delete|exec|execdir|ok|okdir|fprint0?|fprintf|fls)\b/,
19913
+ /\bsed\b[^|;&]*(\s-[A-Za-z]*i|--in-place)/,
19914
+ /\bsort\b[^|;&]*\s-[A-Za-z]*o/,
19915
+ /\btree\b[^|;&]*\s-o\b/,
19916
+ /\brg\b[^|;&]*--pre\b/,
19917
+ /\bfd\b[^|;&]*(\s-[xX]\b|--exec)/,
19918
+ /\bcurl\b[^|;&]*\s(-[A-Za-z]*[oOdXTF]\b|--(output|remote-name|data[\w-]*|request|upload-file|form|json)\b)/,
19919
+ /\bnpm\s+audit\s+fix\b/
19920
+ ];
19921
+ var DANGEROUS_CONTENT = [
19922
+ /\bawk\b.*(system\s*\(|\bgetline\b|\|\s*"|\bprintf?\b[^}]*>)/,
19923
+ /\bsed\b.*['"\s;{0-9$/,][wWe]\s+\S/
19924
+ ];
19925
+ function segments(command) {
19926
+ const result = [];
19927
+ let raw = "";
19928
+ let bare = "";
19929
+ let quote = null;
19930
+ const flush = () => {
19931
+ if (raw.trim() !== "")
19932
+ result.push({ raw, bare });
19933
+ raw = "";
19934
+ bare = "";
19935
+ };
19936
+ for (let i = 0;i < command.length; i++) {
19937
+ const char = command[i];
19938
+ if (quote) {
19939
+ raw += char;
19940
+ if (char === "\\" && quote === '"' && i + 1 < command.length)
19941
+ raw += command[++i];
19942
+ else if (char === quote) {
19943
+ quote = null;
19944
+ bare += char;
19945
+ }
19840
19946
  continue;
19841
- const filePath = join3(dir, entry.name, "SKILL.md");
19842
- let content;
19843
- try {
19844
- content = await readFile2(filePath, "utf8");
19845
- } catch (error) {
19846
- if (!isMissing(error))
19847
- report({ path: filePath, message: "Cannot read skill; skipped." });
19947
+ }
19948
+ if (char === "\\" && i + 1 < command.length) {
19949
+ raw += char + command[++i];
19950
+ bare += " ";
19848
19951
  continue;
19849
19952
  }
19850
- const skill = parseSkillFile(content, filePath, report);
19851
- if (skill) {
19852
- skills.push(skill);
19853
- capture?.(skill, content);
19953
+ if (char === "'" || char === '"') {
19954
+ quote = char;
19955
+ raw += char;
19956
+ bare += char;
19957
+ continue;
19854
19958
  }
19959
+ const two = command.slice(i, i + 2);
19960
+ if (two === "&&" || two === "||") {
19961
+ flush();
19962
+ i++;
19963
+ continue;
19964
+ }
19965
+ if (char === ";" || char === "|" || char === `
19966
+ `) {
19967
+ flush();
19968
+ continue;
19969
+ }
19970
+ raw += char;
19971
+ bare += char;
19855
19972
  }
19856
- return skills;
19857
- };
19858
- var mergeSkills = (groups) => {
19859
- const byName = new Map;
19860
- for (const group of groups)
19861
- for (const skill of group)
19862
- byName.set(skill.name, skill);
19863
- return [...byName.values()];
19864
- };
19865
- var escapeXml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
19866
- var formatSkillsForPrompt = (skills) => {
19867
- const visible = skills.filter((skill) => !skill.disableModelInvocation);
19868
- if (visible.length === 0)
19869
- return "";
19870
- const lines = [
19871
- "The following skills provide specialized instructions for specific tasks.",
19872
- "Use the read tool to load a skill's file when the task matches its description.",
19873
- "When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md) and use that absolute path in tool commands.",
19874
- "",
19875
- "<available_skills>"
19876
- ];
19877
- for (const skill of visible) {
19878
- lines.push(" <skill>");
19879
- lines.push(` <name>${escapeXml(skill.name)}</name>`);
19880
- lines.push(` <description>${escapeXml(skill.description)}</description>`);
19881
- lines.push(` <location>${escapeXml(skill.filePath)}</location>`);
19882
- lines.push(" </skill>");
19883
- }
19884
- lines.push("</available_skills>");
19885
- return lines.join(`
19886
- `);
19887
- };
19888
-
19889
- // packages/core/src/prompt/invoke.ts
19890
- var expandSkillCommand = async (text, skills, readFile, report = warnPromptDiagnostic, metadata) => {
19891
- if (!text.startsWith("/skill:"))
19892
- return text;
19893
- const spaceIndex = text.indexOf(" ");
19894
- const skillName = spaceIndex === -1 ? text.slice(7) : text.slice(7, spaceIndex);
19895
- const args = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1).trim();
19896
- const skill = skills.find((s) => s.name === skillName);
19897
- if (!skill)
19898
- return text;
19899
- let body;
19900
- try {
19901
- body = stripFrontmatter(await readFile(skill.filePath)).trim();
19902
- } catch {
19903
- report({ path: skill.filePath, message: "Cannot expand known skill; original input preserved." });
19904
- return text;
19905
- }
19906
- metadata?.({
19907
- name: skill.name,
19908
- location: skill.filePath,
19909
- body: `References are relative to ${dirname4(skill.filePath)}.
19910
-
19911
- ${body}`,
19912
- args
19973
+ if (quote)
19974
+ return null;
19975
+ flush();
19976
+ return result;
19977
+ }
19978
+ var stripHarmlessRedirects = (text) => text.replace(/[0-9]?>&[0-9-]/g, " ").replace(/(?:[0-9]|&)?>>?\s*\/dev\/null\b/g, " ");
19979
+ function isSafeCommand(command) {
19980
+ const parts = segments(command);
19981
+ if (!parts?.length)
19982
+ return false;
19983
+ if (/\$\(|`|<\(|>\(/.test(command.replace(/'[^']*'/g, "''")))
19984
+ return false;
19985
+ return parts.every(({ raw, bare }) => {
19986
+ const text = stripHarmlessRedirects(bare);
19987
+ if (/[>&]|<</.test(text))
19988
+ return false;
19989
+ return SAFE_PATTERNS.some((pattern) => pattern.test(text)) && !DANGEROUS_FLAGS.some((pattern) => pattern.test(text)) && !DANGEROUS_CONTENT.some((pattern) => pattern.test(raw));
19913
19990
  });
19914
- const block = `<skill name="${escapeXml(skill.name)}" location="${escapeXml(skill.filePath)}">
19915
- References are relative to ${dirname4(skill.filePath)}.
19916
-
19917
- ${body}
19918
- </skill>`;
19919
- return args ? `${block}
19991
+ }
19920
19992
 
19921
- ${args}` : block;
19993
+ // packages/core/src/user-questions.ts
19994
+ import { randomUUID } from "crypto";
19995
+ import {
19996
+ defineDoc as defineDoc4,
19997
+ defineDocFamily,
19998
+ defineExtension as defineExtension5,
19999
+ defineTool as defineTool4
20000
+ } from "@earendil-works/pi-durable";
20001
+ import { Type as Type4 } from "typebox";
20002
+ var QuestionDoc = defineDocFamily({
20003
+ kind: "app.neko.question",
20004
+ version: 1,
20005
+ scope: "conversation",
20006
+ history: "latest",
20007
+ fork: "initial",
20008
+ family: true,
20009
+ initial: (seed) => seed
20010
+ });
20011
+ var QuestionsDoc = defineDoc4({
20012
+ kind: "app.neko.questions",
20013
+ version: 1,
20014
+ scope: "conversation",
20015
+ history: "latest",
20016
+ fork: "initial",
20017
+ initial: () => ({ revision: 0, pending: [] })
20018
+ });
20019
+ var clone = (value) => JSON.parse(JSON.stringify(value));
20020
+ var MISSING = {
20021
+ id: 0,
20022
+ callId: "",
20023
+ version: "",
20024
+ question: "",
20025
+ multiple: false,
20026
+ options: [],
20027
+ status: "pending",
20028
+ answer: null,
20029
+ createdAt: 0,
20030
+ settledAt: null
19922
20031
  };
20032
+ var argsOf = (record) => ({
20033
+ question: record.question,
20034
+ multiple: record.multiple,
20035
+ options: clone(record.options)
20036
+ });
20037
+ async function live(tx, id) {
20038
+ let cursor = id;
20039
+ for (let depth = 0;cursor !== undefined && depth < 16; depth++) {
20040
+ const task = await tx.task(cursor);
20041
+ if (!task || task.abortRequested || task.state.status === "terminal" || task.state.status === "completing")
20042
+ return false;
20043
+ cursor = task.owner;
20044
+ }
20045
+ return true;
20046
+ }
20047
+ function settle(record, index, status) {
20048
+ record.status = status;
20049
+ record.settledAt = Date.now();
20050
+ index.pending = index.pending.filter((id) => id !== record.id);
20051
+ index.revision += 1;
20052
+ }
19923
20053
 
19924
- // packages/core/src/prompt/prompt.ts
19925
- import { defineExtension as defineExtension5, section } from "@earendil-works/pi-durable";
19926
- var KEYS = ["preamble", "rules", "project_context", "skills", "cwd"];
19927
- var PREAMBLE = "You are an expert coding assistant operating inside neko, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.";
19928
- var TOOL_GUIDELINES = {
19929
- subagent: [
19930
- "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."
19931
- ],
19932
- find_files: [
19933
- "Use find_files to discover files; query is a literal substring. Narrow the directory when results are partial."
19934
- ],
19935
- search_text: [
19936
- "Use search_text for literal text search and read for surrounding evidence. Partial results do not prove absence."
19937
- ],
19938
- read: ["Use read to examine files instead of cat or sed."],
19939
- write: ["Use write only for new files or complete rewrites."],
19940
- edit: [
19941
- "Use edit for precise changes (edits[].oldText must match exactly)",
19942
- "When changing multiple separate locations in one file, use one edit call with multiple entries in edits[] instead of multiple edit calls",
19943
- "Each edits[].oldText is matched against the original file, not after earlier edits are applied. Do not emit overlapping or nested edits. Merge nearby changes into one edit.",
19944
- "Keep edits[].oldText as small as possible while still being unique in the file. Do not pad with large unchanged regions."
19945
- ],
19946
- bash: ["Use bash for file operations like ls, rg, find"],
19947
- job: [
19948
- 'Use job {action:"start", command, title?, timeout?} for long-running commands (dev servers, watchers, long builds or tests) instead of bash, which would block. Jobs have no stdin. start returns within 2 seconds, with the early result if the command already ended; a job that ends later reports back to you automatically, so do not poll. Read new output with {action:"output", jobId, since?} using the offset from the previous call, list jobs with {action:"list"}, end one with {action:"stop", jobId}. Run short commands with bash.'
19949
- ],
19950
- web_search: ["Use web_search for current information from the internet instead of guessing."],
19951
- web_extract: ["Use web_extract to read the full content of a web page when you have its URL."],
19952
- todo: [
19953
- `Use todo for multi-step work: {action:"list"} reads the saved list, {action:"replace", items:[{id, text, status}]} overwrites the whole ordered list (status pending, in_progress or completed; [] clears it). Keep a short list and update it as you go, keep an item's id when you update the same task, and mark it completed only after the work is actually done. If you are unsure what the list holds (for example after the context was compacted), read it with list first instead of guessing or creating a second list. Do not create a list for simple questions or one-step tasks.`
19954
- ],
19955
- ask_user: [
19956
- "Use ask_user {question, options?: [{id, label, description?, recommended?}], multiple?} only when you need a decision or information you cannot determine yourself from the code, the environment or a reasonable assumption; do not ask about things you can find out. Keep the question short and self-contained. Make options short, distinct and understandable on their own, mark a clear preference with recommended, and set multiple: true only when several options may apply (it needs at least 2 options). The user can always type text instead of or in addition to choosing, or skip. The call returns when the user answers or skips; do not poll. A skip is not consent or a preference: continue on your best judgment, state the assumptions you make, and do not ask the same question again unless circumstances change. Independent questions may be asked in one turn and are shown one at a time; ask dependent ones in separate turns. Never ask for passwords, API keys or other secrets. An answer is not approval to run a command and never widens tool permissions."
19957
- ]
19958
- };
19959
- var GENERIC_RULES = ["Be concise in your responses", "Show file paths clearly when working with files"];
19960
- var WINDOWS_SHELL_RULE = "On Windows, commands run in Git Bash (not PowerShell, cmd or WSL); write paths with forward slashes, e.g. D:/code or /d/code.";
19961
- var toolRules = (name, platform) => {
19962
- const rules = TOOL_GUIDELINES[name] ?? [];
19963
- return platform === "win32" && (name === "bash" || name === "job") ? [...rules, WINDOWS_SHELL_RULE] : rules;
19964
- };
19965
- var buildRules = (toolNames, platform) => {
19966
- const rules = [];
19967
- const seen = new Set;
19968
- for (const rule of [...toolNames.flatMap((name) => toolRules(name, platform)), ...GENERIC_RULES]) {
19969
- if (seen.has(rule))
19970
- continue;
19971
- seen.add(rule);
19972
- rules.push(`- ${rule}`);
19973
- }
19974
- return rules.join(`
19975
- `);
19976
- };
19977
- var renderProjectContext = (files) => files.length === 0 ? undefined : [
19978
- "Project-specific instructions and guidelines:",
19979
- ...files.map(({ path, content }) => `<project_instructions path="${escapeXml(path)}">
19980
- ${content}
19981
- </project_instructions>`)
19982
- ].join(`
19983
-
19984
- `);
19985
- var createNekoPrompt = (loadResources, platform = process.platform) => {
19986
- const built = new WeakMap;
19987
- const build = async (input, context) => {
19988
- const cwd = input.agent.cwd;
19989
- const role = (await input.read.snapshot(SubagentDoc, input.conversationId, context))?.role;
19990
- const resources = cwd ? await loadResources(input.conversationId, cwd) : { contextFiles: [], skills: [] };
19991
- const sections = {
19992
- preamble: role ? ROLE_PROMPTS[role] + SUBAGENT_DELIVERY : PREAMBLE,
19993
- rules: buildRules(input.agent.tools.map((tool) => tool.name), platform),
19994
- project_context: renderProjectContext(resources.contextFiles),
19995
- skills: formatSkillsForPrompt(resources.skills) || undefined,
19996
- cwd: cwd?.replace(/\\/g, "/")
19997
- };
19998
- return sections;
19999
- };
20000
- return defineExtension5({
20001
- name: "neko-prompt",
20002
- sections: KEYS.map((key) => section(key, async (input, context) => {
20003
- let pending = built.get(input);
20004
- if (!pending) {
20005
- pending = build(input, context);
20006
- built.set(input, pending);
20007
- }
20008
- const text = (await pending)[key];
20009
- if (text === undefined)
20054
+ class UserQuestions {
20055
+ harness;
20056
+ #tracked = new Set;
20057
+ #context;
20058
+ #running = false;
20059
+ #dirty = false;
20060
+ constructor(harness, context) {
20061
+ this.harness = harness;
20062
+ this.#context = context;
20063
+ const stop = harness.subscribeCommits(({ changes }) => {
20064
+ if (!changes.some((c) => c.type === "task" && (c.value.abortRequested || c.value.state.status === "terminal")))
20010
20065
  return;
20011
- return key === "preamble" ? text : `<${key}>
20012
- ${text}
20013
- </${key}>`;
20014
- }, { tag: false }))
20015
- });
20016
- };
20017
-
20018
- // packages/core/src/prompt/resources.ts
20019
- import { existsSync as existsSync2 } from "fs";
20020
- import { readFile as readFile3 } from "fs/promises";
20021
- import { homedir as homedir3 } from "os";
20022
- import { join as join4 } from "path";
20023
- import { fileURLToPath } from "url";
20024
- var userAgentsFile = (home) => join4(home, ".agents", "AGENTS.md");
20025
- var userSkillsDir = (home) => join4(home, ".agents", "skills");
20026
- var workspaceAgentsFile = (workspace) => join4(workspace, "AGENTS.md");
20027
- var workspaceSkillsDir = (workspace) => join4(workspace, ".agents", "skills");
20028
- var resolveBuiltinSkillsDir = () => {
20029
- for (const url of [new URL("./skills/", import.meta.url), new URL("../../skills/", import.meta.url)]) {
20030
- const path = fileURLToPath(url);
20031
- if (existsSync2(join4(path, "skill-creator", "SKILL.md")))
20032
- return path;
20066
+ this.reconcileAll();
20067
+ });
20068
+ harness.closed.finally(stop).catch(() => {});
20033
20069
  }
20034
- return null;
20035
- };
20036
- var BUILTIN_SKILLS_DIR = resolveBuiltinSkillsDir();
20037
- var readResources = async (workspace, home, builtinDir, report, invocationName) => {
20038
- const contextFiles = [];
20039
- for (const path of [userAgentsFile(home), workspaceAgentsFile(workspace)]) {
20070
+ track(...ids) {
20071
+ for (const id of ids)
20072
+ this.#tracked.add(id);
20073
+ }
20074
+ async reconcileAll() {
20075
+ if (this.#running) {
20076
+ this.#dirty = true;
20077
+ return;
20078
+ }
20079
+ this.#running = true;
20040
20080
  try {
20041
- const content = (await readFile3(path, "utf8")).replace(/^\uFEFF/, "");
20042
- if (Buffer.byteLength(content, "utf8") > 64 * 1024)
20043
- report({
20044
- path,
20045
- message: "Project instructions exceed 64 KiB; included in full. History compaction does not reduce this input."
20046
- });
20047
- contextFiles.push({ path, content });
20048
- } catch (error) {
20049
- if (!isMissing(error))
20050
- report({ path, message: "Cannot read project instructions; skipped." });
20081
+ do {
20082
+ this.#dirty = false;
20083
+ for (const id of [...this.#tracked])
20084
+ await this.reconcile(id, this.#context).catch(() => {});
20085
+ } while (this.#dirty);
20086
+ } finally {
20087
+ this.#running = false;
20051
20088
  }
20052
20089
  }
20053
- const candidates = invocationName === undefined ? undefined : new Map;
20054
- const capture = candidates ? (skill, content) => {
20055
- if (skill.name === invocationName)
20056
- candidates.set(skill.filePath, content);
20057
- } : undefined;
20058
- const scan = async (dir, level) => (await scanSkillsDir(dir, report, capture)).map((skill) => ({ ...skill, level }));
20059
- const [builtin, user, project] = await Promise.all([
20060
- builtinDir === null ? Promise.resolve([]) : scan(builtinDir, "builtin"),
20061
- scan(userSkillsDir(home), "user"),
20062
- scan(workspaceSkillsDir(workspace), "project")
20063
- ]);
20064
- const skills = mergeSkills([builtin, user, project]);
20065
- const winner = invocationName === undefined ? undefined : skills.find((skill) => skill.name === invocationName);
20066
- if (winner && candidates) {
20067
- const content = candidates.get(winner.filePath);
20068
- if (content !== undefined)
20069
- return { contextFiles, skills, invocation: { filePath: winner.filePath, content } };
20090
+ async reconcile(conversationId, context) {
20091
+ const index = await this.harness.snapshot(QuestionsDoc, conversationId, context);
20092
+ if (!index?.pending.length)
20093
+ return;
20094
+ await this.harness.commit((tx) => this.#reconcile(tx, conversationId), context);
20070
20095
  }
20071
- return { contextFiles, skills };
20072
- };
20073
- var createResourceLoader = (home = homedir3(), report = warnPromptDiagnostic, options) => {
20074
- const builtinDir = options?.builtinDir === undefined ? BUILTIN_SKILLS_DIR : options.builtinDir;
20075
- const previous = new Map;
20076
- return async (workspace, invocationName) => {
20077
- const diagnostics = [];
20078
- const resources = await readResources(workspace, home, builtinDir, (d) => diagnostics.push(d), invocationName);
20079
- const known = previous.get(workspace);
20080
- const current = new Set;
20081
- for (const diagnostic of diagnostics) {
20082
- const key = JSON.stringify(diagnostic);
20083
- if (!known?.has(key) && !current.has(key))
20084
- report(diagnostic);
20085
- current.add(key);
20086
- }
20087
- if (current.size)
20088
- previous.set(workspace, current);
20089
- else
20090
- previous.delete(workspace);
20091
- return resources;
20092
- };
20093
- };
20094
- var scanSkillsByLevel = async (workspace, home = homedir3(), builtinDir = BUILTIN_SKILLS_DIR, report = warnPromptDiagnostic) => {
20095
- const scan = async (dir, level) => (await scanSkillsDir(dir, report)).map((skill) => ({ ...skill, level }));
20096
- const [builtin, user, project] = await Promise.all([
20097
- builtinDir === null ? [] : scan(builtinDir, "builtin"),
20098
- scan(userSkillsDir(home), "user"),
20099
- scan(workspaceSkillsDir(workspace), "project")
20100
- ]);
20101
- return [...project, ...user, ...builtin];
20102
- };
20103
-
20104
- // packages/core/src/prompt/snapshots.ts
20105
- var createResourceSnapshots = (load, submission) => {
20106
- const conversations = new Map;
20107
- const stateFor = (id, workspace) => {
20108
- let state = conversations.get(id);
20109
- if (!state || state.workspace !== workspace) {
20110
- state = { workspace, pending: new Map };
20111
- conversations.set(id, state);
20112
- }
20113
- return state;
20114
- };
20115
- return {
20116
- stage(id, requestId, workspace, resources) {
20117
- stateFor(id, workspace).pending.set(requestId, {
20118
- contextFiles: resources.contextFiles,
20119
- skills: resources.skills
20120
- });
20121
- },
20122
- async current(id, workspace) {
20123
- const state = stateFor(id, workspace);
20124
- for (const [requestId, snapshot] of state.pending) {
20125
- const record = await submission(id, requestId);
20126
- if (!record || record.status === "queued")
20127
- continue;
20128
- state.pending.delete(requestId);
20129
- if (record.type !== "input" || record.entry === undefined)
20130
- continue;
20131
- if (state.entry === undefined || record.entry > state.entry) {
20132
- state.active = snapshot;
20133
- state.entry = record.entry;
20134
- }
20135
- }
20136
- state.active ??= load(workspace);
20137
- return state.active;
20138
- },
20139
- discard(id, requestId) {
20140
- conversations.get(id)?.pending.delete(requestId);
20141
- },
20142
- move(id, from, to) {
20143
- const pending = conversations.get(id)?.pending;
20144
- const snapshot = pending?.get(from);
20145
- if (!pending || !snapshot)
20146
- return;
20147
- pending.delete(from);
20148
- pending.set(to, snapshot);
20149
- },
20150
- forget(id) {
20151
- conversations.delete(id);
20152
- }
20153
- };
20154
- };
20155
-
20156
- // packages/core/src/session-metadata.ts
20157
- import { stripVTControlCharacters } from "util";
20158
- import { getSupportedThinkingLevels } from "@earendil-works/pi-ai";
20159
- import {
20160
- defineDoc as defineDoc4,
20161
- defineExtension as defineExtension7,
20162
- defineTask as defineTask3,
20163
- InboxDoc,
20164
- LiveDoc as LiveDoc2
20165
- } from "@earendil-works/pi-durable";
20166
-
20167
- // packages/core/src/user-questions.ts
20168
- import { randomUUID } from "crypto";
20169
- import {
20170
- defineDoc as defineDoc3,
20171
- defineDocFamily,
20172
- defineExtension as defineExtension6,
20173
- defineTool as defineTool4
20174
- } from "@earendil-works/pi-durable";
20175
- import { Type as Type4 } from "typebox";
20176
- var QuestionDoc = defineDocFamily({
20177
- kind: "app.neko.question",
20178
- version: 1,
20179
- scope: "conversation",
20180
- history: "latest",
20181
- fork: "initial",
20182
- family: true,
20183
- initial: (seed) => seed
20184
- });
20185
- var QuestionsDoc = defineDoc3({
20186
- kind: "app.neko.questions",
20187
- version: 1,
20188
- scope: "conversation",
20189
- history: "latest",
20190
- fork: "initial",
20191
- initial: () => ({ revision: 0, pending: [] })
20192
- });
20193
- var clone = (value) => JSON.parse(JSON.stringify(value));
20194
- var MISSING = {
20195
- id: 0,
20196
- callId: "",
20197
- version: "",
20198
- question: "",
20199
- multiple: false,
20200
- options: [],
20201
- status: "pending",
20202
- answer: null,
20203
- createdAt: 0,
20204
- settledAt: null
20205
- };
20206
- var argsOf = (record) => ({
20207
- question: record.question,
20208
- multiple: record.multiple,
20209
- options: clone(record.options)
20210
- });
20211
- async function live(tx, id) {
20212
- let cursor = id;
20213
- for (let depth = 0;cursor !== undefined && depth < 16; depth++) {
20214
- const task = await tx.task(cursor);
20215
- if (!task || task.abortRequested || task.state.status === "terminal" || task.state.status === "completing")
20216
- return false;
20217
- cursor = task.owner;
20218
- }
20219
- return true;
20220
- }
20221
- function settle(record, index, status) {
20222
- record.status = status;
20223
- record.settledAt = Date.now();
20224
- index.pending = index.pending.filter((id) => id !== record.id);
20225
- index.revision += 1;
20226
- }
20227
-
20228
- class UserQuestions {
20229
- harness;
20230
- #tracked = new Set;
20231
- #context;
20232
- #running = false;
20233
- #dirty = false;
20234
- constructor(harness, context) {
20235
- this.harness = harness;
20236
- this.#context = context;
20237
- const stop = harness.subscribeCommits(({ changes }) => {
20238
- if (!changes.some((c) => c.type === "task" && (c.value.abortRequested || c.value.state.status === "terminal")))
20239
- return;
20240
- this.reconcileAll();
20241
- });
20242
- harness.closed.finally(stop).catch(() => {});
20243
- }
20244
- track(...ids) {
20245
- for (const id of ids)
20246
- this.#tracked.add(id);
20247
- }
20248
- async reconcileAll() {
20249
- if (this.#running) {
20250
- this.#dirty = true;
20251
- return;
20252
- }
20253
- this.#running = true;
20254
- try {
20255
- do {
20256
- this.#dirty = false;
20257
- for (const id of [...this.#tracked])
20258
- await this.reconcile(id, this.#context).catch(() => {});
20259
- } while (this.#dirty);
20260
- } finally {
20261
- this.#running = false;
20262
- }
20263
- }
20264
- async reconcile(conversationId, context) {
20265
- const index = await this.harness.snapshot(QuestionsDoc, conversationId, context);
20266
- if (!index?.pending.length)
20267
- return;
20268
- await this.harness.commit((tx) => this.#reconcile(tx, conversationId), context);
20269
- }
20270
- async#reconcile(tx, conversationId) {
20271
- const index = await tx.doc(QuestionsDoc, conversationId);
20272
- const stale = [];
20273
- for (const id of index.pending) {
20274
- const record = await tx.doc(QuestionDoc, conversationId, String(id), MISSING);
20275
- if (record.version === "" || record.status !== "pending")
20276
- stale.push(id);
20277
- else if (!await live(tx, id)) {
20278
- record.status = "cancelled";
20279
- record.settledAt = Date.now();
20280
- stale.push(id);
20096
+ async#reconcile(tx, conversationId) {
20097
+ const index = await tx.doc(QuestionsDoc, conversationId);
20098
+ const stale = [];
20099
+ for (const id of index.pending) {
20100
+ const record = await tx.doc(QuestionDoc, conversationId, String(id), MISSING);
20101
+ if (record.version === "" || record.status !== "pending")
20102
+ stale.push(id);
20103
+ else if (!await live(tx, id)) {
20104
+ record.status = "cancelled";
20105
+ record.settledAt = Date.now();
20106
+ stale.push(id);
20281
20107
  }
20282
20108
  }
20283
20109
  if (!stale.length)
@@ -20439,71 +20265,717 @@ function questionOutput(record) {
20439
20265
  return lines.join(`
20440
20266
  `);
20441
20267
  }
20442
- if (record.status === "skipped")
20443
- return "The user skipped this question without answering. This is not approval, rejection or a preference. Continue with your best judgment and state the assumptions you make, or continue without this information. Do not ask the same question again unless circumstances change.";
20444
- return "The question was cancelled because the run was stopped. No answer was given.";
20445
- }
20446
- function outcomeOf(record) {
20447
- if (record.status === "answered" && record.answer)
20448
- return {
20449
- status: "answered",
20450
- optionIds: record.answer.optionIds,
20451
- labels: answerLabels(record, record.answer),
20452
- text: record.answer.text
20453
- };
20454
- return { status: record.status === "skipped" ? "skipped" : "cancelled" };
20455
- }
20456
- function questionDetails(record) {
20457
- const base = {
20458
- id: record.id,
20459
- question: record.question,
20460
- multiple: record.multiple,
20461
- options: record.options.map((o) => ({
20462
- id: o.id,
20463
- label: o.label,
20464
- ...o.recommended ? { recommended: true } : {}
20465
- }))
20268
+ if (record.status === "skipped")
20269
+ return "The user skipped this question without answering. This is not approval, rejection or a preference. Continue with your best judgment and state the assumptions you make, or continue without this information. Do not ask the same question again unless circumstances change.";
20270
+ return "The question was cancelled because the run was stopped. No answer was given.";
20271
+ }
20272
+ function outcomeOf(record) {
20273
+ if (record.status === "answered" && record.answer)
20274
+ return {
20275
+ status: "answered",
20276
+ optionIds: record.answer.optionIds,
20277
+ labels: answerLabels(record, record.answer),
20278
+ text: record.answer.text
20279
+ };
20280
+ return { status: record.status === "skipped" ? "skipped" : "cancelled" };
20281
+ }
20282
+ function questionDetails(record) {
20283
+ const base = {
20284
+ id: record.id,
20285
+ question: record.question,
20286
+ multiple: record.multiple,
20287
+ options: record.options.map((o) => ({
20288
+ id: o.id,
20289
+ label: o.label,
20290
+ ...o.recommended ? { recommended: true } : {}
20291
+ }))
20292
+ };
20293
+ return record.status === "pending" ? { ...base, status: "pending" } : { ...base, ...outcomeOf(record) };
20294
+ }
20295
+ function createAskUserTool(service) {
20296
+ return defineTool4({
20297
+ name: "ask_user",
20298
+ description: "Ask the user one question and wait for the answer. question: the text. options: optional [{id, label, description?, recommended?}]; multiple: true lets the user pick several (needs 2+ options). The user can always type text instead of or in addition to choosing, or skip. Use only when you need a decision or information you cannot determine yourself. The call returns when the user answers or skips; do not poll.",
20299
+ parameters: parameters3,
20300
+ structuredOutputSchema: structuredOutputSchema2,
20301
+ replay: "safe",
20302
+ executionMode: "sequential",
20303
+ callers: ["model"],
20304
+ execute: async (input, api, context) => {
20305
+ context.abortSignal?.throwIfAborted();
20306
+ if ((await api.snapshot(SubagentDoc, api.conversationId, context))?.role)
20307
+ throw new Error("Subagents cannot ask the user; report the missing information to the main agent.");
20308
+ let args;
20309
+ try {
20310
+ args = validateQuestionArgs(input);
20311
+ } catch (error) {
20312
+ if (error instanceof InteractionInputError)
20313
+ throw new Error(`Invalid ask_user arguments: ${error.message}`);
20314
+ throw error;
20315
+ }
20316
+ const questions = service();
20317
+ const created = await questions.ensure(api.conversationId, api.taskId, api.callId, args, context);
20318
+ await api.details(questionDetails(created), context);
20319
+ const record = created.status === "pending" ? await questions.wait(api.conversationId, api.taskId, context) : created;
20320
+ return {
20321
+ output: [{ type: "text", text: questionOutput(record) }],
20322
+ structuredOutput: outcomeOf(record),
20323
+ details: questionDetails(record),
20324
+ isError: record.status === "cancelled"
20325
+ };
20326
+ }
20327
+ });
20328
+ }
20329
+ var createQuestionExtension = (service) => defineExtension5({ name: "neko-questions", tools: [createAskUserTool(service)] });
20330
+
20331
+ // packages/core/src/plan.ts
20332
+ var PLAN_REMOVED_TOOLS = ["edit", "write", "job"];
20333
+ var PLAN_INSTRUCTIONS = [
20334
+ "You are in plan mode: research and plan only, and change nothing.",
20335
+ "- 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.",
20336
+ "- Not available: edit, write, job and any command that modifies files, installs packages or starts processes. Do not try to work around this.",
20337
+ "- 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.",
20338
+ "- After submit_plan do nothing else. If the user asks for changes, research what is needed and submit a revised plan.",
20339
+ "- Use ask_user only for decisions you cannot settle by reading the code."
20340
+ ].join(`
20341
+ `);
20342
+ var PLAN_BUSY = "PLAN_BUSY: Wait for the current run to finish or stop it first.";
20343
+ function planAgentChange(mode) {
20344
+ if (mode === "normal")
20345
+ return { tools: null, modelTools: null, instructions: null };
20346
+ const named = (name) => ({ name });
20347
+ return {
20348
+ tools: { remove: PLAN_REMOVED_TOOLS.map(named) },
20349
+ modelTools: { add: [named("submit_plan")] },
20350
+ instructions: PLAN_INSTRUCTIONS
20351
+ };
20352
+ }
20353
+ var agentInPlanMode = (agent) => agent.instructions === PLAN_INSTRUCTIONS;
20354
+ var clone2 = (value) => JSON.parse(JSON.stringify(value));
20355
+ var toRecord = ({ taskId: _taskId, dispatched: _dispatched, ...record }) => clone2(record);
20356
+ function settle2(state, item, status) {
20357
+ item.status = status;
20358
+ item.settledAt = Date.now();
20359
+ state.revision += 1;
20360
+ }
20361
+
20362
+ class PlanService {
20363
+ harness;
20364
+ #context;
20365
+ constructor(harness, context) {
20366
+ this.harness = harness;
20367
+ this.#context = context;
20368
+ }
20369
+ async#state(id) {
20370
+ return this.harness.snapshot(PlanDoc, id, this.#context);
20371
+ }
20372
+ async mode(id) {
20373
+ return (await this.#state(id))?.mode ?? "normal";
20374
+ }
20375
+ async view(id) {
20376
+ const state = await this.#state(id);
20377
+ const current = state?.items.at(-1);
20378
+ return {
20379
+ mode: state?.mode ?? "normal",
20380
+ revision: state?.revision ?? 0,
20381
+ current: current ? toRecord(current) : null
20382
+ };
20383
+ }
20384
+ async get(id, version) {
20385
+ const item = (await this.#state(id))?.items.find((i) => i.version === version);
20386
+ return item ? toRecord(item) : undefined;
20387
+ }
20388
+ async awaiting(id) {
20389
+ const item = (await this.#state(id))?.items.find((i) => i.status === "awaiting_approval");
20390
+ return item ? toRecord(item) : undefined;
20391
+ }
20392
+ async setMode(id, mode) {
20393
+ return this.harness.commit(async (tx) => {
20394
+ const state = await tx.doc(PlanDoc, id);
20395
+ let changed = false;
20396
+ if (state.mode !== mode) {
20397
+ state.mode = mode;
20398
+ state.revision += 1;
20399
+ changed = true;
20400
+ }
20401
+ if (mode === "normal") {
20402
+ for (const item of state.items)
20403
+ if (item.status === "awaiting_approval") {
20404
+ settle2(state, item, "cancelled");
20405
+ changed = true;
20406
+ }
20407
+ }
20408
+ return changed;
20409
+ }, this.#context);
20410
+ }
20411
+ async submit(id, taskId, args, context) {
20412
+ return this.harness.commit(async (tx) => {
20413
+ if (!await live(tx, taskId))
20414
+ throw new Error("Task stopped");
20415
+ const state = await tx.doc(PlanDoc, id);
20416
+ const existing = state.items.find((item) => item.taskId === taskId);
20417
+ if (existing) {
20418
+ if (existing.title !== args.title || existing.note !== args.note || JSON.stringify(existing.steps) !== JSON.stringify(args.steps))
20419
+ throw new Error("Plan operation changed; a new call is required.");
20420
+ return toRecord(existing);
20421
+ }
20422
+ if (state.mode !== "plan")
20423
+ throw new Error("submit_plan is only available in plan mode.");
20424
+ for (const item of state.items)
20425
+ if (item.status === "awaiting_approval")
20426
+ settle2(state, item, "superseded");
20427
+ const item = {
20428
+ version: randomUUID2(),
20429
+ title: args.title,
20430
+ steps: [...args.steps],
20431
+ note: args.note,
20432
+ status: "awaiting_approval",
20433
+ feedback: null,
20434
+ createdAt: Date.now(),
20435
+ settledAt: null,
20436
+ taskId,
20437
+ dispatched: false
20438
+ };
20439
+ state.items.push(item);
20440
+ if (state.items.length > PLAN_HISTORY)
20441
+ state.items.splice(0, state.items.length - PLAN_HISTORY);
20442
+ state.revision += 1;
20443
+ return toRecord(item);
20444
+ }, context);
20445
+ }
20446
+ async decide(id, version, decision) {
20447
+ return this.harness.commit(async (tx) => {
20448
+ const state = await tx.doc(PlanDoc, id);
20449
+ const item = state.items.find((i) => i.version === version);
20450
+ if (!item)
20451
+ throw new Error("Plan not found.");
20452
+ if (item.status === "awaiting_approval") {
20453
+ if (decision.kind === "approve") {
20454
+ settle2(state, item, "approved");
20455
+ state.mode = "normal";
20456
+ } else {
20457
+ settle2(state, item, "revision_requested");
20458
+ item.feedback = decision.feedback;
20459
+ }
20460
+ }
20461
+ return { record: toRecord(item), item: clone2(item) };
20462
+ }, this.#context);
20463
+ }
20464
+ async supersedePending(id) {
20465
+ if (await this.awaiting(id))
20466
+ await this.#cancel(id, "superseded");
20467
+ }
20468
+ async cancelPending(id) {
20469
+ if (await this.awaiting(id))
20470
+ await this.#cancel(id, "cancelled");
20471
+ }
20472
+ async#cancel(id, status) {
20473
+ await this.harness.commit(async (tx) => {
20474
+ const state = await tx.doc(PlanDoc, id);
20475
+ for (const item of state.items)
20476
+ if (item.status === "awaiting_approval")
20477
+ settle2(state, item, status);
20478
+ }, this.#context);
20479
+ }
20480
+ async undispatched(id) {
20481
+ const item = (await this.#state(id))?.items.findLast((i) => (i.status === "approved" || i.status === "revision_requested") && !i.dispatched);
20482
+ return item ? clone2(item) : undefined;
20483
+ }
20484
+ async markDispatched(id, version) {
20485
+ await this.harness.commit(async (tx) => {
20486
+ const item = (await tx.doc(PlanDoc, id)).items.find((i) => i.version === version);
20487
+ if (item)
20488
+ item.dispatched = true;
20489
+ }, this.#context);
20490
+ }
20491
+ }
20492
+ function approvedPlanPrompt(plan) {
20493
+ return [
20494
+ `The user approved your plan "${plan.title}". Plan mode is off: edit, write and all commands are available again. Execute the plan now, step by step:`,
20495
+ ...plan.steps.map((step, index) => `${index + 1}. ${step}`),
20496
+ ...plan.note ? ["", `Notes: ${plan.note}`] : [],
20497
+ "",
20498
+ "Use the todo tool to track progress, and verify the result when you are done."
20499
+ ].join(`
20500
+ `);
20501
+ }
20502
+ function planRevisionPrompt(plan, feedback) {
20503
+ return [
20504
+ `The user asked for changes to your plan "${plan.title}". Do not start implementing; you are still in plan mode.`,
20505
+ "",
20506
+ feedback,
20507
+ "",
20508
+ "Research what you need, then call submit_plan with the revised plan."
20509
+ ].join(`
20510
+ `);
20511
+ }
20512
+ var parameters4 = Type5.Object({
20513
+ title: Type5.String({ minLength: 1, maxLength: PLAN_LIMITS.titleChars, description: "Short name of the plan." }),
20514
+ steps: Type5.Array(Type5.String({ minLength: 1, maxLength: PLAN_LIMITS.stepChars }), {
20515
+ minItems: 1,
20516
+ maxItems: PLAN_LIMITS.maxSteps,
20517
+ description: "Ordered, concrete implementation steps."
20518
+ }),
20519
+ note: Type5.Optional(Type5.String({ maxLength: PLAN_LIMITS.noteChars, description: "Risks, assumptions and how to verify." }))
20520
+ }, { additionalProperties: false });
20521
+ function submitPlanTool(service) {
20522
+ return defineTool5({
20523
+ name: "submit_plan",
20524
+ description: "Submit your implementation plan for the user's approval. title: short name. steps: ordered, concrete steps. note: optional risks, assumptions and verification. Call it once, as the only tool call of its turn, when your research is done; the turn ends and the user approves it or asks for changes.",
20525
+ parameters: parameters4,
20526
+ deferred: true,
20527
+ replay: "safe",
20528
+ executionMode: "sequential",
20529
+ callers: ["model"],
20530
+ execute: async (input, api, context) => {
20531
+ context.abortSignal?.throwIfAborted();
20532
+ if ((await api.snapshot(SubagentDoc, api.conversationId, context))?.role)
20533
+ throw new Error("Subagents cannot submit plans; report your findings to the main agent.");
20534
+ let args;
20535
+ try {
20536
+ args = validatePlanArgs(input);
20537
+ } catch (error) {
20538
+ if (error instanceof InteractionInputError)
20539
+ throw new Error(`Invalid submit_plan arguments: ${error.message}`);
20540
+ throw error;
20541
+ }
20542
+ let record = await service().submit(api.conversationId, api.taskId, args, context);
20543
+ const inbox = await api.snapshot(InboxDoc, api.conversationId, context);
20544
+ if (record.status === "awaiting_approval" && inbox?.items.some((item) => queuedUserInput(item))) {
20545
+ await service().supersedePending(api.conversationId);
20546
+ record = await service().get(api.conversationId, record.version) ?? record;
20547
+ }
20548
+ const details = {
20549
+ version: record.version,
20550
+ title: record.title,
20551
+ steps: record.steps,
20552
+ note: record.note
20553
+ };
20554
+ return {
20555
+ output: [
20556
+ {
20557
+ type: "text",
20558
+ text: "Plan submitted. It now waits for the user's decision. Stop here: do not call other tools or continue the work."
20559
+ }
20560
+ ],
20561
+ details,
20562
+ control: { terminate: true }
20563
+ };
20564
+ }
20565
+ });
20566
+ }
20567
+ var unsafeBash = (command) => `Plan mode is read-only, so this command was not run (it is not on the read-only allowlist): ${command}
20568
+ 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.`;
20569
+ var blockedTool = (name) => `Plan mode is read-only: ${name} is unavailable. Research with read-only tools, then call submit_plan.`;
20570
+ var createPlanExtension = (service) => defineExtension6({
20571
+ name: "neko-plan",
20572
+ tools: [submitPlanTool(service)],
20573
+ hooks: [
20574
+ hook2(ToolTask2, {
20575
+ beforeTool: async (call, api, context) => {
20576
+ if ((await api.snapshot(PlanDoc, api.conversationId, context))?.mode !== "plan")
20577
+ return;
20578
+ if (PLAN_REMOVED_TOOLS.includes(call.name))
20579
+ return { block: blockedTool(call.name) };
20580
+ if (call.name === "bash") {
20581
+ const command = String(call.arguments.command ?? "");
20582
+ if (!isSafeCommand(command))
20583
+ return { block: unsafeBash(command) };
20584
+ }
20585
+ }
20586
+ })
20587
+ ],
20588
+ wraps: [
20589
+ wrapTool(createBashTool(), (bash) => ({
20590
+ ...bash,
20591
+ execute: async (args, api, context) => {
20592
+ if ((await api.snapshot(PlanDoc, api.conversationId, context))?.mode === "plan" && !isSafeCommand(args.command))
20593
+ throw new Error(unsafeBash(args.command));
20594
+ return bash.execute(args, api, context);
20595
+ }
20596
+ }))
20597
+ ]
20598
+ });
20599
+
20600
+ // packages/core/src/prompt/invoke.ts
20601
+ import { dirname as dirname4 } from "path";
20602
+
20603
+ // packages/core/src/prompt/diagnostics.ts
20604
+ var ignorePromptDiagnostic = () => {};
20605
+ var warnPromptDiagnostic = ({ path, message }) => {
20606
+ console.warn(`[neko-prompt] ${JSON.stringify(path)}: ${message}`);
20607
+ };
20608
+ var isMissing = (error) => typeof error === "object" && error !== null && ("code" in error) && (error.code === "ENOENT" || error.code === "ENOTDIR");
20609
+
20610
+ // packages/core/src/prompt/skills.ts
20611
+ import { readdir, readFile as readFile2 } from "fs/promises";
20612
+ import { basename, dirname as dirname3, join as join3 } from "path";
20613
+ var MAX_NAME_LENGTH = 64;
20614
+ var MAX_DESCRIPTION_LENGTH = 1024;
20615
+ var parseSkillFile = (content, filePath, report = ignorePromptDiagnostic) => {
20616
+ const skip = (message) => {
20617
+ report({ path: filePath, message });
20618
+ return null;
20619
+ };
20620
+ try {
20621
+ const { frontmatter } = parseFrontmatter(content);
20622
+ const description = frontmatter.description;
20623
+ if (typeof description !== "string" || description.trim() === "")
20624
+ return skip("Skill description is missing or empty; skipped.");
20625
+ if (description.length > MAX_DESCRIPTION_LENGTH)
20626
+ return skip("Skill description is too long; skipped.");
20627
+ const name = typeof frontmatter.name === "string" && frontmatter.name.trim() !== "" ? frontmatter.name : basename(dirname3(filePath));
20628
+ if (name.length > MAX_NAME_LENGTH || /[\s\p{Cc}]/u.test(name))
20629
+ return skip("Skill name is too long or contains whitespace/control characters; skipped.");
20630
+ return {
20631
+ name,
20632
+ description,
20633
+ filePath,
20634
+ disableModelInvocation: frontmatter["disable-model-invocation"] === true
20635
+ };
20636
+ } catch {
20637
+ return skip("Invalid skill frontmatter; skipped.");
20638
+ }
20639
+ };
20640
+ var scanSkillsDir = async (dir, report = ignorePromptDiagnostic, capture) => {
20641
+ let entries;
20642
+ try {
20643
+ entries = await readdir(dir, { withFileTypes: true });
20644
+ } catch (error) {
20645
+ if (!isMissing(error))
20646
+ report({ path: dir, message: "Cannot scan skills directory; skipped." });
20647
+ return [];
20648
+ }
20649
+ const skills = [];
20650
+ for (const entry of entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
20651
+ if (!entry.isDirectory())
20652
+ continue;
20653
+ const filePath = join3(dir, entry.name, "SKILL.md");
20654
+ let content;
20655
+ try {
20656
+ content = await readFile2(filePath, "utf8");
20657
+ } catch (error) {
20658
+ if (!isMissing(error))
20659
+ report({ path: filePath, message: "Cannot read skill; skipped." });
20660
+ continue;
20661
+ }
20662
+ const skill = parseSkillFile(content, filePath, report);
20663
+ if (skill) {
20664
+ skills.push(skill);
20665
+ capture?.(skill, content);
20666
+ }
20667
+ }
20668
+ return skills;
20669
+ };
20670
+ var mergeSkills = (groups) => {
20671
+ const byName = new Map;
20672
+ for (const group of groups)
20673
+ for (const skill of group)
20674
+ byName.set(skill.name, skill);
20675
+ return [...byName.values()];
20676
+ };
20677
+ var escapeXml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
20678
+ var formatSkillsForPrompt = (skills) => {
20679
+ const visible = skills.filter((skill) => !skill.disableModelInvocation);
20680
+ if (visible.length === 0)
20681
+ return "";
20682
+ const lines = [
20683
+ "The following skills provide specialized instructions for specific tasks.",
20684
+ "Use the read tool to load a skill's file when the task matches its description.",
20685
+ "When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md) and use that absolute path in tool commands.",
20686
+ "",
20687
+ "<available_skills>"
20688
+ ];
20689
+ for (const skill of visible) {
20690
+ lines.push(" <skill>");
20691
+ lines.push(` <name>${escapeXml(skill.name)}</name>`);
20692
+ lines.push(` <description>${escapeXml(skill.description)}</description>`);
20693
+ lines.push(` <location>${escapeXml(skill.filePath)}</location>`);
20694
+ lines.push(" </skill>");
20695
+ }
20696
+ lines.push("</available_skills>");
20697
+ return lines.join(`
20698
+ `);
20699
+ };
20700
+
20701
+ // packages/core/src/prompt/invoke.ts
20702
+ var expandSkillCommand = async (text, skills, readFile, report = warnPromptDiagnostic, metadata) => {
20703
+ if (!text.startsWith("/skill:"))
20704
+ return text;
20705
+ const spaceIndex = text.indexOf(" ");
20706
+ const skillName = spaceIndex === -1 ? text.slice(7) : text.slice(7, spaceIndex);
20707
+ const args = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1).trim();
20708
+ const skill = skills.find((s) => s.name === skillName);
20709
+ if (!skill)
20710
+ return text;
20711
+ let body;
20712
+ try {
20713
+ body = stripFrontmatter(await readFile(skill.filePath)).trim();
20714
+ } catch {
20715
+ report({ path: skill.filePath, message: "Cannot expand known skill; original input preserved." });
20716
+ return text;
20717
+ }
20718
+ metadata?.({
20719
+ name: skill.name,
20720
+ location: skill.filePath,
20721
+ body: `References are relative to ${dirname4(skill.filePath)}.
20722
+
20723
+ ${body}`,
20724
+ args
20725
+ });
20726
+ const block = `<skill name="${escapeXml(skill.name)}" location="${escapeXml(skill.filePath)}">
20727
+ References are relative to ${dirname4(skill.filePath)}.
20728
+
20729
+ ${body}
20730
+ </skill>`;
20731
+ return args ? `${block}
20732
+
20733
+ ${args}` : block;
20734
+ };
20735
+
20736
+ // packages/core/src/prompt/prompt.ts
20737
+ import { defineExtension as defineExtension7, section } from "@earendil-works/pi-durable";
20738
+ var KEYS = ["preamble", "rules", "project_context", "skills", "cwd"];
20739
+ var PREAMBLE = "You are an expert coding assistant operating inside neko, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.";
20740
+ var TOOL_GUIDELINES = {
20741
+ subagent: [
20742
+ "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."
20743
+ ],
20744
+ find_files: [
20745
+ "Use find_files to discover files; query is a literal substring. Narrow the directory when results are partial."
20746
+ ],
20747
+ search_text: [
20748
+ "Use search_text for literal text search and read for surrounding evidence. Partial results do not prove absence."
20749
+ ],
20750
+ read: ["Use read to examine files instead of cat or sed."],
20751
+ write: ["Use write only for new files or complete rewrites."],
20752
+ edit: [
20753
+ "Use edit for precise changes (edits[].oldText must match exactly)",
20754
+ "When changing multiple separate locations in one file, use one edit call with multiple entries in edits[] instead of multiple edit calls",
20755
+ "Each edits[].oldText is matched against the original file, not after earlier edits are applied. Do not emit overlapping or nested edits. Merge nearby changes into one edit.",
20756
+ "Keep edits[].oldText as small as possible while still being unique in the file. Do not pad with large unchanged regions."
20757
+ ],
20758
+ bash: ["Use bash for file operations like ls, rg, find"],
20759
+ job: [
20760
+ 'Use job {action:"start", command, title?, timeout?} for long-running commands (dev servers, watchers, long builds or tests) instead of bash, which would block. Jobs have no stdin. start returns within 2 seconds, with the early result if the command already ended; a job that ends later reports back to you automatically, so do not poll. Read new output with {action:"output", jobId, since?} using the offset from the previous call, list jobs with {action:"list"}, end one with {action:"stop", jobId}. Run short commands with bash.'
20761
+ ],
20762
+ web_search: ["Use web_search for current information from the internet instead of guessing."],
20763
+ web_extract: ["Use web_extract to read the full content of a web page when you have its URL."],
20764
+ todo: [
20765
+ `Use todo for multi-step work: {action:"list"} reads the saved list, {action:"replace", items:[{id, text, status}]} overwrites the whole ordered list (status pending, in_progress or completed; [] clears it). Keep a short list and update it as you go, keep an item's id when you update the same task, and mark it completed only after the work is actually done. If you are unsure what the list holds (for example after the context was compacted), read it with list first instead of guessing or creating a second list. Do not create a list for simple questions or one-step tasks.`
20766
+ ],
20767
+ ask_user: [
20768
+ "Use ask_user {question, options?: [{id, label, description?, recommended?}], multiple?} only when you need a decision or information you cannot determine yourself from the code, the environment or a reasonable assumption; do not ask about things you can find out. Keep the question short and self-contained. Make options short, distinct and understandable on their own, mark a clear preference with recommended, and set multiple: true only when several options may apply (it needs at least 2 options). The user can always type text instead of or in addition to choosing, or skip. The call returns when the user answers or skips; do not poll. A skip is not consent or a preference: continue on your best judgment, state the assumptions you make, and do not ask the same question again unless circumstances change. Independent questions may be asked in one turn and are shown one at a time; ask dependent ones in separate turns. Never ask for passwords, API keys or other secrets. An answer is not approval to run a command and never widens tool permissions."
20769
+ ]
20770
+ };
20771
+ var GENERIC_RULES = ["Be concise in your responses", "Show file paths clearly when working with files"];
20772
+ var WINDOWS_SHELL_RULE = "On Windows, commands run in Git Bash (not PowerShell, cmd or WSL); write paths with forward slashes, e.g. D:/code or /d/code.";
20773
+ var toolRules = (name, platform) => {
20774
+ const rules = TOOL_GUIDELINES[name] ?? [];
20775
+ return platform === "win32" && (name === "bash" || name === "job") ? [...rules, WINDOWS_SHELL_RULE] : rules;
20776
+ };
20777
+ var buildRules = (toolNames, platform) => {
20778
+ const rules = [];
20779
+ const seen = new Set;
20780
+ for (const rule of [...toolNames.flatMap((name) => toolRules(name, platform)), ...GENERIC_RULES]) {
20781
+ if (seen.has(rule))
20782
+ continue;
20783
+ seen.add(rule);
20784
+ rules.push(`- ${rule}`);
20785
+ }
20786
+ return rules.join(`
20787
+ `);
20788
+ };
20789
+ var renderProjectContext = (files) => files.length === 0 ? undefined : [
20790
+ "Project-specific instructions and guidelines:",
20791
+ ...files.map(({ path, content }) => `<project_instructions path="${escapeXml(path)}">
20792
+ ${content}
20793
+ </project_instructions>`)
20794
+ ].join(`
20795
+
20796
+ `);
20797
+ var createNekoPrompt = (loadResources, platform = process.platform) => {
20798
+ const built = new WeakMap;
20799
+ const build = async (input, context) => {
20800
+ const cwd = input.agent.cwd;
20801
+ const role = (await input.read.snapshot(SubagentDoc, input.conversationId, context))?.role;
20802
+ const resources = cwd ? await loadResources(input.conversationId, cwd) : { contextFiles: [], skills: [] };
20803
+ const sections = {
20804
+ preamble: role ? ROLE_PROMPTS[role] + SUBAGENT_DELIVERY : PREAMBLE,
20805
+ rules: buildRules(input.agent.tools.map((tool) => tool.name), platform),
20806
+ project_context: renderProjectContext(resources.contextFiles),
20807
+ skills: formatSkillsForPrompt(resources.skills) || undefined,
20808
+ cwd: cwd?.replace(/\\/g, "/")
20809
+ };
20810
+ return sections;
20811
+ };
20812
+ return defineExtension7({
20813
+ name: "neko-prompt",
20814
+ sections: KEYS.map((key) => section(key, async (input, context) => {
20815
+ let pending = built.get(input);
20816
+ if (!pending) {
20817
+ pending = build(input, context);
20818
+ built.set(input, pending);
20819
+ }
20820
+ const text = (await pending)[key];
20821
+ if (text === undefined)
20822
+ return;
20823
+ return key === "preamble" ? text : `<${key}>
20824
+ ${text}
20825
+ </${key}>`;
20826
+ }, { tag: false }))
20827
+ });
20828
+ };
20829
+
20830
+ // packages/core/src/prompt/resources.ts
20831
+ import { existsSync as existsSync2 } from "fs";
20832
+ import { readFile as readFile3 } from "fs/promises";
20833
+ import { homedir as homedir3 } from "os";
20834
+ import { join as join4 } from "path";
20835
+ import { fileURLToPath } from "url";
20836
+ var userAgentsFile = (home) => join4(home, ".agents", "AGENTS.md");
20837
+ var userSkillsDir = (home) => join4(home, ".agents", "skills");
20838
+ var workspaceAgentsFile = (workspace) => join4(workspace, "AGENTS.md");
20839
+ var workspaceSkillsDir = (workspace) => join4(workspace, ".agents", "skills");
20840
+ var resolveBuiltinSkillsDir = () => {
20841
+ for (const url of [new URL("./skills/", import.meta.url), new URL("../../skills/", import.meta.url)]) {
20842
+ const path = fileURLToPath(url);
20843
+ if (existsSync2(join4(path, "skill-creator", "SKILL.md")))
20844
+ return path;
20845
+ }
20846
+ return null;
20847
+ };
20848
+ var BUILTIN_SKILLS_DIR = resolveBuiltinSkillsDir();
20849
+ var readResources = async (workspace, home, builtinDir, report, invocationName) => {
20850
+ const contextFiles = [];
20851
+ for (const path of [userAgentsFile(home), workspaceAgentsFile(workspace)]) {
20852
+ try {
20853
+ const content = (await readFile3(path, "utf8")).replace(/^\uFEFF/, "");
20854
+ if (Buffer.byteLength(content, "utf8") > 64 * 1024)
20855
+ report({
20856
+ path,
20857
+ message: "Project instructions exceed 64 KiB; included in full. History compaction does not reduce this input."
20858
+ });
20859
+ contextFiles.push({ path, content });
20860
+ } catch (error) {
20861
+ if (!isMissing(error))
20862
+ report({ path, message: "Cannot read project instructions; skipped." });
20863
+ }
20864
+ }
20865
+ const candidates = invocationName === undefined ? undefined : new Map;
20866
+ const capture = candidates ? (skill, content) => {
20867
+ if (skill.name === invocationName)
20868
+ candidates.set(skill.filePath, content);
20869
+ } : undefined;
20870
+ const scan = async (dir, level) => (await scanSkillsDir(dir, report, capture)).map((skill) => ({ ...skill, level }));
20871
+ const [builtin, user, project] = await Promise.all([
20872
+ builtinDir === null ? Promise.resolve([]) : scan(builtinDir, "builtin"),
20873
+ scan(userSkillsDir(home), "user"),
20874
+ scan(workspaceSkillsDir(workspace), "project")
20875
+ ]);
20876
+ const skills = mergeSkills([builtin, user, project]);
20877
+ const winner = invocationName === undefined ? undefined : skills.find((skill) => skill.name === invocationName);
20878
+ if (winner && candidates) {
20879
+ const content = candidates.get(winner.filePath);
20880
+ if (content !== undefined)
20881
+ return { contextFiles, skills, invocation: { filePath: winner.filePath, content } };
20882
+ }
20883
+ return { contextFiles, skills };
20884
+ };
20885
+ var createResourceLoader = (home = homedir3(), report = warnPromptDiagnostic, options) => {
20886
+ const builtinDir = options?.builtinDir === undefined ? BUILTIN_SKILLS_DIR : options.builtinDir;
20887
+ const previous = new Map;
20888
+ return async (workspace, invocationName) => {
20889
+ const diagnostics = [];
20890
+ const resources = await readResources(workspace, home, builtinDir, (d) => diagnostics.push(d), invocationName);
20891
+ const known = previous.get(workspace);
20892
+ const current = new Set;
20893
+ for (const diagnostic of diagnostics) {
20894
+ const key = JSON.stringify(diagnostic);
20895
+ if (!known?.has(key) && !current.has(key))
20896
+ report(diagnostic);
20897
+ current.add(key);
20898
+ }
20899
+ if (current.size)
20900
+ previous.set(workspace, current);
20901
+ else
20902
+ previous.delete(workspace);
20903
+ return resources;
20466
20904
  };
20467
- return record.status === "pending" ? { ...base, status: "pending" } : { ...base, ...outcomeOf(record) };
20468
- }
20469
- function createAskUserTool(service) {
20470
- return defineTool4({
20471
- name: "ask_user",
20472
- description: "Ask the user one question and wait for the answer. question: the text. options: optional [{id, label, description?, recommended?}]; multiple: true lets the user pick several (needs 2+ options). The user can always type text instead of or in addition to choosing, or skip. Use only when you need a decision or information you cannot determine yourself. The call returns when the user answers or skips; do not poll.",
20473
- parameters: parameters3,
20474
- structuredOutputSchema: structuredOutputSchema2,
20475
- replay: "safe",
20476
- executionMode: "sequential",
20477
- callers: ["model"],
20478
- execute: async (input, api, context) => {
20479
- context.abortSignal?.throwIfAborted();
20480
- if ((await api.snapshot(SubagentDoc, api.conversationId, context))?.role)
20481
- throw new Error("Subagents cannot ask the user; report the missing information to the main agent.");
20482
- let args;
20483
- try {
20484
- args = validateQuestionArgs(input);
20485
- } catch (error) {
20486
- if (error instanceof InteractionInputError)
20487
- throw new Error(`Invalid ask_user arguments: ${error.message}`);
20488
- throw error;
20905
+ };
20906
+ var scanSkillsByLevel = async (workspace, home = homedir3(), builtinDir = BUILTIN_SKILLS_DIR, report = warnPromptDiagnostic) => {
20907
+ const scan = async (dir, level) => (await scanSkillsDir(dir, report)).map((skill) => ({ ...skill, level }));
20908
+ const [builtin, user, project] = await Promise.all([
20909
+ builtinDir === null ? [] : scan(builtinDir, "builtin"),
20910
+ scan(userSkillsDir(home), "user"),
20911
+ scan(workspaceSkillsDir(workspace), "project")
20912
+ ]);
20913
+ return [...project, ...user, ...builtin];
20914
+ };
20915
+
20916
+ // packages/core/src/prompt/snapshots.ts
20917
+ var createResourceSnapshots = (load, submission) => {
20918
+ const conversations = new Map;
20919
+ const stateFor = (id, workspace) => {
20920
+ let state = conversations.get(id);
20921
+ if (!state || state.workspace !== workspace) {
20922
+ state = { workspace, pending: new Map };
20923
+ conversations.set(id, state);
20924
+ }
20925
+ return state;
20926
+ };
20927
+ return {
20928
+ stage(id, requestId, workspace, resources) {
20929
+ stateFor(id, workspace).pending.set(requestId, {
20930
+ contextFiles: resources.contextFiles,
20931
+ skills: resources.skills
20932
+ });
20933
+ },
20934
+ async current(id, workspace) {
20935
+ const state = stateFor(id, workspace);
20936
+ for (const [requestId, snapshot] of state.pending) {
20937
+ const record = await submission(id, requestId);
20938
+ if (!record || record.status === "queued")
20939
+ continue;
20940
+ state.pending.delete(requestId);
20941
+ if (record.type !== "input" || record.entry === undefined)
20942
+ continue;
20943
+ if (state.entry === undefined || record.entry > state.entry) {
20944
+ state.active = snapshot;
20945
+ state.entry = record.entry;
20946
+ }
20489
20947
  }
20490
- const questions = service();
20491
- const created = await questions.ensure(api.conversationId, api.taskId, api.callId, args, context);
20492
- await api.details(questionDetails(created), context);
20493
- const record = created.status === "pending" ? await questions.wait(api.conversationId, api.taskId, context) : created;
20494
- return {
20495
- output: [{ type: "text", text: questionOutput(record) }],
20496
- structuredOutput: outcomeOf(record),
20497
- details: questionDetails(record),
20498
- isError: record.status === "cancelled"
20499
- };
20948
+ state.active ??= load(workspace);
20949
+ return state.active;
20950
+ },
20951
+ discard(id, requestId) {
20952
+ conversations.get(id)?.pending.delete(requestId);
20953
+ },
20954
+ move(id, from, to) {
20955
+ const pending = conversations.get(id)?.pending;
20956
+ const snapshot = pending?.get(from);
20957
+ if (!pending || !snapshot)
20958
+ return;
20959
+ pending.delete(from);
20960
+ pending.set(to, snapshot);
20961
+ },
20962
+ forget(id) {
20963
+ conversations.delete(id);
20500
20964
  }
20501
- });
20502
- }
20503
- var createQuestionExtension = (service) => defineExtension6({ name: "neko-questions", tools: [createAskUserTool(service)] });
20965
+ };
20966
+ };
20504
20967
 
20505
20968
  // packages/core/src/session-metadata.ts
20506
- var SessionMetadataDoc = defineDoc4({
20969
+ import { stripVTControlCharacters } from "util";
20970
+ import { getSupportedThinkingLevels } from "@earendil-works/pi-ai";
20971
+ import {
20972
+ defineDoc as defineDoc5,
20973
+ defineExtension as defineExtension8,
20974
+ defineTask as defineTask3,
20975
+ InboxDoc as InboxDoc2,
20976
+ LiveDoc as LiveDoc2
20977
+ } from "@earendil-works/pi-durable";
20978
+ var SessionMetadataDoc = defineDoc5({
20507
20979
  kind: "app.neko.session-metadata",
20508
20980
  version: 1,
20509
20981
  scope: "conversation",
@@ -20526,6 +20998,8 @@ var SessionMetadataDoc = defineDoc4({
20526
20998
  namingUsage: { models: {}, tools: {} }
20527
20999
  })
20528
21000
  });
21001
+ var NAMING_REQUEST_TIMEOUT_MS = 20000;
21002
+ var NAMING_RETRY_DELAY_MS = 2000;
20529
21003
  function shortText(value, limit = 60) {
20530
21004
  return [
20531
21005
  ...stripVTControlCharacters(value).replace(/\p{Cc}/gu, " ").replace(/\s+/g, " ").trim()
@@ -20563,7 +21037,7 @@ function mergeUsage(base, extra) {
20563
21037
  }
20564
21038
  return result;
20565
21039
  }
20566
- function namingTask(canUse) {
21040
+ function namingTask(canUse, { requestTimeoutMs, retryDelayMs }) {
20567
21041
  let active = 0;
20568
21042
  const waits = new Set;
20569
21043
  return defineTask3({
@@ -20614,7 +21088,7 @@ function namingTask(canUse) {
20614
21088
  return { status: "running", checkpoint: { phase: "generate", attempt: d.attempts } };
20615
21089
  }, context);
20616
21090
  let title = "", usage;
20617
- const signal = AbortSignal.any([runtime.signal, AbortSignal.timeout(20000)]);
21091
+ const signal = AbortSignal.any([runtime.signal, AbortSignal.timeout(requestTimeoutMs)]);
20618
21092
  try {
20619
21093
  const budget = Math.max(32, Math.min(12000, Math.floor((model.contextWindow - 512) / 4)));
20620
21094
  const response = await Promise.race([
@@ -20653,7 +21127,7 @@ function namingTask(canUse) {
20653
21127
  d.answer = "";
20654
21128
  } else
20655
21129
  d.naming = d.attempts >= 2 ? "fallback" : "pending";
20656
- return title || d.attempts >= 2 ? { status: "terminal", outcome: { status: "completed", result: null } } : { status: "running", checkpoint: { phase: "wait", until: runtime.now() + 2000 } };
21130
+ return title || d.attempts >= 2 ? { status: "terminal", outcome: { status: "completed", result: null } } : { status: "running", checkpoint: { phase: "wait", until: runtime.now() + retryDelayMs } };
20657
21131
  }, context);
20658
21132
  } finally {
20659
21133
  active--;
@@ -20685,13 +21159,16 @@ class SessionMetadataManager {
20685
21159
  storage;
20686
21160
  context;
20687
21161
  report;
20688
- constructor(harness, storage, context, registry, report = console.error, canUseModel = async () => true) {
20689
- this.task = namingTask(canUseModel);
21162
+ constructor(harness, storage, context, registry, report = console.error, canUseModel = async () => true, timings = {}) {
21163
+ this.task = namingTask(canUseModel, {
21164
+ requestTimeoutMs: timings.requestTimeoutMs ?? NAMING_REQUEST_TIMEOUT_MS,
21165
+ retryDelayMs: timings.retryDelayMs ?? NAMING_RETRY_DELAY_MS
21166
+ });
20690
21167
  this.harness = harness;
20691
21168
  this.storage = storage;
20692
21169
  this.context = context;
20693
21170
  this.report = report;
20694
- registry.install(defineExtension7({ name: "neko-naming", tasks: [this.task] }));
21171
+ registry.install(defineExtension8({ name: "neko-naming", tasks: [this.task] }));
20695
21172
  this.#unsubscribe = harness.subscribeCommits(({ changes }) => {
20696
21173
  const ids = new Set;
20697
21174
  const submissions = new Map;
@@ -20704,7 +21181,7 @@ class SessionMetadataManager {
20704
21181
  submissions.set(c.value.conversationId, records);
20705
21182
  }
20706
21183
  if (c.type === "document" && c.conversationId) {
20707
- if (c.record.kind === "pi.agent" || c.record.kind === QuestionsDoc.definition.kind)
21184
+ if (c.record.kind === "pi.agent" || c.record.kind === QuestionsDoc.definition.kind || c.record.kind === PlanDoc.definition.kind)
20708
21185
  ids.add(c.conversationId);
20709
21186
  if (c.record.kind === "pi.live") {
20710
21187
  const running = c.value?.run !== undefined;
@@ -20773,7 +21250,7 @@ class SessionMetadataManager {
20773
21250
  cursor = page.next;
20774
21251
  } while (cursor);
20775
21252
  records.sort((a, b) => a.id - b.id);
20776
- const inbox = await this.harness.snapshot(InboxDoc, id, this.context);
21253
+ const inbox = await this.harness.snapshot(InboxDoc2, id, this.context);
20777
21254
  const updates = await Promise.all(records.filter((r) => r.type === "input").map(async (record) => {
20778
21255
  const entry = record.entry === undefined ? undefined : (await this.storage.entry(record.entry, this.context))?.entry;
20779
21256
  const queued = inbox?.items.find((i) => i.id === record.id && i.mode !== "write");
@@ -20783,10 +21260,13 @@ class SessionMetadataManager {
20783
21260
  }));
20784
21261
  const live = await this.harness.snapshot(LiveDoc2, id, this.context);
20785
21262
  const awaiting = live?.run !== undefined && ((await this.harness.snapshot(QuestionsDoc, id, this.context))?.pending.length ?? 0) > 0;
21263
+ const awaitingPlan = (await this.harness.snapshot(PlanDoc, id, this.context))?.items.some((item) => item.status === "awaiting_approval");
20786
21264
  await this.harness.commit(async (tx) => {
20787
21265
  const d = await tx.doc(SessionMetadataDoc, id);
20788
21266
  if ((d.awaiting ?? false) !== awaiting)
20789
21267
  d.awaiting = awaiting;
21268
+ if ((d.awaitingPlan ?? false) !== (awaitingPlan ?? false))
21269
+ d.awaitingPlan = awaitingPlan ?? false;
20790
21270
  for (const { record, data, answer } of updates) {
20791
21271
  if (!data)
20792
21272
  continue;
@@ -20834,9 +21314,9 @@ class SessionMetadataManager {
20834
21314
  }
20835
21315
 
20836
21316
  // packages/core/src/todos.ts
20837
- import { defineDoc as defineDoc5, defineExtension as defineExtension8, defineTool as defineTool5 } from "@earendil-works/pi-durable";
20838
- import { Type as Type5 } from "typebox";
20839
- var TodosDoc = defineDoc5({
21317
+ import { defineDoc as defineDoc6, defineExtension as defineExtension9, defineTool as defineTool6 } from "@earendil-works/pi-durable";
21318
+ import { Type as Type6 } from "typebox";
21319
+ var TodosDoc = defineDoc6({
20840
21320
  kind: "app.neko.todos",
20841
21321
  version: 1,
20842
21322
  scope: "conversation",
@@ -20844,30 +21324,30 @@ var TodosDoc = defineDoc5({
20844
21324
  fork: "asOf",
20845
21325
  initial: () => ({ revision: 0, items: [] })
20846
21326
  });
20847
- var TodoReceiptDoc = defineDoc5({
21327
+ var TodoReceiptDoc = defineDoc6({
20848
21328
  kind: "app.neko.todo-receipt",
20849
21329
  version: 1,
20850
21330
  scope: "task",
20851
21331
  initial: () => ({ applied: false, revision: 0, items: [] })
20852
21332
  });
20853
- var itemSchema = Type5.Object({
20854
- id: Type5.String({
21333
+ var itemSchema = Type6.Object({
21334
+ id: Type6.String({
20855
21335
  minLength: 1,
20856
21336
  maxLength: TODO_LIMITS.idChars,
20857
21337
  description: "Stable id; keep it when you update the same task."
20858
21338
  }),
20859
- text: Type5.String({ minLength: 1, maxLength: TODO_LIMITS.textChars }),
20860
- status: Type5.String({ enum: [...TODO_STATUSES] })
21339
+ text: Type6.String({ minLength: 1, maxLength: TODO_LIMITS.textChars }),
21340
+ status: Type6.String({ enum: [...TODO_STATUSES] })
20861
21341
  }, { additionalProperties: false });
20862
- var parameters4 = Type5.Object({
20863
- action: Type5.String({ enum: ["list", "replace"], description: "list reads the list; replace overwrites it." }),
20864
- items: Type5.Optional(Type5.Array(itemSchema, {
21342
+ var parameters5 = Type6.Object({
21343
+ action: Type6.String({ enum: ["list", "replace"], description: "list reads the list; replace overwrites it." }),
21344
+ items: Type6.Optional(Type6.Array(itemSchema, {
20865
21345
  maxItems: TODO_LIMITS.maxItems,
20866
21346
  description: "Required for replace only: the complete ordered list; [] clears it."
20867
21347
  }))
20868
21348
  }, { additionalProperties: false });
20869
- var structuredOutputSchema3 = Type5.Object({ revision: Type5.Integer({ minimum: 0 }), items: Type5.Array(itemSchema) }, { additionalProperties: false });
20870
- var clone2 = (value) => JSON.parse(JSON.stringify(value));
21349
+ var structuredOutputSchema3 = Type6.Object({ revision: Type6.Integer({ minimum: 0 }), items: Type6.Array(itemSchema) }, { additionalProperties: false });
21350
+ var clone3 = (value) => JSON.parse(JSON.stringify(value));
20871
21351
  function renderTodos(list) {
20872
21352
  const { completed, total } = todoProgress(list.items);
20873
21353
  if (!total)
@@ -20880,10 +21360,10 @@ function renderTodos(list) {
20880
21360
  `);
20881
21361
  }
20882
21362
  function createTodoTool() {
20883
- return defineTool5({
21363
+ return defineTool6({
20884
21364
  name: "todo",
20885
21365
  description: "Keep a structured todo list for the current multi-step task. list: read the saved list. replace: items [{id, text, status}] overwrites the whole ordered list (status pending, in_progress or completed; [] clears it). Keep ids stable when updating a task and mark completed only after the work is actually done. Users see the list but cannot edit it.",
20886
- parameters: parameters4,
21366
+ parameters: parameters5,
20887
21367
  structuredOutputSchema: structuredOutputSchema3,
20888
21368
  replay: "safe",
20889
21369
  executionMode: "sequential",
@@ -20898,7 +21378,7 @@ function createTodoTool() {
20898
21378
  if (args.items !== undefined)
20899
21379
  throw new Error("list does not accept items.");
20900
21380
  const saved = await api.snapshot(TodosDoc, api.conversationId, context);
20901
- list = saved ? clone2(saved) : { revision: 0, items: [] };
21381
+ list = saved ? clone3(saved) : { revision: 0, items: [] };
20902
21382
  } else {
20903
21383
  if (args.items === undefined)
20904
21384
  throw new Error("replace requires items.");
@@ -20906,9 +21386,9 @@ function createTodoTool() {
20906
21386
  list = await api.commit(async (tx) => {
20907
21387
  const receipt = await tx.doc(TodoReceiptDoc, api.taskId);
20908
21388
  if (receipt.applied) {
20909
- if (JSON.stringify(clone2(receipt.items)) !== JSON.stringify(items))
21389
+ if (JSON.stringify(clone3(receipt.items)) !== JSON.stringify(items))
20910
21390
  throw new Error("This todo call was already applied with different items.");
20911
- return { revision: receipt.revision, items: clone2(receipt.items) };
21391
+ return { revision: receipt.revision, items: clone3(receipt.items) };
20912
21392
  }
20913
21393
  const doc = await tx.doc(TodosDoc, api.conversationId);
20914
21394
  doc.revision += 1;
@@ -20916,7 +21396,7 @@ function createTodoTool() {
20916
21396
  receipt.applied = true;
20917
21397
  receipt.revision = doc.revision;
20918
21398
  receipt.items = items;
20919
- return { revision: doc.revision, items: clone2(items) };
21399
+ return { revision: doc.revision, items: clone3(items) };
20920
21400
  }, context);
20921
21401
  }
20922
21402
  const { completed, total } = todoProgress(list.items);
@@ -20928,21 +21408,21 @@ function createTodoTool() {
20928
21408
  }
20929
21409
  });
20930
21410
  }
20931
- var createTodoExtension = () => defineExtension8({ name: "neko-todos", tools: [createTodoTool()] });
21411
+ var createTodoExtension = () => defineExtension9({ name: "neko-todos", tools: [createTodoTool()] });
20932
21412
 
20933
21413
  // packages/core/src/tool-approvals.ts
20934
- import { randomUUID as randomUUID2 } from "crypto";
21414
+ import { randomUUID as randomUUID3 } from "crypto";
20935
21415
  import { realpath } from "fs/promises";
20936
21416
  import {
20937
21417
  configure as configure2,
20938
- defineDoc as defineDoc6,
20939
- defineExtension as defineExtension9,
20940
- hook as hook2,
20941
- ToolTask as ToolTask2,
20942
- wrapTool
21418
+ defineDoc as defineDoc7,
21419
+ defineExtension as defineExtension10,
21420
+ hook as hook3,
21421
+ ToolTask as ToolTask3,
21422
+ wrapTool as wrapTool2
20943
21423
  } from "@earendil-works/pi-durable";
20944
- import { createBashTool } from "@earendil-works/pi-durable/tools";
20945
- var ApprovalsDoc = defineDoc6({
21424
+ import { createBashTool as createBashTool2 } from "@earendil-works/pi-durable/tools";
21425
+ var ApprovalsDoc = defineDoc7({
20946
21426
  kind: "app.neko.approvals",
20947
21427
  version: 1,
20948
21428
  scope: "conversation",
@@ -21041,7 +21521,7 @@ class ToolApprovals {
21041
21521
  }
21042
21522
  doc.items.push({
21043
21523
  ...candidate,
21044
- version: randomUUID2(),
21524
+ version: randomUUID3(),
21045
21525
  status: "pending",
21046
21526
  createdAt: Date.now(),
21047
21527
  decidedAt: null
@@ -21050,10 +21530,10 @@ class ToolApprovals {
21050
21530
  }
21051
21531
  }
21052
21532
  function createApprovalExtension(service) {
21053
- return defineExtension9({
21533
+ return defineExtension10({
21054
21534
  name: "neko-tool-approvals",
21055
21535
  hooks: [
21056
- hook2(ToolTask2, {
21536
+ hook3(ToolTask3, {
21057
21537
  beforeTool: async (call, api, context) => {
21058
21538
  const role = await api.snapshot(SubagentDoc, api.conversationId, context);
21059
21539
  if (role?.role !== "explore" || call.name !== "bash")
@@ -21083,7 +21563,7 @@ function createApprovalExtension(service) {
21083
21563
  })
21084
21564
  ],
21085
21565
  wraps: [
21086
- wrapTool(createBashTool(), (bash) => ({
21566
+ wrapTool2(createBashTool2(), (bash) => ({
21087
21567
  ...bash,
21088
21568
  execute: async (args, api, context) => {
21089
21569
  const role = await api.snapshot(SubagentDoc, api.conversationId, context);
@@ -21107,7 +21587,7 @@ async function prepareApprovalConversations(harness, ids, extension, context) {
21107
21587
  const child = await harness.conversation(id, context);
21108
21588
  const agent = await child.agent(context);
21109
21589
  await harness.commit((tx) => configure2(tx, id, {
21110
- tools: [...agent.tools.filter((t) => t.name !== "bash"), createBashTool()],
21590
+ tools: [...agent.tools.filter((t) => t.name !== "bash"), createBashTool2()],
21111
21591
  extensions: { add: [extension] }
21112
21592
  }), context);
21113
21593
  }
@@ -21334,8 +21814,8 @@ function createDeepseekProvider(resolveKey, fetchImpl = fetch, options = {}) {
21334
21814
  }
21335
21815
 
21336
21816
  // packages/core/src/web-search/index.ts
21337
- import { Type as Type6 } from "@earendil-works/pi-ai";
21338
- import { defineExtension as defineExtension10, defineTool as defineTool6 } from "@earendil-works/pi-durable";
21817
+ import { Type as Type7 } from "@earendil-works/pi-ai";
21818
+ import { defineExtension as defineExtension11, defineTool as defineTool7 } from "@earendil-works/pi-durable";
21339
21819
  var MAX_URLS = 20;
21340
21820
  var MAX_RESULTS = 10;
21341
21821
  var DEFAULT_RESULTS = 5;
@@ -21353,15 +21833,15 @@ function content(text) {
21353
21833
  return { output: [{ type: "text", text }] };
21354
21834
  }
21355
21835
  function createWebSearchTools(resolveProvider) {
21356
- return defineExtension10({
21836
+ return defineExtension11({
21357
21837
  name: "neko-web-search",
21358
21838
  tools: [
21359
- defineTool6({
21839
+ defineTool7({
21360
21840
  name: "web_search",
21361
21841
  description: "Search the live web for current or unknown information. Returns ranked sources (title, URL, bounded excerpts). Keep the query short and focused (ideally under 128 characters). Use only when up-to-date or external information is needed; use web_extract to read a known URL.",
21362
- parameters: Type6.Object({
21363
- query: Type6.String({ minLength: 1, maxLength: 4096, description: "The search query" }),
21364
- maxResults: Type6.Optional(Type6.Number({ description: `Maximum number of results (1-${MAX_RESULTS}, default ${DEFAULT_RESULTS})` }))
21842
+ parameters: Type7.Object({
21843
+ query: Type7.String({ minLength: 1, maxLength: 4096, description: "The search query" }),
21844
+ maxResults: Type7.Optional(Type7.Number({ description: `Maximum number of results (1-${MAX_RESULTS}, default ${DEFAULT_RESULTS})` }))
21365
21845
  }),
21366
21846
  async execute({ query, maxResults }, _api, context) {
21367
21847
  try {
@@ -21387,11 +21867,11 @@ ${item.date ? `Published: ${item.date}
21387
21867
  }
21388
21868
  }
21389
21869
  }),
21390
- defineTool6({
21870
+ defineTool7({
21391
21871
  name: "web_extract",
21392
21872
  description: "Extract bounded markdown excerpts from web pages by URL. Every requested URL has a result or failure entry; long content is truncated.",
21393
- parameters: Type6.Object({
21394
- urls: Type6.Array(Type6.String({ maxLength: 2048, description: "http(s) URL" }), {
21873
+ parameters: Type7.Object({
21874
+ urls: Type7.Array(Type7.String({ maxLength: 2048, description: "http(s) URL" }), {
21395
21875
  minItems: 1,
21396
21876
  maxItems: MAX_URLS,
21397
21877
  description: `URLs to extract (1-${MAX_URLS})`
@@ -22116,7 +22596,7 @@ function cancellable(work, signal) {
22116
22596
  var DEFAULT_MODEL = { provider: "deepseek", modelId: "deepseek-flash" };
22117
22597
  var DEFAULT_STORAGE_PATH = join5(homedir4(), ".neko", "neko.sqlite");
22118
22598
  var neverAborted = () => new AbortController().signal;
22119
- var ArchivedDoc = defineDoc7({
22599
+ var ArchivedDoc = defineDoc8({
22120
22600
  kind: "app.neko.conversation",
22121
22601
  version: 1,
22122
22602
  scope: "conversation",
@@ -22124,7 +22604,7 @@ var ArchivedDoc = defineDoc7({
22124
22604
  fork: "initial",
22125
22605
  initial: () => ({ archived: false })
22126
22606
  });
22127
- var WorkspacesDoc = defineDoc7({
22607
+ var WorkspacesDoc = defineDoc8({
22128
22608
  kind: "app.neko.workspaces",
22129
22609
  version: 1,
22130
22610
  scope: "conversation",
@@ -22132,7 +22612,7 @@ var WorkspacesDoc = defineDoc7({
22132
22612
  fork: "initial",
22133
22613
  initial: () => ({ items: [] })
22134
22614
  });
22135
- var SearchSettingsDoc = defineDoc7({
22615
+ var SearchSettingsDoc = defineDoc8({
22136
22616
  kind: "app.neko.search-settings",
22137
22617
  version: 1,
22138
22618
  scope: "conversation",
@@ -22140,7 +22620,7 @@ var SearchSettingsDoc = defineDoc7({
22140
22620
  fork: "initial",
22141
22621
  initial: () => ({ provider: null })
22142
22622
  });
22143
- var ProviderSettingsDoc = defineDoc7({
22623
+ var ProviderSettingsDoc = defineDoc8({
22144
22624
  kind: "app.neko.provider-settings",
22145
22625
  version: 1,
22146
22626
  scope: "conversation",
@@ -22148,7 +22628,7 @@ var ProviderSettingsDoc = defineDoc7({
22148
22628
  fork: "initial",
22149
22629
  initial: () => ({ enabled: null })
22150
22630
  });
22151
- var DefaultModelDoc = defineDoc7({
22631
+ var DefaultModelDoc = defineDoc8({
22152
22632
  kind: "app.neko.default-model",
22153
22633
  version: 1,
22154
22634
  scope: "conversation",
@@ -22156,7 +22636,7 @@ var DefaultModelDoc = defineDoc7({
22156
22636
  fork: "initial",
22157
22637
  initial: () => ({ model: null })
22158
22638
  });
22159
- var ContextSettingsDoc = defineDoc7({
22639
+ var ContextSettingsDoc = defineDoc8({
22160
22640
  kind: "app.neko.context-settings",
22161
22641
  version: 1,
22162
22642
  scope: "conversation",
@@ -22164,7 +22644,7 @@ var ContextSettingsDoc = defineDoc7({
22164
22644
  fork: "initial",
22165
22645
  initial: emptySavedContextSettings
22166
22646
  });
22167
- var ActivityDoc = defineDoc7({
22647
+ var ActivityDoc = defineDoc8({
22168
22648
  kind: "app.neko.activity",
22169
22649
  version: 1,
22170
22650
  scope: "conversation",
@@ -22263,11 +22743,13 @@ async function createNekoRuntime(options = {}) {
22263
22743
  registry.install(createSubagentExtension(async (id, requestId, cwd) => {
22264
22744
  resourceSnapshots.stage(id, requestId, cwd, await loadResources(cwd));
22265
22745
  }, resourceSnapshots.discard, (ref) => guardProvider(ref)));
22266
- const jobs = new JobManager;
22746
+ const jobs = new JobManager(options.timings?.jobStartWindowMs === undefined ? {} : { startWindowMs: options.timings.jobStartWindowMs });
22267
22747
  registry.install(jobs.extension);
22268
22748
  registry.install(createTodoExtension());
22269
22749
  let questions;
22270
22750
  registry.install(createQuestionExtension(() => questions));
22751
+ let plans;
22752
+ registry.install(createPlanExtension(() => plans));
22271
22753
  for (const extension of options.extensions ?? [])
22272
22754
  registry.install(extension);
22273
22755
  let approvals;
@@ -22279,6 +22761,7 @@ async function createNekoRuntime(options = {}) {
22279
22761
  models,
22280
22762
  registry,
22281
22763
  settings: {
22764
+ ...options.timings?.retryBaseDelayMs === undefined ? {} : { retry: { baseDelayMs: options.timings.retryBaseDelayMs } },
22282
22765
  get compaction() {
22283
22766
  return compactionPolicy(contextState, allModels().filter((model) => !isModelProvider(model.provider) || enabled.has(model.provider)).map((model) => model.contextWindow));
22284
22767
  }
@@ -22301,6 +22784,7 @@ async function createNekoRuntime(options = {}) {
22301
22784
  }, context);
22302
22785
  approvals = new ToolApprovals(harness, context);
22303
22786
  questions = new UserQuestions(harness, context);
22787
+ plans = new PlanService(harness, context);
22304
22788
  const changes = new ChangeTracker(harness, context);
22305
22789
  const existing = await scanConversations();
22306
22790
  questions.track(...existing.map((r) => r.id));
@@ -22376,7 +22860,10 @@ async function createNekoRuntime(options = {}) {
22376
22860
  };
22377
22861
  if (existing.length === 0)
22378
22862
  await touchConversation(conversation.id);
22379
- const metadata = new SessionMetadataManager(harness, storage, context, registry, options.onReport, (provider, modelId) => guardProvider({ provider, modelId }).then(() => true, () => false));
22863
+ const metadata = new SessionMetadataManager(harness, storage, context, registry, options.onReport, (provider, modelId) => guardProvider({ provider, modelId }).then(() => true, () => false), {
22864
+ ...options.timings?.namingRequestTimeoutMs === undefined ? {} : { requestTimeoutMs: options.timings.namingRequestTimeoutMs },
22865
+ ...options.timings?.namingRetryDelayMs === undefined ? {} : { retryDelayMs: options.timings.namingRetryDelayMs }
22866
+ });
22380
22867
  if (existing.length === 0)
22381
22868
  await metadata.initialize(conversation.id);
22382
22869
  for (const record of existing)
@@ -22417,11 +22904,17 @@ async function createNekoRuntime(options = {}) {
22417
22904
  admissions.delete(id);
22418
22905
  }
22419
22906
  };
22420
- const submitTo = async (target, content, submitOptions) => admit(target.id, async () => {
22907
+ const syncPlanAgent = async (target) => {
22908
+ const mode = await plans.mode(target.id);
22909
+ if (mode === "plan" !== agentInPlanMode(await target.agent(context)))
22910
+ await target.configure(planAgentChange(mode), context);
22911
+ };
22912
+ const submitTo = async (target, content, submitOptions, extra) => admit(target.id, async () => {
22913
+ await syncPlanAgent(target);
22421
22914
  const agent = await target.agent(context);
22422
22915
  const workspace = executionWorkspace(agent.cwd);
22423
22916
  const duplicate = submitOptions?.requestId !== undefined && await storage.submissionByRequest(target.id, submitOptions.requestId, context);
22424
- const data = { at: Date.now(), prompt: "", images: [] };
22917
+ const data = { at: Date.now(), prompt: "", images: [], ...extra };
22425
22918
  let invocationName;
22426
22919
  if (!duplicate && typeof content === "string" && content.startsWith("/skill:")) {
22427
22920
  const end = content.indexOf(" ");
@@ -22485,10 +22978,25 @@ async function createNekoRuntime(options = {}) {
22485
22978
  resourceSnapshots.discard(target.id, requestId);
22486
22979
  throw error;
22487
22980
  }
22981
+ if (!extra?.planDecision)
22982
+ await plans.supersedePending(target.id);
22488
22983
  await touchConversation(target.id);
22489
22984
  await metadata.reconcile(target.id);
22490
22985
  return result;
22491
22986
  });
22987
+ const dispatchPlan = async (target, item) => {
22988
+ const approve = item.status === "approved";
22989
+ await submitTo(target, approve ? approvedPlanPrompt(item) : planRevisionPrompt(item, item.feedback ?? ""), { requestId: `${approve ? "plan-exec" : "plan-revise"}:${item.version}`, whenBusy: "followUp" }, {
22990
+ planDecision: {
22991
+ version: item.version,
22992
+ kind: approve ? "approve" : "revise",
22993
+ title: item.title,
22994
+ ...!approve && item.feedback ? { feedback: item.feedback } : {}
22995
+ }
22996
+ });
22997
+ await plans.markDispatched(target.id, item.version);
22998
+ };
22999
+ const isRunning = async (id) => (await harness.snapshot(importLiveDoc, id, context))?.run !== undefined;
22492
23000
  const waitAnswerOf = async (target, submission) => {
22493
23001
  const settled = await submission.wait(context);
22494
23002
  await metadata.flush();
@@ -22553,6 +23061,38 @@ async function createNekoRuntime(options = {}) {
22553
23061
  respondQuestion(id, version, response) {
22554
23062
  return questions.respond(target.id, id, version, response, context);
22555
23063
  },
23064
+ plan() {
23065
+ return plans.view(target.id);
23066
+ },
23067
+ planRecord(version) {
23068
+ return plans.get(target.id, version);
23069
+ },
23070
+ async setPlanMode(mode) {
23071
+ if (mode !== "normal" && mode !== "plan")
23072
+ throw new Error("Unknown plan mode.");
23073
+ return admit(target.id, async () => {
23074
+ if (await isRunning(target.id))
23075
+ throw new Error(PLAN_BUSY);
23076
+ await plans.setMode(target.id, mode);
23077
+ await syncPlanAgent(target);
23078
+ return plans.view(target.id);
23079
+ });
23080
+ },
23081
+ async respondPlan(version, decision) {
23082
+ const parsed = validatePlanDecision(decision);
23083
+ const known = await plans.get(target.id, version);
23084
+ if (!known)
23085
+ throw new Error("Plan not found.");
23086
+ if (known.status === "awaiting_approval") {
23087
+ if (await isRunning(target.id))
23088
+ throw new Error(PLAN_BUSY);
23089
+ await guardProvider((await target.agent(context)).model);
23090
+ }
23091
+ const { record, item } = await plans.decide(target.id, version, parsed);
23092
+ if ((item.status === "approved" || item.status === "revision_requested") && !item.dispatched)
23093
+ await dispatchPlan(target, item);
23094
+ return record;
23095
+ },
22556
23096
  async workspaceDir(path, signal) {
22557
23097
  return workspaceFiles.children(savedWorkspace((await target.agent(context)).cwd), path, signal ?? neverAborted());
22558
23098
  },
@@ -22578,12 +23118,13 @@ async function createNekoRuntime(options = {}) {
22578
23118
  await target.compact(instructions?.trim() || undefined, context);
22579
23119
  });
22580
23120
  },
22581
- abort() {
22582
- return target.abort(context);
23121
+ async abort() {
23122
+ await target.abort(context);
23123
+ await plans.cancelPending(target.id);
22583
23124
  },
22584
23125
  async withdraw(submissionId) {
22585
23126
  const id = submissionId;
22586
- const inbox = await harness.snapshot(InboxDoc2, target.id, context);
23127
+ const inbox = await harness.snapshot(InboxDoc3, target.id, context);
22587
23128
  const item = inbox?.items.find((i) => i.id === id);
22588
23129
  const input = item ? queuedUserInput(item) : undefined;
22589
23130
  if (item && !input)
@@ -22600,7 +23141,7 @@ async function createNekoRuntime(options = {}) {
22600
23141
  },
22601
23142
  async steer(submissionId) {
22602
23143
  const id = submissionId;
22603
- const inbox = await harness.snapshot(InboxDoc2, target.id, context);
23144
+ const inbox = await harness.snapshot(InboxDoc3, target.id, context);
22604
23145
  const item = inbox?.items.find((i) => i.id === id);
22605
23146
  const input = item ? queuedUserInput(item) : undefined;
22606
23147
  if (item && (!input || item.mode === "write"))
@@ -22650,6 +23191,19 @@ async function createNekoRuntime(options = {}) {
22650
23191
  const record = await harness.commit((tx) => tx.conversation(id), context);
22651
23192
  return archived || record?.owner ? undefined : makeSession(target);
22652
23193
  };
23194
+ for (const id of new Set([conversation.id, ...existing.map((r) => r.id)])) {
23195
+ try {
23196
+ const session = await getConversation(id);
23197
+ if (!session)
23198
+ continue;
23199
+ await admit(id, () => syncPlanAgent(session.conversation));
23200
+ const pending = await plans.undispatched(id);
23201
+ if (pending)
23202
+ await dispatchPlan(session.conversation, pending);
23203
+ } catch (error) {
23204
+ options.onReport?.(error);
23205
+ }
23206
+ }
22653
23207
  return {
22654
23208
  context,
22655
23209
  async compact(instructions, id) {
@@ -22788,6 +23342,7 @@ async function createNekoRuntime(options = {}) {
22788
23342
  lastMessageAt: info.lastMessageAt,
22789
23343
  status: info.status,
22790
23344
  awaitingAnswer: info.awaiting === true,
23345
+ awaitingPlan: info.awaitingPlan === true,
22791
23346
  model: (await target.agent(context)).model ?? null,
22792
23347
  id: record.id,
22793
23348
  archived: false,
@@ -22990,7 +23545,7 @@ async function createNekoRuntime(options = {}) {
22990
23545
  }
22991
23546
  // packages/gateway/src/contract.ts
22992
23547
  import { hostNamed, method, remote } from "@earendil-works/chord";
22993
- import { Type as Type7 } from "typebox";
23548
+ import { Type as Type8 } from "typebox";
22994
23549
  var GATEWAY_HOST = "neko";
22995
23550
  var rpc = (args, result) => [args, result];
22996
23551
  function contract(name, def) {
@@ -23003,19 +23558,19 @@ function contract(name, def) {
23003
23558
  ]));
23004
23559
  return remote(name, { ...def, host: hostNamed(GATEWAY_HOST), variants });
23005
23560
  }
23006
- var str = Type7.String();
23007
- var num = Type7.Number();
23008
- var bool = Type7.Boolean();
23009
- var nil = Type7.Null();
23010
- var integer = Type7.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER });
23011
- var nullableString = Type7.Union([str, nil]);
23012
- var phase = Type7.Enum(["pending", "running", "done"]);
23013
- var level = Type7.Enum(["project", "user", "builtin"]);
23014
- var thinking = Type7.Enum(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
23015
- var model2 = Type7.Object({ provider: str, modelId: str });
23016
- var change = Type7.Object({ model: Type7.Optional(model2), thinkingLevel: Type7.Optional(thinking) }, { additionalProperties: false });
23017
- var usage = Type7.Object({ input: num, output: num, cacheRead: num, cacheWrite: num, cost: num });
23018
- var image = Type7.Object({
23561
+ var str = Type8.String();
23562
+ var num = Type8.Number();
23563
+ var bool = Type8.Boolean();
23564
+ var nil = Type8.Null();
23565
+ var integer = Type8.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER });
23566
+ var nullableString = Type8.Union([str, nil]);
23567
+ var phase = Type8.Enum(["pending", "running", "done"]);
23568
+ var level = Type8.Enum(["project", "user", "builtin"]);
23569
+ var thinking = Type8.Enum(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
23570
+ var model2 = Type8.Object({ provider: str, modelId: str });
23571
+ var change = Type8.Object({ model: Type8.Optional(model2), thinkingLevel: Type8.Optional(thinking) }, { additionalProperties: false });
23572
+ var usage = Type8.Object({ input: num, output: num, cacheRead: num, cacheWrite: num, cost: num });
23573
+ var image = Type8.Object({
23019
23574
  name: str,
23020
23575
  mimeType: str,
23021
23576
  width: integer,
@@ -23024,8 +23579,8 @@ var image = Type7.Object({
23024
23579
  entryId: integer,
23025
23580
  contentIndex: integer
23026
23581
  });
23027
- var skill = Type7.Object({ name: str, location: str, body: str, args: str });
23028
- var call = Type7.Object({
23582
+ var skill = Type8.Object({ name: str, location: str, body: str, args: str });
23583
+ var call = Type8.Object({
23029
23584
  callId: str,
23030
23585
  name: str,
23031
23586
  summary: str,
@@ -23033,262 +23588,289 @@ var call = Type7.Object({
23033
23588
  phase,
23034
23589
  output: nullableString,
23035
23590
  diff: nullableString,
23036
- diffAdded: Type7.Union([num, nil]),
23037
- diffRemoved: Type7.Union([num, nil]),
23038
- diffTruncated: Type7.Optional(bool)
23591
+ diffAdded: Type8.Union([num, nil]),
23592
+ diffRemoved: Type8.Union([num, nil]),
23593
+ diffTruncated: Type8.Optional(bool)
23039
23594
  });
23040
- var DelegationSchema = Type7.Object({
23595
+ var DelegationSchema = Type8.Object({
23041
23596
  parentId: integer,
23042
- agentId: Type7.Optional(str),
23043
- round: Type7.Optional(integer),
23044
- runnerId: Type7.Optional(integer),
23045
- inputEntryId: Type7.Optional(integer),
23046
- answerEntryId: Type7.Optional(integer),
23047
- roundUsage: Type7.Optional(Type7.Union([usage, nil])),
23597
+ agentId: Type8.Optional(str),
23598
+ round: Type8.Optional(integer),
23599
+ runnerId: Type8.Optional(integer),
23600
+ inputEntryId: Type8.Optional(integer),
23601
+ answerEntryId: Type8.Optional(integer),
23602
+ roundUsage: Type8.Optional(Type8.Union([usage, nil])),
23048
23603
  taskId: integer,
23049
23604
  callId: str,
23050
23605
  childId: integer,
23051
- role: Type7.Enum(["explore", "plan", "coder"]),
23606
+ role: Type8.Enum(["explore", "plan", "coder"]),
23052
23607
  title: str,
23053
23608
  createdAt: num,
23054
- startedAt: Type7.Union([num, nil]),
23055
- endedAt: Type7.Union([num, nil]),
23056
- status: Type7.Enum(["queued", "running", "completed", "failed", "cancelled"]),
23609
+ startedAt: Type8.Union([num, nil]),
23610
+ endedAt: Type8.Union([num, nil]),
23611
+ status: Type8.Enum(["queued", "running", "completed", "failed", "cancelled"]),
23057
23612
  report: nullableString,
23058
- approvalCount: Type7.Optional(integer),
23059
- historyThrough: Type7.Optional(integer),
23060
- historyUsage: Type7.Optional(usage)
23613
+ approvalCount: Type8.Optional(integer),
23614
+ historyThrough: Type8.Optional(integer),
23615
+ historyUsage: Type8.Optional(usage)
23061
23616
  });
23062
- var SubagentSummarySchema = Type7.Object({
23617
+ var SubagentSummarySchema = Type8.Object({
23063
23618
  agentId: str,
23064
23619
  parentId: integer,
23065
23620
  childId: integer,
23066
- role: Type7.Enum(["explore", "plan", "coder"]),
23621
+ role: Type8.Enum(["explore", "plan", "coder"]),
23067
23622
  title: str,
23068
- status: Type7.Enum(["idle", "queued", "running"]),
23069
- activeRoundId: Type7.Union([integer, nil]),
23623
+ status: Type8.Enum(["idle", "queued", "running"]),
23624
+ activeRoundId: Type8.Union([integer, nil]),
23070
23625
  lastRoundId: integer,
23071
- lastReport: Type7.Optional(nullableString),
23626
+ lastReport: Type8.Optional(nullableString),
23072
23627
  roundCount: integer,
23073
23628
  taskTitle: str,
23074
23629
  approvalCount: integer,
23075
23630
  createdAt: num,
23076
- historyThrough: Type7.Optional(integer),
23077
- historyUsage: Type7.Optional(usage)
23631
+ historyThrough: Type8.Optional(integer),
23632
+ historyUsage: Type8.Optional(usage)
23078
23633
  });
23079
- var jobStatus = Type7.Enum(["running", "exited", "failed", "stopped", "lost"]);
23080
- var JobSummarySchema = Type7.Object({
23634
+ var jobStatus = Type8.Enum(["running", "exited", "failed", "stopped", "lost"]);
23635
+ var JobSummarySchema = Type8.Object({
23081
23636
  jobId: str,
23082
23637
  title: str,
23083
23638
  command: str,
23084
23639
  cwd: str,
23085
23640
  status: jobStatus,
23086
23641
  startedAt: num,
23087
- endedAt: Type7.Union([num, nil]),
23088
- exitCode: Type7.Union([num, nil]),
23089
- reason: Type7.Optional(Type7.Enum(["timeout", "spawn-error", "restart", "aborted"])),
23090
- timeoutSeconds: Type7.Optional(num),
23642
+ endedAt: Type8.Union([num, nil]),
23643
+ exitCode: Type8.Union([num, nil]),
23644
+ reason: Type8.Optional(Type8.Enum(["timeout", "spawn-error", "restart", "aborted"])),
23645
+ timeoutSeconds: Type8.Optional(num),
23091
23646
  outputBytes: integer
23092
23647
  });
23093
- var JobOutputSchema = Type7.Object({
23648
+ var JobOutputSchema = Type8.Object({
23094
23649
  job: JobSummarySchema,
23095
23650
  text: str,
23096
23651
  offset: integer,
23097
23652
  truncated: bool,
23098
23653
  dropped: integer
23099
23654
  });
23100
- var QueuedInputSchema = Type7.Object({
23655
+ var QueuedInputSchema = Type8.Object({
23101
23656
  id: integer,
23102
- mode: Type7.Enum(["steer", "followUp"]),
23657
+ mode: Type8.Enum(["steer", "followUp"]),
23103
23658
  preview: str,
23104
23659
  images: integer,
23105
- skill: Type7.Optional(Type7.Object({ name: str, args: str }))
23660
+ skill: Type8.Optional(Type8.Object({ name: str, args: str }))
23106
23661
  });
23107
- var WithdrawResultSchema = Type7.Object({
23108
- result: Type7.Enum(["withdrawn", "placed", "settled", "unknown"]),
23109
- text: Type7.Optional(str),
23110
- images: Type7.Optional(integer)
23662
+ var WithdrawResultSchema = Type8.Object({
23663
+ result: Type8.Enum(["withdrawn", "placed", "settled", "unknown"]),
23664
+ text: Type8.Optional(str),
23665
+ images: Type8.Optional(integer)
23111
23666
  });
23112
- var SteerResultSchema = Type7.Object({
23113
- result: Type7.Enum(["steered", "withdrawn", "placed", "settled", "unknown"]),
23114
- id: Type7.Optional(integer),
23115
- text: Type7.Optional(str),
23116
- images: Type7.Optional(integer)
23667
+ var SteerResultSchema = Type8.Object({
23668
+ result: Type8.Enum(["steered", "withdrawn", "placed", "settled", "unknown"]),
23669
+ id: Type8.Optional(integer),
23670
+ text: Type8.Optional(str),
23671
+ images: Type8.Optional(integer)
23117
23672
  });
23118
- var TodoItemSchema = Type7.Object({
23119
- id: Type7.String({ minLength: 1, maxLength: TODO_LIMITS.idChars }),
23120
- text: Type7.String({ minLength: 1, maxLength: TODO_LIMITS.textChars }),
23121
- status: Type7.Enum(["pending", "in_progress", "completed"])
23673
+ var TodoItemSchema = Type8.Object({
23674
+ id: Type8.String({ minLength: 1, maxLength: TODO_LIMITS.idChars }),
23675
+ text: Type8.String({ minLength: 1, maxLength: TODO_LIMITS.textChars }),
23676
+ status: Type8.Enum(["pending", "in_progress", "completed"])
23122
23677
  }, { additionalProperties: false });
23123
- var TodoListSchema = Type7.Object({
23678
+ var TodoListSchema = Type8.Object({
23124
23679
  revision: integer,
23125
- items: Type7.Array(TodoItemSchema, { maxItems: TODO_LIMITS.maxItems })
23680
+ items: Type8.Array(TodoItemSchema, { maxItems: TODO_LIMITS.maxItems })
23126
23681
  });
23127
- var QuestionOptionSchema = Type7.Object({
23128
- id: Type7.String({ minLength: 1, maxLength: QUESTION_LIMITS.optionIdChars }),
23129
- label: Type7.String({ minLength: 1, maxLength: QUESTION_LIMITS.labelChars }),
23130
- description: Type7.Optional(Type7.String({ maxLength: QUESTION_LIMITS.descriptionChars })),
23131
- recommended: Type7.Optional(bool)
23682
+ var QuestionOptionSchema = Type8.Object({
23683
+ id: Type8.String({ minLength: 1, maxLength: QUESTION_LIMITS.optionIdChars }),
23684
+ label: Type8.String({ minLength: 1, maxLength: QUESTION_LIMITS.labelChars }),
23685
+ description: Type8.Optional(Type8.String({ maxLength: QUESTION_LIMITS.descriptionChars })),
23686
+ recommended: Type8.Optional(bool)
23132
23687
  }, { additionalProperties: false });
23133
- var questionStatus = Type7.Enum(["pending", "answered", "skipped", "cancelled"]);
23134
- var QuestionSummarySchema = Type7.Object({
23688
+ var questionStatus = Type8.Enum(["pending", "answered", "skipped", "cancelled"]);
23689
+ var QuestionSummarySchema = Type8.Object({
23135
23690
  id: integer,
23136
23691
  version: str,
23137
- preview: Type7.String({ maxLength: QUESTION_LIMITS.previewChars }),
23692
+ preview: Type8.String({ maxLength: QUESTION_LIMITS.previewChars }),
23138
23693
  multiple: bool,
23139
23694
  optionCount: integer,
23140
23695
  createdAt: num
23141
23696
  });
23142
- var WorkspaceEntrySchema = Type7.Object({
23697
+ var WorkspaceEntrySchema = Type8.Object({
23143
23698
  name: str,
23144
23699
  path: str,
23145
- kind: Type7.Enum(["file", "directory", "symlink"]),
23146
- protected: Type7.Optional(Type7.Literal(true))
23700
+ kind: Type8.Enum(["file", "directory", "symlink"]),
23701
+ protected: Type8.Optional(Type8.Literal(true))
23147
23702
  }, { additionalProperties: false });
23148
- var WorkspaceDirSchema = Type7.Object({
23703
+ var WorkspaceDirSchema = Type8.Object({
23149
23704
  path: str,
23150
- entries: Type7.Array(WorkspaceEntrySchema, { maxItems: 1000 }),
23705
+ entries: Type8.Array(WorkspaceEntrySchema, { maxItems: 1000 }),
23151
23706
  limited: bool,
23152
23707
  incomplete: bool
23153
23708
  });
23154
- var WorkspaceFileSchema = Type7.Object({
23709
+ var WorkspaceFileSchema = Type8.Object({
23155
23710
  path: str,
23156
23711
  size: num,
23157
23712
  mtimeMs: num,
23158
- kind: Type7.Enum(["text", "binary", "protected"]),
23159
- content: Type7.Optional(str),
23713
+ kind: Type8.Enum(["text", "binary", "protected"]),
23714
+ content: Type8.Optional(str),
23160
23715
  truncated: bool
23161
23716
  });
23162
- var ChangeSummarySchema = Type7.Object({ revision: integer, files: integer, added: integer, removed: integer });
23163
- var ChangedFileSchema = Type7.Object({
23717
+ var ChangeSummarySchema = Type8.Object({ revision: integer, files: integer, added: integer, removed: integer });
23718
+ var ChangedFileSchema = Type8.Object({
23164
23719
  path: str,
23165
- outside: Type7.Optional(Type7.Literal(true)),
23720
+ outside: Type8.Optional(Type8.Literal(true)),
23166
23721
  ops: integer,
23167
23722
  added: integer,
23168
23723
  removed: integer,
23169
- agents: Type7.Array(str, { maxItems: 64 }),
23724
+ agents: Type8.Array(str, { maxItems: 64 }),
23170
23725
  lastSeq: integer
23171
23726
  }, { additionalProperties: false });
23172
- var ChangeListSchema = Type7.Object({
23727
+ var ChangeListSchema = Type8.Object({
23173
23728
  revision: integer,
23174
- files: Type7.Array(ChangedFileSchema, { maxItems: 500 }),
23729
+ files: Type8.Array(ChangedFileSchema, { maxItems: 500 }),
23175
23730
  truncated: bool
23176
23731
  });
23177
- var ChangeOpSchema = Type7.Object({
23732
+ var ChangeOpSchema = Type8.Object({
23178
23733
  seq: integer,
23179
23734
  agent: str,
23180
- tool: Type7.Enum(["edit", "write"]),
23735
+ tool: Type8.Enum(["edit", "write"]),
23181
23736
  diff: str,
23182
23737
  added: integer,
23183
23738
  removed: integer,
23184
23739
  truncated: bool
23185
23740
  }, { additionalProperties: false });
23186
- var ChangeDetailSchema = Type7.Object({
23741
+ var ChangeDetailSchema = Type8.Object({
23187
23742
  path: str,
23188
- outside: Type7.Optional(Type7.Literal(true)),
23189
- ops: Type7.Array(ChangeOpSchema, { maxItems: 50 }),
23743
+ outside: Type8.Optional(Type8.Literal(true)),
23744
+ ops: Type8.Array(ChangeOpSchema, { maxItems: 50 }),
23190
23745
  truncated: bool
23191
23746
  });
23192
- var QuestionListSchema = Type7.Object({
23747
+ var QuestionListSchema = Type8.Object({
23193
23748
  revision: integer,
23194
- pending: Type7.Array(QuestionSummarySchema, { maxItems: QUESTION_LIMITS.maxPending })
23749
+ pending: Type8.Array(QuestionSummarySchema, { maxItems: QUESTION_LIMITS.maxPending })
23195
23750
  });
23196
- var QuestionRecordSchema = Type7.Object({
23751
+ var QuestionRecordSchema = Type8.Object({
23197
23752
  id: integer,
23198
23753
  callId: str,
23199
23754
  version: str,
23200
- question: Type7.String({ maxLength: QUESTION_LIMITS.questionChars }),
23755
+ question: Type8.String({ maxLength: QUESTION_LIMITS.questionChars }),
23201
23756
  multiple: bool,
23202
- options: Type7.Array(QuestionOptionSchema, { maxItems: QUESTION_LIMITS.maxOptions }),
23757
+ options: Type8.Array(QuestionOptionSchema, { maxItems: QUESTION_LIMITS.maxOptions }),
23203
23758
  status: questionStatus,
23204
- answer: Type7.Union([
23205
- Type7.Object({
23206
- optionIds: Type7.Array(Type7.String(), { maxItems: QUESTION_LIMITS.maxOptions }),
23207
- text: Type7.Union([Type7.String({ maxLength: QUESTION_LIMITS.textChars }), nil])
23759
+ answer: Type8.Union([
23760
+ Type8.Object({
23761
+ optionIds: Type8.Array(Type8.String(), { maxItems: QUESTION_LIMITS.maxOptions }),
23762
+ text: Type8.Union([Type8.String({ maxLength: QUESTION_LIMITS.textChars }), nil])
23208
23763
  }),
23209
23764
  nil
23210
23765
  ]),
23211
23766
  createdAt: num,
23212
- settledAt: Type7.Union([num, nil])
23767
+ settledAt: Type8.Union([num, nil])
23213
23768
  });
23214
- var QuestionResponseSchema = Type7.Union([
23215
- Type7.Object({
23216
- kind: Type7.Literal("answer"),
23217
- optionIds: Type7.Optional(Type7.Array(Type7.String(), { maxItems: QUESTION_LIMITS.maxOptions })),
23218
- text: Type7.Optional(Type7.String({ maxLength: QUESTION_LIMITS.textChars }))
23769
+ var QuestionResponseSchema = Type8.Union([
23770
+ Type8.Object({
23771
+ kind: Type8.Literal("answer"),
23772
+ optionIds: Type8.Optional(Type8.Array(Type8.String(), { maxItems: QUESTION_LIMITS.maxOptions })),
23773
+ text: Type8.Optional(Type8.String({ maxLength: QUESTION_LIMITS.textChars }))
23219
23774
  }, { additionalProperties: false }),
23220
- Type7.Object({ kind: Type7.Literal("skip") }, { additionalProperties: false })
23775
+ Type8.Object({ kind: Type8.Literal("skip") }, { additionalProperties: false })
23776
+ ]);
23777
+ var PlanRecordSchema = Type8.Object({
23778
+ version: str,
23779
+ title: Type8.String({ maxLength: PLAN_LIMITS.titleChars }),
23780
+ steps: Type8.Array(Type8.String({ maxLength: PLAN_LIMITS.stepChars }), { maxItems: PLAN_LIMITS.maxSteps }),
23781
+ note: Type8.String({ maxLength: PLAN_LIMITS.noteChars }),
23782
+ status: Type8.Enum(["awaiting_approval", "approved", "revision_requested", "superseded", "cancelled"]),
23783
+ feedback: Type8.Union([Type8.String({ maxLength: PLAN_LIMITS.feedbackChars }), nil]),
23784
+ createdAt: num,
23785
+ settledAt: Type8.Union([num, nil])
23786
+ });
23787
+ var PlanViewSchema = Type8.Object({
23788
+ mode: Type8.Enum(["normal", "plan"]),
23789
+ revision: integer,
23790
+ current: Type8.Union([PlanRecordSchema, nil])
23791
+ });
23792
+ var PlanDecisionSchema = Type8.Union([
23793
+ Type8.Object({ kind: Type8.Literal("approve") }, { additionalProperties: false }),
23794
+ Type8.Object({ kind: Type8.Literal("revise"), feedback: Type8.String({ minLength: 1, maxLength: PLAN_LIMITS.feedbackChars }) }, { additionalProperties: false })
23221
23795
  ]);
23222
- var ToolInteractionSchema = Type7.Union([
23223
- Type7.Object({
23224
- kind: Type7.Literal("ask_user"),
23796
+ var ToolInteractionSchema = Type8.Union([
23797
+ Type8.Object({
23798
+ kind: Type8.Literal("ask_user"),
23225
23799
  id: integer,
23226
23800
  question: str,
23227
23801
  multiple: bool,
23228
- options: Type7.Array(Type7.Object({ id: str, label: str, recommended: Type7.Optional(bool) })),
23802
+ options: Type8.Array(Type8.Object({ id: str, label: str, recommended: Type8.Optional(bool) })),
23229
23803
  status: questionStatus,
23230
- optionIds: Type7.Optional(Type7.Array(str)),
23231
- labels: Type7.Optional(Type7.Array(str)),
23232
- text: Type7.Optional(Type7.Union([str, nil]))
23804
+ optionIds: Type8.Optional(Type8.Array(str)),
23805
+ labels: Type8.Optional(Type8.Array(str)),
23806
+ text: Type8.Optional(Type8.Union([str, nil]))
23233
23807
  }),
23234
- Type7.Object({
23235
- kind: Type7.Literal("todo"),
23236
- action: Type7.Enum(["list", "replace"]),
23808
+ Type8.Object({
23809
+ kind: Type8.Literal("todo"),
23810
+ action: Type8.Enum(["list", "replace"]),
23237
23811
  revision: integer,
23238
23812
  completed: integer,
23239
23813
  total: integer
23814
+ }),
23815
+ Type8.Object({
23816
+ kind: Type8.Literal("submit_plan"),
23817
+ version: str,
23818
+ title: str,
23819
+ steps: Type8.Array(str),
23820
+ note: str
23240
23821
  })
23241
23822
  ]);
23242
- var ChatViewSchema = Type7.Object({
23243
- historyBefore: Type7.Optional(Type7.Union([integer, nil])),
23244
- agents: Type7.Optional(Type7.Array(SubagentSummarySchema)),
23245
- jobs: Type7.Optional(Type7.Array(JobSummarySchema)),
23246
- queued: Type7.Optional(Type7.Array(QueuedInputSchema)),
23247
- todos: Type7.Optional(TodoListSchema),
23248
- questions: Type7.Optional(QuestionListSchema),
23249
- changes: Type7.Optional(ChangeSummarySchema),
23250
- rounds: Type7.Optional(Type7.Array(DelegationSchema)),
23251
- subagents: Type7.Optional(Type7.Array(DelegationSchema)),
23252
- taskUsage: Type7.Optional(usage),
23823
+ var ChatViewSchema = Type8.Object({
23824
+ historyBefore: Type8.Optional(Type8.Union([integer, nil])),
23825
+ agents: Type8.Optional(Type8.Array(SubagentSummarySchema)),
23826
+ jobs: Type8.Optional(Type8.Array(JobSummarySchema)),
23827
+ queued: Type8.Optional(Type8.Array(QueuedInputSchema)),
23828
+ todos: Type8.Optional(TodoListSchema),
23829
+ questions: Type8.Optional(QuestionListSchema),
23830
+ plan: Type8.Optional(PlanViewSchema),
23831
+ changes: Type8.Optional(ChangeSummarySchema),
23832
+ rounds: Type8.Optional(Type8.Array(DelegationSchema)),
23833
+ subagents: Type8.Optional(Type8.Array(DelegationSchema)),
23834
+ taskUsage: Type8.Optional(usage),
23253
23835
  model: str,
23254
- agent: Type7.Object({ model: Type7.Union([model2, nil]), thinkingLevel: Type7.Union([thinking, nil]) }),
23836
+ agent: Type8.Object({ model: Type8.Union([model2, nil]), thinkingLevel: Type8.Union([thinking, nil]) }),
23255
23837
  cwd: str,
23256
23838
  busy: bool,
23257
23839
  status: nullableString,
23258
23840
  usage,
23259
- contextUsage: Type7.Optional(Type7.Object({ tokens: Type7.Union([num, nil]), window: num, percent: Type7.Union([num, nil]) })),
23260
- compaction: Type7.Optional(Type7.Object({
23261
- failure: Type7.Optional(Type7.Object({ taskId: integer, reason: Type7.Enum(["manual", "threshold", "overflow"]), error: str })),
23262
- active: Type7.Array(Type7.Object({
23841
+ contextUsage: Type8.Optional(Type8.Object({ tokens: Type8.Union([num, nil]), window: num, percent: Type8.Union([num, nil]) })),
23842
+ compaction: Type8.Optional(Type8.Object({
23843
+ failure: Type8.Optional(Type8.Object({ taskId: integer, reason: Type8.Enum(["manual", "threshold", "overflow"]), error: str })),
23844
+ active: Type8.Array(Type8.Object({
23263
23845
  taskId: integer,
23264
- reason: Type7.Enum(["manual", "threshold", "overflow"]),
23846
+ reason: Type8.Enum(["manual", "threshold", "overflow"]),
23265
23847
  blocking: bool,
23266
23848
  attempt: integer,
23267
- retry: Type7.Optional(Type7.Object({ at: num, error: str }))
23849
+ retry: Type8.Optional(Type8.Object({ at: num, error: str }))
23268
23850
  })),
23269
- latest: Type7.Union([
23851
+ latest: Type8.Union([
23270
23852
  nil,
23271
- Type7.Object({
23853
+ Type8.Object({
23272
23854
  taskId: integer,
23273
- phase: Type7.Enum(["summarizing", "waiting-to-apply", "applied", "nothing", "skipped", "cancelled", "failed"]),
23855
+ phase: Type8.Enum(["summarizing", "waiting-to-apply", "applied", "nothing", "skipped", "cancelled", "failed"]),
23274
23856
  at: num
23275
23857
  })
23276
23858
  ])
23277
23859
  })),
23278
- items: Type7.Array(Type7.Union([
23279
- Type7.Object({
23280
- type: Type7.Literal("user"),
23281
- order: Type7.Optional(num),
23860
+ items: Type8.Array(Type8.Union([
23861
+ Type8.Object({
23862
+ type: Type8.Literal("user"),
23863
+ order: Type8.Optional(num),
23282
23864
  key: str,
23283
23865
  id: integer,
23284
23866
  text: str,
23285
- images: Type7.Optional(Type7.Array(image)),
23286
- skill: Type7.Optional(skill),
23287
- defaultImagePrompt: Type7.Optional(Type7.Object({ version: Type7.Integer({ minimum: 1 }), text: str }))
23867
+ images: Type8.Optional(Type8.Array(image)),
23868
+ skill: Type8.Optional(skill),
23869
+ defaultImagePrompt: Type8.Optional(Type8.Object({ version: Type8.Integer({ minimum: 1 }), text: str }))
23288
23870
  }),
23289
- Type7.Object({
23290
- type: Type7.Literal("assistant"),
23291
- order: Type7.Optional(num),
23871
+ Type8.Object({
23872
+ type: Type8.Literal("assistant"),
23873
+ order: Type8.Optional(num),
23292
23874
  key: str,
23293
23875
  id: integer,
23294
23876
  thinking: str,
@@ -23297,11 +23879,11 @@ var ChatViewSchema = Type7.Object({
23297
23879
  errorMessage: nullableString,
23298
23880
  note: nullableString
23299
23881
  }),
23300
- Type7.Object({
23301
- type: Type7.Literal("tool"),
23302
- order: Type7.Optional(num),
23882
+ Type8.Object({
23883
+ type: Type8.Literal("tool"),
23884
+ order: Type8.Optional(num),
23303
23885
  key: str,
23304
- images: Type7.Optional(Type7.Array(image)),
23886
+ images: Type8.Optional(Type8.Array(image)),
23305
23887
  callId: str,
23306
23888
  name: str,
23307
23889
  summary: str,
@@ -23310,64 +23892,66 @@ var ChatViewSchema = Type7.Object({
23310
23892
  isError: bool,
23311
23893
  output: nullableString,
23312
23894
  result: nullableString,
23313
- durationMs: Type7.Union([num, nil]),
23895
+ durationMs: Type8.Union([num, nil]),
23314
23896
  note: nullableString,
23315
23897
  diff: nullableString,
23316
- diffAdded: Type7.Union([num, nil]),
23317
- diffRemoved: Type7.Union([num, nil]),
23318
- diffTruncated: Type7.Optional(bool),
23319
- interaction: Type7.Optional(ToolInteractionSchema)
23898
+ diffAdded: Type8.Union([num, nil]),
23899
+ diffRemoved: Type8.Union([num, nil]),
23900
+ diffTruncated: Type8.Optional(bool),
23901
+ interaction: Type8.Optional(ToolInteractionSchema)
23320
23902
  }),
23321
- Type7.Object({
23322
- type: Type7.Literal("system"),
23323
- order: Type7.Optional(num),
23903
+ Type8.Object({
23904
+ type: Type8.Literal("system"),
23905
+ order: Type8.Optional(num),
23324
23906
  key: str,
23325
23907
  id: integer,
23326
23908
  text: str,
23327
- jobId: Type7.Optional(str)
23909
+ jobId: Type8.Optional(str),
23910
+ planDecision: Type8.Optional(Type8.Object({ version: str, kind: Type8.Enum(["approve", "revise"]) }, { additionalProperties: false }))
23328
23911
  })
23329
23912
  ])),
23330
- streaming: Type7.Union([nil, Type7.Object({ thinking: str, text: str, toolCalls: Type7.Array(call) })])
23913
+ streaming: Type8.Union([nil, Type8.Object({ thinking: str, text: str, toolCalls: Type8.Array(call) })])
23331
23914
  });
23332
- var SessionSummarySchema = Type7.Object({
23915
+ var SessionSummarySchema = Type8.Object({
23333
23916
  workspaceRoot: str,
23334
23917
  lastUsedAt: num,
23335
23918
  title: str,
23336
23919
  firstPromptPreview: str,
23337
23920
  createdAt: num,
23338
- lastMessageAt: Type7.Union([num, nil]),
23339
- model: Type7.Union([model2, nil]),
23340
- status: Type7.Enum(["idle", "running", "error"]),
23341
- awaitingAnswer: Type7.Optional(bool),
23921
+ lastMessageAt: Type8.Union([num, nil]),
23922
+ model: Type8.Union([model2, nil]),
23923
+ status: Type8.Enum(["idle", "running", "error"]),
23924
+ awaitingAnswer: Type8.Optional(bool),
23925
+ awaitingPlan: Type8.Optional(bool),
23342
23926
  id: str
23343
23927
  });
23344
- var DirectoryStateSchema = Type7.Object({ revision: integer, sessions: Type7.Array(SessionSummarySchema) });
23345
- var WorkspaceSummarySchema = Type7.Object({ path: str, createdAt: num });
23346
- var WorkspaceStateSchema = Type7.Object({ revision: integer, workspaces: Type7.Array(WorkspaceSummarySchema) });
23347
- var credentialStatus = Type7.Object({
23928
+ var DirectoryStateSchema = Type8.Object({ revision: integer, sessions: Type8.Array(SessionSummarySchema) });
23929
+ var WorkspaceSummarySchema = Type8.Object({ path: str, createdAt: num });
23930
+ var WorkspaceStateSchema = Type8.Object({ revision: integer, workspaces: Type8.Array(WorkspaceSummarySchema) });
23931
+ var credentialStatus = Type8.Object({
23348
23932
  savedConfigured: bool,
23349
23933
  environmentConfigured: bool,
23350
- source: Type7.Enum(["saved", "environment", "runtime", "keyless", "none"]),
23351
- enabled: Type7.Optional(bool)
23934
+ source: Type8.Enum(["saved", "environment", "runtime", "keyless", "none"]),
23935
+ enabled: Type8.Optional(bool)
23352
23936
  });
23353
- var CredentialStateSchema = Type7.Object({
23937
+ var CredentialStateSchema = Type8.Object({
23354
23938
  revision: integer,
23355
- statuses: Type7.Record(str, credentialStatus)
23939
+ statuses: Type8.Record(str, credentialStatus)
23356
23940
  });
23357
- var AttachmentSchema = Type7.Object({ sessionId: str, attachmentId: str });
23358
- var chatInput = Type7.Object({ sessionId: str, attachmentId: str }, { additionalProperties: false });
23941
+ var AttachmentSchema = Type8.Object({ sessionId: str, attachmentId: str });
23942
+ var chatInput = Type8.Object({ sessionId: str, attachmentId: str }, { additionalProperties: false });
23359
23943
  var NekoSessionDirectory = contract("neko.session-directory", {
23360
23944
  version: 1,
23361
- input: Type7.Object({}, { additionalProperties: false }),
23945
+ input: Type8.Object({}, { additionalProperties: false }),
23362
23946
  variants: { ready: { state: { state: DirectoryStateSchema } } }
23363
23947
  });
23364
23948
  var NekoSessionManagement = contract("neko.session-management", {
23365
23949
  version: 1,
23366
- input: Type7.Object({}, { additionalProperties: false }),
23950
+ input: Type8.Object({}, { additionalProperties: false }),
23367
23951
  variants: {
23368
23952
  ready: {
23369
23953
  methods: {
23370
- create: rpc(Type7.Object({ ...change.properties, cwd: Type7.Optional(str) }, { additionalProperties: false }), SessionSummarySchema),
23954
+ create: rpc(Type8.Object({ ...change.properties, cwd: Type8.Optional(str) }, { additionalProperties: false }), SessionSummarySchema),
23371
23955
  archive: rpc(str, nil),
23372
23956
  attach: rpc(str, AttachmentSchema),
23373
23957
  detach: rpc(nil, nil),
@@ -23376,31 +23960,31 @@ var NekoSessionManagement = contract("neko.session-management", {
23376
23960
  }
23377
23961
  }
23378
23962
  });
23379
- var resize = Type7.Object({
23380
- maxWidth: Type7.Optional(integer),
23381
- maxHeight: Type7.Optional(integer),
23382
- maxBytes: Type7.Optional(integer),
23383
- jpegQuality: Type7.Optional(integer)
23963
+ var resize = Type8.Object({
23964
+ maxWidth: Type8.Optional(integer),
23965
+ maxHeight: Type8.Optional(integer),
23966
+ maxBytes: Type8.Optional(integer),
23967
+ jpegQuality: Type8.Optional(integer)
23384
23968
  });
23385
- var inputLimits = Type7.Object({
23386
- maxRequestBytes: Type7.Optional(integer),
23387
- images: Type7.Optional(Type7.Object({
23388
- resize: Type7.Optional(resize),
23389
- maxPerMessage: Type7.Optional(integer),
23390
- maxPerRequest: Type7.Optional(integer)
23969
+ var inputLimits = Type8.Object({
23970
+ maxRequestBytes: Type8.Optional(integer),
23971
+ images: Type8.Optional(Type8.Object({
23972
+ resize: Type8.Optional(resize),
23973
+ maxPerMessage: Type8.Optional(integer),
23974
+ maxPerRequest: Type8.Optional(integer)
23391
23975
  }))
23392
23976
  });
23393
- var modelInfo = Type7.Object({
23977
+ var modelInfo = Type8.Object({
23394
23978
  provider: str,
23395
23979
  modelId: str,
23396
23980
  name: str,
23397
23981
  available: bool,
23398
23982
  contextWindow: num,
23399
- input: Type7.Array(str),
23400
- inputLimits: Type7.Optional(inputLimits),
23983
+ input: Type8.Array(str),
23984
+ inputLimits: Type8.Optional(inputLimits),
23401
23985
  reasoning: bool,
23402
- supportedThinkingLevels: Type7.Array(thinking),
23403
- thinkingLevelAdjustments: Type7.Object({
23986
+ supportedThinkingLevels: Type8.Array(thinking),
23987
+ thinkingLevelAdjustments: Type8.Object({
23404
23988
  off: thinking,
23405
23989
  minimal: thinking,
23406
23990
  low: thinking,
@@ -23412,31 +23996,31 @@ var modelInfo = Type7.Object({
23412
23996
  });
23413
23997
  var NekoModels = contract("neko.models", {
23414
23998
  version: 1,
23415
- input: Type7.Object({}, { additionalProperties: false }),
23416
- variants: { ready: { methods: { list: rpc(nil, Type7.Array(modelInfo)) } } }
23999
+ input: Type8.Object({}, { additionalProperties: false }),
24000
+ variants: { ready: { methods: { list: rpc(nil, Type8.Array(modelInfo)) } } }
23417
24001
  });
23418
- var imageChunk = Type7.Object({ data: str, total: integer, mimeType: str });
24002
+ var imageChunk = Type8.Object({ data: str, total: integer, mimeType: str });
23419
24003
  var NekoSubagent = contract("neko.subagent", {
23420
24004
  version: 1,
23421
- input: Type7.Object({
24005
+ input: Type8.Object({
23422
24006
  sessionId: str,
23423
24007
  attachmentId: str,
23424
- taskId: Type7.Optional(integer),
23425
- agentId: Type7.Optional(str)
24008
+ taskId: Type8.Optional(integer),
24009
+ agentId: Type8.Optional(str)
23426
24010
  }),
23427
24011
  variants: {
23428
24012
  ready: {
23429
24013
  state: { revision: integer },
23430
24014
  methods: {
23431
- chunk: rpc(Type7.Object({
23432
- kind: Type7.Enum(["view", "history"]),
23433
- before: Type7.Optional(integer),
23434
- token: Type7.Optional(integer),
24015
+ chunk: rpc(Type8.Object({
24016
+ kind: Type8.Enum(["view", "history"]),
24017
+ before: Type8.Optional(integer),
24018
+ token: Type8.Optional(integer),
23435
24019
  offset: integer
23436
- }), Type7.Object({ token: integer, data: str, next: Type7.Union([integer, nil]) })),
23437
- getImage: rpc(Type7.Object({ entryId: integer, contentIndex: integer, offset: integer }), imageChunk),
23438
- decideApproval: rpc(Type7.Object({ id: integer, version: str, decision: Type7.Enum(["allowed", "denied"]) }), Type7.Enum(["pending", "allowed", "denied", "cancelled"])),
23439
- stop: rpc(Type7.Union([nil, Type7.Object({ taskId: integer })]), nil)
24020
+ }), Type8.Object({ token: integer, data: str, next: Type8.Union([integer, nil]) })),
24021
+ getImage: rpc(Type8.Object({ entryId: integer, contentIndex: integer, offset: integer }), imageChunk),
24022
+ decideApproval: rpc(Type8.Object({ id: integer, version: str, decision: Type8.Enum(["allowed", "denied"]) }), Type8.Enum(["pending", "allowed", "denied", "cancelled"])),
24023
+ stop: rpc(Type8.Union([nil, Type8.Object({ taskId: integer })]), nil)
23440
24024
  }
23441
24025
  }
23442
24026
  }
@@ -23448,99 +24032,102 @@ var NekoChat = contract("neko.chat", {
23448
24032
  ready: {
23449
24033
  state: { view: ChatViewSchema },
23450
24034
  methods: {
23451
- submit: rpc(Type7.Object({
24035
+ submit: rpc(Type8.Object({
23452
24036
  content: str,
23453
- images: Type7.Optional(Type7.Array(str, { maxItems: 4 })),
23454
- requestId: Type7.Optional(str),
23455
- whenBusy: Type7.Optional(Type7.Enum(["steer", "followUp"]))
24037
+ images: Type8.Optional(Type8.Array(str, { maxItems: 4 })),
24038
+ requestId: Type8.Optional(str),
24039
+ whenBusy: Type8.Optional(Type8.Enum(["steer", "followUp"]))
23456
24040
  }, { additionalProperties: false }), nil),
23457
24041
  abort: rpc(nil, nil),
23458
- withdraw: rpc(Type7.Object({ id: integer }, { additionalProperties: false }), WithdrawResultSchema),
23459
- steer: rpc(Type7.Object({ id: integer }, { additionalProperties: false }), SteerResultSchema),
23460
- questionGet: rpc(Type7.Object({ id: integer }, { additionalProperties: false }), Type7.Union([QuestionRecordSchema, nil])),
23461
- questionRespond: rpc(Type7.Object({ id: integer, version: str, response: QuestionResponseSchema }, { additionalProperties: false }), QuestionRecordSchema),
24042
+ withdraw: rpc(Type8.Object({ id: integer }, { additionalProperties: false }), WithdrawResultSchema),
24043
+ steer: rpc(Type8.Object({ id: integer }, { additionalProperties: false }), SteerResultSchema),
24044
+ questionGet: rpc(Type8.Object({ id: integer }, { additionalProperties: false }), Type8.Union([QuestionRecordSchema, nil])),
24045
+ questionRespond: rpc(Type8.Object({ id: integer, version: str, response: QuestionResponseSchema }, { additionalProperties: false }), QuestionRecordSchema),
24046
+ planGet: rpc(Type8.Object({ version: str }, { additionalProperties: false }), Type8.Union([PlanRecordSchema, nil])),
24047
+ planRespond: rpc(Type8.Object({ version: str, decision: PlanDecisionSchema }, { additionalProperties: false }), PlanRecordSchema),
24048
+ planMode: rpc(Type8.Object({ mode: Type8.Enum(["normal", "plan"]) }, { additionalProperties: false }), PlanViewSchema),
23462
24049
  usage: rpc(nil, usage),
23463
- workspaceDir: rpc(Type7.Object({ path: str }, { additionalProperties: false }), WorkspaceDirSchema),
23464
- workspaceFile: rpc(Type7.Object({ path: str }, { additionalProperties: false }), WorkspaceFileSchema),
24050
+ workspaceDir: rpc(Type8.Object({ path: str }, { additionalProperties: false }), WorkspaceDirSchema),
24051
+ workspaceFile: rpc(Type8.Object({ path: str }, { additionalProperties: false }), WorkspaceFileSchema),
23465
24052
  changesList: rpc(nil, ChangeListSchema),
23466
- changeGet: rpc(Type7.Object({ path: str }, { additionalProperties: false }), Type7.Union([ChangeDetailSchema, nil])),
23467
- skills: rpc(nil, Type7.Array(Type7.Object({ name: str, description: str, location: str, level }))),
23468
- jobOutput: rpc(Type7.Object({ jobId: str, since: Type7.Optional(integer) }, { additionalProperties: false }), JobOutputSchema),
23469
- jobStop: rpc(Type7.Object({ jobId: str }, { additionalProperties: false }), JobSummarySchema),
24053
+ changeGet: rpc(Type8.Object({ path: str }, { additionalProperties: false }), Type8.Union([ChangeDetailSchema, nil])),
24054
+ skills: rpc(nil, Type8.Array(Type8.Object({ name: str, description: str, location: str, level }))),
24055
+ jobOutput: rpc(Type8.Object({ jobId: str, since: Type8.Optional(integer) }, { additionalProperties: false }), JobOutputSchema),
24056
+ jobStop: rpc(Type8.Object({ jobId: str }, { additionalProperties: false }), JobSummarySchema),
23470
24057
  configure: rpc(change, nil),
23471
- compact: rpc(Type7.Object({ instructions: Type7.Optional(str) }, { additionalProperties: false }), nil),
23472
- importImage: rpc(str, Type7.Object({ id: str, name: str })),
23473
- draftImage: rpc(Type7.Object({ id: str, offset: integer }, { additionalProperties: false }), imageChunk),
23474
- beginImage: rpc(Type7.Object({ name: str, mimeType: str, size: Type7.Integer({ minimum: 1, maximum: 20971520 }) }, { additionalProperties: false }), str),
23475
- appendImage: rpc(Type7.Object({ id: str, offset: integer, data: Type7.String({ maxLength: 350000 }) }, { additionalProperties: false }), nil),
24058
+ compact: rpc(Type8.Object({ instructions: Type8.Optional(str) }, { additionalProperties: false }), nil),
24059
+ importImage: rpc(str, Type8.Object({ id: str, name: str })),
24060
+ draftImage: rpc(Type8.Object({ id: str, offset: integer }, { additionalProperties: false }), imageChunk),
24061
+ beginImage: rpc(Type8.Object({ name: str, mimeType: str, size: Type8.Integer({ minimum: 1, maximum: 20971520 }) }, { additionalProperties: false }), str),
24062
+ appendImage: rpc(Type8.Object({ id: str, offset: integer, data: Type8.String({ maxLength: 350000 }) }, { additionalProperties: false }), nil),
23476
24063
  finishImage: rpc(str, nil),
23477
24064
  releaseImage: rpc(str, nil),
23478
- getImage: rpc(Type7.Object({ entryId: integer, contentIndex: integer, offset: integer }, { additionalProperties: false }), imageChunk)
24065
+ getImage: rpc(Type8.Object({ entryId: integer, contentIndex: integer, offset: integer }, { additionalProperties: false }), imageChunk)
23479
24066
  }
23480
24067
  }
23481
24068
  }
23482
24069
  });
23483
- var browse = Type7.Object({ path: str, query: Type7.Optional(str), showHidden: Type7.Optional(bool), cursor: Type7.Optional(str) }, { additionalProperties: false });
23484
- var page = Type7.Object({
24070
+ var browse = Type8.Object({ path: str, query: Type8.Optional(str), showHidden: Type8.Optional(bool), cursor: Type8.Optional(str) }, { additionalProperties: false });
24071
+ var page = Type8.Object({
23485
24072
  path: str,
23486
24073
  parent: nullableString,
23487
- entries: Type7.Array(Type7.Object({ name: str, path: str, error: Type7.Optional(str) })),
24074
+ entries: Type8.Array(Type8.Object({ name: str, path: str, error: Type8.Optional(str) })),
23488
24075
  next: nullableString
23489
24076
  });
23490
24077
  var NekoWorkspaces = contract("neko.workspaces", {
23491
24078
  version: 1,
23492
- input: Type7.Object({}, { additionalProperties: false }),
24079
+ input: Type8.Object({}, { additionalProperties: false }),
23493
24080
  variants: {
23494
24081
  ready: {
23495
24082
  state: { state: WorkspaceStateSchema },
23496
24083
  methods: {
23497
- add: rpc(Type7.Object({ path: str }, { additionalProperties: false }), WorkspaceSummarySchema),
24084
+ add: rpc(Type8.Object({ path: str }, { additionalProperties: false }), WorkspaceSummarySchema),
23498
24085
  browse: rpc(browse, page)
23499
24086
  }
23500
24087
  }
23501
24088
  }
23502
24089
  });
23503
- var provider = Type7.String({ minLength: 1, maxLength: 64 });
24090
+ var provider = Type8.String({ minLength: 1, maxLength: 64 });
23504
24091
  var NekoCredentials = contract("neko.credentials", {
23505
24092
  version: 2,
23506
- input: Type7.Object({}, { additionalProperties: false }),
24093
+ input: Type8.Object({}, { additionalProperties: false }),
23507
24094
  variants: {
23508
24095
  ready: {
23509
24096
  state: { state: CredentialStateSchema },
23510
24097
  methods: {
23511
- save: rpc(Type7.Object({ provider, key: Type7.String({ minLength: 1, maxLength: 8192 }) }, { additionalProperties: false }), nil),
23512
- remove: rpc(Type7.Object({ provider }, { additionalProperties: false }), nil),
23513
- setEnabled: rpc(Type7.Object({ provider, enabled: bool }, { additionalProperties: false }), nil)
24098
+ save: rpc(Type8.Object({ provider, key: Type8.String({ minLength: 1, maxLength: 8192 }) }, { additionalProperties: false }), nil),
24099
+ remove: rpc(Type8.Object({ provider }, { additionalProperties: false }), nil),
24100
+ setEnabled: rpc(Type8.Object({ provider, enabled: bool }, { additionalProperties: false }), nil)
23514
24101
  }
23515
24102
  }
23516
24103
  }
23517
24104
  });
23518
- var SearchSettingsStateSchema = Type7.Object({
24105
+ var SearchSettingsStateSchema = Type8.Object({
23519
24106
  revision: integer,
23520
- selected: Type7.Enum(["tavily", "deepseek"])
24107
+ selected: Type8.Enum(["tavily", "deepseek"])
23521
24108
  });
23522
24109
  var NekoSearchSettings = contract("neko.search-settings", {
23523
24110
  version: 1,
23524
- input: Type7.Object({}, { additionalProperties: false }),
24111
+ input: Type8.Object({}, { additionalProperties: false }),
23525
24112
  variants: {
23526
24113
  ready: {
23527
24114
  state: { state: SearchSettingsStateSchema },
23528
24115
  methods: {
23529
- set: rpc(Type7.Object({ provider: Type7.Enum(["tavily", "deepseek"]) }, { additionalProperties: false }), nil)
24116
+ set: rpc(Type8.Object({ provider: Type8.Enum(["tavily", "deepseek"]) }, { additionalProperties: false }), nil)
23530
24117
  }
23531
24118
  }
23532
24119
  }
23533
24120
  });
23534
- var contextSource = Type7.Enum(["default", "config", "saved"]);
23535
- var ContextSettingsStateSchema = Type7.Object({
24121
+ var contextSource = Type8.Enum(["default", "config", "saved"]);
24122
+ var ContextSettingsStateSchema = Type8.Object({
23536
24123
  revision: integer,
23537
- effective: Type7.Object({
23538
- compaction: Type7.Object({
24124
+ effective: Type8.Object({
24125
+ compaction: Type8.Object({
23539
24126
  enabled: bool,
23540
- triggerPercent: Type7.Union([integer, nil]),
24127
+ triggerPercent: Type8.Union([integer, nil]),
23541
24128
  keepRecentTokens: integer
23542
24129
  }),
23543
- pruning: Type7.Object({
24130
+ pruning: Type8.Object({
23544
24131
  enabled: bool,
23545
24132
  triggerPercent: integer,
23546
24133
  keepRecent: integer,
@@ -23548,9 +24135,9 @@ var ContextSettingsStateSchema = Type7.Object({
23548
24135
  minSavePercent: integer
23549
24136
  })
23550
24137
  }),
23551
- sources: Type7.Object({
23552
- compaction: Type7.Object({ enabled: contextSource, triggerPercent: contextSource, keepRecentTokens: contextSource }),
23553
- pruning: Type7.Object({
24138
+ sources: Type8.Object({
24139
+ compaction: Type8.Object({ enabled: contextSource, triggerPercent: contextSource, keepRecentTokens: contextSource }),
24140
+ pruning: Type8.Object({
23554
24141
  enabled: contextSource,
23555
24142
  triggerPercent: contextSource,
23556
24143
  keepRecent: contextSource,
@@ -23559,42 +24146,42 @@ var ContextSettingsStateSchema = Type7.Object({
23559
24146
  })
23560
24147
  })
23561
24148
  });
23562
- var nullable = (schema) => Type7.Union([schema, nil]);
23563
- var ContextSettingsPatchSchema = Type7.Object({
23564
- compaction: Type7.Optional(Type7.Object({
23565
- enabled: Type7.Optional(nullable(bool)),
23566
- triggerPercent: Type7.Optional(nullable(integer)),
23567
- keepRecentTokens: Type7.Optional(nullable(integer))
24149
+ var nullable = (schema) => Type8.Union([schema, nil]);
24150
+ var ContextSettingsPatchSchema = Type8.Object({
24151
+ compaction: Type8.Optional(Type8.Object({
24152
+ enabled: Type8.Optional(nullable(bool)),
24153
+ triggerPercent: Type8.Optional(nullable(integer)),
24154
+ keepRecentTokens: Type8.Optional(nullable(integer))
23568
24155
  }, { additionalProperties: false })),
23569
- pruning: Type7.Optional(Type7.Object({
23570
- enabled: Type7.Optional(nullable(bool)),
23571
- triggerPercent: Type7.Optional(nullable(integer)),
23572
- keepRecent: Type7.Optional(nullable(integer)),
23573
- minChars: Type7.Optional(nullable(integer)),
23574
- minSavePercent: Type7.Optional(nullable(integer))
24156
+ pruning: Type8.Optional(Type8.Object({
24157
+ enabled: Type8.Optional(nullable(bool)),
24158
+ triggerPercent: Type8.Optional(nullable(integer)),
24159
+ keepRecent: Type8.Optional(nullable(integer)),
24160
+ minChars: Type8.Optional(nullable(integer)),
24161
+ minSavePercent: Type8.Optional(nullable(integer))
23575
24162
  }, { additionalProperties: false }))
23576
24163
  }, { additionalProperties: false });
23577
24164
  var NekoContextSettings = contract("neko.context-settings", {
23578
24165
  version: 1,
23579
- input: Type7.Object({}, { additionalProperties: false }),
24166
+ input: Type8.Object({}, { additionalProperties: false }),
23580
24167
  variants: {
23581
24168
  ready: {
23582
24169
  state: { state: ContextSettingsStateSchema },
23583
24170
  methods: {
23584
24171
  set: rpc(ContextSettingsPatchSchema, nil),
23585
- reset: rpc(Type7.Object({}, { additionalProperties: false }), nil)
24172
+ reset: rpc(Type8.Object({}, { additionalProperties: false }), nil)
23586
24173
  }
23587
24174
  }
23588
24175
  }
23589
24176
  });
23590
- var defaultModelSource = Type7.Enum(["cli", "saved", "config", "default"]);
23591
- var DefaultModelStateSchema = Type7.Object({
24177
+ var defaultModelSource = Type8.Enum(["cli", "saved", "config", "default"]);
24178
+ var DefaultModelStateSchema = Type8.Object({
23592
24179
  effective: model2,
23593
24180
  source: defaultModelSource
23594
24181
  });
23595
24182
  var NekoDefaultModel = contract("neko.default-model", {
23596
24183
  version: 1,
23597
- input: Type7.Object({}, { additionalProperties: false }),
24184
+ input: Type8.Object({}, { additionalProperties: false }),
23598
24185
  variants: {
23599
24186
  ready: {
23600
24187
  state: { state: DefaultModelStateSchema },
@@ -23605,23 +24192,23 @@ var NekoDefaultModel = contract("neko.default-model", {
23605
24192
  }
23606
24193
  }
23607
24194
  });
23608
- var FileCandidatesSchema = Type7.Object({
23609
- entries: Type7.Array(Type7.Object({ name: str, path: str, kind: Type7.Enum(["file", "directory"]) }), { maxItems: 100 }),
24195
+ var FileCandidatesSchema = Type8.Object({
24196
+ entries: Type8.Array(Type8.Object({ name: str, path: str, kind: Type8.Enum(["file", "directory"]) }), { maxItems: 100 }),
23610
24197
  limited: bool,
23611
24198
  incomplete: bool
23612
24199
  });
23613
24200
  var NekoWorkspaceFiles = contract("neko.workspace-files", {
23614
24201
  version: 1,
23615
- input: Type7.Object({}, { additionalProperties: false }),
24202
+ input: Type8.Object({}, { additionalProperties: false }),
23616
24203
  variants: {
23617
24204
  ready: {
23618
24205
  methods: {
23619
- search: rpc(Type7.Object({ workspaceRoot: str, query: str }, { additionalProperties: false }), FileCandidatesSchema)
24206
+ search: rpc(Type8.Object({ workspaceRoot: str, query: str }, { additionalProperties: false }), FileCandidatesSchema)
23620
24207
  }
23621
24208
  }
23622
24209
  }
23623
24210
  });
23624
- var marketSkill = Type7.Object({
24211
+ var marketSkill = Type8.Object({
23625
24212
  name: str,
23626
24213
  description: str,
23627
24214
  category: str,
@@ -23631,48 +24218,48 @@ var marketSkill = Type7.Object({
23631
24218
  sha256: str,
23632
24219
  sizeBytes: integer,
23633
24220
  uploadedAt: str,
23634
- installed: Type7.Object({ project: bool, user: bool, builtin: bool }),
23635
- activeLevel: Type7.Union([level, nil])
24221
+ installed: Type8.Object({ project: bool, user: bool, builtin: bool }),
24222
+ activeLevel: Type8.Union([level, nil])
23636
24223
  });
23637
- var preview = Type7.Object({ body: str, files: Type7.Array(str) });
23638
- var scope = Type7.Enum(["user", "project"]);
23639
- var installResult = Type7.Object({ name: str, scope, path: str, shadowedBy: Type7.Optional(scope) });
24224
+ var preview = Type8.Object({ body: str, files: Type8.Array(str) });
24225
+ var scope = Type8.Enum(["user", "project"]);
24226
+ var installResult = Type8.Object({ name: str, scope, path: str, shadowedBy: Type8.Optional(scope) });
23640
24227
  var NekoSkillMarket = contract("neko.skill-market", {
23641
24228
  version: 1,
23642
- input: Type7.Object({}, { additionalProperties: false }),
24229
+ input: Type8.Object({}, { additionalProperties: false }),
23643
24230
  variants: {
23644
24231
  ready: {
23645
24232
  methods: {
23646
- list: rpc(Type7.Object({ query: Type7.Optional(str), workspaceRoot: Type7.Optional(str) }, { additionalProperties: false }), Type7.Array(marketSkill)),
23647
- detail: rpc(Type7.Object({ name: str, workspaceRoot: Type7.Optional(str) }, { additionalProperties: false }), marketSkill),
23648
- preview: rpc(Type7.Object({ name: str }, { additionalProperties: false }), preview),
23649
- install: rpc(Type7.Object({ name: str, scope, workspaceRoot: Type7.Optional(str), overwrite: Type7.Optional(bool) }, { additionalProperties: false }), installResult),
23650
- uninstall: rpc(Type7.Object({ name: str, scope, workspaceRoot: Type7.Optional(str) }, { additionalProperties: false }), nil)
24233
+ list: rpc(Type8.Object({ query: Type8.Optional(str), workspaceRoot: Type8.Optional(str) }, { additionalProperties: false }), Type8.Array(marketSkill)),
24234
+ detail: rpc(Type8.Object({ name: str, workspaceRoot: Type8.Optional(str) }, { additionalProperties: false }), marketSkill),
24235
+ preview: rpc(Type8.Object({ name: str }, { additionalProperties: false }), preview),
24236
+ install: rpc(Type8.Object({ name: str, scope, workspaceRoot: Type8.Optional(str), overwrite: Type8.Optional(bool) }, { additionalProperties: false }), installResult),
24237
+ uninstall: rpc(Type8.Object({ name: str, scope, workspaceRoot: Type8.Optional(str) }, { additionalProperties: false }), nil)
23651
24238
  }
23652
24239
  }
23653
24240
  }
23654
24241
  });
23655
- var ShellStatusSchema = Type7.Union([
23656
- Type7.Object({ status: Type7.Literal("ready"), path: str, source: Type7.Enum(["config", "auto"]) }),
23657
- Type7.Object({
23658
- status: Type7.Literal("missing"),
23659
- reason: Type7.Enum(["not_found", "invalid_config"]),
23660
- detail: Type7.Optional(str)
24242
+ var ShellStatusSchema = Type8.Union([
24243
+ Type8.Object({ status: Type8.Literal("ready"), path: str, source: Type8.Enum(["config", "auto"]) }),
24244
+ Type8.Object({
24245
+ status: Type8.Literal("missing"),
24246
+ reason: Type8.Enum(["not_found", "invalid_config"]),
24247
+ detail: Type8.Optional(str)
23661
24248
  })
23662
24249
  ]);
23663
- var ApplicationInfoSchema = Type7.Object({
24250
+ var ApplicationInfoSchema = Type8.Object({
23664
24251
  runningVersion: str,
23665
24252
  installedVersion: nullableString,
23666
- protocolVersion: Type7.Integer({ minimum: 1 }),
24253
+ protocolVersion: Type8.Integer({ minimum: 1 }),
23667
24254
  latestVersion: nullableString,
23668
- checkedAt: Type7.Union([integer, nil]),
23669
- checkState: Type7.Enum(["unknown", "checking", "ok", "failed", "development"]),
24255
+ checkedAt: Type8.Union([integer, nil]),
24256
+ checkState: Type8.Enum(["unknown", "checking", "ok", "failed", "development"]),
23670
24257
  serverId: str,
23671
- shell: Type7.Optional(ShellStatusSchema)
24258
+ shell: Type8.Optional(ShellStatusSchema)
23672
24259
  });
23673
24260
  var NekoApplicationInfo = contract("neko.application-info", {
23674
24261
  version: 1,
23675
- input: Type7.Object({}),
24262
+ input: Type8.Object({}),
23676
24263
  variants: {
23677
24264
  ready: { state: { info: ApplicationInfoSchema }, methods: { check: rpc(nil, ApplicationInfoSchema) } }
23678
24265
  }
@@ -23686,7 +24273,7 @@ function isServerId(value) {
23686
24273
  // packages/gateway/src/mobile/admin.ts
23687
24274
  import { createHash, timingSafeEqual } from "crypto";
23688
24275
  import { ChordError } from "@earendil-works/chord";
23689
- import { Type as Type8 } from "typebox";
24276
+ import { Type as Type9 } from "typebox";
23690
24277
 
23691
24278
  // packages/gateway/src/principal.ts
23692
24279
  var HOST_PRINCIPAL = { kind: "host" };
@@ -24015,62 +24602,62 @@ class MobileStore {
24015
24602
  }
24016
24603
 
24017
24604
  // packages/gateway/src/mobile/admin.ts
24018
- var listenerStateSchema = Type8.Union([
24019
- Type8.Object({ status: Type8.Literal("disabled") }),
24020
- Type8.Object({ status: Type8.Literal("starting") }),
24021
- Type8.Object({
24022
- status: Type8.Literal("listening"),
24023
- address: Type8.String(),
24024
- port: Type8.Integer({ minimum: 1, maximum: 65535 }),
24025
- bindAddress: Type8.Optional(Type8.String()),
24026
- mode: Type8.Optional(Type8.Union([Type8.Literal("direct"), Type8.Literal("wsl-relay")]))
24605
+ var listenerStateSchema = Type9.Union([
24606
+ Type9.Object({ status: Type9.Literal("disabled") }),
24607
+ Type9.Object({ status: Type9.Literal("starting") }),
24608
+ Type9.Object({
24609
+ status: Type9.Literal("listening"),
24610
+ address: Type9.String(),
24611
+ port: Type9.Integer({ minimum: 1, maximum: 65535 }),
24612
+ bindAddress: Type9.Optional(Type9.String()),
24613
+ mode: Type9.Optional(Type9.Union([Type9.Literal("direct"), Type9.Literal("wsl-relay")]))
24027
24614
  }),
24028
- Type8.Object({ status: Type8.Literal("error"), error: Type8.String() }),
24029
- Type8.Object({ status: Type8.Literal("stopping") })
24615
+ Type9.Object({ status: Type9.Literal("error"), error: Type9.String() }),
24616
+ Type9.Object({ status: Type9.Literal("stopping") })
24030
24617
  ]);
24031
- var adminDeviceSchema = Type8.Object({
24032
- deviceId: Type8.String(),
24033
- name: Type8.String(),
24034
- pairedAt: Type8.Integer(),
24035
- lastSeenAt: Type8.Integer(),
24036
- online: Type8.Boolean()
24618
+ var adminDeviceSchema = Type9.Object({
24619
+ deviceId: Type9.String(),
24620
+ name: Type9.String(),
24621
+ pairedAt: Type9.Integer(),
24622
+ lastSeenAt: Type9.Integer(),
24623
+ online: Type9.Boolean()
24037
24624
  });
24038
24625
  var NekoMobileAdmin = contract("neko.mobile-admin", {
24039
24626
  version: 1,
24040
- input: Type8.Object({ token: Type8.String() }),
24627
+ input: Type9.Object({ token: Type9.String() }),
24041
24628
  variants: {
24042
24629
  admin: {
24043
24630
  methods: {
24044
- state: rpc(Type8.Null(), Type8.Object({
24631
+ state: rpc(Type9.Null(), Type9.Object({
24045
24632
  state: listenerStateSchema,
24046
- candidates: Type8.Array(Type8.String()),
24047
- port: Type8.Integer({ minimum: 1, maximum: 65535 }),
24048
- certificate: Type8.Union([
24049
- Type8.Object({ fingerprint: Type8.String(), expiresAt: Type8.Integer() }),
24050
- Type8.Null()
24633
+ candidates: Type9.Array(Type9.String()),
24634
+ port: Type9.Integer({ minimum: 1, maximum: 65535 }),
24635
+ certificate: Type9.Union([
24636
+ Type9.Object({ fingerprint: Type9.String(), expiresAt: Type9.Integer() }),
24637
+ Type9.Null()
24051
24638
  ]),
24052
- devices: Type8.Array(adminDeviceSchema),
24053
- lastConnectedAddress: Type8.Union([Type8.String(), Type8.Null()]),
24054
- wsl: Type8.Optional(Type8.Union([
24055
- Type8.Object({ interop: Type8.Boolean(), error: Type8.Union([Type8.String(), Type8.Null()]) }),
24056
- Type8.Null()
24639
+ devices: Type9.Array(adminDeviceSchema),
24640
+ lastConnectedAddress: Type9.Union([Type9.String(), Type9.Null()]),
24641
+ wsl: Type9.Optional(Type9.Union([
24642
+ Type9.Object({ interop: Type9.Boolean(), error: Type9.Union([Type9.String(), Type9.Null()]) }),
24643
+ Type9.Null()
24057
24644
  ]))
24058
24645
  })),
24059
- enable: rpc(Type8.Object({
24060
- address: Type8.String(),
24061
- port: Type8.Optional(Type8.Integer({ minimum: 1, maximum: 65535 }))
24062
- }), Type8.Object({
24063
- status: Type8.Literal("listening"),
24064
- address: Type8.String(),
24065
- port: Type8.Integer({ minimum: 1, maximum: 65535 }),
24066
- bindAddress: Type8.Optional(Type8.String()),
24067
- mode: Type8.Optional(Type8.Union([Type8.Literal("direct"), Type8.Literal("wsl-relay")]))
24646
+ enable: rpc(Type9.Object({
24647
+ address: Type9.String(),
24648
+ port: Type9.Optional(Type9.Integer({ minimum: 1, maximum: 65535 }))
24649
+ }), Type9.Object({
24650
+ status: Type9.Literal("listening"),
24651
+ address: Type9.String(),
24652
+ port: Type9.Integer({ minimum: 1, maximum: 65535 }),
24653
+ bindAddress: Type9.Optional(Type9.String()),
24654
+ mode: Type9.Optional(Type9.Union([Type9.Literal("direct"), Type9.Literal("wsl-relay")]))
24068
24655
  })),
24069
- disable: rpc(Type8.Null(), listenerStateSchema),
24070
- createPairing: rpc(Type8.Null(), Type8.Object({ pairingText: Type8.String(), expiresAt: Type8.Integer() })),
24071
- cancelPairing: rpc(Type8.Null(), Type8.Null()),
24072
- revoke: rpc(Type8.Object({ deviceId: Type8.String({ minLength: 1 }) }), Type8.Object({ revoked: Type8.Boolean() })),
24073
- regenerateCertificate: rpc(Type8.Null(), Type8.Object({ fingerprint: Type8.String(), expiresAt: Type8.Integer() }))
24656
+ disable: rpc(Type9.Null(), listenerStateSchema),
24657
+ createPairing: rpc(Type9.Null(), Type9.Object({ pairingText: Type9.String(), expiresAt: Type9.Integer() })),
24658
+ cancelPairing: rpc(Type9.Null(), Type9.Null()),
24659
+ revoke: rpc(Type9.Object({ deviceId: Type9.String({ minLength: 1 }) }), Type9.Object({ revoked: Type9.Boolean() })),
24660
+ regenerateCertificate: rpc(Type9.Null(), Type9.Object({ fingerprint: Type9.String(), expiresAt: Type9.Integer() }))
24074
24661
  }
24075
24662
  }
24076
24663
  }
@@ -24138,7 +24725,7 @@ function createMobileAdminFactory(deps) {
24138
24725
  }
24139
24726
 
24140
24727
  // packages/gateway/src/mobile/listener.ts
24141
- import { randomUUID as randomUUID3 } from "crypto";
24728
+ import { randomUUID as randomUUID4 } from "crypto";
24142
24729
  import { createServer } from "https";
24143
24730
  import { hostname } from "os";
24144
24731
  import { memoryLinkPair } from "@earendil-works/chord";
@@ -24393,14 +24980,14 @@ function subagentClient(connect) {
24393
24980
  import { createHash as createHash2 } from "crypto";
24394
24981
 
24395
24982
  // packages/core/src/application-version.ts
24396
- var RUNNING_VERSION = "0.4.7";
24983
+ var RUNNING_VERSION = "0.4.9";
24397
24984
  var APPLICATION_PROTOCOL = 8;
24398
24985
  function stableVersion(value) {
24399
24986
  return typeof value === "string" && /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(value) && value.split(".").every((n) => Number.isSafeInteger(Number(n)));
24400
24987
  }
24401
24988
 
24402
24989
  // packages/gateway/src/mobile/protocol.ts
24403
- import { Type as Type9 } from "typebox";
24990
+ import { Type as Type10 } from "typebox";
24404
24991
  import { Check } from "typebox/value";
24405
24992
  var MOBILE_LIMITS = {
24406
24993
  maxFrameBytes: 262144,
@@ -24419,17 +25006,18 @@ var MOBILE_CAPABILITIES = [
24419
25006
  "approvals",
24420
25007
  "media",
24421
25008
  "todos",
24422
- "questions"
25009
+ "questions",
25010
+ "plan"
24423
25011
  ];
24424
25012
  var MOBILE_CLOSE_REVOKED = 4401;
24425
25013
  var MOBILE_CLOSE_INCOMPATIBLE = 4402;
24426
25014
  var MOBILE_CLOSE_SLOW_CONSUMER = 4408;
24427
- var MobileHelloParams = Type9.Object({
24428
- apiVersions: Type9.Array(Type9.Integer(), { minItems: 1 }),
24429
- app: Type9.Object({
24430
- version: Type9.String({ minLength: 1, maxLength: 64 }),
24431
- platform: Type9.String({ minLength: 1, maxLength: 64 }),
24432
- build: Type9.String({ maxLength: 128 })
25015
+ var MobileHelloParams = Type10.Object({
25016
+ apiVersions: Type10.Array(Type10.Integer(), { minItems: 1 }),
25017
+ app: Type10.Object({
25018
+ version: Type10.String({ minLength: 1, maxLength: 64 }),
25019
+ platform: Type10.String({ minLength: 1, maxLength: 64 }),
25020
+ build: Type10.String({ maxLength: 128 })
24433
25021
  })
24434
25022
  });
24435
25023
  function asJsonRpcRequest(value) {
@@ -24469,11 +25057,11 @@ function buildHelloResult(serverId, serverName, apiVersion) {
24469
25057
  limits: MOBILE_LIMITS
24470
25058
  };
24471
25059
  }
24472
- var opt = Type9.Optional;
24473
- var str2 = Type9.String();
24474
- var int = Type9.Integer({ minimum: 0 });
24475
- var nullable2 = (schema) => Type9.Union([schema, Type9.Null()]);
24476
- var MobileContentRefSchema = Type9.Object({
25060
+ var opt = Type10.Optional;
25061
+ var str2 = Type10.String();
25062
+ var int = Type10.Integer({ minimum: 0 });
25063
+ var nullable2 = (schema) => Type10.Union([schema, Type10.Null()]);
25064
+ var MobileContentRefSchema = Type10.Object({
24477
25065
  sessionId: str2,
24478
25066
  agentId: opt(str2),
24479
25067
  key: str2,
@@ -24481,7 +25069,7 @@ var MobileContentRefSchema = Type9.Object({
24481
25069
  digest: str2,
24482
25070
  bytes: int
24483
25071
  });
24484
- var MobileImageRefSchema = Type9.Object({
25072
+ var MobileImageRefSchema = Type10.Object({
24485
25073
  name: str2,
24486
25074
  mimeType: str2,
24487
25075
  width: int,
@@ -24490,56 +25078,70 @@ var MobileImageRefSchema = Type9.Object({
24490
25078
  entryId: int,
24491
25079
  contentIndex: int
24492
25080
  });
24493
- var questionStatus2 = Type9.Union([
24494
- Type9.Literal("pending"),
24495
- Type9.Literal("answered"),
24496
- Type9.Literal("skipped"),
24497
- Type9.Literal("cancelled")
25081
+ var planStatus = Type10.Union([
25082
+ Type10.Literal("awaiting_approval"),
25083
+ Type10.Literal("approved"),
25084
+ Type10.Literal("revision_requested"),
25085
+ Type10.Literal("superseded"),
25086
+ Type10.Literal("cancelled")
24498
25087
  ]);
24499
- var MobileToolInteractionSchema = Type9.Union([
24500
- Type9.Object({
24501
- kind: Type9.Literal("ask_user"),
25088
+ var questionStatus2 = Type10.Union([
25089
+ Type10.Literal("pending"),
25090
+ Type10.Literal("answered"),
25091
+ Type10.Literal("skipped"),
25092
+ Type10.Literal("cancelled")
25093
+ ]);
25094
+ var MobileToolInteractionSchema = Type10.Union([
25095
+ Type10.Object({
25096
+ kind: Type10.Literal("ask_user"),
24502
25097
  id: int,
24503
25098
  question: str2,
24504
- multiple: Type9.Boolean(),
24505
- options: Type9.Array(Type9.Object({ id: str2, label: str2, recommended: opt(Type9.Boolean()) })),
25099
+ multiple: Type10.Boolean(),
25100
+ options: Type10.Array(Type10.Object({ id: str2, label: str2, recommended: opt(Type10.Boolean()) })),
24506
25101
  status: questionStatus2,
24507
- optionIds: opt(Type9.Array(str2)),
24508
- labels: opt(Type9.Array(str2)),
25102
+ optionIds: opt(Type10.Array(str2)),
25103
+ labels: opt(Type10.Array(str2)),
24509
25104
  text: opt(nullable2(str2))
24510
25105
  }),
24511
- Type9.Object({
24512
- kind: Type9.Literal("todo"),
24513
- action: Type9.Union([Type9.Literal("list"), Type9.Literal("replace")]),
25106
+ Type10.Object({
25107
+ kind: Type10.Literal("todo"),
25108
+ action: Type10.Union([Type10.Literal("list"), Type10.Literal("replace")]),
24514
25109
  revision: int,
24515
25110
  completed: int,
24516
25111
  total: int
25112
+ }),
25113
+ Type10.Object({
25114
+ kind: Type10.Literal("submit_plan"),
25115
+ version: str2,
25116
+ title: str2,
25117
+ steps: Type10.Array(str2),
25118
+ note: str2
24517
25119
  })
24518
25120
  ]);
24519
- var MobileToolItemSchema = Type9.Object({
25121
+ var MobileToolItemSchema = Type10.Object({
24520
25122
  callId: str2,
24521
25123
  name: str2,
24522
25124
  summary: str2,
24523
- status: Type9.Union([Type9.Literal("running"), Type9.Literal("done"), Type9.Literal("error")]),
25125
+ status: Type10.Union([Type10.Literal("running"), Type10.Literal("done"), Type10.Literal("error")]),
24524
25126
  detail: opt(str2),
24525
25127
  output: opt(str2),
24526
25128
  result: opt(str2),
24527
25129
  diff: opt(str2),
24528
- diffAdded: opt(Type9.Number()),
24529
- diffRemoved: opt(Type9.Number()),
24530
- diffTruncated: opt(Type9.Boolean()),
24531
- durationMs: opt(Type9.Number()),
25130
+ diffAdded: opt(Type10.Number()),
25131
+ diffRemoved: opt(Type10.Number()),
25132
+ diffTruncated: opt(Type10.Boolean()),
25133
+ durationMs: opt(Type10.Number()),
24532
25134
  note: opt(str2),
24533
25135
  interaction: opt(MobileToolInteractionSchema)
24534
25136
  });
24535
- var MobileChatItemSchema = Type9.Object({
25137
+ var MobileChatItemSchema = Type10.Object({
24536
25138
  key: str2,
24537
- type: Type9.Union([
24538
- Type9.Literal("user"),
24539
- Type9.Literal("assistant"),
24540
- Type9.Literal("tool"),
24541
- Type9.Literal("error"),
24542
- Type9.Literal("system")
25139
+ type: Type10.Union([
25140
+ Type10.Literal("user"),
25141
+ Type10.Literal("assistant"),
25142
+ Type10.Literal("tool"),
25143
+ Type10.Literal("error"),
25144
+ Type10.Literal("system")
24543
25145
  ]),
24544
25146
  text: opt(str2),
24545
25147
  thinking: opt(str2),
@@ -24548,99 +25150,101 @@ var MobileChatItemSchema = Type9.Object({
24548
25150
  skill: opt(str2),
24549
25151
  jobId: opt(str2),
24550
25152
  tool: opt(MobileToolItemSchema),
24551
- images: opt(Type9.Array(MobileImageRefSchema)),
24552
- refs: opt(Type9.Array(MobileContentRefSchema))
25153
+ images: opt(Type10.Array(MobileImageRefSchema)),
25154
+ refs: opt(Type10.Array(MobileContentRefSchema))
24553
25155
  });
24554
- var MobileStreamingSchema = Type9.Object({
25156
+ var MobileStreamingSchema = Type10.Object({
24555
25157
  text: str2,
24556
25158
  thinking: str2,
24557
25159
  textOmitted: int,
24558
25160
  thinkingOmitted: int,
24559
- toolCalls: Type9.Array(Type9.Object({
25161
+ toolCalls: Type10.Array(Type10.Object({
24560
25162
  callId: str2,
24561
25163
  name: str2,
24562
25164
  summary: str2,
24563
- status: Type9.Union([Type9.Literal("running"), Type9.Literal("done")])
25165
+ status: Type10.Union([Type10.Literal("running"), Type10.Literal("done")])
24564
25166
  }))
24565
25167
  });
24566
- var MobileUsageSchema = Type9.Object({
25168
+ var MobileUsageSchema = Type10.Object({
24567
25169
  input: int,
24568
25170
  output: int,
24569
25171
  cacheRead: int,
24570
25172
  cacheWrite: int,
24571
25173
  totalTokens: int,
24572
- cost: Type9.Number({ minimum: 0 })
25174
+ cost: Type10.Number({ minimum: 0 })
24573
25175
  });
24574
- var MobileSubagentRoundSchema = Type9.Object({
25176
+ var MobileSubagentRoundSchema = Type10.Object({
24575
25177
  taskId: int,
24576
25178
  callId: str2,
24577
25179
  agentId: str2,
24578
25180
  round: opt(int),
24579
25181
  role: str2,
24580
25182
  title: str2,
24581
- status: Type9.Union([
24582
- Type9.Literal("queued"),
24583
- Type9.Literal("running"),
24584
- Type9.Literal("completed"),
24585
- Type9.Literal("failed"),
24586
- Type9.Literal("cancelled")
25183
+ status: Type10.Union([
25184
+ Type10.Literal("queued"),
25185
+ Type10.Literal("running"),
25186
+ Type10.Literal("completed"),
25187
+ Type10.Literal("failed"),
25188
+ Type10.Literal("cancelled")
24587
25189
  ]),
24588
25190
  approvalCount: int,
24589
- readonly: Type9.Boolean(),
24590
- createdAt: Type9.Number(),
24591
- endedAt: nullable2(Type9.Number()),
25191
+ readonly: Type10.Boolean(),
25192
+ createdAt: Type10.Number(),
25193
+ endedAt: nullable2(Type10.Number()),
24592
25194
  usage: opt(MobileUsageSchema)
24593
25195
  });
24594
- var MobileAgentSchema = Type9.Object({
25196
+ var MobileAgentSchema = Type10.Object({
24595
25197
  agentId: str2,
24596
25198
  role: str2,
24597
25199
  title: str2,
24598
- status: Type9.Union([Type9.Literal("idle"), Type9.Literal("queued"), Type9.Literal("running")]),
25200
+ status: Type10.Union([Type10.Literal("idle"), Type10.Literal("queued"), Type10.Literal("running")]),
24599
25201
  activeRoundId: nullable2(int),
24600
25202
  lastRoundId: int,
24601
25203
  roundCount: int,
24602
25204
  approvalCount: int
24603
25205
  });
24604
- var MobileQueuedSchema = Type9.Object({
25206
+ var MobileQueuedSchema = Type10.Object({
24605
25207
  id: int,
24606
- mode: Type9.Union([Type9.Literal("steer"), Type9.Literal("followUp")]),
24607
- preview: Type9.String(),
25208
+ mode: Type10.Union([Type10.Literal("steer"), Type10.Literal("followUp")]),
25209
+ preview: Type10.String(),
24608
25210
  images: int
24609
25211
  });
24610
- var MobileChatStatusSchema = Type9.Object({
24611
- busy: Type9.Boolean(),
24612
- queued: opt(Type9.Array(MobileQueuedSchema)),
24613
- todos: opt(Type9.Object({ revision: int, count: int, completed: int })),
24614
- questions: opt(Type9.Object({ revision: int, pendingCount: int })),
25212
+ var MobileChatStatusSchema = Type10.Object({
25213
+ busy: Type10.Boolean(),
25214
+ queued: opt(Type10.Array(MobileQueuedSchema)),
25215
+ todos: opt(Type10.Object({ revision: int, count: int, completed: int })),
25216
+ questions: opt(Type10.Object({ revision: int, pendingCount: int })),
25217
+ plan: opt(Type10.Object({ revision: int, pending: Type10.Boolean(), version: opt(Type10.String({ minLength: 1 })) })),
24615
25218
  status: nullable2(str2),
24616
25219
  usage: MobileUsageSchema,
24617
25220
  taskUsage: opt(MobileUsageSchema),
24618
- contextUsage: opt(Type9.Object({ tokens: nullable2(Type9.Number()), window: Type9.Number(), percent: nullable2(Type9.Number()) })),
24619
- agent: Type9.Object({ model: nullable2(str2), thinkingLevel: nullable2(str2) }),
24620
- subagents: Type9.Array(MobileSubagentRoundSchema),
24621
- agents: Type9.Array(MobileAgentSchema),
25221
+ contextUsage: opt(Type10.Object({ tokens: nullable2(Type10.Number()), window: Type10.Number(), percent: nullable2(Type10.Number()) })),
25222
+ agent: Type10.Object({ model: nullable2(str2), thinkingLevel: nullable2(str2) }),
25223
+ subagents: Type10.Array(MobileSubagentRoundSchema),
25224
+ agents: Type10.Array(MobileAgentSchema),
24622
25225
  approvalCount: int,
24623
- compaction: opt(Type9.Object({ phase: str2, at: Type9.Number() })),
24624
- readonly: opt(Type9.Boolean())
25226
+ compaction: opt(Type10.Object({ phase: str2, at: Type10.Number() })),
25227
+ readonly: opt(Type10.Boolean())
24625
25228
  });
24626
- var MobileChatSnapshotSchema = Type9.Object({
25229
+ var MobileChatSnapshotSchema = Type10.Object({
24627
25230
  ...MobileChatStatusSchema.properties,
24628
- items: Type9.Array(MobileChatItemSchema),
25231
+ items: Type10.Array(MobileChatItemSchema),
24629
25232
  historyBefore: nullable2(str2),
24630
25233
  streaming: nullable2(MobileStreamingSchema)
24631
25234
  });
24632
- var MobileWorkspaceSchema = Type9.Object({ root: str2, name: str2 });
24633
- var MobileSessionSummarySchema = Type9.Object({
25235
+ var MobileWorkspaceSchema = Type10.Object({ root: str2, name: str2 });
25236
+ var MobileSessionSummarySchema = Type10.Object({
24634
25237
  id: str2,
24635
25238
  workspace: str2,
24636
25239
  title: str2,
24637
25240
  preview: str2,
24638
- lastUsedAt: Type9.Integer(),
24639
- lastMessageAt: Type9.Integer(),
24640
- archived: Type9.Boolean(),
24641
- awaitingAnswer: opt(Type9.Boolean())
25241
+ lastUsedAt: Type10.Integer(),
25242
+ lastMessageAt: Type10.Integer(),
25243
+ archived: Type10.Boolean(),
25244
+ awaitingAnswer: opt(Type10.Boolean()),
25245
+ awaitingPlan: opt(Type10.Boolean())
24642
25246
  });
24643
- var MobileApprovalSchema = Type9.Object({
25247
+ var MobileApprovalSchema = Type10.Object({
24644
25248
  id: int,
24645
25249
  version: str2,
24646
25250
  taskId: int,
@@ -24648,236 +25252,267 @@ var MobileApprovalSchema = Type9.Object({
24648
25252
  title: opt(str2),
24649
25253
  command: str2,
24650
25254
  cwd: str2,
24651
- timeoutSeconds: opt(Type9.Number({ minimum: 0 })),
24652
- requestedAt: Type9.Number()
25255
+ timeoutSeconds: opt(Type10.Number({ minimum: 0 })),
25256
+ requestedAt: Type10.Number()
24653
25257
  });
24654
- var MobileTodoItemSchema = Type9.Object({
24655
- id: Type9.String({ minLength: 1, maxLength: TODO_LIMITS.idChars }),
24656
- text: Type9.String({ minLength: 1, maxLength: TODO_LIMITS.textChars }),
24657
- status: Type9.Union([Type9.Literal("pending"), Type9.Literal("in_progress"), Type9.Literal("completed")])
25258
+ var MobileTodoItemSchema = Type10.Object({
25259
+ id: Type10.String({ minLength: 1, maxLength: TODO_LIMITS.idChars }),
25260
+ text: Type10.String({ minLength: 1, maxLength: TODO_LIMITS.textChars }),
25261
+ status: Type10.Union([Type10.Literal("pending"), Type10.Literal("in_progress"), Type10.Literal("completed")])
24658
25262
  });
24659
- var MobileTodoListSchema = Type9.Object({
25263
+ var MobileTodoListSchema = Type10.Object({
24660
25264
  revision: int,
24661
- items: Type9.Array(MobileTodoItemSchema, { maxItems: TODO_LIMITS.maxItems })
25265
+ items: Type10.Array(MobileTodoItemSchema, { maxItems: TODO_LIMITS.maxItems })
24662
25266
  });
24663
- var MobileQuestionOptionSchema = Type9.Object({
24664
- id: Type9.String({ minLength: 1, maxLength: QUESTION_LIMITS.optionIdChars }),
24665
- label: Type9.String({ minLength: 1, maxLength: QUESTION_LIMITS.labelChars }),
24666
- description: opt(Type9.String({ maxLength: QUESTION_LIMITS.descriptionChars })),
24667
- recommended: opt(Type9.Boolean())
25267
+ var MobileQuestionOptionSchema = Type10.Object({
25268
+ id: Type10.String({ minLength: 1, maxLength: QUESTION_LIMITS.optionIdChars }),
25269
+ label: Type10.String({ minLength: 1, maxLength: QUESTION_LIMITS.labelChars }),
25270
+ description: opt(Type10.String({ maxLength: QUESTION_LIMITS.descriptionChars })),
25271
+ recommended: opt(Type10.Boolean())
24668
25272
  });
24669
- var MobileQuestionSummarySchema = Type9.Object({
25273
+ var MobileQuestionSummarySchema = Type10.Object({
24670
25274
  id: int,
24671
25275
  version: str2,
24672
- preview: Type9.String({ maxLength: QUESTION_LIMITS.previewChars }),
24673
- multiple: Type9.Boolean(),
25276
+ preview: Type10.String({ maxLength: QUESTION_LIMITS.previewChars }),
25277
+ multiple: Type10.Boolean(),
24674
25278
  optionCount: int,
24675
- createdAt: Type9.Number()
25279
+ createdAt: Type10.Number()
24676
25280
  });
24677
- var MobileQuestionListSchema = Type9.Object({
25281
+ var MobileQuestionListSchema = Type10.Object({
24678
25282
  revision: int,
24679
- pending: Type9.Array(MobileQuestionSummarySchema, { maxItems: QUESTION_LIMITS.maxPending })
25283
+ pending: Type10.Array(MobileQuestionSummarySchema, { maxItems: QUESTION_LIMITS.maxPending })
24680
25284
  });
24681
- var MobileQuestionSchema = Type9.Object({
25285
+ var MobileQuestionSchema = Type10.Object({
24682
25286
  id: int,
24683
25287
  callId: str2,
24684
25288
  version: str2,
24685
- question: Type9.String({ maxLength: QUESTION_LIMITS.questionChars }),
24686
- multiple: Type9.Boolean(),
24687
- options: Type9.Array(MobileQuestionOptionSchema, { maxItems: QUESTION_LIMITS.maxOptions }),
25289
+ question: Type10.String({ maxLength: QUESTION_LIMITS.questionChars }),
25290
+ multiple: Type10.Boolean(),
25291
+ options: Type10.Array(MobileQuestionOptionSchema, { maxItems: QUESTION_LIMITS.maxOptions }),
24688
25292
  status: questionStatus2,
24689
- answer: nullable2(Type9.Object({
24690
- optionIds: Type9.Array(Type9.String(), { maxItems: QUESTION_LIMITS.maxOptions }),
24691
- text: nullable2(Type9.String({ maxLength: QUESTION_LIMITS.textChars }))
25293
+ answer: nullable2(Type10.Object({
25294
+ optionIds: Type10.Array(Type10.String(), { maxItems: QUESTION_LIMITS.maxOptions }),
25295
+ text: nullable2(Type10.String({ maxLength: QUESTION_LIMITS.textChars }))
24692
25296
  })),
24693
- createdAt: Type9.Number(),
24694
- settledAt: nullable2(Type9.Number())
25297
+ createdAt: Type10.Number(),
25298
+ settledAt: nullable2(Type10.Number())
24695
25299
  });
24696
- var MobileQuestionResponseSchema = Type9.Union([
24697
- Type9.Object({
24698
- kind: Type9.Literal("answer"),
24699
- optionIds: opt(Type9.Array(Type9.String(), { maxItems: QUESTION_LIMITS.maxOptions })),
24700
- text: opt(Type9.String({ maxLength: QUESTION_LIMITS.textChars }))
25300
+ var MobileQuestionResponseSchema = Type10.Union([
25301
+ Type10.Object({
25302
+ kind: Type10.Literal("answer"),
25303
+ optionIds: opt(Type10.Array(Type10.String(), { maxItems: QUESTION_LIMITS.maxOptions })),
25304
+ text: opt(Type10.String({ maxLength: QUESTION_LIMITS.textChars }))
24701
25305
  }, { additionalProperties: false }),
24702
- Type9.Object({ kind: Type9.Literal("skip") }, { additionalProperties: false })
25306
+ Type10.Object({ kind: Type10.Literal("skip") }, { additionalProperties: false })
25307
+ ]);
25308
+ var MobilePlanSchema = Type10.Object({
25309
+ version: Type10.String({ minLength: 1 }),
25310
+ title: Type10.String({ maxLength: PLAN_LIMITS.titleChars }),
25311
+ steps: Type10.Array(Type10.String({ maxLength: PLAN_LIMITS.stepChars }), { maxItems: PLAN_LIMITS.maxSteps }),
25312
+ note: Type10.String({ maxLength: PLAN_LIMITS.noteChars }),
25313
+ status: planStatus,
25314
+ feedback: nullable2(Type10.String({ maxLength: PLAN_LIMITS.feedbackChars })),
25315
+ createdAt: Type10.Number(),
25316
+ settledAt: nullable2(Type10.Number())
25317
+ });
25318
+ var MobilePlanDecisionSchema = Type10.Union([
25319
+ Type10.Object({ kind: Type10.Literal("approve") }, { additionalProperties: false }),
25320
+ Type10.Object({ kind: Type10.Literal("revise"), feedback: Type10.String({ minLength: 1, maxLength: PLAN_LIMITS.feedbackChars }) }, { additionalProperties: false })
24703
25321
  ]);
24704
- var sessionListParams = Type9.Object({
25322
+ var sessionListParams = Type10.Object({
24705
25323
  workspace: str2,
24706
25324
  query: opt(str2),
24707
25325
  cursor: opt(str2)
24708
25326
  });
24709
- var sessionListResult = Type9.Object({
24710
- sessions: Type9.Array(MobileSessionSummarySchema),
25327
+ var sessionListResult = Type10.Object({
25328
+ sessions: Type10.Array(MobileSessionSummarySchema),
24711
25329
  nextCursor: opt(str2)
24712
25330
  });
24713
- var subscription = { subscriptionId: Type9.String({ minLength: 1 }) };
24714
- var pageResult = Type9.Object({ items: Type9.Array(MobileChatItemSchema), historyBefore: nullable2(str2) });
24715
- var pageParams = Type9.Object({
25331
+ var subscription = { subscriptionId: Type10.String({ minLength: 1 }) };
25332
+ var pageResult = Type10.Object({ items: Type10.Array(MobileChatItemSchema), historyBefore: nullable2(str2) });
25333
+ var pageParams = Type10.Object({
24716
25334
  ...subscription,
24717
- before: Type9.String({ minLength: 1 }),
24718
- limit: opt(Type9.Integer({ minimum: 1, maximum: 50 }))
25335
+ before: Type10.String({ minLength: 1 }),
25336
+ limit: opt(Type10.Integer({ minimum: 1, maximum: 50 }))
24719
25337
  });
24720
- var chunkResult = Type9.Object({ data: str2, nextOffset: int, total: int, done: Type9.Boolean() });
25338
+ var chunkResult = Type10.Object({ data: str2, nextOffset: int, total: int, done: Type10.Boolean() });
24721
25339
  var MOBILE_API_V1_METHODS = {
24722
25340
  "server.hello": { params: MobileHelloParams, result: buildHelloResultSchema() },
24723
- "workspaces.list": { params: Type9.Null(), result: Type9.Object({ workspaces: Type9.Array(MobileWorkspaceSchema) }) },
25341
+ "workspaces.list": { params: Type10.Null(), result: Type10.Object({ workspaces: Type10.Array(MobileWorkspaceSchema) }) },
24724
25342
  "sessions.list": { params: sessionListParams, result: sessionListResult },
24725
- "sessions.create": { params: Type9.Object({ workspace: str2 }), result: MobileSessionSummarySchema },
25343
+ "sessions.create": { params: Type10.Object({ workspace: str2 }), result: MobileSessionSummarySchema },
24726
25344
  "chat.attach": {
24727
- params: Type9.Object({ sessionId: Type9.String({ minLength: 1 }) }),
24728
- result: Type9.Object({ subscriptionId: str2, sessionId: str2, snapshot: MobileChatSnapshotSchema })
25345
+ params: Type10.Object({ sessionId: Type10.String({ minLength: 1 }) }),
25346
+ result: Type10.Object({ subscriptionId: str2, sessionId: str2, snapshot: MobileChatSnapshotSchema })
24729
25347
  },
24730
- "chat.detach": { params: Type9.Object(subscription), result: Type9.Null() },
25348
+ "chat.detach": { params: Type10.Object(subscription), result: Type10.Null() },
24731
25349
  "chat.history": { params: pageParams, result: pageResult },
24732
25350
  "chat.content": {
24733
- params: Type9.Object({ ref: MobileContentRefSchema, offset: int }),
25351
+ params: Type10.Object({ ref: MobileContentRefSchema, offset: int }),
24734
25352
  result: chunkResult
24735
25353
  },
24736
25354
  "chat.submit": {
24737
- params: Type9.Object({
25355
+ params: Type10.Object({
24738
25356
  ...subscription,
24739
- requestId: Type9.String({ minLength: 1, maxLength: 128 }),
24740
- text: Type9.String({ minLength: 1, maxLength: 65536 }),
24741
- mode: opt(Type9.Literal("steer"))
25357
+ requestId: Type10.String({ minLength: 1, maxLength: 128 }),
25358
+ text: Type10.String({ minLength: 1, maxLength: 65536 }),
25359
+ mode: opt(Type10.Literal("steer"))
24742
25360
  }),
24743
- result: Type9.Object({ accepted: Type9.Literal(true), queued: Type9.Boolean() })
25361
+ result: Type10.Object({ accepted: Type10.Literal(true), queued: Type10.Boolean() })
24744
25362
  },
24745
25363
  "chat.submissionStatus": {
24746
- params: Type9.Object({ ...subscription, requestId: Type9.String({ minLength: 1, maxLength: 128 }) }),
24747
- result: Type9.Object({ status: Type9.Union([Type9.Literal("accepted"), Type9.Literal("unknown")]) })
25364
+ params: Type10.Object({ ...subscription, requestId: Type10.String({ minLength: 1, maxLength: 128 }) }),
25365
+ result: Type10.Object({ status: Type10.Union([Type10.Literal("accepted"), Type10.Literal("unknown")]) })
24748
25366
  },
24749
25367
  "chat.steer": {
24750
- params: Type9.Object({ ...subscription, id: int }),
24751
- result: Type9.Object({
24752
- result: Type9.Union([
24753
- Type9.Literal("steered"),
24754
- Type9.Literal("withdrawn"),
24755
- Type9.Literal("placed"),
24756
- Type9.Literal("settled"),
24757
- Type9.Literal("unknown")
25368
+ params: Type10.Object({ ...subscription, id: int }),
25369
+ result: Type10.Object({
25370
+ result: Type10.Union([
25371
+ Type10.Literal("steered"),
25372
+ Type10.Literal("withdrawn"),
25373
+ Type10.Literal("placed"),
25374
+ Type10.Literal("settled"),
25375
+ Type10.Literal("unknown")
24758
25376
  ]),
24759
25377
  id: opt(int),
24760
- text: opt(Type9.String()),
25378
+ text: opt(Type10.String()),
24761
25379
  images: opt(int)
24762
25380
  })
24763
25381
  },
24764
25382
  "chat.withdraw": {
24765
- params: Type9.Object({ ...subscription, id: int }),
24766
- result: Type9.Object({
24767
- result: Type9.Union([
24768
- Type9.Literal("withdrawn"),
24769
- Type9.Literal("placed"),
24770
- Type9.Literal("settled"),
24771
- Type9.Literal("unknown")
25383
+ params: Type10.Object({ ...subscription, id: int }),
25384
+ result: Type10.Object({
25385
+ result: Type10.Union([
25386
+ Type10.Literal("withdrawn"),
25387
+ Type10.Literal("placed"),
25388
+ Type10.Literal("settled"),
25389
+ Type10.Literal("unknown")
24772
25390
  ]),
24773
- text: opt(Type9.String()),
25391
+ text: opt(Type10.String()),
24774
25392
  images: opt(int)
24775
25393
  })
24776
25394
  },
24777
- "chat.abort": { params: Type9.Object(subscription), result: Type9.Object({ accepted: Type9.Boolean() }) },
25395
+ "chat.abort": { params: Type10.Object(subscription), result: Type10.Object({ accepted: Type10.Boolean() }) },
24778
25396
  "subagents.open": {
24779
- params: Type9.Object({ ...subscription, agentId: opt(Type9.String({ minLength: 1 })), taskId: opt(int) }),
24780
- result: Type9.Object({
25397
+ params: Type10.Object({ ...subscription, agentId: opt(Type10.String({ minLength: 1 })), taskId: opt(int) }),
25398
+ result: Type10.Object({
24781
25399
  subagentSubscriptionId: str2,
24782
25400
  agentId: str2,
24783
25401
  snapshot: MobileChatSnapshotSchema
24784
25402
  })
24785
25403
  },
24786
25404
  "subagents.history": {
24787
- params: Type9.Object({
24788
- subagentSubscriptionId: Type9.String({ minLength: 1 }),
24789
- before: Type9.String({ minLength: 1 }),
24790
- limit: opt(Type9.Integer({ minimum: 1, maximum: 50 }))
25405
+ params: Type10.Object({
25406
+ subagentSubscriptionId: Type10.String({ minLength: 1 }),
25407
+ before: Type10.String({ minLength: 1 }),
25408
+ limit: opt(Type10.Integer({ minimum: 1, maximum: 50 }))
24791
25409
  }),
24792
25410
  result: pageResult
24793
25411
  },
24794
25412
  "subagents.close": {
24795
- params: Type9.Object({ subagentSubscriptionId: Type9.String({ minLength: 1 }) }),
24796
- result: Type9.Null()
25413
+ params: Type10.Object({ subagentSubscriptionId: Type10.String({ minLength: 1 }) }),
25414
+ result: Type10.Null()
24797
25415
  },
24798
25416
  "subagents.stop": {
24799
- params: Type9.Object({ ...subscription, taskId: int }),
24800
- result: Type9.Object({ stopped: Type9.Boolean() })
25417
+ params: Type10.Object({ ...subscription, taskId: int }),
25418
+ result: Type10.Object({ stopped: Type10.Boolean() })
24801
25419
  },
24802
25420
  "approvals.list": {
24803
- params: Type9.Object(subscription),
24804
- result: Type9.Object({ approvals: Type9.Array(MobileApprovalSchema) })
25421
+ params: Type10.Object(subscription),
25422
+ result: Type10.Object({ approvals: Type10.Array(MobileApprovalSchema) })
24805
25423
  },
24806
- "approvals.get": { params: Type9.Object({ ...subscription, id: int }), result: MobileApprovalSchema },
25424
+ "approvals.get": { params: Type10.Object({ ...subscription, id: int }), result: MobileApprovalSchema },
24807
25425
  "approvals.decide": {
24808
- params: Type9.Object({
25426
+ params: Type10.Object({
24809
25427
  ...subscription,
24810
25428
  taskId: int,
24811
25429
  id: int,
24812
- version: Type9.String({ minLength: 1 }),
24813
- decision: Type9.Union([Type9.Literal("allow"), Type9.Literal("deny")])
25430
+ version: Type10.String({ minLength: 1 }),
25431
+ decision: Type10.Union([Type10.Literal("allow"), Type10.Literal("deny")])
24814
25432
  }),
24815
- result: Type9.Object({
24816
- status: Type9.Union([
24817
- Type9.Literal("pending"),
24818
- Type9.Literal("allowed"),
24819
- Type9.Literal("denied"),
24820
- Type9.Literal("cancelled")
25433
+ result: Type10.Object({
25434
+ status: Type10.Union([
25435
+ Type10.Literal("pending"),
25436
+ Type10.Literal("allowed"),
25437
+ Type10.Literal("denied"),
25438
+ Type10.Literal("cancelled")
24821
25439
  ])
24822
25440
  })
24823
25441
  },
24824
- "todos.get": { params: Type9.Object(subscription), result: MobileTodoListSchema },
24825
- "questions.list": { params: Type9.Object(subscription), result: MobileQuestionListSchema },
24826
- "questions.get": { params: Type9.Object({ ...subscription, id: int }), result: MobileQuestionSchema },
25442
+ "todos.get": { params: Type10.Object(subscription), result: MobileTodoListSchema },
25443
+ "questions.list": { params: Type10.Object(subscription), result: MobileQuestionListSchema },
25444
+ "questions.get": { params: Type10.Object({ ...subscription, id: int }), result: MobileQuestionSchema },
24827
25445
  "questions.respond": {
24828
- params: Type9.Object({
25446
+ params: Type10.Object({
24829
25447
  ...subscription,
24830
25448
  id: int,
24831
- version: Type9.String({ minLength: 1 }),
25449
+ version: Type10.String({ minLength: 1 }),
24832
25450
  response: MobileQuestionResponseSchema
24833
25451
  }),
24834
25452
  result: MobileQuestionSchema
24835
25453
  },
25454
+ "plan.get": {
25455
+ params: Type10.Object({ ...subscription, version: Type10.String({ minLength: 1 }) }),
25456
+ result: MobilePlanSchema
25457
+ },
25458
+ "plan.respond": {
25459
+ params: Type10.Object({
25460
+ ...subscription,
25461
+ version: Type10.String({ minLength: 1 }),
25462
+ decision: MobilePlanDecisionSchema
25463
+ }),
25464
+ result: MobilePlanSchema
25465
+ },
25466
+ "plan.exit": {
25467
+ params: Type10.Object(subscription),
25468
+ result: Type10.Object({ mode: Type10.Literal("normal"), revision: int })
25469
+ },
24836
25470
  "media.read": {
24837
- params: Type9.Object({
25471
+ params: Type10.Object({
24838
25472
  ...subscription,
24839
- agentId: opt(Type9.String({ minLength: 1 })),
25473
+ agentId: opt(Type10.String({ minLength: 1 })),
24840
25474
  entryId: int,
24841
25475
  contentIndex: int,
24842
25476
  offset: int
24843
25477
  }),
24844
- result: Type9.Object({ ...chunkResult.properties, mimeType: str2 })
25478
+ result: Type10.Object({ ...chunkResult.properties, mimeType: str2 })
24845
25479
  }
24846
25480
  };
24847
25481
  function buildHelloResultSchema() {
24848
- return Type9.Object({
24849
- apiVersion: Type9.Integer({ minimum: 1 }),
25482
+ return Type10.Object({
25483
+ apiVersion: Type10.Integer({ minimum: 1 }),
24850
25484
  serverId: str2,
24851
25485
  serverName: str2,
24852
25486
  gatewayVersion: str2,
24853
- capabilities: Type9.Array(str2),
24854
- limits: Type9.Object({
24855
- maxFrameBytes: Type9.Integer(),
24856
- maxPayloadBytes: Type9.Integer(),
24857
- pageItems: Type9.Integer(),
24858
- chunkBytes: Type9.Integer(),
24859
- maxConcurrentRequests: Type9.Integer(),
24860
- maxSendBufferBytes: Type9.Integer()
25487
+ capabilities: Type10.Array(str2),
25488
+ limits: Type10.Object({
25489
+ maxFrameBytes: Type10.Integer(),
25490
+ maxPayloadBytes: Type10.Integer(),
25491
+ pageItems: Type10.Integer(),
25492
+ chunkBytes: Type10.Integer(),
25493
+ maxConcurrentRequests: Type10.Integer(),
25494
+ maxSendBufferBytes: Type10.Integer()
24861
25495
  })
24862
25496
  });
24863
25497
  }
24864
25498
  var patchBody = {
24865
- seq: Type9.Integer({ minimum: 1 }),
24866
- upserts: Type9.Array(MobileChatItemSchema),
25499
+ seq: Type10.Integer({ minimum: 1 }),
25500
+ upserts: Type10.Array(MobileChatItemSchema),
24867
25501
  status: opt(MobileChatStatusSchema),
24868
25502
  streaming: opt(nullable2(MobileStreamingSchema)),
24869
- streamingAppend: opt(Type9.Object({ text: opt(str2), thinking: opt(str2) }))
25503
+ streamingAppend: opt(Type10.Object({ text: opt(str2), thinking: opt(str2) }))
24870
25504
  };
24871
25505
  var MOBILE_API_V1_NOTIFICATIONS = {
24872
- "workspaces.changed": Type9.Object({ revision: Type9.Integer({ minimum: 1 }) }),
24873
- "sessions.changed": Type9.Object({ revision: Type9.Integer({ minimum: 1 }) }),
24874
- "chat.patch": Type9.Object({ subscriptionId: str2, ...patchBody }),
24875
- "chat.reset": Type9.Object({ subscriptionId: str2, seq: Type9.Integer({ minimum: 1 }) }),
24876
- "subagents.patch": Type9.Object({ subagentSubscriptionId: str2, ...patchBody }),
24877
- "subagents.reset": Type9.Object({ subagentSubscriptionId: str2, seq: Type9.Integer({ minimum: 1 }) }),
24878
- "approvals.changed": Type9.Object({ subscriptionId: str2, revision: Type9.Integer({ minimum: 1 }) }),
24879
- "todos.changed": Type9.Object({ subscriptionId: str2, revision: Type9.Integer({ minimum: 1 }) }),
24880
- "questions.changed": Type9.Object({ subscriptionId: str2, revision: Type9.Integer({ minimum: 1 }) })
25506
+ "workspaces.changed": Type10.Object({ revision: Type10.Integer({ minimum: 1 }) }),
25507
+ "sessions.changed": Type10.Object({ revision: Type10.Integer({ minimum: 1 }) }),
25508
+ "chat.patch": Type10.Object({ subscriptionId: str2, ...patchBody }),
25509
+ "chat.reset": Type10.Object({ subscriptionId: str2, seq: Type10.Integer({ minimum: 1 }) }),
25510
+ "subagents.patch": Type10.Object({ subagentSubscriptionId: str2, ...patchBody }),
25511
+ "subagents.reset": Type10.Object({ subagentSubscriptionId: str2, seq: Type10.Integer({ minimum: 1 }) }),
25512
+ "approvals.changed": Type10.Object({ subscriptionId: str2, revision: Type10.Integer({ minimum: 1 }) }),
25513
+ "todos.changed": Type10.Object({ subscriptionId: str2, revision: Type10.Integer({ minimum: 1 }) }),
25514
+ "questions.changed": Type10.Object({ subscriptionId: str2, revision: Type10.Integer({ minimum: 1 }) }),
25515
+ "plan.changed": Type10.Object({ subscriptionId: str2, revision: Type10.Integer({ minimum: 1 }) })
24881
25516
  };
24882
25517
 
24883
25518
  // packages/gateway/src/mobile/projection.ts
@@ -25088,6 +25723,13 @@ function mapStatus(view, options = {}) {
25088
25723
  }
25089
25724
  },
25090
25725
  ...view.questions === undefined ? {} : { questions: { revision: view.questions.revision, pendingCount: view.questions.pending.length } },
25726
+ ...view.plan === undefined ? {} : {
25727
+ plan: {
25728
+ revision: view.plan.revision,
25729
+ pending: view.plan.current?.status === "awaiting_approval",
25730
+ ...view.plan.current === null ? {} : { version: view.plan.current.version }
25731
+ }
25732
+ },
25091
25733
  status: view.status,
25092
25734
  usage: mapUsage(view.usage),
25093
25735
  ...view.taskUsage === undefined ? {} : { taskUsage: mapUsage(view.taskUsage) },
@@ -25602,6 +26244,12 @@ class MobileAdapter {
25602
26244
  return this.questionsGet(params);
25603
26245
  case "questions.respond":
25604
26246
  return this.questionsRespond(params);
26247
+ case "plan.get":
26248
+ return this.planGet(params);
26249
+ case "plan.respond":
26250
+ return this.planRespond(params);
26251
+ case "plan.exit":
26252
+ return this.planExit(params);
25605
26253
  case "media.read":
25606
26254
  return this.mediaRead(params);
25607
26255
  default:
@@ -25656,7 +26304,8 @@ class MobileAdapter {
25656
26304
  lastUsedAt: Math.round(session.lastUsedAt),
25657
26305
  lastMessageAt: Math.round(messageTime(session)),
25658
26306
  archived: false,
25659
- ...session.awaitingAnswer === undefined ? {} : { awaitingAnswer: session.awaitingAnswer }
26307
+ ...session.awaitingAnswer === undefined ? {} : { awaitingAnswer: session.awaitingAnswer },
26308
+ ...session.awaitingPlan === undefined ? {} : { awaitingPlan: session.awaitingPlan }
25660
26309
  })),
25661
26310
  ...offset + page.length < sessions.length ? { nextCursor: String(offset + page.length) } : {}
25662
26311
  };
@@ -25675,7 +26324,8 @@ class MobileAdapter {
25675
26324
  lastUsedAt: Math.round(created.lastUsedAt),
25676
26325
  lastMessageAt: Math.round(messageTime(created)),
25677
26326
  archived: false,
25678
- ...created.awaitingAnswer === undefined ? {} : { awaitingAnswer: created.awaitingAnswer }
26327
+ ...created.awaitingAnswer === undefined ? {} : { awaitingAnswer: created.awaitingAnswer },
26328
+ ...created.awaitingPlan === undefined ? {} : { awaitingPlan: created.awaitingPlan }
25679
26329
  };
25680
26330
  }
25681
26331
  nextId(prefix) {
@@ -25752,7 +26402,8 @@ class MobileAdapter {
25752
26402
  approvalsRunning: false,
25753
26403
  approvalsDirty: false,
25754
26404
  todosRevision: chat.view.value?.todos?.revision ?? 0,
25755
- questionsRevision: chat.view.value?.questions?.revision ?? 0
26405
+ questionsRevision: chat.view.value?.questions?.revision ?? 0,
26406
+ planRevision: chat.view.value?.plan?.revision ?? 0
25756
26407
  };
25757
26408
  this.chatState = state;
25758
26409
  const snapshot = projection.snapshot();
@@ -25937,6 +26588,11 @@ class MobileAdapter {
25937
26588
  state.questionsRevision = questions;
25938
26589
  this.sendNotification("questions.changed", { subscriptionId: state.subscriptionId, revision: questions });
25939
26590
  }
26591
+ const plan = view?.plan?.revision;
26592
+ if (plan !== undefined && plan > state.planRevision) {
26593
+ state.planRevision = plan;
26594
+ this.sendNotification("plan.changed", { subscriptionId: state.subscriptionId, revision: plan });
26595
+ }
25940
26596
  }
25941
26597
  async todosGet(params) {
25942
26598
  const state = this.requireChat(params);
@@ -25975,6 +26631,31 @@ class MobileAdapter {
25975
26631
  response: params.response
25976
26632
  }), "rejected");
25977
26633
  }
26634
+ async planGet(params) {
26635
+ const state = this.requireChat(params);
26636
+ this.assertAuthorized();
26637
+ if (state.chat.view.value?.plan === undefined)
26638
+ throw new MobileMethodError("not_found", "plan mode is not available");
26639
+ const record = await remote2(() => state.chat.planGet({ version: params.version }));
26640
+ if (record === null)
26641
+ throw new MobileMethodError("not_found", "plan not found in this session");
26642
+ return record;
26643
+ }
26644
+ async planExit(params) {
26645
+ const state = this.requireChat(params);
26646
+ this.assertAuthorized();
26647
+ if (state.chat.view.value?.plan === undefined)
26648
+ throw new MobileMethodError("not_found", "plan mode is not available");
26649
+ const view = await remote2(() => state.chat.planMode({ mode: "normal" }), "rejected");
26650
+ return { mode: "normal", revision: view.revision };
26651
+ }
26652
+ async planRespond(params) {
26653
+ const state = this.requireChat(params);
26654
+ this.assertAuthorized();
26655
+ if (state.chat.view.value?.plan === undefined)
26656
+ throw new MobileMethodError("not_found", "plan mode is not available");
26657
+ return remote2(() => state.chat.planRespond({ version: params.version, decision: params.decision }), "rejected");
26658
+ }
25978
26659
  requireSubagent(params) {
25979
26660
  const state = this.subagentState;
25980
26661
  if (state === undefined || state.subscriptionId !== params.subagentSubscriptionId)
@@ -27278,7 +27959,7 @@ class MobileGateway {
27278
27959
  }
27279
27960
  }
27280
27961
  attachConnection(socket, auth, localAddress) {
27281
- const connectionId = randomUUID3();
27962
+ const connectionId = randomUUID4();
27282
27963
  const [serverEnd, clientEnd] = memoryLinkPair();
27283
27964
  this.chord.accept(serverEnd, {
27284
27965
  principal: { connectionId, kind: "mobile", deviceId: auth.deviceId, grantRevision: auth.grantRevision },
@@ -27637,7 +28318,7 @@ async function removeGatewayPortFile(nekoDir, serverId) {
27637
28318
  }
27638
28319
 
27639
28320
  // packages/gateway/src/server-services.ts
27640
- import { randomUUID as randomUUID5 } from "crypto";
28321
+ import { randomUUID as randomUUID6 } from "crypto";
27641
28322
  import { ChordError as ChordError5, local as local3, method as method2 } from "@earendil-works/chord";
27642
28323
 
27643
28324
  // packages/core/src/application-updates.ts
@@ -27788,7 +28469,7 @@ class UpdateChecker {
27788
28469
  }
27789
28470
 
27790
28471
  // packages/gateway/src/server-services.ts
27791
- import { Type as Type12 } from "typebox";
28472
+ import { Type as Type13 } from "typebox";
27792
28473
 
27793
28474
  // packages/gateway/src/session-host.ts
27794
28475
  import { open as open3 } from "fs/promises";
@@ -27800,7 +28481,7 @@ import {
27800
28481
  } from "@earendil-works/chord";
27801
28482
  import { withAbortSignal as withAbortSignal4 } from "@earendil-works/chord/context";
27802
28483
  import { UsageDoc as UsageDoc4 } from "@earendil-works/pi-durable";
27803
- import { Type as Type11 } from "typebox";
28484
+ import { Type as Type12 } from "typebox";
27804
28485
 
27805
28486
  // packages/gateway/src/image-uploads.ts
27806
28487
  var IMAGE_CHUNK_BYTES = 256 * 1024;
@@ -27899,7 +28580,7 @@ class ImageUploads {
27899
28580
  // packages/gateway/src/subagent-host.ts
27900
28581
  import { ChordError as ChordError3, local, opaque } from "@earendil-works/chord";
27901
28582
  import { UsageDoc as UsageDoc3 } from "@earendil-works/pi-durable";
27902
- import { Type as Type10 } from "typebox";
28583
+ import { Type as Type11 } from "typebox";
27903
28584
  async function openReader(runtime, childId, parentId, context, through, historyUsage) {
27904
28585
  const child = await runtime.harness.conversation(childId, context);
27905
28586
  if (!child)
@@ -28024,15 +28705,15 @@ async function openReader(runtime, childId, parentId, context, through, historyU
28024
28705
  }
28025
28706
  var ChildProjection = local("neko.subagent-projection", {
28026
28707
  version: 1,
28027
- input: Type10.Object({ parentId: Type10.Integer(), agentId: Type10.String() }),
28708
+ input: Type11.Object({ parentId: Type11.Integer(), agentId: Type11.String() }),
28028
28709
  members: {
28029
- revision: Type10.Integer(),
28710
+ revision: Type11.Integer(),
28030
28711
  reader: opaque("neko.subagent-reader@1")
28031
28712
  }
28032
28713
  });
28033
28714
  function installSubagentHost(host, runtime, context) {
28034
28715
  host.serve(ChildProjection, (input, _principal, self2) => {
28035
- const revision = self2.state("revision", Type10.Integer(), 0);
28716
+ const revision = self2.state("revision", Type11.Integer(), 0);
28036
28717
  const reader = self2.resource("reader", {
28037
28718
  version: 1,
28038
28719
  async open() {
@@ -28073,7 +28754,7 @@ function installSubagentHost(host, runtime, context) {
28073
28754
  parentId: Number(input.sessionId),
28074
28755
  agentId: input.agentId ?? `round-${input.taskId}`
28075
28756
  });
28076
- const authorizedRevision = self2.state("authorizedRevision", Type10.Integer(), 0);
28757
+ const authorizedRevision = self2.state("authorizedRevision", Type11.Integer(), 0);
28077
28758
  self2.on("authorizedView", shared.revision, async (value, ctx) => {
28078
28759
  try {
28079
28760
  target.value;
@@ -28126,7 +28807,7 @@ function installSubagentHost(host, runtime, context) {
28126
28807
  }
28127
28808
 
28128
28809
  // packages/gateway/src/session-host.ts
28129
- var OwnerInput = Type11.Object({});
28810
+ var OwnerInput = Type12.Object({});
28130
28811
  var Owner = local2("neko.attachment-owner", {
28131
28812
  version: 1,
28132
28813
  input: OwnerInput,
@@ -28134,7 +28815,7 @@ var Owner = local2("neko.attachment-owner", {
28134
28815
  });
28135
28816
  var Projection = local2("neko.projection", {
28136
28817
  version: 1,
28137
- input: Type11.Object({ id: Type11.String() }),
28818
+ input: Type12.Object({ id: Type12.String() }),
28138
28819
  members: {
28139
28820
  view: ChatViewSchema,
28140
28821
  reader: opaque2("neko.projection-reader@1")
@@ -28174,7 +28855,8 @@ async function openProjection(runtime, session, context) {
28174
28855
  "app.neko.subagents",
28175
28856
  "app.neko.jobs",
28176
28857
  "app.neko.todos",
28177
- "app.neko.questions"
28858
+ "app.neko.questions",
28859
+ "app.neko.plan"
28178
28860
  ].includes(change.record.kind) && change.conversationId === session.id || change.record.kind === "pi.usage" && change.conversationId !== undefined && childIds.has(change.conversationId))))
28179
28861
  changed?.(null);
28180
28862
  });
@@ -28202,7 +28884,7 @@ async function openProjection(runtime, session, context) {
28202
28884
  }
28203
28885
  }, "", { models: runtime.listModels(), contextEntries: view.value.entries, compaction });
28204
28886
  const jobs = (await session.jobs()).map(clipJob);
28205
- const [todos, questions] = await Promise.all([session.todos(), session.questions()]);
28887
+ const [todos, questions, plan] = await Promise.all([session.todos(), session.questions(), session.plan()]);
28206
28888
  const changes = await session.changeSummary().catch(() => {
28207
28889
  return;
28208
28890
  });
@@ -28223,6 +28905,7 @@ async function openProjection(runtime, session, context) {
28223
28905
  jobs,
28224
28906
  todos,
28225
28907
  questions,
28908
+ plan,
28226
28909
  ...changes ? { changes } : {},
28227
28910
  agents: summarizeSubagents(subagents, (await runtime.harness.snapshot(SubagentsDoc, session.id, context))?.items),
28228
28911
  subagents: subagents.map(({ usageStart: _start, usageEnd: _end, ...entry }) => ({
@@ -28435,6 +29118,21 @@ function serveSessions(host, runtime, context) {
28435
29118
  throw new ChordError4("invalid", error instanceof Error ? error.message : "Cannot answer the question");
28436
29119
  }
28437
29120
  },
29121
+ planGet: async ({ version }) => await projection.value.session.planRecord(version) ?? null,
29122
+ planRespond: async ({ version, decision }) => {
29123
+ try {
29124
+ return await projection.value.session.respondPlan(version, decision);
29125
+ } catch (error) {
29126
+ throw new ChordError4("invalid", error instanceof Error ? error.message : "Cannot decide the plan");
29127
+ }
29128
+ },
29129
+ planMode: async ({ mode }) => {
29130
+ try {
29131
+ return await projection.value.session.setPlanMode(mode);
29132
+ } catch (error) {
29133
+ throw new ChordError4("invalid", error instanceof Error ? error.message : "Cannot change plan mode");
29134
+ }
29135
+ },
28438
29136
  skills: () => runtime.listSkills(projection.value.session.id),
28439
29137
  workspaceDir: async ({ path }, ctx) => {
28440
29138
  try {
@@ -28504,7 +29202,7 @@ function serveSessions(host, runtime, context) {
28504
29202
  }
28505
29203
 
28506
29204
  // packages/gateway/src/skill-market.ts
28507
- import { createHash as createHash6, randomUUID as randomUUID4 } from "crypto";
29205
+ import { createHash as createHash6, randomUUID as randomUUID5 } from "crypto";
28508
29206
  import { mkdir as mkdir4, readdir as readdir2, rm as rm4, stat as stat3, writeFile as writeFile4 } from "fs/promises";
28509
29207
  import { homedir as homedir6 } from "os";
28510
29208
  import { dirname as dirname6, join as join11 } from "path";
@@ -29096,7 +29794,7 @@ function createSkillMarketClient(options) {
29096
29794
  const skill = await detail(name);
29097
29795
  const files = unpack(skill, await download(skill));
29098
29796
  await sweepTempDirs(skillsDir);
29099
- const temp = join11(dirname6(skillsDir), `.tmp-${randomUUID4()}`);
29797
+ const temp = join11(dirname6(skillsDir), `.tmp-${randomUUID5()}`);
29100
29798
  try {
29101
29799
  for (const [relative, bytes] of files) {
29102
29800
  const dest = join11(temp, ...relative.split("/"));
@@ -29105,7 +29803,7 @@ function createSkillMarketClient(options) {
29105
29803
  }
29106
29804
  await mkdir4(skillsDir, { recursive: true });
29107
29805
  if (exists) {
29108
- const backup = join11(dirname6(skillsDir), `${BACKUP_PREFIX}${randomUUID4()}-${name}`);
29806
+ const backup = join11(dirname6(skillsDir), `${BACKUP_PREFIX}${randomUUID5()}-${name}`);
29109
29807
  await renameWithRetry(target, backup);
29110
29808
  try {
29111
29809
  await renameWithRetry(temp, target, placeRename ? { rename: placeRename } : {});
@@ -29229,7 +29927,7 @@ async function listDrives() {
29229
29927
  // packages/gateway/src/server-services.ts
29230
29928
  var ServerState = local3("neko.server-state", {
29231
29929
  version: 1,
29232
- input: Type12.Object({}),
29930
+ input: Type13.Object({}),
29233
29931
  members: {
29234
29932
  directory: DirectoryStateSchema,
29235
29933
  workspaces: WorkspaceStateSchema,
@@ -29237,10 +29935,10 @@ var ServerState = local3("neko.server-state", {
29237
29935
  searchSettings: SearchSettingsStateSchema,
29238
29936
  contextSettings: ContextSettingsStateSchema,
29239
29937
  defaultModel: DefaultModelStateSchema,
29240
- refreshCredentials: method2(Type12.Null(), Type12.Null()),
29241
- refreshSearchSettings: method2(Type12.Null(), Type12.Null()),
29242
- refreshContextSettings: method2(Type12.Null(), Type12.Null()),
29243
- refreshDefaultModel: method2(Type12.Null(), Type12.Null())
29938
+ refreshCredentials: method2(Type13.Null(), Type13.Null()),
29939
+ refreshSearchSettings: method2(Type13.Null(), Type13.Null()),
29940
+ refreshContextSettings: method2(Type13.Null(), Type13.Null()),
29941
+ refreshDefaultModel: method2(Type13.Null(), Type13.Null())
29244
29942
  }
29245
29943
  });
29246
29944
  async function createNekoServerServices(options) {
@@ -29320,7 +30018,8 @@ async function createNekoServerServices(options) {
29320
30018
  lastMessageAt: info.lastMessageAt,
29321
30019
  model: info.model,
29322
30020
  status: info.status,
29323
- awaitingAnswer: info.awaitingAnswer
30021
+ awaitingAnswer: info.awaitingAnswer,
30022
+ awaitingPlan: info.awaitingPlan
29324
30023
  }));
29325
30024
  const readStatuses = async () => {
29326
30025
  const enabled = new Set(runtime.enabledProviders());
@@ -29519,7 +30218,7 @@ async function createNekoServerServices(options) {
29519
30218
  if (!await runtime.getConversation(toConversationId(id)))
29520
30219
  throw new ChordError5("gone", `Unknown session: ${id}`);
29521
30220
  if (owner.owner.active?.sessionId !== id) {
29522
- owner.owner.active = { sessionId: id, attachmentId: randomUUID5() };
30221
+ owner.owner.active = { sessionId: id, attachmentId: randomUUID6() };
29523
30222
  await runtime.touchConversation(toConversationId(id));
29524
30223
  }
29525
30224
  return owner.owner.active;
@@ -29802,7 +30501,8 @@ async function startGateway(options = {}) {
29802
30501
  ...options.extensions !== undefined ? { extensions: options.extensions } : {},
29803
30502
  ...options.promptHome !== undefined ? { promptHome: options.promptHome } : {},
29804
30503
  ...fileConfig.shell?.path !== undefined ? { shellPath: fileConfig.shell.path } : {},
29805
- ...options.shellResolver !== undefined ? { shellResolver: options.shellResolver } : {}
30504
+ ...options.shellResolver !== undefined ? { shellResolver: options.shellResolver } : {},
30505
+ ...options.timings !== undefined ? { timings: options.timings } : {}
29806
30506
  });
29807
30507
  try {
29808
30508
  return await bindGateway(runtime, {
@@ -29853,7 +30553,7 @@ async function bindGateway(runtime, options) {
29853
30553
  listeners.push(wsListener);
29854
30554
  }
29855
30555
  const acceptLocal = (link) => chord.accept(link, {
29856
- principal: { kind: "local", connectionId: randomUUID6() },
30556
+ principal: { kind: "local", connectionId: randomUUID7() },
29857
30557
  onProtocolMismatch: ({ peer }) => options.onError?.(new Error(`Rejected a client on Chord protocol ${peer ?? "unknown"}; restart or update it.`))
29858
30558
  });
29859
30559
  try {
@@ -29955,7 +30655,7 @@ async function loadOrCreateServerId(nekoDir) {
29955
30655
  if (existing !== "")
29956
30656
  return existing;
29957
30657
  } catch {}
29958
- const created = randomUUID6();
30658
+ const created = randomUUID7();
29959
30659
  await mkdir6(nekoDir, { recursive: true, mode: 448 });
29960
30660
  await writeFile5(file, `${created}
29961
30661
  `, { mode: 384 });