neko-harness 0.4.7 → 0.4.8

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,
@@ -19785,499 +19870,238 @@ ${text}` : `No new output (next offset ${offset}).`);
19785
19870
  }
19786
19871
  }
19787
19872
 
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");
19873
+ // packages/core/src/plan.ts
19874
+ import { randomUUID as randomUUID2 } from "crypto";
19875
+ import {
19876
+ defineExtension as defineExtension6,
19877
+ defineTool as defineTool5,
19878
+ hook as hook2,
19879
+ InboxDoc,
19880
+ ToolTask as ToolTask2,
19881
+ wrapTool
19882
+ } from "@earendil-works/pi-durable";
19883
+ import { createBashTool } from "@earendil-works/pi-durable/tools";
19884
+ import { Type as Type5 } from "typebox";
19797
19885
 
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())
19886
+ // packages/core/src/plan-bash.ts
19887
+ var SAFE_PATTERNS = [
19888
+ /^\s*(cat|head|tail|nl|tac|rev|cut|tr|paste|column|fold|comm|wc|sort|uniq|diff|file|stat|du|df|tree)\b/,
19889
+ /^\s*(grep|egrep|fgrep|rg|fd|find|jq|bat|eza|ls|pwd|echo|printf|true|cd)\b/,
19890
+ /^\s*(basename|dirname|realpath|readlink|which|whereis|type|sha256sum|shasum|md5sum)\b/,
19891
+ /^\s*(uname|whoami|id|date|cal|uptime|ps|free)\b/,
19892
+ /^\s*env\s*$/,
19893
+ /^\s*printenv\b/,
19894
+ /^\s*sed\s+-n\b/,
19895
+ /^\s*awk\b/,
19896
+ /^\s*curl\b/,
19897
+ /^\s*wget\s+-O\s*-/,
19898
+ /^\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/,
19899
+ /^\s*git\s+config\s+--get\b/,
19900
+ /^\s*git\s+stash\s+list\b/,
19901
+ /^\s*git\s+branch(\s+(-a|-r|-v|-vv|-l|--all|--remotes|--list|--show-current))*\s*$/,
19902
+ /^\s*git\s+remote(\s+(-v|show\b.*|get-url\b.*))?\s*$/,
19903
+ /^\s*npm\s+(list|ls|view|info|search|outdated|audit)\b/,
19904
+ /^\s*(bun|pnpm)\s+(pm\s+ls|list|ls|why|outdated|info|view)\b/,
19905
+ /^\s*yarn\s+(list|info|why|audit)\b/,
19906
+ /^\s*(node|python3?|bun|deno)\s+(-v|--version)\s*$/
19907
+ ];
19908
+ var DANGEROUS_FLAGS = [
19909
+ /--output\b/,
19910
+ /\bfind\b[^|;&]*\s-(delete|exec|execdir|ok|okdir|fprint0?|fprintf|fls)\b/,
19911
+ /\bsed\b[^|;&]*(\s-[A-Za-z]*i|--in-place)/,
19912
+ /\bsort\b[^|;&]*\s-[A-Za-z]*o/,
19913
+ /\btree\b[^|;&]*\s-o\b/,
19914
+ /\brg\b[^|;&]*--pre\b/,
19915
+ /\bfd\b[^|;&]*(\s-[xX]\b|--exec)/,
19916
+ /\bcurl\b[^|;&]*\s(-[A-Za-z]*[oOdXTF]\b|--(output|remote-name|data[\w-]*|request|upload-file|form|json)\b)/,
19917
+ /\bnpm\s+audit\s+fix\b/
19918
+ ];
19919
+ var DANGEROUS_CONTENT = [
19920
+ /\bawk\b.*(system\s*\(|\bgetline\b|\|\s*"|\bprintf?\b[^}]*>)/,
19921
+ /\bsed\b.*['"\s;{0-9$/,][wWe]\s+\S/
19922
+ ];
19923
+ function segments(command) {
19924
+ const result = [];
19925
+ let raw = "";
19926
+ let bare = "";
19927
+ let quote = null;
19928
+ const flush = () => {
19929
+ if (raw.trim() !== "")
19930
+ result.push({ raw, bare });
19931
+ raw = "";
19932
+ bare = "";
19933
+ };
19934
+ for (let i = 0;i < command.length; i++) {
19935
+ const char = command[i];
19936
+ if (quote) {
19937
+ raw += char;
19938
+ if (char === "\\" && quote === '"' && i + 1 < command.length)
19939
+ raw += command[++i];
19940
+ else if (char === quote) {
19941
+ quote = null;
19942
+ bare += char;
19943
+ }
19840
19944
  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." });
19945
+ }
19946
+ if (char === "\\" && i + 1 < command.length) {
19947
+ raw += char + command[++i];
19948
+ bare += " ";
19848
19949
  continue;
19849
19950
  }
19850
- const skill = parseSkillFile(content, filePath, report);
19851
- if (skill) {
19852
- skills.push(skill);
19853
- capture?.(skill, content);
19951
+ if (char === "'" || char === '"') {
19952
+ quote = char;
19953
+ raw += char;
19954
+ bare += char;
19955
+ continue;
19854
19956
  }
19957
+ const two = command.slice(i, i + 2);
19958
+ if (two === "&&" || two === "||") {
19959
+ flush();
19960
+ i++;
19961
+ continue;
19962
+ }
19963
+ if (char === ";" || char === "|" || char === `
19964
+ `) {
19965
+ flush();
19966
+ continue;
19967
+ }
19968
+ raw += char;
19969
+ bare += char;
19855
19970
  }
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
19971
+ if (quote)
19972
+ return null;
19973
+ flush();
19974
+ return result;
19975
+ }
19976
+ var stripHarmlessRedirects = (text) => text.replace(/[0-9]?>&[0-9-]/g, " ").replace(/(?:[0-9]|&)?>>?\s*\/dev\/null\b/g, " ");
19977
+ function isSafeCommand(command) {
19978
+ const parts = segments(command);
19979
+ if (!parts?.length)
19980
+ return false;
19981
+ if (/\$\(|`|<\(|>\(/.test(command.replace(/'[^']*'/g, "''")))
19982
+ return false;
19983
+ return parts.every(({ raw, bare }) => {
19984
+ const text = stripHarmlessRedirects(bare);
19985
+ if (/[>&]|<</.test(text))
19986
+ return false;
19987
+ return SAFE_PATTERNS.some((pattern) => pattern.test(text)) && !DANGEROUS_FLAGS.some((pattern) => pattern.test(text)) && !DANGEROUS_CONTENT.some((pattern) => pattern.test(raw));
19913
19988
  });
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}
19989
+ }
19920
19990
 
19921
- ${args}` : block;
19991
+ // packages/core/src/user-questions.ts
19992
+ import { randomUUID } from "crypto";
19993
+ import {
19994
+ defineDoc as defineDoc4,
19995
+ defineDocFamily,
19996
+ defineExtension as defineExtension5,
19997
+ defineTool as defineTool4
19998
+ } from "@earendil-works/pi-durable";
19999
+ import { Type as Type4 } from "typebox";
20000
+ var QuestionDoc = defineDocFamily({
20001
+ kind: "app.neko.question",
20002
+ version: 1,
20003
+ scope: "conversation",
20004
+ history: "latest",
20005
+ fork: "initial",
20006
+ family: true,
20007
+ initial: (seed) => seed
20008
+ });
20009
+ var QuestionsDoc = defineDoc4({
20010
+ kind: "app.neko.questions",
20011
+ version: 1,
20012
+ scope: "conversation",
20013
+ history: "latest",
20014
+ fork: "initial",
20015
+ initial: () => ({ revision: 0, pending: [] })
20016
+ });
20017
+ var clone = (value) => JSON.parse(JSON.stringify(value));
20018
+ var MISSING = {
20019
+ id: 0,
20020
+ callId: "",
20021
+ version: "",
20022
+ question: "",
20023
+ multiple: false,
20024
+ options: [],
20025
+ status: "pending",
20026
+ answer: null,
20027
+ createdAt: 0,
20028
+ settledAt: null
19922
20029
  };
20030
+ var argsOf = (record) => ({
20031
+ question: record.question,
20032
+ multiple: record.multiple,
20033
+ options: clone(record.options)
20034
+ });
20035
+ async function live(tx, id) {
20036
+ let cursor = id;
20037
+ for (let depth = 0;cursor !== undefined && depth < 16; depth++) {
20038
+ const task = await tx.task(cursor);
20039
+ if (!task || task.abortRequested || task.state.status === "terminal" || task.state.status === "completing")
20040
+ return false;
20041
+ cursor = task.owner;
20042
+ }
20043
+ return true;
20044
+ }
20045
+ function settle(record, index, status) {
20046
+ record.status = status;
20047
+ record.settledAt = Date.now();
20048
+ index.pending = index.pending.filter((id) => id !== record.id);
20049
+ index.revision += 1;
20050
+ }
19923
20051
 
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)
20052
+ class UserQuestions {
20053
+ harness;
20054
+ #tracked = new Set;
20055
+ #context;
20056
+ #running = false;
20057
+ #dirty = false;
20058
+ constructor(harness, context) {
20059
+ this.harness = harness;
20060
+ this.#context = context;
20061
+ const stop = harness.subscribeCommits(({ changes }) => {
20062
+ if (!changes.some((c) => c.type === "task" && (c.value.abortRequested || c.value.state.status === "terminal")))
20010
20063
  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;
20064
+ this.reconcileAll();
20065
+ });
20066
+ harness.closed.finally(stop).catch(() => {});
20033
20067
  }
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)]) {
20068
+ track(...ids) {
20069
+ for (const id of ids)
20070
+ this.#tracked.add(id);
20071
+ }
20072
+ async reconcileAll() {
20073
+ if (this.#running) {
20074
+ this.#dirty = true;
20075
+ return;
20076
+ }
20077
+ this.#running = true;
20040
20078
  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." });
20079
+ do {
20080
+ this.#dirty = false;
20081
+ for (const id of [...this.#tracked])
20082
+ await this.reconcile(id, this.#context).catch(() => {});
20083
+ } while (this.#dirty);
20084
+ } finally {
20085
+ this.#running = false;
20051
20086
  }
20052
20087
  }
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 } };
20088
+ async reconcile(conversationId, context) {
20089
+ const index = await this.harness.snapshot(QuestionsDoc, conversationId, context);
20090
+ if (!index?.pending.length)
20091
+ return;
20092
+ await this.harness.commit((tx) => this.#reconcile(tx, conversationId), context);
20070
20093
  }
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);
20094
+ async#reconcile(tx, conversationId) {
20095
+ const index = await tx.doc(QuestionsDoc, conversationId);
20096
+ const stale = [];
20097
+ for (const id of index.pending) {
20098
+ const record = await tx.doc(QuestionDoc, conversationId, String(id), MISSING);
20099
+ if (record.version === "" || record.status !== "pending")
20100
+ stale.push(id);
20101
+ else if (!await live(tx, id)) {
20102
+ record.status = "cancelled";
20103
+ record.settledAt = Date.now();
20104
+ stale.push(id);
20281
20105
  }
20282
20106
  }
20283
20107
  if (!stale.length)
@@ -20439,71 +20263,717 @@ function questionOutput(record) {
20439
20263
  return lines.join(`
20440
20264
  `);
20441
20265
  }
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
- }))
20266
+ if (record.status === "skipped")
20267
+ 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.";
20268
+ return "The question was cancelled because the run was stopped. No answer was given.";
20269
+ }
20270
+ function outcomeOf(record) {
20271
+ if (record.status === "answered" && record.answer)
20272
+ return {
20273
+ status: "answered",
20274
+ optionIds: record.answer.optionIds,
20275
+ labels: answerLabels(record, record.answer),
20276
+ text: record.answer.text
20277
+ };
20278
+ return { status: record.status === "skipped" ? "skipped" : "cancelled" };
20279
+ }
20280
+ function questionDetails(record) {
20281
+ const base = {
20282
+ id: record.id,
20283
+ question: record.question,
20284
+ multiple: record.multiple,
20285
+ options: record.options.map((o) => ({
20286
+ id: o.id,
20287
+ label: o.label,
20288
+ ...o.recommended ? { recommended: true } : {}
20289
+ }))
20290
+ };
20291
+ return record.status === "pending" ? { ...base, status: "pending" } : { ...base, ...outcomeOf(record) };
20292
+ }
20293
+ function createAskUserTool(service) {
20294
+ return defineTool4({
20295
+ name: "ask_user",
20296
+ 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.",
20297
+ parameters: parameters3,
20298
+ structuredOutputSchema: structuredOutputSchema2,
20299
+ replay: "safe",
20300
+ executionMode: "sequential",
20301
+ callers: ["model"],
20302
+ execute: async (input, api, context) => {
20303
+ context.abortSignal?.throwIfAborted();
20304
+ if ((await api.snapshot(SubagentDoc, api.conversationId, context))?.role)
20305
+ throw new Error("Subagents cannot ask the user; report the missing information to the main agent.");
20306
+ let args;
20307
+ try {
20308
+ args = validateQuestionArgs(input);
20309
+ } catch (error) {
20310
+ if (error instanceof InteractionInputError)
20311
+ throw new Error(`Invalid ask_user arguments: ${error.message}`);
20312
+ throw error;
20313
+ }
20314
+ const questions = service();
20315
+ const created = await questions.ensure(api.conversationId, api.taskId, api.callId, args, context);
20316
+ await api.details(questionDetails(created), context);
20317
+ const record = created.status === "pending" ? await questions.wait(api.conversationId, api.taskId, context) : created;
20318
+ return {
20319
+ output: [{ type: "text", text: questionOutput(record) }],
20320
+ structuredOutput: outcomeOf(record),
20321
+ details: questionDetails(record),
20322
+ isError: record.status === "cancelled"
20323
+ };
20324
+ }
20325
+ });
20326
+ }
20327
+ var createQuestionExtension = (service) => defineExtension5({ name: "neko-questions", tools: [createAskUserTool(service)] });
20328
+
20329
+ // packages/core/src/plan.ts
20330
+ var PLAN_REMOVED_TOOLS = ["edit", "write", "job"];
20331
+ var PLAN_INSTRUCTIONS = [
20332
+ "You are in plan mode: research and plan only, and change nothing.",
20333
+ "- 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.",
20334
+ "- Not available: edit, write, job and any command that modifies files, installs packages or starts processes. Do not try to work around this.",
20335
+ "- 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.",
20336
+ "- After submit_plan do nothing else. If the user asks for changes, research what is needed and submit a revised plan.",
20337
+ "- Use ask_user only for decisions you cannot settle by reading the code."
20338
+ ].join(`
20339
+ `);
20340
+ var PLAN_BUSY = "PLAN_BUSY: Wait for the current run to finish or stop it first.";
20341
+ function planAgentChange(mode) {
20342
+ if (mode === "normal")
20343
+ return { tools: null, modelTools: null, instructions: null };
20344
+ const named = (name) => ({ name });
20345
+ return {
20346
+ tools: { remove: PLAN_REMOVED_TOOLS.map(named) },
20347
+ modelTools: { add: [named("submit_plan")] },
20348
+ instructions: PLAN_INSTRUCTIONS
20349
+ };
20350
+ }
20351
+ var agentInPlanMode = (agent) => agent.instructions === PLAN_INSTRUCTIONS;
20352
+ var clone2 = (value) => JSON.parse(JSON.stringify(value));
20353
+ var toRecord = ({ taskId: _taskId, dispatched: _dispatched, ...record }) => clone2(record);
20354
+ function settle2(state, item, status) {
20355
+ item.status = status;
20356
+ item.settledAt = Date.now();
20357
+ state.revision += 1;
20358
+ }
20359
+
20360
+ class PlanService {
20361
+ harness;
20362
+ #context;
20363
+ constructor(harness, context) {
20364
+ this.harness = harness;
20365
+ this.#context = context;
20366
+ }
20367
+ async#state(id) {
20368
+ return this.harness.snapshot(PlanDoc, id, this.#context);
20369
+ }
20370
+ async mode(id) {
20371
+ return (await this.#state(id))?.mode ?? "normal";
20372
+ }
20373
+ async view(id) {
20374
+ const state = await this.#state(id);
20375
+ const current = state?.items.at(-1);
20376
+ return {
20377
+ mode: state?.mode ?? "normal",
20378
+ revision: state?.revision ?? 0,
20379
+ current: current ? toRecord(current) : null
20380
+ };
20381
+ }
20382
+ async get(id, version) {
20383
+ const item = (await this.#state(id))?.items.find((i) => i.version === version);
20384
+ return item ? toRecord(item) : undefined;
20385
+ }
20386
+ async awaiting(id) {
20387
+ const item = (await this.#state(id))?.items.find((i) => i.status === "awaiting_approval");
20388
+ return item ? toRecord(item) : undefined;
20389
+ }
20390
+ async setMode(id, mode) {
20391
+ return this.harness.commit(async (tx) => {
20392
+ const state = await tx.doc(PlanDoc, id);
20393
+ let changed = false;
20394
+ if (state.mode !== mode) {
20395
+ state.mode = mode;
20396
+ state.revision += 1;
20397
+ changed = true;
20398
+ }
20399
+ if (mode === "normal") {
20400
+ for (const item of state.items)
20401
+ if (item.status === "awaiting_approval") {
20402
+ settle2(state, item, "cancelled");
20403
+ changed = true;
20404
+ }
20405
+ }
20406
+ return changed;
20407
+ }, this.#context);
20408
+ }
20409
+ async submit(id, taskId, args, context) {
20410
+ return this.harness.commit(async (tx) => {
20411
+ if (!await live(tx, taskId))
20412
+ throw new Error("Task stopped");
20413
+ const state = await tx.doc(PlanDoc, id);
20414
+ const existing = state.items.find((item) => item.taskId === taskId);
20415
+ if (existing) {
20416
+ if (existing.title !== args.title || existing.note !== args.note || JSON.stringify(existing.steps) !== JSON.stringify(args.steps))
20417
+ throw new Error("Plan operation changed; a new call is required.");
20418
+ return toRecord(existing);
20419
+ }
20420
+ if (state.mode !== "plan")
20421
+ throw new Error("submit_plan is only available in plan mode.");
20422
+ for (const item of state.items)
20423
+ if (item.status === "awaiting_approval")
20424
+ settle2(state, item, "superseded");
20425
+ const item = {
20426
+ version: randomUUID2(),
20427
+ title: args.title,
20428
+ steps: [...args.steps],
20429
+ note: args.note,
20430
+ status: "awaiting_approval",
20431
+ feedback: null,
20432
+ createdAt: Date.now(),
20433
+ settledAt: null,
20434
+ taskId,
20435
+ dispatched: false
20436
+ };
20437
+ state.items.push(item);
20438
+ if (state.items.length > PLAN_HISTORY)
20439
+ state.items.splice(0, state.items.length - PLAN_HISTORY);
20440
+ state.revision += 1;
20441
+ return toRecord(item);
20442
+ }, context);
20443
+ }
20444
+ async decide(id, version, decision) {
20445
+ return this.harness.commit(async (tx) => {
20446
+ const state = await tx.doc(PlanDoc, id);
20447
+ const item = state.items.find((i) => i.version === version);
20448
+ if (!item)
20449
+ throw new Error("Plan not found.");
20450
+ if (item.status === "awaiting_approval") {
20451
+ if (decision.kind === "approve") {
20452
+ settle2(state, item, "approved");
20453
+ state.mode = "normal";
20454
+ } else {
20455
+ settle2(state, item, "revision_requested");
20456
+ item.feedback = decision.feedback;
20457
+ }
20458
+ }
20459
+ return { record: toRecord(item), item: clone2(item) };
20460
+ }, this.#context);
20461
+ }
20462
+ async supersedePending(id) {
20463
+ if (await this.awaiting(id))
20464
+ await this.#cancel(id, "superseded");
20465
+ }
20466
+ async cancelPending(id) {
20467
+ if (await this.awaiting(id))
20468
+ await this.#cancel(id, "cancelled");
20469
+ }
20470
+ async#cancel(id, status) {
20471
+ await this.harness.commit(async (tx) => {
20472
+ const state = await tx.doc(PlanDoc, id);
20473
+ for (const item of state.items)
20474
+ if (item.status === "awaiting_approval")
20475
+ settle2(state, item, status);
20476
+ }, this.#context);
20477
+ }
20478
+ async undispatched(id) {
20479
+ const item = (await this.#state(id))?.items.findLast((i) => (i.status === "approved" || i.status === "revision_requested") && !i.dispatched);
20480
+ return item ? clone2(item) : undefined;
20481
+ }
20482
+ async markDispatched(id, version) {
20483
+ await this.harness.commit(async (tx) => {
20484
+ const item = (await tx.doc(PlanDoc, id)).items.find((i) => i.version === version);
20485
+ if (item)
20486
+ item.dispatched = true;
20487
+ }, this.#context);
20488
+ }
20489
+ }
20490
+ function approvedPlanPrompt(plan) {
20491
+ return [
20492
+ `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:`,
20493
+ ...plan.steps.map((step, index) => `${index + 1}. ${step}`),
20494
+ ...plan.note ? ["", `Notes: ${plan.note}`] : [],
20495
+ "",
20496
+ "Use the todo tool to track progress, and verify the result when you are done."
20497
+ ].join(`
20498
+ `);
20499
+ }
20500
+ function planRevisionPrompt(plan, feedback) {
20501
+ return [
20502
+ `The user asked for changes to your plan "${plan.title}". Do not start implementing; you are still in plan mode.`,
20503
+ "",
20504
+ feedback,
20505
+ "",
20506
+ "Research what you need, then call submit_plan with the revised plan."
20507
+ ].join(`
20508
+ `);
20509
+ }
20510
+ var parameters4 = Type5.Object({
20511
+ title: Type5.String({ minLength: 1, maxLength: PLAN_LIMITS.titleChars, description: "Short name of the plan." }),
20512
+ steps: Type5.Array(Type5.String({ minLength: 1, maxLength: PLAN_LIMITS.stepChars }), {
20513
+ minItems: 1,
20514
+ maxItems: PLAN_LIMITS.maxSteps,
20515
+ description: "Ordered, concrete implementation steps."
20516
+ }),
20517
+ note: Type5.Optional(Type5.String({ maxLength: PLAN_LIMITS.noteChars, description: "Risks, assumptions and how to verify." }))
20518
+ }, { additionalProperties: false });
20519
+ function submitPlanTool(service) {
20520
+ return defineTool5({
20521
+ name: "submit_plan",
20522
+ 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.",
20523
+ parameters: parameters4,
20524
+ deferred: true,
20525
+ replay: "safe",
20526
+ executionMode: "sequential",
20527
+ callers: ["model"],
20528
+ execute: async (input, api, context) => {
20529
+ context.abortSignal?.throwIfAborted();
20530
+ if ((await api.snapshot(SubagentDoc, api.conversationId, context))?.role)
20531
+ throw new Error("Subagents cannot submit plans; report your findings to the main agent.");
20532
+ let args;
20533
+ try {
20534
+ args = validatePlanArgs(input);
20535
+ } catch (error) {
20536
+ if (error instanceof InteractionInputError)
20537
+ throw new Error(`Invalid submit_plan arguments: ${error.message}`);
20538
+ throw error;
20539
+ }
20540
+ let record = await service().submit(api.conversationId, api.taskId, args, context);
20541
+ const inbox = await api.snapshot(InboxDoc, api.conversationId, context);
20542
+ if (record.status === "awaiting_approval" && inbox?.items.some((item) => queuedUserInput(item))) {
20543
+ await service().supersedePending(api.conversationId);
20544
+ record = await service().get(api.conversationId, record.version) ?? record;
20545
+ }
20546
+ const details = {
20547
+ version: record.version,
20548
+ title: record.title,
20549
+ steps: record.steps,
20550
+ note: record.note
20551
+ };
20552
+ return {
20553
+ output: [
20554
+ {
20555
+ type: "text",
20556
+ text: "Plan submitted. It now waits for the user's decision. Stop here: do not call other tools or continue the work."
20557
+ }
20558
+ ],
20559
+ details,
20560
+ control: { terminate: true }
20561
+ };
20562
+ }
20563
+ });
20564
+ }
20565
+ var unsafeBash = (command) => `Plan mode is read-only, so this command was not run (it is not on the read-only allowlist): ${command}
20566
+ 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.`;
20567
+ var blockedTool = (name) => `Plan mode is read-only: ${name} is unavailable. Research with read-only tools, then call submit_plan.`;
20568
+ var createPlanExtension = (service) => defineExtension6({
20569
+ name: "neko-plan",
20570
+ tools: [submitPlanTool(service)],
20571
+ hooks: [
20572
+ hook2(ToolTask2, {
20573
+ beforeTool: async (call, api, context) => {
20574
+ if ((await api.snapshot(PlanDoc, api.conversationId, context))?.mode !== "plan")
20575
+ return;
20576
+ if (PLAN_REMOVED_TOOLS.includes(call.name))
20577
+ return { block: blockedTool(call.name) };
20578
+ if (call.name === "bash") {
20579
+ const command = String(call.arguments.command ?? "");
20580
+ if (!isSafeCommand(command))
20581
+ return { block: unsafeBash(command) };
20582
+ }
20583
+ }
20584
+ })
20585
+ ],
20586
+ wraps: [
20587
+ wrapTool(createBashTool(), (bash) => ({
20588
+ ...bash,
20589
+ execute: async (args, api, context) => {
20590
+ if ((await api.snapshot(PlanDoc, api.conversationId, context))?.mode === "plan" && !isSafeCommand(args.command))
20591
+ throw new Error(unsafeBash(args.command));
20592
+ return bash.execute(args, api, context);
20593
+ }
20594
+ }))
20595
+ ]
20596
+ });
20597
+
20598
+ // packages/core/src/prompt/invoke.ts
20599
+ import { dirname as dirname4 } from "path";
20600
+
20601
+ // packages/core/src/prompt/diagnostics.ts
20602
+ var ignorePromptDiagnostic = () => {};
20603
+ var warnPromptDiagnostic = ({ path, message }) => {
20604
+ console.warn(`[neko-prompt] ${JSON.stringify(path)}: ${message}`);
20605
+ };
20606
+ var isMissing = (error) => typeof error === "object" && error !== null && ("code" in error) && (error.code === "ENOENT" || error.code === "ENOTDIR");
20607
+
20608
+ // packages/core/src/prompt/skills.ts
20609
+ import { readdir, readFile as readFile2 } from "fs/promises";
20610
+ import { basename, dirname as dirname3, join as join3 } from "path";
20611
+ var MAX_NAME_LENGTH = 64;
20612
+ var MAX_DESCRIPTION_LENGTH = 1024;
20613
+ var parseSkillFile = (content, filePath, report = ignorePromptDiagnostic) => {
20614
+ const skip = (message) => {
20615
+ report({ path: filePath, message });
20616
+ return null;
20617
+ };
20618
+ try {
20619
+ const { frontmatter } = parseFrontmatter(content);
20620
+ const description = frontmatter.description;
20621
+ if (typeof description !== "string" || description.trim() === "")
20622
+ return skip("Skill description is missing or empty; skipped.");
20623
+ if (description.length > MAX_DESCRIPTION_LENGTH)
20624
+ return skip("Skill description is too long; skipped.");
20625
+ const name = typeof frontmatter.name === "string" && frontmatter.name.trim() !== "" ? frontmatter.name : basename(dirname3(filePath));
20626
+ if (name.length > MAX_NAME_LENGTH || /[\s\p{Cc}]/u.test(name))
20627
+ return skip("Skill name is too long or contains whitespace/control characters; skipped.");
20628
+ return {
20629
+ name,
20630
+ description,
20631
+ filePath,
20632
+ disableModelInvocation: frontmatter["disable-model-invocation"] === true
20633
+ };
20634
+ } catch {
20635
+ return skip("Invalid skill frontmatter; skipped.");
20636
+ }
20637
+ };
20638
+ var scanSkillsDir = async (dir, report = ignorePromptDiagnostic, capture) => {
20639
+ let entries;
20640
+ try {
20641
+ entries = await readdir(dir, { withFileTypes: true });
20642
+ } catch (error) {
20643
+ if (!isMissing(error))
20644
+ report({ path: dir, message: "Cannot scan skills directory; skipped." });
20645
+ return [];
20646
+ }
20647
+ const skills = [];
20648
+ for (const entry of entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
20649
+ if (!entry.isDirectory())
20650
+ continue;
20651
+ const filePath = join3(dir, entry.name, "SKILL.md");
20652
+ let content;
20653
+ try {
20654
+ content = await readFile2(filePath, "utf8");
20655
+ } catch (error) {
20656
+ if (!isMissing(error))
20657
+ report({ path: filePath, message: "Cannot read skill; skipped." });
20658
+ continue;
20659
+ }
20660
+ const skill = parseSkillFile(content, filePath, report);
20661
+ if (skill) {
20662
+ skills.push(skill);
20663
+ capture?.(skill, content);
20664
+ }
20665
+ }
20666
+ return skills;
20667
+ };
20668
+ var mergeSkills = (groups) => {
20669
+ const byName = new Map;
20670
+ for (const group of groups)
20671
+ for (const skill of group)
20672
+ byName.set(skill.name, skill);
20673
+ return [...byName.values()];
20674
+ };
20675
+ var escapeXml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
20676
+ var formatSkillsForPrompt = (skills) => {
20677
+ const visible = skills.filter((skill) => !skill.disableModelInvocation);
20678
+ if (visible.length === 0)
20679
+ return "";
20680
+ const lines = [
20681
+ "The following skills provide specialized instructions for specific tasks.",
20682
+ "Use the read tool to load a skill's file when the task matches its description.",
20683
+ "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.",
20684
+ "",
20685
+ "<available_skills>"
20686
+ ];
20687
+ for (const skill of visible) {
20688
+ lines.push(" <skill>");
20689
+ lines.push(` <name>${escapeXml(skill.name)}</name>`);
20690
+ lines.push(` <description>${escapeXml(skill.description)}</description>`);
20691
+ lines.push(` <location>${escapeXml(skill.filePath)}</location>`);
20692
+ lines.push(" </skill>");
20693
+ }
20694
+ lines.push("</available_skills>");
20695
+ return lines.join(`
20696
+ `);
20697
+ };
20698
+
20699
+ // packages/core/src/prompt/invoke.ts
20700
+ var expandSkillCommand = async (text, skills, readFile, report = warnPromptDiagnostic, metadata) => {
20701
+ if (!text.startsWith("/skill:"))
20702
+ return text;
20703
+ const spaceIndex = text.indexOf(" ");
20704
+ const skillName = spaceIndex === -1 ? text.slice(7) : text.slice(7, spaceIndex);
20705
+ const args = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1).trim();
20706
+ const skill = skills.find((s) => s.name === skillName);
20707
+ if (!skill)
20708
+ return text;
20709
+ let body;
20710
+ try {
20711
+ body = stripFrontmatter(await readFile(skill.filePath)).trim();
20712
+ } catch {
20713
+ report({ path: skill.filePath, message: "Cannot expand known skill; original input preserved." });
20714
+ return text;
20715
+ }
20716
+ metadata?.({
20717
+ name: skill.name,
20718
+ location: skill.filePath,
20719
+ body: `References are relative to ${dirname4(skill.filePath)}.
20720
+
20721
+ ${body}`,
20722
+ args
20723
+ });
20724
+ const block = `<skill name="${escapeXml(skill.name)}" location="${escapeXml(skill.filePath)}">
20725
+ References are relative to ${dirname4(skill.filePath)}.
20726
+
20727
+ ${body}
20728
+ </skill>`;
20729
+ return args ? `${block}
20730
+
20731
+ ${args}` : block;
20732
+ };
20733
+
20734
+ // packages/core/src/prompt/prompt.ts
20735
+ import { defineExtension as defineExtension7, section } from "@earendil-works/pi-durable";
20736
+ var KEYS = ["preamble", "rules", "project_context", "skills", "cwd"];
20737
+ 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.";
20738
+ var TOOL_GUIDELINES = {
20739
+ subagent: [
20740
+ "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."
20741
+ ],
20742
+ find_files: [
20743
+ "Use find_files to discover files; query is a literal substring. Narrow the directory when results are partial."
20744
+ ],
20745
+ search_text: [
20746
+ "Use search_text for literal text search and read for surrounding evidence. Partial results do not prove absence."
20747
+ ],
20748
+ read: ["Use read to examine files instead of cat or sed."],
20749
+ write: ["Use write only for new files or complete rewrites."],
20750
+ edit: [
20751
+ "Use edit for precise changes (edits[].oldText must match exactly)",
20752
+ "When changing multiple separate locations in one file, use one edit call with multiple entries in edits[] instead of multiple edit calls",
20753
+ "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.",
20754
+ "Keep edits[].oldText as small as possible while still being unique in the file. Do not pad with large unchanged regions."
20755
+ ],
20756
+ bash: ["Use bash for file operations like ls, rg, find"],
20757
+ job: [
20758
+ '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.'
20759
+ ],
20760
+ web_search: ["Use web_search for current information from the internet instead of guessing."],
20761
+ web_extract: ["Use web_extract to read the full content of a web page when you have its URL."],
20762
+ todo: [
20763
+ `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.`
20764
+ ],
20765
+ ask_user: [
20766
+ "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."
20767
+ ]
20768
+ };
20769
+ var GENERIC_RULES = ["Be concise in your responses", "Show file paths clearly when working with files"];
20770
+ 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.";
20771
+ var toolRules = (name, platform) => {
20772
+ const rules = TOOL_GUIDELINES[name] ?? [];
20773
+ return platform === "win32" && (name === "bash" || name === "job") ? [...rules, WINDOWS_SHELL_RULE] : rules;
20774
+ };
20775
+ var buildRules = (toolNames, platform) => {
20776
+ const rules = [];
20777
+ const seen = new Set;
20778
+ for (const rule of [...toolNames.flatMap((name) => toolRules(name, platform)), ...GENERIC_RULES]) {
20779
+ if (seen.has(rule))
20780
+ continue;
20781
+ seen.add(rule);
20782
+ rules.push(`- ${rule}`);
20783
+ }
20784
+ return rules.join(`
20785
+ `);
20786
+ };
20787
+ var renderProjectContext = (files) => files.length === 0 ? undefined : [
20788
+ "Project-specific instructions and guidelines:",
20789
+ ...files.map(({ path, content }) => `<project_instructions path="${escapeXml(path)}">
20790
+ ${content}
20791
+ </project_instructions>`)
20792
+ ].join(`
20793
+
20794
+ `);
20795
+ var createNekoPrompt = (loadResources, platform = process.platform) => {
20796
+ const built = new WeakMap;
20797
+ const build = async (input, context) => {
20798
+ const cwd = input.agent.cwd;
20799
+ const role = (await input.read.snapshot(SubagentDoc, input.conversationId, context))?.role;
20800
+ const resources = cwd ? await loadResources(input.conversationId, cwd) : { contextFiles: [], skills: [] };
20801
+ const sections = {
20802
+ preamble: role ? ROLE_PROMPTS[role] + SUBAGENT_DELIVERY : PREAMBLE,
20803
+ rules: buildRules(input.agent.tools.map((tool) => tool.name), platform),
20804
+ project_context: renderProjectContext(resources.contextFiles),
20805
+ skills: formatSkillsForPrompt(resources.skills) || undefined,
20806
+ cwd: cwd?.replace(/\\/g, "/")
20807
+ };
20808
+ return sections;
20809
+ };
20810
+ return defineExtension7({
20811
+ name: "neko-prompt",
20812
+ sections: KEYS.map((key) => section(key, async (input, context) => {
20813
+ let pending = built.get(input);
20814
+ if (!pending) {
20815
+ pending = build(input, context);
20816
+ built.set(input, pending);
20817
+ }
20818
+ const text = (await pending)[key];
20819
+ if (text === undefined)
20820
+ return;
20821
+ return key === "preamble" ? text : `<${key}>
20822
+ ${text}
20823
+ </${key}>`;
20824
+ }, { tag: false }))
20825
+ });
20826
+ };
20827
+
20828
+ // packages/core/src/prompt/resources.ts
20829
+ import { existsSync as existsSync2 } from "fs";
20830
+ import { readFile as readFile3 } from "fs/promises";
20831
+ import { homedir as homedir3 } from "os";
20832
+ import { join as join4 } from "path";
20833
+ import { fileURLToPath } from "url";
20834
+ var userAgentsFile = (home) => join4(home, ".agents", "AGENTS.md");
20835
+ var userSkillsDir = (home) => join4(home, ".agents", "skills");
20836
+ var workspaceAgentsFile = (workspace) => join4(workspace, "AGENTS.md");
20837
+ var workspaceSkillsDir = (workspace) => join4(workspace, ".agents", "skills");
20838
+ var resolveBuiltinSkillsDir = () => {
20839
+ for (const url of [new URL("./skills/", import.meta.url), new URL("../../skills/", import.meta.url)]) {
20840
+ const path = fileURLToPath(url);
20841
+ if (existsSync2(join4(path, "skill-creator", "SKILL.md")))
20842
+ return path;
20843
+ }
20844
+ return null;
20845
+ };
20846
+ var BUILTIN_SKILLS_DIR = resolveBuiltinSkillsDir();
20847
+ var readResources = async (workspace, home, builtinDir, report, invocationName) => {
20848
+ const contextFiles = [];
20849
+ for (const path of [userAgentsFile(home), workspaceAgentsFile(workspace)]) {
20850
+ try {
20851
+ const content = (await readFile3(path, "utf8")).replace(/^\uFEFF/, "");
20852
+ if (Buffer.byteLength(content, "utf8") > 64 * 1024)
20853
+ report({
20854
+ path,
20855
+ message: "Project instructions exceed 64 KiB; included in full. History compaction does not reduce this input."
20856
+ });
20857
+ contextFiles.push({ path, content });
20858
+ } catch (error) {
20859
+ if (!isMissing(error))
20860
+ report({ path, message: "Cannot read project instructions; skipped." });
20861
+ }
20862
+ }
20863
+ const candidates = invocationName === undefined ? undefined : new Map;
20864
+ const capture = candidates ? (skill, content) => {
20865
+ if (skill.name === invocationName)
20866
+ candidates.set(skill.filePath, content);
20867
+ } : undefined;
20868
+ const scan = async (dir, level) => (await scanSkillsDir(dir, report, capture)).map((skill) => ({ ...skill, level }));
20869
+ const [builtin, user, project] = await Promise.all([
20870
+ builtinDir === null ? Promise.resolve([]) : scan(builtinDir, "builtin"),
20871
+ scan(userSkillsDir(home), "user"),
20872
+ scan(workspaceSkillsDir(workspace), "project")
20873
+ ]);
20874
+ const skills = mergeSkills([builtin, user, project]);
20875
+ const winner = invocationName === undefined ? undefined : skills.find((skill) => skill.name === invocationName);
20876
+ if (winner && candidates) {
20877
+ const content = candidates.get(winner.filePath);
20878
+ if (content !== undefined)
20879
+ return { contextFiles, skills, invocation: { filePath: winner.filePath, content } };
20880
+ }
20881
+ return { contextFiles, skills };
20882
+ };
20883
+ var createResourceLoader = (home = homedir3(), report = warnPromptDiagnostic, options) => {
20884
+ const builtinDir = options?.builtinDir === undefined ? BUILTIN_SKILLS_DIR : options.builtinDir;
20885
+ const previous = new Map;
20886
+ return async (workspace, invocationName) => {
20887
+ const diagnostics = [];
20888
+ const resources = await readResources(workspace, home, builtinDir, (d) => diagnostics.push(d), invocationName);
20889
+ const known = previous.get(workspace);
20890
+ const current = new Set;
20891
+ for (const diagnostic of diagnostics) {
20892
+ const key = JSON.stringify(diagnostic);
20893
+ if (!known?.has(key) && !current.has(key))
20894
+ report(diagnostic);
20895
+ current.add(key);
20896
+ }
20897
+ if (current.size)
20898
+ previous.set(workspace, current);
20899
+ else
20900
+ previous.delete(workspace);
20901
+ return resources;
20466
20902
  };
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;
20903
+ };
20904
+ var scanSkillsByLevel = async (workspace, home = homedir3(), builtinDir = BUILTIN_SKILLS_DIR, report = warnPromptDiagnostic) => {
20905
+ const scan = async (dir, level) => (await scanSkillsDir(dir, report)).map((skill) => ({ ...skill, level }));
20906
+ const [builtin, user, project] = await Promise.all([
20907
+ builtinDir === null ? [] : scan(builtinDir, "builtin"),
20908
+ scan(userSkillsDir(home), "user"),
20909
+ scan(workspaceSkillsDir(workspace), "project")
20910
+ ]);
20911
+ return [...project, ...user, ...builtin];
20912
+ };
20913
+
20914
+ // packages/core/src/prompt/snapshots.ts
20915
+ var createResourceSnapshots = (load, submission) => {
20916
+ const conversations = new Map;
20917
+ const stateFor = (id, workspace) => {
20918
+ let state = conversations.get(id);
20919
+ if (!state || state.workspace !== workspace) {
20920
+ state = { workspace, pending: new Map };
20921
+ conversations.set(id, state);
20922
+ }
20923
+ return state;
20924
+ };
20925
+ return {
20926
+ stage(id, requestId, workspace, resources) {
20927
+ stateFor(id, workspace).pending.set(requestId, {
20928
+ contextFiles: resources.contextFiles,
20929
+ skills: resources.skills
20930
+ });
20931
+ },
20932
+ async current(id, workspace) {
20933
+ const state = stateFor(id, workspace);
20934
+ for (const [requestId, snapshot] of state.pending) {
20935
+ const record = await submission(id, requestId);
20936
+ if (!record || record.status === "queued")
20937
+ continue;
20938
+ state.pending.delete(requestId);
20939
+ if (record.type !== "input" || record.entry === undefined)
20940
+ continue;
20941
+ if (state.entry === undefined || record.entry > state.entry) {
20942
+ state.active = snapshot;
20943
+ state.entry = record.entry;
20944
+ }
20489
20945
  }
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
- };
20946
+ state.active ??= load(workspace);
20947
+ return state.active;
20948
+ },
20949
+ discard(id, requestId) {
20950
+ conversations.get(id)?.pending.delete(requestId);
20951
+ },
20952
+ move(id, from, to) {
20953
+ const pending = conversations.get(id)?.pending;
20954
+ const snapshot = pending?.get(from);
20955
+ if (!pending || !snapshot)
20956
+ return;
20957
+ pending.delete(from);
20958
+ pending.set(to, snapshot);
20959
+ },
20960
+ forget(id) {
20961
+ conversations.delete(id);
20500
20962
  }
20501
- });
20502
- }
20503
- var createQuestionExtension = (service) => defineExtension6({ name: "neko-questions", tools: [createAskUserTool(service)] });
20963
+ };
20964
+ };
20504
20965
 
20505
20966
  // packages/core/src/session-metadata.ts
20506
- var SessionMetadataDoc = defineDoc4({
20967
+ import { stripVTControlCharacters } from "util";
20968
+ import { getSupportedThinkingLevels } from "@earendil-works/pi-ai";
20969
+ import {
20970
+ defineDoc as defineDoc5,
20971
+ defineExtension as defineExtension8,
20972
+ defineTask as defineTask3,
20973
+ InboxDoc as InboxDoc2,
20974
+ LiveDoc as LiveDoc2
20975
+ } from "@earendil-works/pi-durable";
20976
+ var SessionMetadataDoc = defineDoc5({
20507
20977
  kind: "app.neko.session-metadata",
20508
20978
  version: 1,
20509
20979
  scope: "conversation",
@@ -20691,7 +21161,7 @@ class SessionMetadataManager {
20691
21161
  this.storage = storage;
20692
21162
  this.context = context;
20693
21163
  this.report = report;
20694
- registry.install(defineExtension7({ name: "neko-naming", tasks: [this.task] }));
21164
+ registry.install(defineExtension8({ name: "neko-naming", tasks: [this.task] }));
20695
21165
  this.#unsubscribe = harness.subscribeCommits(({ changes }) => {
20696
21166
  const ids = new Set;
20697
21167
  const submissions = new Map;
@@ -20704,7 +21174,7 @@ class SessionMetadataManager {
20704
21174
  submissions.set(c.value.conversationId, records);
20705
21175
  }
20706
21176
  if (c.type === "document" && c.conversationId) {
20707
- if (c.record.kind === "pi.agent" || c.record.kind === QuestionsDoc.definition.kind)
21177
+ if (c.record.kind === "pi.agent" || c.record.kind === QuestionsDoc.definition.kind || c.record.kind === PlanDoc.definition.kind)
20708
21178
  ids.add(c.conversationId);
20709
21179
  if (c.record.kind === "pi.live") {
20710
21180
  const running = c.value?.run !== undefined;
@@ -20773,7 +21243,7 @@ class SessionMetadataManager {
20773
21243
  cursor = page.next;
20774
21244
  } while (cursor);
20775
21245
  records.sort((a, b) => a.id - b.id);
20776
- const inbox = await this.harness.snapshot(InboxDoc, id, this.context);
21246
+ const inbox = await this.harness.snapshot(InboxDoc2, id, this.context);
20777
21247
  const updates = await Promise.all(records.filter((r) => r.type === "input").map(async (record) => {
20778
21248
  const entry = record.entry === undefined ? undefined : (await this.storage.entry(record.entry, this.context))?.entry;
20779
21249
  const queued = inbox?.items.find((i) => i.id === record.id && i.mode !== "write");
@@ -20783,10 +21253,13 @@ class SessionMetadataManager {
20783
21253
  }));
20784
21254
  const live = await this.harness.snapshot(LiveDoc2, id, this.context);
20785
21255
  const awaiting = live?.run !== undefined && ((await this.harness.snapshot(QuestionsDoc, id, this.context))?.pending.length ?? 0) > 0;
21256
+ const awaitingPlan = (await this.harness.snapshot(PlanDoc, id, this.context))?.items.some((item) => item.status === "awaiting_approval");
20786
21257
  await this.harness.commit(async (tx) => {
20787
21258
  const d = await tx.doc(SessionMetadataDoc, id);
20788
21259
  if ((d.awaiting ?? false) !== awaiting)
20789
21260
  d.awaiting = awaiting;
21261
+ if ((d.awaitingPlan ?? false) !== (awaitingPlan ?? false))
21262
+ d.awaitingPlan = awaitingPlan ?? false;
20790
21263
  for (const { record, data, answer } of updates) {
20791
21264
  if (!data)
20792
21265
  continue;
@@ -20834,9 +21307,9 @@ class SessionMetadataManager {
20834
21307
  }
20835
21308
 
20836
21309
  // 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({
21310
+ import { defineDoc as defineDoc6, defineExtension as defineExtension9, defineTool as defineTool6 } from "@earendil-works/pi-durable";
21311
+ import { Type as Type6 } from "typebox";
21312
+ var TodosDoc = defineDoc6({
20840
21313
  kind: "app.neko.todos",
20841
21314
  version: 1,
20842
21315
  scope: "conversation",
@@ -20844,30 +21317,30 @@ var TodosDoc = defineDoc5({
20844
21317
  fork: "asOf",
20845
21318
  initial: () => ({ revision: 0, items: [] })
20846
21319
  });
20847
- var TodoReceiptDoc = defineDoc5({
21320
+ var TodoReceiptDoc = defineDoc6({
20848
21321
  kind: "app.neko.todo-receipt",
20849
21322
  version: 1,
20850
21323
  scope: "task",
20851
21324
  initial: () => ({ applied: false, revision: 0, items: [] })
20852
21325
  });
20853
- var itemSchema = Type5.Object({
20854
- id: Type5.String({
21326
+ var itemSchema = Type6.Object({
21327
+ id: Type6.String({
20855
21328
  minLength: 1,
20856
21329
  maxLength: TODO_LIMITS.idChars,
20857
21330
  description: "Stable id; keep it when you update the same task."
20858
21331
  }),
20859
- text: Type5.String({ minLength: 1, maxLength: TODO_LIMITS.textChars }),
20860
- status: Type5.String({ enum: [...TODO_STATUSES] })
21332
+ text: Type6.String({ minLength: 1, maxLength: TODO_LIMITS.textChars }),
21333
+ status: Type6.String({ enum: [...TODO_STATUSES] })
20861
21334
  }, { 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, {
21335
+ var parameters5 = Type6.Object({
21336
+ action: Type6.String({ enum: ["list", "replace"], description: "list reads the list; replace overwrites it." }),
21337
+ items: Type6.Optional(Type6.Array(itemSchema, {
20865
21338
  maxItems: TODO_LIMITS.maxItems,
20866
21339
  description: "Required for replace only: the complete ordered list; [] clears it."
20867
21340
  }))
20868
21341
  }, { 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));
21342
+ var structuredOutputSchema3 = Type6.Object({ revision: Type6.Integer({ minimum: 0 }), items: Type6.Array(itemSchema) }, { additionalProperties: false });
21343
+ var clone3 = (value) => JSON.parse(JSON.stringify(value));
20871
21344
  function renderTodos(list) {
20872
21345
  const { completed, total } = todoProgress(list.items);
20873
21346
  if (!total)
@@ -20880,10 +21353,10 @@ function renderTodos(list) {
20880
21353
  `);
20881
21354
  }
20882
21355
  function createTodoTool() {
20883
- return defineTool5({
21356
+ return defineTool6({
20884
21357
  name: "todo",
20885
21358
  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,
21359
+ parameters: parameters5,
20887
21360
  structuredOutputSchema: structuredOutputSchema3,
20888
21361
  replay: "safe",
20889
21362
  executionMode: "sequential",
@@ -20898,7 +21371,7 @@ function createTodoTool() {
20898
21371
  if (args.items !== undefined)
20899
21372
  throw new Error("list does not accept items.");
20900
21373
  const saved = await api.snapshot(TodosDoc, api.conversationId, context);
20901
- list = saved ? clone2(saved) : { revision: 0, items: [] };
21374
+ list = saved ? clone3(saved) : { revision: 0, items: [] };
20902
21375
  } else {
20903
21376
  if (args.items === undefined)
20904
21377
  throw new Error("replace requires items.");
@@ -20906,9 +21379,9 @@ function createTodoTool() {
20906
21379
  list = await api.commit(async (tx) => {
20907
21380
  const receipt = await tx.doc(TodoReceiptDoc, api.taskId);
20908
21381
  if (receipt.applied) {
20909
- if (JSON.stringify(clone2(receipt.items)) !== JSON.stringify(items))
21382
+ if (JSON.stringify(clone3(receipt.items)) !== JSON.stringify(items))
20910
21383
  throw new Error("This todo call was already applied with different items.");
20911
- return { revision: receipt.revision, items: clone2(receipt.items) };
21384
+ return { revision: receipt.revision, items: clone3(receipt.items) };
20912
21385
  }
20913
21386
  const doc = await tx.doc(TodosDoc, api.conversationId);
20914
21387
  doc.revision += 1;
@@ -20916,7 +21389,7 @@ function createTodoTool() {
20916
21389
  receipt.applied = true;
20917
21390
  receipt.revision = doc.revision;
20918
21391
  receipt.items = items;
20919
- return { revision: doc.revision, items: clone2(items) };
21392
+ return { revision: doc.revision, items: clone3(items) };
20920
21393
  }, context);
20921
21394
  }
20922
21395
  const { completed, total } = todoProgress(list.items);
@@ -20928,21 +21401,21 @@ function createTodoTool() {
20928
21401
  }
20929
21402
  });
20930
21403
  }
20931
- var createTodoExtension = () => defineExtension8({ name: "neko-todos", tools: [createTodoTool()] });
21404
+ var createTodoExtension = () => defineExtension9({ name: "neko-todos", tools: [createTodoTool()] });
20932
21405
 
20933
21406
  // packages/core/src/tool-approvals.ts
20934
- import { randomUUID as randomUUID2 } from "crypto";
21407
+ import { randomUUID as randomUUID3 } from "crypto";
20935
21408
  import { realpath } from "fs/promises";
20936
21409
  import {
20937
21410
  configure as configure2,
20938
- defineDoc as defineDoc6,
20939
- defineExtension as defineExtension9,
20940
- hook as hook2,
20941
- ToolTask as ToolTask2,
20942
- wrapTool
21411
+ defineDoc as defineDoc7,
21412
+ defineExtension as defineExtension10,
21413
+ hook as hook3,
21414
+ ToolTask as ToolTask3,
21415
+ wrapTool as wrapTool2
20943
21416
  } from "@earendil-works/pi-durable";
20944
- import { createBashTool } from "@earendil-works/pi-durable/tools";
20945
- var ApprovalsDoc = defineDoc6({
21417
+ import { createBashTool as createBashTool2 } from "@earendil-works/pi-durable/tools";
21418
+ var ApprovalsDoc = defineDoc7({
20946
21419
  kind: "app.neko.approvals",
20947
21420
  version: 1,
20948
21421
  scope: "conversation",
@@ -21041,7 +21514,7 @@ class ToolApprovals {
21041
21514
  }
21042
21515
  doc.items.push({
21043
21516
  ...candidate,
21044
- version: randomUUID2(),
21517
+ version: randomUUID3(),
21045
21518
  status: "pending",
21046
21519
  createdAt: Date.now(),
21047
21520
  decidedAt: null
@@ -21050,10 +21523,10 @@ class ToolApprovals {
21050
21523
  }
21051
21524
  }
21052
21525
  function createApprovalExtension(service) {
21053
- return defineExtension9({
21526
+ return defineExtension10({
21054
21527
  name: "neko-tool-approvals",
21055
21528
  hooks: [
21056
- hook2(ToolTask2, {
21529
+ hook3(ToolTask3, {
21057
21530
  beforeTool: async (call, api, context) => {
21058
21531
  const role = await api.snapshot(SubagentDoc, api.conversationId, context);
21059
21532
  if (role?.role !== "explore" || call.name !== "bash")
@@ -21083,7 +21556,7 @@ function createApprovalExtension(service) {
21083
21556
  })
21084
21557
  ],
21085
21558
  wraps: [
21086
- wrapTool(createBashTool(), (bash) => ({
21559
+ wrapTool2(createBashTool2(), (bash) => ({
21087
21560
  ...bash,
21088
21561
  execute: async (args, api, context) => {
21089
21562
  const role = await api.snapshot(SubagentDoc, api.conversationId, context);
@@ -21107,7 +21580,7 @@ async function prepareApprovalConversations(harness, ids, extension, context) {
21107
21580
  const child = await harness.conversation(id, context);
21108
21581
  const agent = await child.agent(context);
21109
21582
  await harness.commit((tx) => configure2(tx, id, {
21110
- tools: [...agent.tools.filter((t) => t.name !== "bash"), createBashTool()],
21583
+ tools: [...agent.tools.filter((t) => t.name !== "bash"), createBashTool2()],
21111
21584
  extensions: { add: [extension] }
21112
21585
  }), context);
21113
21586
  }
@@ -21334,8 +21807,8 @@ function createDeepseekProvider(resolveKey, fetchImpl = fetch, options = {}) {
21334
21807
  }
21335
21808
 
21336
21809
  // 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";
21810
+ import { Type as Type7 } from "@earendil-works/pi-ai";
21811
+ import { defineExtension as defineExtension11, defineTool as defineTool7 } from "@earendil-works/pi-durable";
21339
21812
  var MAX_URLS = 20;
21340
21813
  var MAX_RESULTS = 10;
21341
21814
  var DEFAULT_RESULTS = 5;
@@ -21353,15 +21826,15 @@ function content(text) {
21353
21826
  return { output: [{ type: "text", text }] };
21354
21827
  }
21355
21828
  function createWebSearchTools(resolveProvider) {
21356
- return defineExtension10({
21829
+ return defineExtension11({
21357
21830
  name: "neko-web-search",
21358
21831
  tools: [
21359
- defineTool6({
21832
+ defineTool7({
21360
21833
  name: "web_search",
21361
21834
  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})` }))
21835
+ parameters: Type7.Object({
21836
+ query: Type7.String({ minLength: 1, maxLength: 4096, description: "The search query" }),
21837
+ maxResults: Type7.Optional(Type7.Number({ description: `Maximum number of results (1-${MAX_RESULTS}, default ${DEFAULT_RESULTS})` }))
21365
21838
  }),
21366
21839
  async execute({ query, maxResults }, _api, context) {
21367
21840
  try {
@@ -21387,11 +21860,11 @@ ${item.date ? `Published: ${item.date}
21387
21860
  }
21388
21861
  }
21389
21862
  }),
21390
- defineTool6({
21863
+ defineTool7({
21391
21864
  name: "web_extract",
21392
21865
  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" }), {
21866
+ parameters: Type7.Object({
21867
+ urls: Type7.Array(Type7.String({ maxLength: 2048, description: "http(s) URL" }), {
21395
21868
  minItems: 1,
21396
21869
  maxItems: MAX_URLS,
21397
21870
  description: `URLs to extract (1-${MAX_URLS})`
@@ -22116,7 +22589,7 @@ function cancellable(work, signal) {
22116
22589
  var DEFAULT_MODEL = { provider: "deepseek", modelId: "deepseek-flash" };
22117
22590
  var DEFAULT_STORAGE_PATH = join5(homedir4(), ".neko", "neko.sqlite");
22118
22591
  var neverAborted = () => new AbortController().signal;
22119
- var ArchivedDoc = defineDoc7({
22592
+ var ArchivedDoc = defineDoc8({
22120
22593
  kind: "app.neko.conversation",
22121
22594
  version: 1,
22122
22595
  scope: "conversation",
@@ -22124,7 +22597,7 @@ var ArchivedDoc = defineDoc7({
22124
22597
  fork: "initial",
22125
22598
  initial: () => ({ archived: false })
22126
22599
  });
22127
- var WorkspacesDoc = defineDoc7({
22600
+ var WorkspacesDoc = defineDoc8({
22128
22601
  kind: "app.neko.workspaces",
22129
22602
  version: 1,
22130
22603
  scope: "conversation",
@@ -22132,7 +22605,7 @@ var WorkspacesDoc = defineDoc7({
22132
22605
  fork: "initial",
22133
22606
  initial: () => ({ items: [] })
22134
22607
  });
22135
- var SearchSettingsDoc = defineDoc7({
22608
+ var SearchSettingsDoc = defineDoc8({
22136
22609
  kind: "app.neko.search-settings",
22137
22610
  version: 1,
22138
22611
  scope: "conversation",
@@ -22140,7 +22613,7 @@ var SearchSettingsDoc = defineDoc7({
22140
22613
  fork: "initial",
22141
22614
  initial: () => ({ provider: null })
22142
22615
  });
22143
- var ProviderSettingsDoc = defineDoc7({
22616
+ var ProviderSettingsDoc = defineDoc8({
22144
22617
  kind: "app.neko.provider-settings",
22145
22618
  version: 1,
22146
22619
  scope: "conversation",
@@ -22148,7 +22621,7 @@ var ProviderSettingsDoc = defineDoc7({
22148
22621
  fork: "initial",
22149
22622
  initial: () => ({ enabled: null })
22150
22623
  });
22151
- var DefaultModelDoc = defineDoc7({
22624
+ var DefaultModelDoc = defineDoc8({
22152
22625
  kind: "app.neko.default-model",
22153
22626
  version: 1,
22154
22627
  scope: "conversation",
@@ -22156,7 +22629,7 @@ var DefaultModelDoc = defineDoc7({
22156
22629
  fork: "initial",
22157
22630
  initial: () => ({ model: null })
22158
22631
  });
22159
- var ContextSettingsDoc = defineDoc7({
22632
+ var ContextSettingsDoc = defineDoc8({
22160
22633
  kind: "app.neko.context-settings",
22161
22634
  version: 1,
22162
22635
  scope: "conversation",
@@ -22164,7 +22637,7 @@ var ContextSettingsDoc = defineDoc7({
22164
22637
  fork: "initial",
22165
22638
  initial: emptySavedContextSettings
22166
22639
  });
22167
- var ActivityDoc = defineDoc7({
22640
+ var ActivityDoc = defineDoc8({
22168
22641
  kind: "app.neko.activity",
22169
22642
  version: 1,
22170
22643
  scope: "conversation",
@@ -22268,6 +22741,8 @@ async function createNekoRuntime(options = {}) {
22268
22741
  registry.install(createTodoExtension());
22269
22742
  let questions;
22270
22743
  registry.install(createQuestionExtension(() => questions));
22744
+ let plans;
22745
+ registry.install(createPlanExtension(() => plans));
22271
22746
  for (const extension of options.extensions ?? [])
22272
22747
  registry.install(extension);
22273
22748
  let approvals;
@@ -22301,6 +22776,7 @@ async function createNekoRuntime(options = {}) {
22301
22776
  }, context);
22302
22777
  approvals = new ToolApprovals(harness, context);
22303
22778
  questions = new UserQuestions(harness, context);
22779
+ plans = new PlanService(harness, context);
22304
22780
  const changes = new ChangeTracker(harness, context);
22305
22781
  const existing = await scanConversations();
22306
22782
  questions.track(...existing.map((r) => r.id));
@@ -22417,11 +22893,17 @@ async function createNekoRuntime(options = {}) {
22417
22893
  admissions.delete(id);
22418
22894
  }
22419
22895
  };
22420
- const submitTo = async (target, content, submitOptions) => admit(target.id, async () => {
22896
+ const syncPlanAgent = async (target) => {
22897
+ const mode = await plans.mode(target.id);
22898
+ if (mode === "plan" !== agentInPlanMode(await target.agent(context)))
22899
+ await target.configure(planAgentChange(mode), context);
22900
+ };
22901
+ const submitTo = async (target, content, submitOptions, extra) => admit(target.id, async () => {
22902
+ await syncPlanAgent(target);
22421
22903
  const agent = await target.agent(context);
22422
22904
  const workspace = executionWorkspace(agent.cwd);
22423
22905
  const duplicate = submitOptions?.requestId !== undefined && await storage.submissionByRequest(target.id, submitOptions.requestId, context);
22424
- const data = { at: Date.now(), prompt: "", images: [] };
22906
+ const data = { at: Date.now(), prompt: "", images: [], ...extra };
22425
22907
  let invocationName;
22426
22908
  if (!duplicate && typeof content === "string" && content.startsWith("/skill:")) {
22427
22909
  const end = content.indexOf(" ");
@@ -22485,10 +22967,25 @@ async function createNekoRuntime(options = {}) {
22485
22967
  resourceSnapshots.discard(target.id, requestId);
22486
22968
  throw error;
22487
22969
  }
22970
+ if (!extra?.planDecision)
22971
+ await plans.supersedePending(target.id);
22488
22972
  await touchConversation(target.id);
22489
22973
  await metadata.reconcile(target.id);
22490
22974
  return result;
22491
22975
  });
22976
+ const dispatchPlan = async (target, item) => {
22977
+ const approve = item.status === "approved";
22978
+ await submitTo(target, approve ? approvedPlanPrompt(item) : planRevisionPrompt(item, item.feedback ?? ""), { requestId: `${approve ? "plan-exec" : "plan-revise"}:${item.version}`, whenBusy: "followUp" }, {
22979
+ planDecision: {
22980
+ version: item.version,
22981
+ kind: approve ? "approve" : "revise",
22982
+ title: item.title,
22983
+ ...!approve && item.feedback ? { feedback: item.feedback } : {}
22984
+ }
22985
+ });
22986
+ await plans.markDispatched(target.id, item.version);
22987
+ };
22988
+ const isRunning = async (id) => (await harness.snapshot(importLiveDoc, id, context))?.run !== undefined;
22492
22989
  const waitAnswerOf = async (target, submission) => {
22493
22990
  const settled = await submission.wait(context);
22494
22991
  await metadata.flush();
@@ -22553,6 +23050,38 @@ async function createNekoRuntime(options = {}) {
22553
23050
  respondQuestion(id, version, response) {
22554
23051
  return questions.respond(target.id, id, version, response, context);
22555
23052
  },
23053
+ plan() {
23054
+ return plans.view(target.id);
23055
+ },
23056
+ planRecord(version) {
23057
+ return plans.get(target.id, version);
23058
+ },
23059
+ async setPlanMode(mode) {
23060
+ if (mode !== "normal" && mode !== "plan")
23061
+ throw new Error("Unknown plan mode.");
23062
+ return admit(target.id, async () => {
23063
+ if (await isRunning(target.id))
23064
+ throw new Error(PLAN_BUSY);
23065
+ await plans.setMode(target.id, mode);
23066
+ await syncPlanAgent(target);
23067
+ return plans.view(target.id);
23068
+ });
23069
+ },
23070
+ async respondPlan(version, decision) {
23071
+ const parsed = validatePlanDecision(decision);
23072
+ const known = await plans.get(target.id, version);
23073
+ if (!known)
23074
+ throw new Error("Plan not found.");
23075
+ if (known.status === "awaiting_approval") {
23076
+ if (await isRunning(target.id))
23077
+ throw new Error(PLAN_BUSY);
23078
+ await guardProvider((await target.agent(context)).model);
23079
+ }
23080
+ const { record, item } = await plans.decide(target.id, version, parsed);
23081
+ if ((item.status === "approved" || item.status === "revision_requested") && !item.dispatched)
23082
+ await dispatchPlan(target, item);
23083
+ return record;
23084
+ },
22556
23085
  async workspaceDir(path, signal) {
22557
23086
  return workspaceFiles.children(savedWorkspace((await target.agent(context)).cwd), path, signal ?? neverAborted());
22558
23087
  },
@@ -22578,12 +23107,13 @@ async function createNekoRuntime(options = {}) {
22578
23107
  await target.compact(instructions?.trim() || undefined, context);
22579
23108
  });
22580
23109
  },
22581
- abort() {
22582
- return target.abort(context);
23110
+ async abort() {
23111
+ await target.abort(context);
23112
+ await plans.cancelPending(target.id);
22583
23113
  },
22584
23114
  async withdraw(submissionId) {
22585
23115
  const id = submissionId;
22586
- const inbox = await harness.snapshot(InboxDoc2, target.id, context);
23116
+ const inbox = await harness.snapshot(InboxDoc3, target.id, context);
22587
23117
  const item = inbox?.items.find((i) => i.id === id);
22588
23118
  const input = item ? queuedUserInput(item) : undefined;
22589
23119
  if (item && !input)
@@ -22600,7 +23130,7 @@ async function createNekoRuntime(options = {}) {
22600
23130
  },
22601
23131
  async steer(submissionId) {
22602
23132
  const id = submissionId;
22603
- const inbox = await harness.snapshot(InboxDoc2, target.id, context);
23133
+ const inbox = await harness.snapshot(InboxDoc3, target.id, context);
22604
23134
  const item = inbox?.items.find((i) => i.id === id);
22605
23135
  const input = item ? queuedUserInput(item) : undefined;
22606
23136
  if (item && (!input || item.mode === "write"))
@@ -22650,6 +23180,19 @@ async function createNekoRuntime(options = {}) {
22650
23180
  const record = await harness.commit((tx) => tx.conversation(id), context);
22651
23181
  return archived || record?.owner ? undefined : makeSession(target);
22652
23182
  };
23183
+ for (const id of new Set([conversation.id, ...existing.map((r) => r.id)])) {
23184
+ try {
23185
+ const session = await getConversation(id);
23186
+ if (!session)
23187
+ continue;
23188
+ await admit(id, () => syncPlanAgent(session.conversation));
23189
+ const pending = await plans.undispatched(id);
23190
+ if (pending)
23191
+ await dispatchPlan(session.conversation, pending);
23192
+ } catch (error) {
23193
+ options.onReport?.(error);
23194
+ }
23195
+ }
22653
23196
  return {
22654
23197
  context,
22655
23198
  async compact(instructions, id) {
@@ -22788,6 +23331,7 @@ async function createNekoRuntime(options = {}) {
22788
23331
  lastMessageAt: info.lastMessageAt,
22789
23332
  status: info.status,
22790
23333
  awaitingAnswer: info.awaiting === true,
23334
+ awaitingPlan: info.awaitingPlan === true,
22791
23335
  model: (await target.agent(context)).model ?? null,
22792
23336
  id: record.id,
22793
23337
  archived: false,
@@ -22990,7 +23534,7 @@ async function createNekoRuntime(options = {}) {
22990
23534
  }
22991
23535
  // packages/gateway/src/contract.ts
22992
23536
  import { hostNamed, method, remote } from "@earendil-works/chord";
22993
- import { Type as Type7 } from "typebox";
23537
+ import { Type as Type8 } from "typebox";
22994
23538
  var GATEWAY_HOST = "neko";
22995
23539
  var rpc = (args, result) => [args, result];
22996
23540
  function contract(name, def) {
@@ -23003,19 +23547,19 @@ function contract(name, def) {
23003
23547
  ]));
23004
23548
  return remote(name, { ...def, host: hostNamed(GATEWAY_HOST), variants });
23005
23549
  }
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({
23550
+ var str = Type8.String();
23551
+ var num = Type8.Number();
23552
+ var bool = Type8.Boolean();
23553
+ var nil = Type8.Null();
23554
+ var integer = Type8.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER });
23555
+ var nullableString = Type8.Union([str, nil]);
23556
+ var phase = Type8.Enum(["pending", "running", "done"]);
23557
+ var level = Type8.Enum(["project", "user", "builtin"]);
23558
+ var thinking = Type8.Enum(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
23559
+ var model2 = Type8.Object({ provider: str, modelId: str });
23560
+ var change = Type8.Object({ model: Type8.Optional(model2), thinkingLevel: Type8.Optional(thinking) }, { additionalProperties: false });
23561
+ var usage = Type8.Object({ input: num, output: num, cacheRead: num, cacheWrite: num, cost: num });
23562
+ var image = Type8.Object({
23019
23563
  name: str,
23020
23564
  mimeType: str,
23021
23565
  width: integer,
@@ -23024,8 +23568,8 @@ var image = Type7.Object({
23024
23568
  entryId: integer,
23025
23569
  contentIndex: integer
23026
23570
  });
23027
- var skill = Type7.Object({ name: str, location: str, body: str, args: str });
23028
- var call = Type7.Object({
23571
+ var skill = Type8.Object({ name: str, location: str, body: str, args: str });
23572
+ var call = Type8.Object({
23029
23573
  callId: str,
23030
23574
  name: str,
23031
23575
  summary: str,
@@ -23033,262 +23577,289 @@ var call = Type7.Object({
23033
23577
  phase,
23034
23578
  output: nullableString,
23035
23579
  diff: nullableString,
23036
- diffAdded: Type7.Union([num, nil]),
23037
- diffRemoved: Type7.Union([num, nil]),
23038
- diffTruncated: Type7.Optional(bool)
23580
+ diffAdded: Type8.Union([num, nil]),
23581
+ diffRemoved: Type8.Union([num, nil]),
23582
+ diffTruncated: Type8.Optional(bool)
23039
23583
  });
23040
- var DelegationSchema = Type7.Object({
23584
+ var DelegationSchema = Type8.Object({
23041
23585
  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])),
23586
+ agentId: Type8.Optional(str),
23587
+ round: Type8.Optional(integer),
23588
+ runnerId: Type8.Optional(integer),
23589
+ inputEntryId: Type8.Optional(integer),
23590
+ answerEntryId: Type8.Optional(integer),
23591
+ roundUsage: Type8.Optional(Type8.Union([usage, nil])),
23048
23592
  taskId: integer,
23049
23593
  callId: str,
23050
23594
  childId: integer,
23051
- role: Type7.Enum(["explore", "plan", "coder"]),
23595
+ role: Type8.Enum(["explore", "plan", "coder"]),
23052
23596
  title: str,
23053
23597
  createdAt: num,
23054
- startedAt: Type7.Union([num, nil]),
23055
- endedAt: Type7.Union([num, nil]),
23056
- status: Type7.Enum(["queued", "running", "completed", "failed", "cancelled"]),
23598
+ startedAt: Type8.Union([num, nil]),
23599
+ endedAt: Type8.Union([num, nil]),
23600
+ status: Type8.Enum(["queued", "running", "completed", "failed", "cancelled"]),
23057
23601
  report: nullableString,
23058
- approvalCount: Type7.Optional(integer),
23059
- historyThrough: Type7.Optional(integer),
23060
- historyUsage: Type7.Optional(usage)
23602
+ approvalCount: Type8.Optional(integer),
23603
+ historyThrough: Type8.Optional(integer),
23604
+ historyUsage: Type8.Optional(usage)
23061
23605
  });
23062
- var SubagentSummarySchema = Type7.Object({
23606
+ var SubagentSummarySchema = Type8.Object({
23063
23607
  agentId: str,
23064
23608
  parentId: integer,
23065
23609
  childId: integer,
23066
- role: Type7.Enum(["explore", "plan", "coder"]),
23610
+ role: Type8.Enum(["explore", "plan", "coder"]),
23067
23611
  title: str,
23068
- status: Type7.Enum(["idle", "queued", "running"]),
23069
- activeRoundId: Type7.Union([integer, nil]),
23612
+ status: Type8.Enum(["idle", "queued", "running"]),
23613
+ activeRoundId: Type8.Union([integer, nil]),
23070
23614
  lastRoundId: integer,
23071
- lastReport: Type7.Optional(nullableString),
23615
+ lastReport: Type8.Optional(nullableString),
23072
23616
  roundCount: integer,
23073
23617
  taskTitle: str,
23074
23618
  approvalCount: integer,
23075
23619
  createdAt: num,
23076
- historyThrough: Type7.Optional(integer),
23077
- historyUsage: Type7.Optional(usage)
23620
+ historyThrough: Type8.Optional(integer),
23621
+ historyUsage: Type8.Optional(usage)
23078
23622
  });
23079
- var jobStatus = Type7.Enum(["running", "exited", "failed", "stopped", "lost"]);
23080
- var JobSummarySchema = Type7.Object({
23623
+ var jobStatus = Type8.Enum(["running", "exited", "failed", "stopped", "lost"]);
23624
+ var JobSummarySchema = Type8.Object({
23081
23625
  jobId: str,
23082
23626
  title: str,
23083
23627
  command: str,
23084
23628
  cwd: str,
23085
23629
  status: jobStatus,
23086
23630
  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),
23631
+ endedAt: Type8.Union([num, nil]),
23632
+ exitCode: Type8.Union([num, nil]),
23633
+ reason: Type8.Optional(Type8.Enum(["timeout", "spawn-error", "restart", "aborted"])),
23634
+ timeoutSeconds: Type8.Optional(num),
23091
23635
  outputBytes: integer
23092
23636
  });
23093
- var JobOutputSchema = Type7.Object({
23637
+ var JobOutputSchema = Type8.Object({
23094
23638
  job: JobSummarySchema,
23095
23639
  text: str,
23096
23640
  offset: integer,
23097
23641
  truncated: bool,
23098
23642
  dropped: integer
23099
23643
  });
23100
- var QueuedInputSchema = Type7.Object({
23644
+ var QueuedInputSchema = Type8.Object({
23101
23645
  id: integer,
23102
- mode: Type7.Enum(["steer", "followUp"]),
23646
+ mode: Type8.Enum(["steer", "followUp"]),
23103
23647
  preview: str,
23104
23648
  images: integer,
23105
- skill: Type7.Optional(Type7.Object({ name: str, args: str }))
23649
+ skill: Type8.Optional(Type8.Object({ name: str, args: str }))
23106
23650
  });
23107
- var WithdrawResultSchema = Type7.Object({
23108
- result: Type7.Enum(["withdrawn", "placed", "settled", "unknown"]),
23109
- text: Type7.Optional(str),
23110
- images: Type7.Optional(integer)
23651
+ var WithdrawResultSchema = Type8.Object({
23652
+ result: Type8.Enum(["withdrawn", "placed", "settled", "unknown"]),
23653
+ text: Type8.Optional(str),
23654
+ images: Type8.Optional(integer)
23111
23655
  });
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)
23656
+ var SteerResultSchema = Type8.Object({
23657
+ result: Type8.Enum(["steered", "withdrawn", "placed", "settled", "unknown"]),
23658
+ id: Type8.Optional(integer),
23659
+ text: Type8.Optional(str),
23660
+ images: Type8.Optional(integer)
23117
23661
  });
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"])
23662
+ var TodoItemSchema = Type8.Object({
23663
+ id: Type8.String({ minLength: 1, maxLength: TODO_LIMITS.idChars }),
23664
+ text: Type8.String({ minLength: 1, maxLength: TODO_LIMITS.textChars }),
23665
+ status: Type8.Enum(["pending", "in_progress", "completed"])
23122
23666
  }, { additionalProperties: false });
23123
- var TodoListSchema = Type7.Object({
23667
+ var TodoListSchema = Type8.Object({
23124
23668
  revision: integer,
23125
- items: Type7.Array(TodoItemSchema, { maxItems: TODO_LIMITS.maxItems })
23669
+ items: Type8.Array(TodoItemSchema, { maxItems: TODO_LIMITS.maxItems })
23126
23670
  });
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)
23671
+ var QuestionOptionSchema = Type8.Object({
23672
+ id: Type8.String({ minLength: 1, maxLength: QUESTION_LIMITS.optionIdChars }),
23673
+ label: Type8.String({ minLength: 1, maxLength: QUESTION_LIMITS.labelChars }),
23674
+ description: Type8.Optional(Type8.String({ maxLength: QUESTION_LIMITS.descriptionChars })),
23675
+ recommended: Type8.Optional(bool)
23132
23676
  }, { additionalProperties: false });
23133
- var questionStatus = Type7.Enum(["pending", "answered", "skipped", "cancelled"]);
23134
- var QuestionSummarySchema = Type7.Object({
23677
+ var questionStatus = Type8.Enum(["pending", "answered", "skipped", "cancelled"]);
23678
+ var QuestionSummarySchema = Type8.Object({
23135
23679
  id: integer,
23136
23680
  version: str,
23137
- preview: Type7.String({ maxLength: QUESTION_LIMITS.previewChars }),
23681
+ preview: Type8.String({ maxLength: QUESTION_LIMITS.previewChars }),
23138
23682
  multiple: bool,
23139
23683
  optionCount: integer,
23140
23684
  createdAt: num
23141
23685
  });
23142
- var WorkspaceEntrySchema = Type7.Object({
23686
+ var WorkspaceEntrySchema = Type8.Object({
23143
23687
  name: str,
23144
23688
  path: str,
23145
- kind: Type7.Enum(["file", "directory", "symlink"]),
23146
- protected: Type7.Optional(Type7.Literal(true))
23689
+ kind: Type8.Enum(["file", "directory", "symlink"]),
23690
+ protected: Type8.Optional(Type8.Literal(true))
23147
23691
  }, { additionalProperties: false });
23148
- var WorkspaceDirSchema = Type7.Object({
23692
+ var WorkspaceDirSchema = Type8.Object({
23149
23693
  path: str,
23150
- entries: Type7.Array(WorkspaceEntrySchema, { maxItems: 1000 }),
23694
+ entries: Type8.Array(WorkspaceEntrySchema, { maxItems: 1000 }),
23151
23695
  limited: bool,
23152
23696
  incomplete: bool
23153
23697
  });
23154
- var WorkspaceFileSchema = Type7.Object({
23698
+ var WorkspaceFileSchema = Type8.Object({
23155
23699
  path: str,
23156
23700
  size: num,
23157
23701
  mtimeMs: num,
23158
- kind: Type7.Enum(["text", "binary", "protected"]),
23159
- content: Type7.Optional(str),
23702
+ kind: Type8.Enum(["text", "binary", "protected"]),
23703
+ content: Type8.Optional(str),
23160
23704
  truncated: bool
23161
23705
  });
23162
- var ChangeSummarySchema = Type7.Object({ revision: integer, files: integer, added: integer, removed: integer });
23163
- var ChangedFileSchema = Type7.Object({
23706
+ var ChangeSummarySchema = Type8.Object({ revision: integer, files: integer, added: integer, removed: integer });
23707
+ var ChangedFileSchema = Type8.Object({
23164
23708
  path: str,
23165
- outside: Type7.Optional(Type7.Literal(true)),
23709
+ outside: Type8.Optional(Type8.Literal(true)),
23166
23710
  ops: integer,
23167
23711
  added: integer,
23168
23712
  removed: integer,
23169
- agents: Type7.Array(str, { maxItems: 64 }),
23713
+ agents: Type8.Array(str, { maxItems: 64 }),
23170
23714
  lastSeq: integer
23171
23715
  }, { additionalProperties: false });
23172
- var ChangeListSchema = Type7.Object({
23716
+ var ChangeListSchema = Type8.Object({
23173
23717
  revision: integer,
23174
- files: Type7.Array(ChangedFileSchema, { maxItems: 500 }),
23718
+ files: Type8.Array(ChangedFileSchema, { maxItems: 500 }),
23175
23719
  truncated: bool
23176
23720
  });
23177
- var ChangeOpSchema = Type7.Object({
23721
+ var ChangeOpSchema = Type8.Object({
23178
23722
  seq: integer,
23179
23723
  agent: str,
23180
- tool: Type7.Enum(["edit", "write"]),
23724
+ tool: Type8.Enum(["edit", "write"]),
23181
23725
  diff: str,
23182
23726
  added: integer,
23183
23727
  removed: integer,
23184
23728
  truncated: bool
23185
23729
  }, { additionalProperties: false });
23186
- var ChangeDetailSchema = Type7.Object({
23730
+ var ChangeDetailSchema = Type8.Object({
23187
23731
  path: str,
23188
- outside: Type7.Optional(Type7.Literal(true)),
23189
- ops: Type7.Array(ChangeOpSchema, { maxItems: 50 }),
23732
+ outside: Type8.Optional(Type8.Literal(true)),
23733
+ ops: Type8.Array(ChangeOpSchema, { maxItems: 50 }),
23190
23734
  truncated: bool
23191
23735
  });
23192
- var QuestionListSchema = Type7.Object({
23736
+ var QuestionListSchema = Type8.Object({
23193
23737
  revision: integer,
23194
- pending: Type7.Array(QuestionSummarySchema, { maxItems: QUESTION_LIMITS.maxPending })
23738
+ pending: Type8.Array(QuestionSummarySchema, { maxItems: QUESTION_LIMITS.maxPending })
23195
23739
  });
23196
- var QuestionRecordSchema = Type7.Object({
23740
+ var QuestionRecordSchema = Type8.Object({
23197
23741
  id: integer,
23198
23742
  callId: str,
23199
23743
  version: str,
23200
- question: Type7.String({ maxLength: QUESTION_LIMITS.questionChars }),
23744
+ question: Type8.String({ maxLength: QUESTION_LIMITS.questionChars }),
23201
23745
  multiple: bool,
23202
- options: Type7.Array(QuestionOptionSchema, { maxItems: QUESTION_LIMITS.maxOptions }),
23746
+ options: Type8.Array(QuestionOptionSchema, { maxItems: QUESTION_LIMITS.maxOptions }),
23203
23747
  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])
23748
+ answer: Type8.Union([
23749
+ Type8.Object({
23750
+ optionIds: Type8.Array(Type8.String(), { maxItems: QUESTION_LIMITS.maxOptions }),
23751
+ text: Type8.Union([Type8.String({ maxLength: QUESTION_LIMITS.textChars }), nil])
23208
23752
  }),
23209
23753
  nil
23210
23754
  ]),
23211
23755
  createdAt: num,
23212
- settledAt: Type7.Union([num, nil])
23756
+ settledAt: Type8.Union([num, nil])
23213
23757
  });
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 }))
23758
+ var QuestionResponseSchema = Type8.Union([
23759
+ Type8.Object({
23760
+ kind: Type8.Literal("answer"),
23761
+ optionIds: Type8.Optional(Type8.Array(Type8.String(), { maxItems: QUESTION_LIMITS.maxOptions })),
23762
+ text: Type8.Optional(Type8.String({ maxLength: QUESTION_LIMITS.textChars }))
23219
23763
  }, { additionalProperties: false }),
23220
- Type7.Object({ kind: Type7.Literal("skip") }, { additionalProperties: false })
23764
+ Type8.Object({ kind: Type8.Literal("skip") }, { additionalProperties: false })
23765
+ ]);
23766
+ var PlanRecordSchema = Type8.Object({
23767
+ version: str,
23768
+ title: Type8.String({ maxLength: PLAN_LIMITS.titleChars }),
23769
+ steps: Type8.Array(Type8.String({ maxLength: PLAN_LIMITS.stepChars }), { maxItems: PLAN_LIMITS.maxSteps }),
23770
+ note: Type8.String({ maxLength: PLAN_LIMITS.noteChars }),
23771
+ status: Type8.Enum(["awaiting_approval", "approved", "revision_requested", "superseded", "cancelled"]),
23772
+ feedback: Type8.Union([Type8.String({ maxLength: PLAN_LIMITS.feedbackChars }), nil]),
23773
+ createdAt: num,
23774
+ settledAt: Type8.Union([num, nil])
23775
+ });
23776
+ var PlanViewSchema = Type8.Object({
23777
+ mode: Type8.Enum(["normal", "plan"]),
23778
+ revision: integer,
23779
+ current: Type8.Union([PlanRecordSchema, nil])
23780
+ });
23781
+ var PlanDecisionSchema = Type8.Union([
23782
+ Type8.Object({ kind: Type8.Literal("approve") }, { additionalProperties: false }),
23783
+ Type8.Object({ kind: Type8.Literal("revise"), feedback: Type8.String({ minLength: 1, maxLength: PLAN_LIMITS.feedbackChars }) }, { additionalProperties: false })
23221
23784
  ]);
23222
- var ToolInteractionSchema = Type7.Union([
23223
- Type7.Object({
23224
- kind: Type7.Literal("ask_user"),
23785
+ var ToolInteractionSchema = Type8.Union([
23786
+ Type8.Object({
23787
+ kind: Type8.Literal("ask_user"),
23225
23788
  id: integer,
23226
23789
  question: str,
23227
23790
  multiple: bool,
23228
- options: Type7.Array(Type7.Object({ id: str, label: str, recommended: Type7.Optional(bool) })),
23791
+ options: Type8.Array(Type8.Object({ id: str, label: str, recommended: Type8.Optional(bool) })),
23229
23792
  status: questionStatus,
23230
- optionIds: Type7.Optional(Type7.Array(str)),
23231
- labels: Type7.Optional(Type7.Array(str)),
23232
- text: Type7.Optional(Type7.Union([str, nil]))
23793
+ optionIds: Type8.Optional(Type8.Array(str)),
23794
+ labels: Type8.Optional(Type8.Array(str)),
23795
+ text: Type8.Optional(Type8.Union([str, nil]))
23233
23796
  }),
23234
- Type7.Object({
23235
- kind: Type7.Literal("todo"),
23236
- action: Type7.Enum(["list", "replace"]),
23797
+ Type8.Object({
23798
+ kind: Type8.Literal("todo"),
23799
+ action: Type8.Enum(["list", "replace"]),
23237
23800
  revision: integer,
23238
23801
  completed: integer,
23239
23802
  total: integer
23803
+ }),
23804
+ Type8.Object({
23805
+ kind: Type8.Literal("submit_plan"),
23806
+ version: str,
23807
+ title: str,
23808
+ steps: Type8.Array(str),
23809
+ note: str
23240
23810
  })
23241
23811
  ]);
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),
23812
+ var ChatViewSchema = Type8.Object({
23813
+ historyBefore: Type8.Optional(Type8.Union([integer, nil])),
23814
+ agents: Type8.Optional(Type8.Array(SubagentSummarySchema)),
23815
+ jobs: Type8.Optional(Type8.Array(JobSummarySchema)),
23816
+ queued: Type8.Optional(Type8.Array(QueuedInputSchema)),
23817
+ todos: Type8.Optional(TodoListSchema),
23818
+ questions: Type8.Optional(QuestionListSchema),
23819
+ plan: Type8.Optional(PlanViewSchema),
23820
+ changes: Type8.Optional(ChangeSummarySchema),
23821
+ rounds: Type8.Optional(Type8.Array(DelegationSchema)),
23822
+ subagents: Type8.Optional(Type8.Array(DelegationSchema)),
23823
+ taskUsage: Type8.Optional(usage),
23253
23824
  model: str,
23254
- agent: Type7.Object({ model: Type7.Union([model2, nil]), thinkingLevel: Type7.Union([thinking, nil]) }),
23825
+ agent: Type8.Object({ model: Type8.Union([model2, nil]), thinkingLevel: Type8.Union([thinking, nil]) }),
23255
23826
  cwd: str,
23256
23827
  busy: bool,
23257
23828
  status: nullableString,
23258
23829
  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({
23830
+ contextUsage: Type8.Optional(Type8.Object({ tokens: Type8.Union([num, nil]), window: num, percent: Type8.Union([num, nil]) })),
23831
+ compaction: Type8.Optional(Type8.Object({
23832
+ failure: Type8.Optional(Type8.Object({ taskId: integer, reason: Type8.Enum(["manual", "threshold", "overflow"]), error: str })),
23833
+ active: Type8.Array(Type8.Object({
23263
23834
  taskId: integer,
23264
- reason: Type7.Enum(["manual", "threshold", "overflow"]),
23835
+ reason: Type8.Enum(["manual", "threshold", "overflow"]),
23265
23836
  blocking: bool,
23266
23837
  attempt: integer,
23267
- retry: Type7.Optional(Type7.Object({ at: num, error: str }))
23838
+ retry: Type8.Optional(Type8.Object({ at: num, error: str }))
23268
23839
  })),
23269
- latest: Type7.Union([
23840
+ latest: Type8.Union([
23270
23841
  nil,
23271
- Type7.Object({
23842
+ Type8.Object({
23272
23843
  taskId: integer,
23273
- phase: Type7.Enum(["summarizing", "waiting-to-apply", "applied", "nothing", "skipped", "cancelled", "failed"]),
23844
+ phase: Type8.Enum(["summarizing", "waiting-to-apply", "applied", "nothing", "skipped", "cancelled", "failed"]),
23274
23845
  at: num
23275
23846
  })
23276
23847
  ])
23277
23848
  })),
23278
- items: Type7.Array(Type7.Union([
23279
- Type7.Object({
23280
- type: Type7.Literal("user"),
23281
- order: Type7.Optional(num),
23849
+ items: Type8.Array(Type8.Union([
23850
+ Type8.Object({
23851
+ type: Type8.Literal("user"),
23852
+ order: Type8.Optional(num),
23282
23853
  key: str,
23283
23854
  id: integer,
23284
23855
  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 }))
23856
+ images: Type8.Optional(Type8.Array(image)),
23857
+ skill: Type8.Optional(skill),
23858
+ defaultImagePrompt: Type8.Optional(Type8.Object({ version: Type8.Integer({ minimum: 1 }), text: str }))
23288
23859
  }),
23289
- Type7.Object({
23290
- type: Type7.Literal("assistant"),
23291
- order: Type7.Optional(num),
23860
+ Type8.Object({
23861
+ type: Type8.Literal("assistant"),
23862
+ order: Type8.Optional(num),
23292
23863
  key: str,
23293
23864
  id: integer,
23294
23865
  thinking: str,
@@ -23297,11 +23868,11 @@ var ChatViewSchema = Type7.Object({
23297
23868
  errorMessage: nullableString,
23298
23869
  note: nullableString
23299
23870
  }),
23300
- Type7.Object({
23301
- type: Type7.Literal("tool"),
23302
- order: Type7.Optional(num),
23871
+ Type8.Object({
23872
+ type: Type8.Literal("tool"),
23873
+ order: Type8.Optional(num),
23303
23874
  key: str,
23304
- images: Type7.Optional(Type7.Array(image)),
23875
+ images: Type8.Optional(Type8.Array(image)),
23305
23876
  callId: str,
23306
23877
  name: str,
23307
23878
  summary: str,
@@ -23310,64 +23881,66 @@ var ChatViewSchema = Type7.Object({
23310
23881
  isError: bool,
23311
23882
  output: nullableString,
23312
23883
  result: nullableString,
23313
- durationMs: Type7.Union([num, nil]),
23884
+ durationMs: Type8.Union([num, nil]),
23314
23885
  note: nullableString,
23315
23886
  diff: nullableString,
23316
- diffAdded: Type7.Union([num, nil]),
23317
- diffRemoved: Type7.Union([num, nil]),
23318
- diffTruncated: Type7.Optional(bool),
23319
- interaction: Type7.Optional(ToolInteractionSchema)
23887
+ diffAdded: Type8.Union([num, nil]),
23888
+ diffRemoved: Type8.Union([num, nil]),
23889
+ diffTruncated: Type8.Optional(bool),
23890
+ interaction: Type8.Optional(ToolInteractionSchema)
23320
23891
  }),
23321
- Type7.Object({
23322
- type: Type7.Literal("system"),
23323
- order: Type7.Optional(num),
23892
+ Type8.Object({
23893
+ type: Type8.Literal("system"),
23894
+ order: Type8.Optional(num),
23324
23895
  key: str,
23325
23896
  id: integer,
23326
23897
  text: str,
23327
- jobId: Type7.Optional(str)
23898
+ jobId: Type8.Optional(str),
23899
+ planDecision: Type8.Optional(Type8.Object({ version: str, kind: Type8.Enum(["approve", "revise"]) }, { additionalProperties: false }))
23328
23900
  })
23329
23901
  ])),
23330
- streaming: Type7.Union([nil, Type7.Object({ thinking: str, text: str, toolCalls: Type7.Array(call) })])
23902
+ streaming: Type8.Union([nil, Type8.Object({ thinking: str, text: str, toolCalls: Type8.Array(call) })])
23331
23903
  });
23332
- var SessionSummarySchema = Type7.Object({
23904
+ var SessionSummarySchema = Type8.Object({
23333
23905
  workspaceRoot: str,
23334
23906
  lastUsedAt: num,
23335
23907
  title: str,
23336
23908
  firstPromptPreview: str,
23337
23909
  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),
23910
+ lastMessageAt: Type8.Union([num, nil]),
23911
+ model: Type8.Union([model2, nil]),
23912
+ status: Type8.Enum(["idle", "running", "error"]),
23913
+ awaitingAnswer: Type8.Optional(bool),
23914
+ awaitingPlan: Type8.Optional(bool),
23342
23915
  id: str
23343
23916
  });
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({
23917
+ var DirectoryStateSchema = Type8.Object({ revision: integer, sessions: Type8.Array(SessionSummarySchema) });
23918
+ var WorkspaceSummarySchema = Type8.Object({ path: str, createdAt: num });
23919
+ var WorkspaceStateSchema = Type8.Object({ revision: integer, workspaces: Type8.Array(WorkspaceSummarySchema) });
23920
+ var credentialStatus = Type8.Object({
23348
23921
  savedConfigured: bool,
23349
23922
  environmentConfigured: bool,
23350
- source: Type7.Enum(["saved", "environment", "runtime", "keyless", "none"]),
23351
- enabled: Type7.Optional(bool)
23923
+ source: Type8.Enum(["saved", "environment", "runtime", "keyless", "none"]),
23924
+ enabled: Type8.Optional(bool)
23352
23925
  });
23353
- var CredentialStateSchema = Type7.Object({
23926
+ var CredentialStateSchema = Type8.Object({
23354
23927
  revision: integer,
23355
- statuses: Type7.Record(str, credentialStatus)
23928
+ statuses: Type8.Record(str, credentialStatus)
23356
23929
  });
23357
- var AttachmentSchema = Type7.Object({ sessionId: str, attachmentId: str });
23358
- var chatInput = Type7.Object({ sessionId: str, attachmentId: str }, { additionalProperties: false });
23930
+ var AttachmentSchema = Type8.Object({ sessionId: str, attachmentId: str });
23931
+ var chatInput = Type8.Object({ sessionId: str, attachmentId: str }, { additionalProperties: false });
23359
23932
  var NekoSessionDirectory = contract("neko.session-directory", {
23360
23933
  version: 1,
23361
- input: Type7.Object({}, { additionalProperties: false }),
23934
+ input: Type8.Object({}, { additionalProperties: false }),
23362
23935
  variants: { ready: { state: { state: DirectoryStateSchema } } }
23363
23936
  });
23364
23937
  var NekoSessionManagement = contract("neko.session-management", {
23365
23938
  version: 1,
23366
- input: Type7.Object({}, { additionalProperties: false }),
23939
+ input: Type8.Object({}, { additionalProperties: false }),
23367
23940
  variants: {
23368
23941
  ready: {
23369
23942
  methods: {
23370
- create: rpc(Type7.Object({ ...change.properties, cwd: Type7.Optional(str) }, { additionalProperties: false }), SessionSummarySchema),
23943
+ create: rpc(Type8.Object({ ...change.properties, cwd: Type8.Optional(str) }, { additionalProperties: false }), SessionSummarySchema),
23371
23944
  archive: rpc(str, nil),
23372
23945
  attach: rpc(str, AttachmentSchema),
23373
23946
  detach: rpc(nil, nil),
@@ -23376,31 +23949,31 @@ var NekoSessionManagement = contract("neko.session-management", {
23376
23949
  }
23377
23950
  }
23378
23951
  });
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)
23952
+ var resize = Type8.Object({
23953
+ maxWidth: Type8.Optional(integer),
23954
+ maxHeight: Type8.Optional(integer),
23955
+ maxBytes: Type8.Optional(integer),
23956
+ jpegQuality: Type8.Optional(integer)
23384
23957
  });
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)
23958
+ var inputLimits = Type8.Object({
23959
+ maxRequestBytes: Type8.Optional(integer),
23960
+ images: Type8.Optional(Type8.Object({
23961
+ resize: Type8.Optional(resize),
23962
+ maxPerMessage: Type8.Optional(integer),
23963
+ maxPerRequest: Type8.Optional(integer)
23391
23964
  }))
23392
23965
  });
23393
- var modelInfo = Type7.Object({
23966
+ var modelInfo = Type8.Object({
23394
23967
  provider: str,
23395
23968
  modelId: str,
23396
23969
  name: str,
23397
23970
  available: bool,
23398
23971
  contextWindow: num,
23399
- input: Type7.Array(str),
23400
- inputLimits: Type7.Optional(inputLimits),
23972
+ input: Type8.Array(str),
23973
+ inputLimits: Type8.Optional(inputLimits),
23401
23974
  reasoning: bool,
23402
- supportedThinkingLevels: Type7.Array(thinking),
23403
- thinkingLevelAdjustments: Type7.Object({
23975
+ supportedThinkingLevels: Type8.Array(thinking),
23976
+ thinkingLevelAdjustments: Type8.Object({
23404
23977
  off: thinking,
23405
23978
  minimal: thinking,
23406
23979
  low: thinking,
@@ -23412,31 +23985,31 @@ var modelInfo = Type7.Object({
23412
23985
  });
23413
23986
  var NekoModels = contract("neko.models", {
23414
23987
  version: 1,
23415
- input: Type7.Object({}, { additionalProperties: false }),
23416
- variants: { ready: { methods: { list: rpc(nil, Type7.Array(modelInfo)) } } }
23988
+ input: Type8.Object({}, { additionalProperties: false }),
23989
+ variants: { ready: { methods: { list: rpc(nil, Type8.Array(modelInfo)) } } }
23417
23990
  });
23418
- var imageChunk = Type7.Object({ data: str, total: integer, mimeType: str });
23991
+ var imageChunk = Type8.Object({ data: str, total: integer, mimeType: str });
23419
23992
  var NekoSubagent = contract("neko.subagent", {
23420
23993
  version: 1,
23421
- input: Type7.Object({
23994
+ input: Type8.Object({
23422
23995
  sessionId: str,
23423
23996
  attachmentId: str,
23424
- taskId: Type7.Optional(integer),
23425
- agentId: Type7.Optional(str)
23997
+ taskId: Type8.Optional(integer),
23998
+ agentId: Type8.Optional(str)
23426
23999
  }),
23427
24000
  variants: {
23428
24001
  ready: {
23429
24002
  state: { revision: integer },
23430
24003
  methods: {
23431
- chunk: rpc(Type7.Object({
23432
- kind: Type7.Enum(["view", "history"]),
23433
- before: Type7.Optional(integer),
23434
- token: Type7.Optional(integer),
24004
+ chunk: rpc(Type8.Object({
24005
+ kind: Type8.Enum(["view", "history"]),
24006
+ before: Type8.Optional(integer),
24007
+ token: Type8.Optional(integer),
23435
24008
  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)
24009
+ }), Type8.Object({ token: integer, data: str, next: Type8.Union([integer, nil]) })),
24010
+ getImage: rpc(Type8.Object({ entryId: integer, contentIndex: integer, offset: integer }), imageChunk),
24011
+ decideApproval: rpc(Type8.Object({ id: integer, version: str, decision: Type8.Enum(["allowed", "denied"]) }), Type8.Enum(["pending", "allowed", "denied", "cancelled"])),
24012
+ stop: rpc(Type8.Union([nil, Type8.Object({ taskId: integer })]), nil)
23440
24013
  }
23441
24014
  }
23442
24015
  }
@@ -23448,99 +24021,102 @@ var NekoChat = contract("neko.chat", {
23448
24021
  ready: {
23449
24022
  state: { view: ChatViewSchema },
23450
24023
  methods: {
23451
- submit: rpc(Type7.Object({
24024
+ submit: rpc(Type8.Object({
23452
24025
  content: str,
23453
- images: Type7.Optional(Type7.Array(str, { maxItems: 4 })),
23454
- requestId: Type7.Optional(str),
23455
- whenBusy: Type7.Optional(Type7.Enum(["steer", "followUp"]))
24026
+ images: Type8.Optional(Type8.Array(str, { maxItems: 4 })),
24027
+ requestId: Type8.Optional(str),
24028
+ whenBusy: Type8.Optional(Type8.Enum(["steer", "followUp"]))
23456
24029
  }, { additionalProperties: false }), nil),
23457
24030
  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),
24031
+ withdraw: rpc(Type8.Object({ id: integer }, { additionalProperties: false }), WithdrawResultSchema),
24032
+ steer: rpc(Type8.Object({ id: integer }, { additionalProperties: false }), SteerResultSchema),
24033
+ questionGet: rpc(Type8.Object({ id: integer }, { additionalProperties: false }), Type8.Union([QuestionRecordSchema, nil])),
24034
+ questionRespond: rpc(Type8.Object({ id: integer, version: str, response: QuestionResponseSchema }, { additionalProperties: false }), QuestionRecordSchema),
24035
+ planGet: rpc(Type8.Object({ version: str }, { additionalProperties: false }), Type8.Union([PlanRecordSchema, nil])),
24036
+ planRespond: rpc(Type8.Object({ version: str, decision: PlanDecisionSchema }, { additionalProperties: false }), PlanRecordSchema),
24037
+ planMode: rpc(Type8.Object({ mode: Type8.Enum(["normal", "plan"]) }, { additionalProperties: false }), PlanViewSchema),
23462
24038
  usage: rpc(nil, usage),
23463
- workspaceDir: rpc(Type7.Object({ path: str }, { additionalProperties: false }), WorkspaceDirSchema),
23464
- workspaceFile: rpc(Type7.Object({ path: str }, { additionalProperties: false }), WorkspaceFileSchema),
24039
+ workspaceDir: rpc(Type8.Object({ path: str }, { additionalProperties: false }), WorkspaceDirSchema),
24040
+ workspaceFile: rpc(Type8.Object({ path: str }, { additionalProperties: false }), WorkspaceFileSchema),
23465
24041
  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),
24042
+ changeGet: rpc(Type8.Object({ path: str }, { additionalProperties: false }), Type8.Union([ChangeDetailSchema, nil])),
24043
+ skills: rpc(nil, Type8.Array(Type8.Object({ name: str, description: str, location: str, level }))),
24044
+ jobOutput: rpc(Type8.Object({ jobId: str, since: Type8.Optional(integer) }, { additionalProperties: false }), JobOutputSchema),
24045
+ jobStop: rpc(Type8.Object({ jobId: str }, { additionalProperties: false }), JobSummarySchema),
23470
24046
  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),
24047
+ compact: rpc(Type8.Object({ instructions: Type8.Optional(str) }, { additionalProperties: false }), nil),
24048
+ importImage: rpc(str, Type8.Object({ id: str, name: str })),
24049
+ draftImage: rpc(Type8.Object({ id: str, offset: integer }, { additionalProperties: false }), imageChunk),
24050
+ beginImage: rpc(Type8.Object({ name: str, mimeType: str, size: Type8.Integer({ minimum: 1, maximum: 20971520 }) }, { additionalProperties: false }), str),
24051
+ appendImage: rpc(Type8.Object({ id: str, offset: integer, data: Type8.String({ maxLength: 350000 }) }, { additionalProperties: false }), nil),
23476
24052
  finishImage: rpc(str, nil),
23477
24053
  releaseImage: rpc(str, nil),
23478
- getImage: rpc(Type7.Object({ entryId: integer, contentIndex: integer, offset: integer }, { additionalProperties: false }), imageChunk)
24054
+ getImage: rpc(Type8.Object({ entryId: integer, contentIndex: integer, offset: integer }, { additionalProperties: false }), imageChunk)
23479
24055
  }
23480
24056
  }
23481
24057
  }
23482
24058
  });
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({
24059
+ var browse = Type8.Object({ path: str, query: Type8.Optional(str), showHidden: Type8.Optional(bool), cursor: Type8.Optional(str) }, { additionalProperties: false });
24060
+ var page = Type8.Object({
23485
24061
  path: str,
23486
24062
  parent: nullableString,
23487
- entries: Type7.Array(Type7.Object({ name: str, path: str, error: Type7.Optional(str) })),
24063
+ entries: Type8.Array(Type8.Object({ name: str, path: str, error: Type8.Optional(str) })),
23488
24064
  next: nullableString
23489
24065
  });
23490
24066
  var NekoWorkspaces = contract("neko.workspaces", {
23491
24067
  version: 1,
23492
- input: Type7.Object({}, { additionalProperties: false }),
24068
+ input: Type8.Object({}, { additionalProperties: false }),
23493
24069
  variants: {
23494
24070
  ready: {
23495
24071
  state: { state: WorkspaceStateSchema },
23496
24072
  methods: {
23497
- add: rpc(Type7.Object({ path: str }, { additionalProperties: false }), WorkspaceSummarySchema),
24073
+ add: rpc(Type8.Object({ path: str }, { additionalProperties: false }), WorkspaceSummarySchema),
23498
24074
  browse: rpc(browse, page)
23499
24075
  }
23500
24076
  }
23501
24077
  }
23502
24078
  });
23503
- var provider = Type7.String({ minLength: 1, maxLength: 64 });
24079
+ var provider = Type8.String({ minLength: 1, maxLength: 64 });
23504
24080
  var NekoCredentials = contract("neko.credentials", {
23505
24081
  version: 2,
23506
- input: Type7.Object({}, { additionalProperties: false }),
24082
+ input: Type8.Object({}, { additionalProperties: false }),
23507
24083
  variants: {
23508
24084
  ready: {
23509
24085
  state: { state: CredentialStateSchema },
23510
24086
  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)
24087
+ save: rpc(Type8.Object({ provider, key: Type8.String({ minLength: 1, maxLength: 8192 }) }, { additionalProperties: false }), nil),
24088
+ remove: rpc(Type8.Object({ provider }, { additionalProperties: false }), nil),
24089
+ setEnabled: rpc(Type8.Object({ provider, enabled: bool }, { additionalProperties: false }), nil)
23514
24090
  }
23515
24091
  }
23516
24092
  }
23517
24093
  });
23518
- var SearchSettingsStateSchema = Type7.Object({
24094
+ var SearchSettingsStateSchema = Type8.Object({
23519
24095
  revision: integer,
23520
- selected: Type7.Enum(["tavily", "deepseek"])
24096
+ selected: Type8.Enum(["tavily", "deepseek"])
23521
24097
  });
23522
24098
  var NekoSearchSettings = contract("neko.search-settings", {
23523
24099
  version: 1,
23524
- input: Type7.Object({}, { additionalProperties: false }),
24100
+ input: Type8.Object({}, { additionalProperties: false }),
23525
24101
  variants: {
23526
24102
  ready: {
23527
24103
  state: { state: SearchSettingsStateSchema },
23528
24104
  methods: {
23529
- set: rpc(Type7.Object({ provider: Type7.Enum(["tavily", "deepseek"]) }, { additionalProperties: false }), nil)
24105
+ set: rpc(Type8.Object({ provider: Type8.Enum(["tavily", "deepseek"]) }, { additionalProperties: false }), nil)
23530
24106
  }
23531
24107
  }
23532
24108
  }
23533
24109
  });
23534
- var contextSource = Type7.Enum(["default", "config", "saved"]);
23535
- var ContextSettingsStateSchema = Type7.Object({
24110
+ var contextSource = Type8.Enum(["default", "config", "saved"]);
24111
+ var ContextSettingsStateSchema = Type8.Object({
23536
24112
  revision: integer,
23537
- effective: Type7.Object({
23538
- compaction: Type7.Object({
24113
+ effective: Type8.Object({
24114
+ compaction: Type8.Object({
23539
24115
  enabled: bool,
23540
- triggerPercent: Type7.Union([integer, nil]),
24116
+ triggerPercent: Type8.Union([integer, nil]),
23541
24117
  keepRecentTokens: integer
23542
24118
  }),
23543
- pruning: Type7.Object({
24119
+ pruning: Type8.Object({
23544
24120
  enabled: bool,
23545
24121
  triggerPercent: integer,
23546
24122
  keepRecent: integer,
@@ -23548,9 +24124,9 @@ var ContextSettingsStateSchema = Type7.Object({
23548
24124
  minSavePercent: integer
23549
24125
  })
23550
24126
  }),
23551
- sources: Type7.Object({
23552
- compaction: Type7.Object({ enabled: contextSource, triggerPercent: contextSource, keepRecentTokens: contextSource }),
23553
- pruning: Type7.Object({
24127
+ sources: Type8.Object({
24128
+ compaction: Type8.Object({ enabled: contextSource, triggerPercent: contextSource, keepRecentTokens: contextSource }),
24129
+ pruning: Type8.Object({
23554
24130
  enabled: contextSource,
23555
24131
  triggerPercent: contextSource,
23556
24132
  keepRecent: contextSource,
@@ -23559,42 +24135,42 @@ var ContextSettingsStateSchema = Type7.Object({
23559
24135
  })
23560
24136
  })
23561
24137
  });
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))
24138
+ var nullable = (schema) => Type8.Union([schema, nil]);
24139
+ var ContextSettingsPatchSchema = Type8.Object({
24140
+ compaction: Type8.Optional(Type8.Object({
24141
+ enabled: Type8.Optional(nullable(bool)),
24142
+ triggerPercent: Type8.Optional(nullable(integer)),
24143
+ keepRecentTokens: Type8.Optional(nullable(integer))
23568
24144
  }, { 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))
24145
+ pruning: Type8.Optional(Type8.Object({
24146
+ enabled: Type8.Optional(nullable(bool)),
24147
+ triggerPercent: Type8.Optional(nullable(integer)),
24148
+ keepRecent: Type8.Optional(nullable(integer)),
24149
+ minChars: Type8.Optional(nullable(integer)),
24150
+ minSavePercent: Type8.Optional(nullable(integer))
23575
24151
  }, { additionalProperties: false }))
23576
24152
  }, { additionalProperties: false });
23577
24153
  var NekoContextSettings = contract("neko.context-settings", {
23578
24154
  version: 1,
23579
- input: Type7.Object({}, { additionalProperties: false }),
24155
+ input: Type8.Object({}, { additionalProperties: false }),
23580
24156
  variants: {
23581
24157
  ready: {
23582
24158
  state: { state: ContextSettingsStateSchema },
23583
24159
  methods: {
23584
24160
  set: rpc(ContextSettingsPatchSchema, nil),
23585
- reset: rpc(Type7.Object({}, { additionalProperties: false }), nil)
24161
+ reset: rpc(Type8.Object({}, { additionalProperties: false }), nil)
23586
24162
  }
23587
24163
  }
23588
24164
  }
23589
24165
  });
23590
- var defaultModelSource = Type7.Enum(["cli", "saved", "config", "default"]);
23591
- var DefaultModelStateSchema = Type7.Object({
24166
+ var defaultModelSource = Type8.Enum(["cli", "saved", "config", "default"]);
24167
+ var DefaultModelStateSchema = Type8.Object({
23592
24168
  effective: model2,
23593
24169
  source: defaultModelSource
23594
24170
  });
23595
24171
  var NekoDefaultModel = contract("neko.default-model", {
23596
24172
  version: 1,
23597
- input: Type7.Object({}, { additionalProperties: false }),
24173
+ input: Type8.Object({}, { additionalProperties: false }),
23598
24174
  variants: {
23599
24175
  ready: {
23600
24176
  state: { state: DefaultModelStateSchema },
@@ -23605,23 +24181,23 @@ var NekoDefaultModel = contract("neko.default-model", {
23605
24181
  }
23606
24182
  }
23607
24183
  });
23608
- var FileCandidatesSchema = Type7.Object({
23609
- entries: Type7.Array(Type7.Object({ name: str, path: str, kind: Type7.Enum(["file", "directory"]) }), { maxItems: 100 }),
24184
+ var FileCandidatesSchema = Type8.Object({
24185
+ entries: Type8.Array(Type8.Object({ name: str, path: str, kind: Type8.Enum(["file", "directory"]) }), { maxItems: 100 }),
23610
24186
  limited: bool,
23611
24187
  incomplete: bool
23612
24188
  });
23613
24189
  var NekoWorkspaceFiles = contract("neko.workspace-files", {
23614
24190
  version: 1,
23615
- input: Type7.Object({}, { additionalProperties: false }),
24191
+ input: Type8.Object({}, { additionalProperties: false }),
23616
24192
  variants: {
23617
24193
  ready: {
23618
24194
  methods: {
23619
- search: rpc(Type7.Object({ workspaceRoot: str, query: str }, { additionalProperties: false }), FileCandidatesSchema)
24195
+ search: rpc(Type8.Object({ workspaceRoot: str, query: str }, { additionalProperties: false }), FileCandidatesSchema)
23620
24196
  }
23621
24197
  }
23622
24198
  }
23623
24199
  });
23624
- var marketSkill = Type7.Object({
24200
+ var marketSkill = Type8.Object({
23625
24201
  name: str,
23626
24202
  description: str,
23627
24203
  category: str,
@@ -23631,48 +24207,48 @@ var marketSkill = Type7.Object({
23631
24207
  sha256: str,
23632
24208
  sizeBytes: integer,
23633
24209
  uploadedAt: str,
23634
- installed: Type7.Object({ project: bool, user: bool, builtin: bool }),
23635
- activeLevel: Type7.Union([level, nil])
24210
+ installed: Type8.Object({ project: bool, user: bool, builtin: bool }),
24211
+ activeLevel: Type8.Union([level, nil])
23636
24212
  });
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) });
24213
+ var preview = Type8.Object({ body: str, files: Type8.Array(str) });
24214
+ var scope = Type8.Enum(["user", "project"]);
24215
+ var installResult = Type8.Object({ name: str, scope, path: str, shadowedBy: Type8.Optional(scope) });
23640
24216
  var NekoSkillMarket = contract("neko.skill-market", {
23641
24217
  version: 1,
23642
- input: Type7.Object({}, { additionalProperties: false }),
24218
+ input: Type8.Object({}, { additionalProperties: false }),
23643
24219
  variants: {
23644
24220
  ready: {
23645
24221
  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)
24222
+ list: rpc(Type8.Object({ query: Type8.Optional(str), workspaceRoot: Type8.Optional(str) }, { additionalProperties: false }), Type8.Array(marketSkill)),
24223
+ detail: rpc(Type8.Object({ name: str, workspaceRoot: Type8.Optional(str) }, { additionalProperties: false }), marketSkill),
24224
+ preview: rpc(Type8.Object({ name: str }, { additionalProperties: false }), preview),
24225
+ install: rpc(Type8.Object({ name: str, scope, workspaceRoot: Type8.Optional(str), overwrite: Type8.Optional(bool) }, { additionalProperties: false }), installResult),
24226
+ uninstall: rpc(Type8.Object({ name: str, scope, workspaceRoot: Type8.Optional(str) }, { additionalProperties: false }), nil)
23651
24227
  }
23652
24228
  }
23653
24229
  }
23654
24230
  });
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)
24231
+ var ShellStatusSchema = Type8.Union([
24232
+ Type8.Object({ status: Type8.Literal("ready"), path: str, source: Type8.Enum(["config", "auto"]) }),
24233
+ Type8.Object({
24234
+ status: Type8.Literal("missing"),
24235
+ reason: Type8.Enum(["not_found", "invalid_config"]),
24236
+ detail: Type8.Optional(str)
23661
24237
  })
23662
24238
  ]);
23663
- var ApplicationInfoSchema = Type7.Object({
24239
+ var ApplicationInfoSchema = Type8.Object({
23664
24240
  runningVersion: str,
23665
24241
  installedVersion: nullableString,
23666
- protocolVersion: Type7.Integer({ minimum: 1 }),
24242
+ protocolVersion: Type8.Integer({ minimum: 1 }),
23667
24243
  latestVersion: nullableString,
23668
- checkedAt: Type7.Union([integer, nil]),
23669
- checkState: Type7.Enum(["unknown", "checking", "ok", "failed", "development"]),
24244
+ checkedAt: Type8.Union([integer, nil]),
24245
+ checkState: Type8.Enum(["unknown", "checking", "ok", "failed", "development"]),
23670
24246
  serverId: str,
23671
- shell: Type7.Optional(ShellStatusSchema)
24247
+ shell: Type8.Optional(ShellStatusSchema)
23672
24248
  });
23673
24249
  var NekoApplicationInfo = contract("neko.application-info", {
23674
24250
  version: 1,
23675
- input: Type7.Object({}),
24251
+ input: Type8.Object({}),
23676
24252
  variants: {
23677
24253
  ready: { state: { info: ApplicationInfoSchema }, methods: { check: rpc(nil, ApplicationInfoSchema) } }
23678
24254
  }
@@ -23686,7 +24262,7 @@ function isServerId(value) {
23686
24262
  // packages/gateway/src/mobile/admin.ts
23687
24263
  import { createHash, timingSafeEqual } from "crypto";
23688
24264
  import { ChordError } from "@earendil-works/chord";
23689
- import { Type as Type8 } from "typebox";
24265
+ import { Type as Type9 } from "typebox";
23690
24266
 
23691
24267
  // packages/gateway/src/principal.ts
23692
24268
  var HOST_PRINCIPAL = { kind: "host" };
@@ -24015,62 +24591,62 @@ class MobileStore {
24015
24591
  }
24016
24592
 
24017
24593
  // 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")]))
24594
+ var listenerStateSchema = Type9.Union([
24595
+ Type9.Object({ status: Type9.Literal("disabled") }),
24596
+ Type9.Object({ status: Type9.Literal("starting") }),
24597
+ Type9.Object({
24598
+ status: Type9.Literal("listening"),
24599
+ address: Type9.String(),
24600
+ port: Type9.Integer({ minimum: 1, maximum: 65535 }),
24601
+ bindAddress: Type9.Optional(Type9.String()),
24602
+ mode: Type9.Optional(Type9.Union([Type9.Literal("direct"), Type9.Literal("wsl-relay")]))
24027
24603
  }),
24028
- Type8.Object({ status: Type8.Literal("error"), error: Type8.String() }),
24029
- Type8.Object({ status: Type8.Literal("stopping") })
24604
+ Type9.Object({ status: Type9.Literal("error"), error: Type9.String() }),
24605
+ Type9.Object({ status: Type9.Literal("stopping") })
24030
24606
  ]);
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()
24607
+ var adminDeviceSchema = Type9.Object({
24608
+ deviceId: Type9.String(),
24609
+ name: Type9.String(),
24610
+ pairedAt: Type9.Integer(),
24611
+ lastSeenAt: Type9.Integer(),
24612
+ online: Type9.Boolean()
24037
24613
  });
24038
24614
  var NekoMobileAdmin = contract("neko.mobile-admin", {
24039
24615
  version: 1,
24040
- input: Type8.Object({ token: Type8.String() }),
24616
+ input: Type9.Object({ token: Type9.String() }),
24041
24617
  variants: {
24042
24618
  admin: {
24043
24619
  methods: {
24044
- state: rpc(Type8.Null(), Type8.Object({
24620
+ state: rpc(Type9.Null(), Type9.Object({
24045
24621
  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()
24622
+ candidates: Type9.Array(Type9.String()),
24623
+ port: Type9.Integer({ minimum: 1, maximum: 65535 }),
24624
+ certificate: Type9.Union([
24625
+ Type9.Object({ fingerprint: Type9.String(), expiresAt: Type9.Integer() }),
24626
+ Type9.Null()
24051
24627
  ]),
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()
24628
+ devices: Type9.Array(adminDeviceSchema),
24629
+ lastConnectedAddress: Type9.Union([Type9.String(), Type9.Null()]),
24630
+ wsl: Type9.Optional(Type9.Union([
24631
+ Type9.Object({ interop: Type9.Boolean(), error: Type9.Union([Type9.String(), Type9.Null()]) }),
24632
+ Type9.Null()
24057
24633
  ]))
24058
24634
  })),
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")]))
24635
+ enable: rpc(Type9.Object({
24636
+ address: Type9.String(),
24637
+ port: Type9.Optional(Type9.Integer({ minimum: 1, maximum: 65535 }))
24638
+ }), Type9.Object({
24639
+ status: Type9.Literal("listening"),
24640
+ address: Type9.String(),
24641
+ port: Type9.Integer({ minimum: 1, maximum: 65535 }),
24642
+ bindAddress: Type9.Optional(Type9.String()),
24643
+ mode: Type9.Optional(Type9.Union([Type9.Literal("direct"), Type9.Literal("wsl-relay")]))
24068
24644
  })),
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() }))
24645
+ disable: rpc(Type9.Null(), listenerStateSchema),
24646
+ createPairing: rpc(Type9.Null(), Type9.Object({ pairingText: Type9.String(), expiresAt: Type9.Integer() })),
24647
+ cancelPairing: rpc(Type9.Null(), Type9.Null()),
24648
+ revoke: rpc(Type9.Object({ deviceId: Type9.String({ minLength: 1 }) }), Type9.Object({ revoked: Type9.Boolean() })),
24649
+ regenerateCertificate: rpc(Type9.Null(), Type9.Object({ fingerprint: Type9.String(), expiresAt: Type9.Integer() }))
24074
24650
  }
24075
24651
  }
24076
24652
  }
@@ -24138,7 +24714,7 @@ function createMobileAdminFactory(deps) {
24138
24714
  }
24139
24715
 
24140
24716
  // packages/gateway/src/mobile/listener.ts
24141
- import { randomUUID as randomUUID3 } from "crypto";
24717
+ import { randomUUID as randomUUID4 } from "crypto";
24142
24718
  import { createServer } from "https";
24143
24719
  import { hostname } from "os";
24144
24720
  import { memoryLinkPair } from "@earendil-works/chord";
@@ -24393,14 +24969,14 @@ function subagentClient(connect) {
24393
24969
  import { createHash as createHash2 } from "crypto";
24394
24970
 
24395
24971
  // packages/core/src/application-version.ts
24396
- var RUNNING_VERSION = "0.4.7";
24972
+ var RUNNING_VERSION = "0.4.8";
24397
24973
  var APPLICATION_PROTOCOL = 8;
24398
24974
  function stableVersion(value) {
24399
24975
  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
24976
  }
24401
24977
 
24402
24978
  // packages/gateway/src/mobile/protocol.ts
24403
- import { Type as Type9 } from "typebox";
24979
+ import { Type as Type10 } from "typebox";
24404
24980
  import { Check } from "typebox/value";
24405
24981
  var MOBILE_LIMITS = {
24406
24982
  maxFrameBytes: 262144,
@@ -24419,17 +24995,18 @@ var MOBILE_CAPABILITIES = [
24419
24995
  "approvals",
24420
24996
  "media",
24421
24997
  "todos",
24422
- "questions"
24998
+ "questions",
24999
+ "plan"
24423
25000
  ];
24424
25001
  var MOBILE_CLOSE_REVOKED = 4401;
24425
25002
  var MOBILE_CLOSE_INCOMPATIBLE = 4402;
24426
25003
  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 })
25004
+ var MobileHelloParams = Type10.Object({
25005
+ apiVersions: Type10.Array(Type10.Integer(), { minItems: 1 }),
25006
+ app: Type10.Object({
25007
+ version: Type10.String({ minLength: 1, maxLength: 64 }),
25008
+ platform: Type10.String({ minLength: 1, maxLength: 64 }),
25009
+ build: Type10.String({ maxLength: 128 })
24433
25010
  })
24434
25011
  });
24435
25012
  function asJsonRpcRequest(value) {
@@ -24469,11 +25046,11 @@ function buildHelloResult(serverId, serverName, apiVersion) {
24469
25046
  limits: MOBILE_LIMITS
24470
25047
  };
24471
25048
  }
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({
25049
+ var opt = Type10.Optional;
25050
+ var str2 = Type10.String();
25051
+ var int = Type10.Integer({ minimum: 0 });
25052
+ var nullable2 = (schema) => Type10.Union([schema, Type10.Null()]);
25053
+ var MobileContentRefSchema = Type10.Object({
24477
25054
  sessionId: str2,
24478
25055
  agentId: opt(str2),
24479
25056
  key: str2,
@@ -24481,7 +25058,7 @@ var MobileContentRefSchema = Type9.Object({
24481
25058
  digest: str2,
24482
25059
  bytes: int
24483
25060
  });
24484
- var MobileImageRefSchema = Type9.Object({
25061
+ var MobileImageRefSchema = Type10.Object({
24485
25062
  name: str2,
24486
25063
  mimeType: str2,
24487
25064
  width: int,
@@ -24490,56 +25067,70 @@ var MobileImageRefSchema = Type9.Object({
24490
25067
  entryId: int,
24491
25068
  contentIndex: int
24492
25069
  });
24493
- var questionStatus2 = Type9.Union([
24494
- Type9.Literal("pending"),
24495
- Type9.Literal("answered"),
24496
- Type9.Literal("skipped"),
24497
- Type9.Literal("cancelled")
25070
+ var planStatus = Type10.Union([
25071
+ Type10.Literal("awaiting_approval"),
25072
+ Type10.Literal("approved"),
25073
+ Type10.Literal("revision_requested"),
25074
+ Type10.Literal("superseded"),
25075
+ Type10.Literal("cancelled")
24498
25076
  ]);
24499
- var MobileToolInteractionSchema = Type9.Union([
24500
- Type9.Object({
24501
- kind: Type9.Literal("ask_user"),
25077
+ var questionStatus2 = Type10.Union([
25078
+ Type10.Literal("pending"),
25079
+ Type10.Literal("answered"),
25080
+ Type10.Literal("skipped"),
25081
+ Type10.Literal("cancelled")
25082
+ ]);
25083
+ var MobileToolInteractionSchema = Type10.Union([
25084
+ Type10.Object({
25085
+ kind: Type10.Literal("ask_user"),
24502
25086
  id: int,
24503
25087
  question: str2,
24504
- multiple: Type9.Boolean(),
24505
- options: Type9.Array(Type9.Object({ id: str2, label: str2, recommended: opt(Type9.Boolean()) })),
25088
+ multiple: Type10.Boolean(),
25089
+ options: Type10.Array(Type10.Object({ id: str2, label: str2, recommended: opt(Type10.Boolean()) })),
24506
25090
  status: questionStatus2,
24507
- optionIds: opt(Type9.Array(str2)),
24508
- labels: opt(Type9.Array(str2)),
25091
+ optionIds: opt(Type10.Array(str2)),
25092
+ labels: opt(Type10.Array(str2)),
24509
25093
  text: opt(nullable2(str2))
24510
25094
  }),
24511
- Type9.Object({
24512
- kind: Type9.Literal("todo"),
24513
- action: Type9.Union([Type9.Literal("list"), Type9.Literal("replace")]),
25095
+ Type10.Object({
25096
+ kind: Type10.Literal("todo"),
25097
+ action: Type10.Union([Type10.Literal("list"), Type10.Literal("replace")]),
24514
25098
  revision: int,
24515
25099
  completed: int,
24516
25100
  total: int
25101
+ }),
25102
+ Type10.Object({
25103
+ kind: Type10.Literal("submit_plan"),
25104
+ version: str2,
25105
+ title: str2,
25106
+ steps: Type10.Array(str2),
25107
+ note: str2
24517
25108
  })
24518
25109
  ]);
24519
- var MobileToolItemSchema = Type9.Object({
25110
+ var MobileToolItemSchema = Type10.Object({
24520
25111
  callId: str2,
24521
25112
  name: str2,
24522
25113
  summary: str2,
24523
- status: Type9.Union([Type9.Literal("running"), Type9.Literal("done"), Type9.Literal("error")]),
25114
+ status: Type10.Union([Type10.Literal("running"), Type10.Literal("done"), Type10.Literal("error")]),
24524
25115
  detail: opt(str2),
24525
25116
  output: opt(str2),
24526
25117
  result: opt(str2),
24527
25118
  diff: opt(str2),
24528
- diffAdded: opt(Type9.Number()),
24529
- diffRemoved: opt(Type9.Number()),
24530
- diffTruncated: opt(Type9.Boolean()),
24531
- durationMs: opt(Type9.Number()),
25119
+ diffAdded: opt(Type10.Number()),
25120
+ diffRemoved: opt(Type10.Number()),
25121
+ diffTruncated: opt(Type10.Boolean()),
25122
+ durationMs: opt(Type10.Number()),
24532
25123
  note: opt(str2),
24533
25124
  interaction: opt(MobileToolInteractionSchema)
24534
25125
  });
24535
- var MobileChatItemSchema = Type9.Object({
25126
+ var MobileChatItemSchema = Type10.Object({
24536
25127
  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")
25128
+ type: Type10.Union([
25129
+ Type10.Literal("user"),
25130
+ Type10.Literal("assistant"),
25131
+ Type10.Literal("tool"),
25132
+ Type10.Literal("error"),
25133
+ Type10.Literal("system")
24543
25134
  ]),
24544
25135
  text: opt(str2),
24545
25136
  thinking: opt(str2),
@@ -24548,99 +25139,101 @@ var MobileChatItemSchema = Type9.Object({
24548
25139
  skill: opt(str2),
24549
25140
  jobId: opt(str2),
24550
25141
  tool: opt(MobileToolItemSchema),
24551
- images: opt(Type9.Array(MobileImageRefSchema)),
24552
- refs: opt(Type9.Array(MobileContentRefSchema))
25142
+ images: opt(Type10.Array(MobileImageRefSchema)),
25143
+ refs: opt(Type10.Array(MobileContentRefSchema))
24553
25144
  });
24554
- var MobileStreamingSchema = Type9.Object({
25145
+ var MobileStreamingSchema = Type10.Object({
24555
25146
  text: str2,
24556
25147
  thinking: str2,
24557
25148
  textOmitted: int,
24558
25149
  thinkingOmitted: int,
24559
- toolCalls: Type9.Array(Type9.Object({
25150
+ toolCalls: Type10.Array(Type10.Object({
24560
25151
  callId: str2,
24561
25152
  name: str2,
24562
25153
  summary: str2,
24563
- status: Type9.Union([Type9.Literal("running"), Type9.Literal("done")])
25154
+ status: Type10.Union([Type10.Literal("running"), Type10.Literal("done")])
24564
25155
  }))
24565
25156
  });
24566
- var MobileUsageSchema = Type9.Object({
25157
+ var MobileUsageSchema = Type10.Object({
24567
25158
  input: int,
24568
25159
  output: int,
24569
25160
  cacheRead: int,
24570
25161
  cacheWrite: int,
24571
25162
  totalTokens: int,
24572
- cost: Type9.Number({ minimum: 0 })
25163
+ cost: Type10.Number({ minimum: 0 })
24573
25164
  });
24574
- var MobileSubagentRoundSchema = Type9.Object({
25165
+ var MobileSubagentRoundSchema = Type10.Object({
24575
25166
  taskId: int,
24576
25167
  callId: str2,
24577
25168
  agentId: str2,
24578
25169
  round: opt(int),
24579
25170
  role: str2,
24580
25171
  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")
25172
+ status: Type10.Union([
25173
+ Type10.Literal("queued"),
25174
+ Type10.Literal("running"),
25175
+ Type10.Literal("completed"),
25176
+ Type10.Literal("failed"),
25177
+ Type10.Literal("cancelled")
24587
25178
  ]),
24588
25179
  approvalCount: int,
24589
- readonly: Type9.Boolean(),
24590
- createdAt: Type9.Number(),
24591
- endedAt: nullable2(Type9.Number()),
25180
+ readonly: Type10.Boolean(),
25181
+ createdAt: Type10.Number(),
25182
+ endedAt: nullable2(Type10.Number()),
24592
25183
  usage: opt(MobileUsageSchema)
24593
25184
  });
24594
- var MobileAgentSchema = Type9.Object({
25185
+ var MobileAgentSchema = Type10.Object({
24595
25186
  agentId: str2,
24596
25187
  role: str2,
24597
25188
  title: str2,
24598
- status: Type9.Union([Type9.Literal("idle"), Type9.Literal("queued"), Type9.Literal("running")]),
25189
+ status: Type10.Union([Type10.Literal("idle"), Type10.Literal("queued"), Type10.Literal("running")]),
24599
25190
  activeRoundId: nullable2(int),
24600
25191
  lastRoundId: int,
24601
25192
  roundCount: int,
24602
25193
  approvalCount: int
24603
25194
  });
24604
- var MobileQueuedSchema = Type9.Object({
25195
+ var MobileQueuedSchema = Type10.Object({
24605
25196
  id: int,
24606
- mode: Type9.Union([Type9.Literal("steer"), Type9.Literal("followUp")]),
24607
- preview: Type9.String(),
25197
+ mode: Type10.Union([Type10.Literal("steer"), Type10.Literal("followUp")]),
25198
+ preview: Type10.String(),
24608
25199
  images: int
24609
25200
  });
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 })),
25201
+ var MobileChatStatusSchema = Type10.Object({
25202
+ busy: Type10.Boolean(),
25203
+ queued: opt(Type10.Array(MobileQueuedSchema)),
25204
+ todos: opt(Type10.Object({ revision: int, count: int, completed: int })),
25205
+ questions: opt(Type10.Object({ revision: int, pendingCount: int })),
25206
+ plan: opt(Type10.Object({ revision: int, pending: Type10.Boolean(), version: opt(Type10.String({ minLength: 1 })) })),
24615
25207
  status: nullable2(str2),
24616
25208
  usage: MobileUsageSchema,
24617
25209
  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),
25210
+ contextUsage: opt(Type10.Object({ tokens: nullable2(Type10.Number()), window: Type10.Number(), percent: nullable2(Type10.Number()) })),
25211
+ agent: Type10.Object({ model: nullable2(str2), thinkingLevel: nullable2(str2) }),
25212
+ subagents: Type10.Array(MobileSubagentRoundSchema),
25213
+ agents: Type10.Array(MobileAgentSchema),
24622
25214
  approvalCount: int,
24623
- compaction: opt(Type9.Object({ phase: str2, at: Type9.Number() })),
24624
- readonly: opt(Type9.Boolean())
25215
+ compaction: opt(Type10.Object({ phase: str2, at: Type10.Number() })),
25216
+ readonly: opt(Type10.Boolean())
24625
25217
  });
24626
- var MobileChatSnapshotSchema = Type9.Object({
25218
+ var MobileChatSnapshotSchema = Type10.Object({
24627
25219
  ...MobileChatStatusSchema.properties,
24628
- items: Type9.Array(MobileChatItemSchema),
25220
+ items: Type10.Array(MobileChatItemSchema),
24629
25221
  historyBefore: nullable2(str2),
24630
25222
  streaming: nullable2(MobileStreamingSchema)
24631
25223
  });
24632
- var MobileWorkspaceSchema = Type9.Object({ root: str2, name: str2 });
24633
- var MobileSessionSummarySchema = Type9.Object({
25224
+ var MobileWorkspaceSchema = Type10.Object({ root: str2, name: str2 });
25225
+ var MobileSessionSummarySchema = Type10.Object({
24634
25226
  id: str2,
24635
25227
  workspace: str2,
24636
25228
  title: str2,
24637
25229
  preview: str2,
24638
- lastUsedAt: Type9.Integer(),
24639
- lastMessageAt: Type9.Integer(),
24640
- archived: Type9.Boolean(),
24641
- awaitingAnswer: opt(Type9.Boolean())
25230
+ lastUsedAt: Type10.Integer(),
25231
+ lastMessageAt: Type10.Integer(),
25232
+ archived: Type10.Boolean(),
25233
+ awaitingAnswer: opt(Type10.Boolean()),
25234
+ awaitingPlan: opt(Type10.Boolean())
24642
25235
  });
24643
- var MobileApprovalSchema = Type9.Object({
25236
+ var MobileApprovalSchema = Type10.Object({
24644
25237
  id: int,
24645
25238
  version: str2,
24646
25239
  taskId: int,
@@ -24648,236 +25241,267 @@ var MobileApprovalSchema = Type9.Object({
24648
25241
  title: opt(str2),
24649
25242
  command: str2,
24650
25243
  cwd: str2,
24651
- timeoutSeconds: opt(Type9.Number({ minimum: 0 })),
24652
- requestedAt: Type9.Number()
25244
+ timeoutSeconds: opt(Type10.Number({ minimum: 0 })),
25245
+ requestedAt: Type10.Number()
24653
25246
  });
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")])
25247
+ var MobileTodoItemSchema = Type10.Object({
25248
+ id: Type10.String({ minLength: 1, maxLength: TODO_LIMITS.idChars }),
25249
+ text: Type10.String({ minLength: 1, maxLength: TODO_LIMITS.textChars }),
25250
+ status: Type10.Union([Type10.Literal("pending"), Type10.Literal("in_progress"), Type10.Literal("completed")])
24658
25251
  });
24659
- var MobileTodoListSchema = Type9.Object({
25252
+ var MobileTodoListSchema = Type10.Object({
24660
25253
  revision: int,
24661
- items: Type9.Array(MobileTodoItemSchema, { maxItems: TODO_LIMITS.maxItems })
25254
+ items: Type10.Array(MobileTodoItemSchema, { maxItems: TODO_LIMITS.maxItems })
24662
25255
  });
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())
25256
+ var MobileQuestionOptionSchema = Type10.Object({
25257
+ id: Type10.String({ minLength: 1, maxLength: QUESTION_LIMITS.optionIdChars }),
25258
+ label: Type10.String({ minLength: 1, maxLength: QUESTION_LIMITS.labelChars }),
25259
+ description: opt(Type10.String({ maxLength: QUESTION_LIMITS.descriptionChars })),
25260
+ recommended: opt(Type10.Boolean())
24668
25261
  });
24669
- var MobileQuestionSummarySchema = Type9.Object({
25262
+ var MobileQuestionSummarySchema = Type10.Object({
24670
25263
  id: int,
24671
25264
  version: str2,
24672
- preview: Type9.String({ maxLength: QUESTION_LIMITS.previewChars }),
24673
- multiple: Type9.Boolean(),
25265
+ preview: Type10.String({ maxLength: QUESTION_LIMITS.previewChars }),
25266
+ multiple: Type10.Boolean(),
24674
25267
  optionCount: int,
24675
- createdAt: Type9.Number()
25268
+ createdAt: Type10.Number()
24676
25269
  });
24677
- var MobileQuestionListSchema = Type9.Object({
25270
+ var MobileQuestionListSchema = Type10.Object({
24678
25271
  revision: int,
24679
- pending: Type9.Array(MobileQuestionSummarySchema, { maxItems: QUESTION_LIMITS.maxPending })
25272
+ pending: Type10.Array(MobileQuestionSummarySchema, { maxItems: QUESTION_LIMITS.maxPending })
24680
25273
  });
24681
- var MobileQuestionSchema = Type9.Object({
25274
+ var MobileQuestionSchema = Type10.Object({
24682
25275
  id: int,
24683
25276
  callId: str2,
24684
25277
  version: str2,
24685
- question: Type9.String({ maxLength: QUESTION_LIMITS.questionChars }),
24686
- multiple: Type9.Boolean(),
24687
- options: Type9.Array(MobileQuestionOptionSchema, { maxItems: QUESTION_LIMITS.maxOptions }),
25278
+ question: Type10.String({ maxLength: QUESTION_LIMITS.questionChars }),
25279
+ multiple: Type10.Boolean(),
25280
+ options: Type10.Array(MobileQuestionOptionSchema, { maxItems: QUESTION_LIMITS.maxOptions }),
24688
25281
  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 }))
25282
+ answer: nullable2(Type10.Object({
25283
+ optionIds: Type10.Array(Type10.String(), { maxItems: QUESTION_LIMITS.maxOptions }),
25284
+ text: nullable2(Type10.String({ maxLength: QUESTION_LIMITS.textChars }))
24692
25285
  })),
24693
- createdAt: Type9.Number(),
24694
- settledAt: nullable2(Type9.Number())
25286
+ createdAt: Type10.Number(),
25287
+ settledAt: nullable2(Type10.Number())
24695
25288
  });
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 }))
25289
+ var MobileQuestionResponseSchema = Type10.Union([
25290
+ Type10.Object({
25291
+ kind: Type10.Literal("answer"),
25292
+ optionIds: opt(Type10.Array(Type10.String(), { maxItems: QUESTION_LIMITS.maxOptions })),
25293
+ text: opt(Type10.String({ maxLength: QUESTION_LIMITS.textChars }))
24701
25294
  }, { additionalProperties: false }),
24702
- Type9.Object({ kind: Type9.Literal("skip") }, { additionalProperties: false })
25295
+ Type10.Object({ kind: Type10.Literal("skip") }, { additionalProperties: false })
25296
+ ]);
25297
+ var MobilePlanSchema = Type10.Object({
25298
+ version: Type10.String({ minLength: 1 }),
25299
+ title: Type10.String({ maxLength: PLAN_LIMITS.titleChars }),
25300
+ steps: Type10.Array(Type10.String({ maxLength: PLAN_LIMITS.stepChars }), { maxItems: PLAN_LIMITS.maxSteps }),
25301
+ note: Type10.String({ maxLength: PLAN_LIMITS.noteChars }),
25302
+ status: planStatus,
25303
+ feedback: nullable2(Type10.String({ maxLength: PLAN_LIMITS.feedbackChars })),
25304
+ createdAt: Type10.Number(),
25305
+ settledAt: nullable2(Type10.Number())
25306
+ });
25307
+ var MobilePlanDecisionSchema = Type10.Union([
25308
+ Type10.Object({ kind: Type10.Literal("approve") }, { additionalProperties: false }),
25309
+ Type10.Object({ kind: Type10.Literal("revise"), feedback: Type10.String({ minLength: 1, maxLength: PLAN_LIMITS.feedbackChars }) }, { additionalProperties: false })
24703
25310
  ]);
24704
- var sessionListParams = Type9.Object({
25311
+ var sessionListParams = Type10.Object({
24705
25312
  workspace: str2,
24706
25313
  query: opt(str2),
24707
25314
  cursor: opt(str2)
24708
25315
  });
24709
- var sessionListResult = Type9.Object({
24710
- sessions: Type9.Array(MobileSessionSummarySchema),
25316
+ var sessionListResult = Type10.Object({
25317
+ sessions: Type10.Array(MobileSessionSummarySchema),
24711
25318
  nextCursor: opt(str2)
24712
25319
  });
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({
25320
+ var subscription = { subscriptionId: Type10.String({ minLength: 1 }) };
25321
+ var pageResult = Type10.Object({ items: Type10.Array(MobileChatItemSchema), historyBefore: nullable2(str2) });
25322
+ var pageParams = Type10.Object({
24716
25323
  ...subscription,
24717
- before: Type9.String({ minLength: 1 }),
24718
- limit: opt(Type9.Integer({ minimum: 1, maximum: 50 }))
25324
+ before: Type10.String({ minLength: 1 }),
25325
+ limit: opt(Type10.Integer({ minimum: 1, maximum: 50 }))
24719
25326
  });
24720
- var chunkResult = Type9.Object({ data: str2, nextOffset: int, total: int, done: Type9.Boolean() });
25327
+ var chunkResult = Type10.Object({ data: str2, nextOffset: int, total: int, done: Type10.Boolean() });
24721
25328
  var MOBILE_API_V1_METHODS = {
24722
25329
  "server.hello": { params: MobileHelloParams, result: buildHelloResultSchema() },
24723
- "workspaces.list": { params: Type9.Null(), result: Type9.Object({ workspaces: Type9.Array(MobileWorkspaceSchema) }) },
25330
+ "workspaces.list": { params: Type10.Null(), result: Type10.Object({ workspaces: Type10.Array(MobileWorkspaceSchema) }) },
24724
25331
  "sessions.list": { params: sessionListParams, result: sessionListResult },
24725
- "sessions.create": { params: Type9.Object({ workspace: str2 }), result: MobileSessionSummarySchema },
25332
+ "sessions.create": { params: Type10.Object({ workspace: str2 }), result: MobileSessionSummarySchema },
24726
25333
  "chat.attach": {
24727
- params: Type9.Object({ sessionId: Type9.String({ minLength: 1 }) }),
24728
- result: Type9.Object({ subscriptionId: str2, sessionId: str2, snapshot: MobileChatSnapshotSchema })
25334
+ params: Type10.Object({ sessionId: Type10.String({ minLength: 1 }) }),
25335
+ result: Type10.Object({ subscriptionId: str2, sessionId: str2, snapshot: MobileChatSnapshotSchema })
24729
25336
  },
24730
- "chat.detach": { params: Type9.Object(subscription), result: Type9.Null() },
25337
+ "chat.detach": { params: Type10.Object(subscription), result: Type10.Null() },
24731
25338
  "chat.history": { params: pageParams, result: pageResult },
24732
25339
  "chat.content": {
24733
- params: Type9.Object({ ref: MobileContentRefSchema, offset: int }),
25340
+ params: Type10.Object({ ref: MobileContentRefSchema, offset: int }),
24734
25341
  result: chunkResult
24735
25342
  },
24736
25343
  "chat.submit": {
24737
- params: Type9.Object({
25344
+ params: Type10.Object({
24738
25345
  ...subscription,
24739
- requestId: Type9.String({ minLength: 1, maxLength: 128 }),
24740
- text: Type9.String({ minLength: 1, maxLength: 65536 }),
24741
- mode: opt(Type9.Literal("steer"))
25346
+ requestId: Type10.String({ minLength: 1, maxLength: 128 }),
25347
+ text: Type10.String({ minLength: 1, maxLength: 65536 }),
25348
+ mode: opt(Type10.Literal("steer"))
24742
25349
  }),
24743
- result: Type9.Object({ accepted: Type9.Literal(true), queued: Type9.Boolean() })
25350
+ result: Type10.Object({ accepted: Type10.Literal(true), queued: Type10.Boolean() })
24744
25351
  },
24745
25352
  "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")]) })
25353
+ params: Type10.Object({ ...subscription, requestId: Type10.String({ minLength: 1, maxLength: 128 }) }),
25354
+ result: Type10.Object({ status: Type10.Union([Type10.Literal("accepted"), Type10.Literal("unknown")]) })
24748
25355
  },
24749
25356
  "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")
25357
+ params: Type10.Object({ ...subscription, id: int }),
25358
+ result: Type10.Object({
25359
+ result: Type10.Union([
25360
+ Type10.Literal("steered"),
25361
+ Type10.Literal("withdrawn"),
25362
+ Type10.Literal("placed"),
25363
+ Type10.Literal("settled"),
25364
+ Type10.Literal("unknown")
24758
25365
  ]),
24759
25366
  id: opt(int),
24760
- text: opt(Type9.String()),
25367
+ text: opt(Type10.String()),
24761
25368
  images: opt(int)
24762
25369
  })
24763
25370
  },
24764
25371
  "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")
25372
+ params: Type10.Object({ ...subscription, id: int }),
25373
+ result: Type10.Object({
25374
+ result: Type10.Union([
25375
+ Type10.Literal("withdrawn"),
25376
+ Type10.Literal("placed"),
25377
+ Type10.Literal("settled"),
25378
+ Type10.Literal("unknown")
24772
25379
  ]),
24773
- text: opt(Type9.String()),
25380
+ text: opt(Type10.String()),
24774
25381
  images: opt(int)
24775
25382
  })
24776
25383
  },
24777
- "chat.abort": { params: Type9.Object(subscription), result: Type9.Object({ accepted: Type9.Boolean() }) },
25384
+ "chat.abort": { params: Type10.Object(subscription), result: Type10.Object({ accepted: Type10.Boolean() }) },
24778
25385
  "subagents.open": {
24779
- params: Type9.Object({ ...subscription, agentId: opt(Type9.String({ minLength: 1 })), taskId: opt(int) }),
24780
- result: Type9.Object({
25386
+ params: Type10.Object({ ...subscription, agentId: opt(Type10.String({ minLength: 1 })), taskId: opt(int) }),
25387
+ result: Type10.Object({
24781
25388
  subagentSubscriptionId: str2,
24782
25389
  agentId: str2,
24783
25390
  snapshot: MobileChatSnapshotSchema
24784
25391
  })
24785
25392
  },
24786
25393
  "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 }))
25394
+ params: Type10.Object({
25395
+ subagentSubscriptionId: Type10.String({ minLength: 1 }),
25396
+ before: Type10.String({ minLength: 1 }),
25397
+ limit: opt(Type10.Integer({ minimum: 1, maximum: 50 }))
24791
25398
  }),
24792
25399
  result: pageResult
24793
25400
  },
24794
25401
  "subagents.close": {
24795
- params: Type9.Object({ subagentSubscriptionId: Type9.String({ minLength: 1 }) }),
24796
- result: Type9.Null()
25402
+ params: Type10.Object({ subagentSubscriptionId: Type10.String({ minLength: 1 }) }),
25403
+ result: Type10.Null()
24797
25404
  },
24798
25405
  "subagents.stop": {
24799
- params: Type9.Object({ ...subscription, taskId: int }),
24800
- result: Type9.Object({ stopped: Type9.Boolean() })
25406
+ params: Type10.Object({ ...subscription, taskId: int }),
25407
+ result: Type10.Object({ stopped: Type10.Boolean() })
24801
25408
  },
24802
25409
  "approvals.list": {
24803
- params: Type9.Object(subscription),
24804
- result: Type9.Object({ approvals: Type9.Array(MobileApprovalSchema) })
25410
+ params: Type10.Object(subscription),
25411
+ result: Type10.Object({ approvals: Type10.Array(MobileApprovalSchema) })
24805
25412
  },
24806
- "approvals.get": { params: Type9.Object({ ...subscription, id: int }), result: MobileApprovalSchema },
25413
+ "approvals.get": { params: Type10.Object({ ...subscription, id: int }), result: MobileApprovalSchema },
24807
25414
  "approvals.decide": {
24808
- params: Type9.Object({
25415
+ params: Type10.Object({
24809
25416
  ...subscription,
24810
25417
  taskId: int,
24811
25418
  id: int,
24812
- version: Type9.String({ minLength: 1 }),
24813
- decision: Type9.Union([Type9.Literal("allow"), Type9.Literal("deny")])
25419
+ version: Type10.String({ minLength: 1 }),
25420
+ decision: Type10.Union([Type10.Literal("allow"), Type10.Literal("deny")])
24814
25421
  }),
24815
- result: Type9.Object({
24816
- status: Type9.Union([
24817
- Type9.Literal("pending"),
24818
- Type9.Literal("allowed"),
24819
- Type9.Literal("denied"),
24820
- Type9.Literal("cancelled")
25422
+ result: Type10.Object({
25423
+ status: Type10.Union([
25424
+ Type10.Literal("pending"),
25425
+ Type10.Literal("allowed"),
25426
+ Type10.Literal("denied"),
25427
+ Type10.Literal("cancelled")
24821
25428
  ])
24822
25429
  })
24823
25430
  },
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 },
25431
+ "todos.get": { params: Type10.Object(subscription), result: MobileTodoListSchema },
25432
+ "questions.list": { params: Type10.Object(subscription), result: MobileQuestionListSchema },
25433
+ "questions.get": { params: Type10.Object({ ...subscription, id: int }), result: MobileQuestionSchema },
24827
25434
  "questions.respond": {
24828
- params: Type9.Object({
25435
+ params: Type10.Object({
24829
25436
  ...subscription,
24830
25437
  id: int,
24831
- version: Type9.String({ minLength: 1 }),
25438
+ version: Type10.String({ minLength: 1 }),
24832
25439
  response: MobileQuestionResponseSchema
24833
25440
  }),
24834
25441
  result: MobileQuestionSchema
24835
25442
  },
25443
+ "plan.get": {
25444
+ params: Type10.Object({ ...subscription, version: Type10.String({ minLength: 1 }) }),
25445
+ result: MobilePlanSchema
25446
+ },
25447
+ "plan.respond": {
25448
+ params: Type10.Object({
25449
+ ...subscription,
25450
+ version: Type10.String({ minLength: 1 }),
25451
+ decision: MobilePlanDecisionSchema
25452
+ }),
25453
+ result: MobilePlanSchema
25454
+ },
25455
+ "plan.exit": {
25456
+ params: Type10.Object(subscription),
25457
+ result: Type10.Object({ mode: Type10.Literal("normal"), revision: int })
25458
+ },
24836
25459
  "media.read": {
24837
- params: Type9.Object({
25460
+ params: Type10.Object({
24838
25461
  ...subscription,
24839
- agentId: opt(Type9.String({ minLength: 1 })),
25462
+ agentId: opt(Type10.String({ minLength: 1 })),
24840
25463
  entryId: int,
24841
25464
  contentIndex: int,
24842
25465
  offset: int
24843
25466
  }),
24844
- result: Type9.Object({ ...chunkResult.properties, mimeType: str2 })
25467
+ result: Type10.Object({ ...chunkResult.properties, mimeType: str2 })
24845
25468
  }
24846
25469
  };
24847
25470
  function buildHelloResultSchema() {
24848
- return Type9.Object({
24849
- apiVersion: Type9.Integer({ minimum: 1 }),
25471
+ return Type10.Object({
25472
+ apiVersion: Type10.Integer({ minimum: 1 }),
24850
25473
  serverId: str2,
24851
25474
  serverName: str2,
24852
25475
  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()
25476
+ capabilities: Type10.Array(str2),
25477
+ limits: Type10.Object({
25478
+ maxFrameBytes: Type10.Integer(),
25479
+ maxPayloadBytes: Type10.Integer(),
25480
+ pageItems: Type10.Integer(),
25481
+ chunkBytes: Type10.Integer(),
25482
+ maxConcurrentRequests: Type10.Integer(),
25483
+ maxSendBufferBytes: Type10.Integer()
24861
25484
  })
24862
25485
  });
24863
25486
  }
24864
25487
  var patchBody = {
24865
- seq: Type9.Integer({ minimum: 1 }),
24866
- upserts: Type9.Array(MobileChatItemSchema),
25488
+ seq: Type10.Integer({ minimum: 1 }),
25489
+ upserts: Type10.Array(MobileChatItemSchema),
24867
25490
  status: opt(MobileChatStatusSchema),
24868
25491
  streaming: opt(nullable2(MobileStreamingSchema)),
24869
- streamingAppend: opt(Type9.Object({ text: opt(str2), thinking: opt(str2) }))
25492
+ streamingAppend: opt(Type10.Object({ text: opt(str2), thinking: opt(str2) }))
24870
25493
  };
24871
25494
  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 }) })
25495
+ "workspaces.changed": Type10.Object({ revision: Type10.Integer({ minimum: 1 }) }),
25496
+ "sessions.changed": Type10.Object({ revision: Type10.Integer({ minimum: 1 }) }),
25497
+ "chat.patch": Type10.Object({ subscriptionId: str2, ...patchBody }),
25498
+ "chat.reset": Type10.Object({ subscriptionId: str2, seq: Type10.Integer({ minimum: 1 }) }),
25499
+ "subagents.patch": Type10.Object({ subagentSubscriptionId: str2, ...patchBody }),
25500
+ "subagents.reset": Type10.Object({ subagentSubscriptionId: str2, seq: Type10.Integer({ minimum: 1 }) }),
25501
+ "approvals.changed": Type10.Object({ subscriptionId: str2, revision: Type10.Integer({ minimum: 1 }) }),
25502
+ "todos.changed": Type10.Object({ subscriptionId: str2, revision: Type10.Integer({ minimum: 1 }) }),
25503
+ "questions.changed": Type10.Object({ subscriptionId: str2, revision: Type10.Integer({ minimum: 1 }) }),
25504
+ "plan.changed": Type10.Object({ subscriptionId: str2, revision: Type10.Integer({ minimum: 1 }) })
24881
25505
  };
24882
25506
 
24883
25507
  // packages/gateway/src/mobile/projection.ts
@@ -25088,6 +25712,13 @@ function mapStatus(view, options = {}) {
25088
25712
  }
25089
25713
  },
25090
25714
  ...view.questions === undefined ? {} : { questions: { revision: view.questions.revision, pendingCount: view.questions.pending.length } },
25715
+ ...view.plan === undefined ? {} : {
25716
+ plan: {
25717
+ revision: view.plan.revision,
25718
+ pending: view.plan.current?.status === "awaiting_approval",
25719
+ ...view.plan.current === null ? {} : { version: view.plan.current.version }
25720
+ }
25721
+ },
25091
25722
  status: view.status,
25092
25723
  usage: mapUsage(view.usage),
25093
25724
  ...view.taskUsage === undefined ? {} : { taskUsage: mapUsage(view.taskUsage) },
@@ -25602,6 +26233,12 @@ class MobileAdapter {
25602
26233
  return this.questionsGet(params);
25603
26234
  case "questions.respond":
25604
26235
  return this.questionsRespond(params);
26236
+ case "plan.get":
26237
+ return this.planGet(params);
26238
+ case "plan.respond":
26239
+ return this.planRespond(params);
26240
+ case "plan.exit":
26241
+ return this.planExit(params);
25605
26242
  case "media.read":
25606
26243
  return this.mediaRead(params);
25607
26244
  default:
@@ -25656,7 +26293,8 @@ class MobileAdapter {
25656
26293
  lastUsedAt: Math.round(session.lastUsedAt),
25657
26294
  lastMessageAt: Math.round(messageTime(session)),
25658
26295
  archived: false,
25659
- ...session.awaitingAnswer === undefined ? {} : { awaitingAnswer: session.awaitingAnswer }
26296
+ ...session.awaitingAnswer === undefined ? {} : { awaitingAnswer: session.awaitingAnswer },
26297
+ ...session.awaitingPlan === undefined ? {} : { awaitingPlan: session.awaitingPlan }
25660
26298
  })),
25661
26299
  ...offset + page.length < sessions.length ? { nextCursor: String(offset + page.length) } : {}
25662
26300
  };
@@ -25675,7 +26313,8 @@ class MobileAdapter {
25675
26313
  lastUsedAt: Math.round(created.lastUsedAt),
25676
26314
  lastMessageAt: Math.round(messageTime(created)),
25677
26315
  archived: false,
25678
- ...created.awaitingAnswer === undefined ? {} : { awaitingAnswer: created.awaitingAnswer }
26316
+ ...created.awaitingAnswer === undefined ? {} : { awaitingAnswer: created.awaitingAnswer },
26317
+ ...created.awaitingPlan === undefined ? {} : { awaitingPlan: created.awaitingPlan }
25679
26318
  };
25680
26319
  }
25681
26320
  nextId(prefix) {
@@ -25752,7 +26391,8 @@ class MobileAdapter {
25752
26391
  approvalsRunning: false,
25753
26392
  approvalsDirty: false,
25754
26393
  todosRevision: chat.view.value?.todos?.revision ?? 0,
25755
- questionsRevision: chat.view.value?.questions?.revision ?? 0
26394
+ questionsRevision: chat.view.value?.questions?.revision ?? 0,
26395
+ planRevision: chat.view.value?.plan?.revision ?? 0
25756
26396
  };
25757
26397
  this.chatState = state;
25758
26398
  const snapshot = projection.snapshot();
@@ -25937,6 +26577,11 @@ class MobileAdapter {
25937
26577
  state.questionsRevision = questions;
25938
26578
  this.sendNotification("questions.changed", { subscriptionId: state.subscriptionId, revision: questions });
25939
26579
  }
26580
+ const plan = view?.plan?.revision;
26581
+ if (plan !== undefined && plan > state.planRevision) {
26582
+ state.planRevision = plan;
26583
+ this.sendNotification("plan.changed", { subscriptionId: state.subscriptionId, revision: plan });
26584
+ }
25940
26585
  }
25941
26586
  async todosGet(params) {
25942
26587
  const state = this.requireChat(params);
@@ -25975,6 +26620,31 @@ class MobileAdapter {
25975
26620
  response: params.response
25976
26621
  }), "rejected");
25977
26622
  }
26623
+ async planGet(params) {
26624
+ const state = this.requireChat(params);
26625
+ this.assertAuthorized();
26626
+ if (state.chat.view.value?.plan === undefined)
26627
+ throw new MobileMethodError("not_found", "plan mode is not available");
26628
+ const record = await remote2(() => state.chat.planGet({ version: params.version }));
26629
+ if (record === null)
26630
+ throw new MobileMethodError("not_found", "plan not found in this session");
26631
+ return record;
26632
+ }
26633
+ async planExit(params) {
26634
+ const state = this.requireChat(params);
26635
+ this.assertAuthorized();
26636
+ if (state.chat.view.value?.plan === undefined)
26637
+ throw new MobileMethodError("not_found", "plan mode is not available");
26638
+ const view = await remote2(() => state.chat.planMode({ mode: "normal" }), "rejected");
26639
+ return { mode: "normal", revision: view.revision };
26640
+ }
26641
+ async planRespond(params) {
26642
+ const state = this.requireChat(params);
26643
+ this.assertAuthorized();
26644
+ if (state.chat.view.value?.plan === undefined)
26645
+ throw new MobileMethodError("not_found", "plan mode is not available");
26646
+ return remote2(() => state.chat.planRespond({ version: params.version, decision: params.decision }), "rejected");
26647
+ }
25978
26648
  requireSubagent(params) {
25979
26649
  const state = this.subagentState;
25980
26650
  if (state === undefined || state.subscriptionId !== params.subagentSubscriptionId)
@@ -27278,7 +27948,7 @@ class MobileGateway {
27278
27948
  }
27279
27949
  }
27280
27950
  attachConnection(socket, auth, localAddress) {
27281
- const connectionId = randomUUID3();
27951
+ const connectionId = randomUUID4();
27282
27952
  const [serverEnd, clientEnd] = memoryLinkPair();
27283
27953
  this.chord.accept(serverEnd, {
27284
27954
  principal: { connectionId, kind: "mobile", deviceId: auth.deviceId, grantRevision: auth.grantRevision },
@@ -27637,7 +28307,7 @@ async function removeGatewayPortFile(nekoDir, serverId) {
27637
28307
  }
27638
28308
 
27639
28309
  // packages/gateway/src/server-services.ts
27640
- import { randomUUID as randomUUID5 } from "crypto";
28310
+ import { randomUUID as randomUUID6 } from "crypto";
27641
28311
  import { ChordError as ChordError5, local as local3, method as method2 } from "@earendil-works/chord";
27642
28312
 
27643
28313
  // packages/core/src/application-updates.ts
@@ -27788,7 +28458,7 @@ class UpdateChecker {
27788
28458
  }
27789
28459
 
27790
28460
  // packages/gateway/src/server-services.ts
27791
- import { Type as Type12 } from "typebox";
28461
+ import { Type as Type13 } from "typebox";
27792
28462
 
27793
28463
  // packages/gateway/src/session-host.ts
27794
28464
  import { open as open3 } from "fs/promises";
@@ -27800,7 +28470,7 @@ import {
27800
28470
  } from "@earendil-works/chord";
27801
28471
  import { withAbortSignal as withAbortSignal4 } from "@earendil-works/chord/context";
27802
28472
  import { UsageDoc as UsageDoc4 } from "@earendil-works/pi-durable";
27803
- import { Type as Type11 } from "typebox";
28473
+ import { Type as Type12 } from "typebox";
27804
28474
 
27805
28475
  // packages/gateway/src/image-uploads.ts
27806
28476
  var IMAGE_CHUNK_BYTES = 256 * 1024;
@@ -27899,7 +28569,7 @@ class ImageUploads {
27899
28569
  // packages/gateway/src/subagent-host.ts
27900
28570
  import { ChordError as ChordError3, local, opaque } from "@earendil-works/chord";
27901
28571
  import { UsageDoc as UsageDoc3 } from "@earendil-works/pi-durable";
27902
- import { Type as Type10 } from "typebox";
28572
+ import { Type as Type11 } from "typebox";
27903
28573
  async function openReader(runtime, childId, parentId, context, through, historyUsage) {
27904
28574
  const child = await runtime.harness.conversation(childId, context);
27905
28575
  if (!child)
@@ -28024,15 +28694,15 @@ async function openReader(runtime, childId, parentId, context, through, historyU
28024
28694
  }
28025
28695
  var ChildProjection = local("neko.subagent-projection", {
28026
28696
  version: 1,
28027
- input: Type10.Object({ parentId: Type10.Integer(), agentId: Type10.String() }),
28697
+ input: Type11.Object({ parentId: Type11.Integer(), agentId: Type11.String() }),
28028
28698
  members: {
28029
- revision: Type10.Integer(),
28699
+ revision: Type11.Integer(),
28030
28700
  reader: opaque("neko.subagent-reader@1")
28031
28701
  }
28032
28702
  });
28033
28703
  function installSubagentHost(host, runtime, context) {
28034
28704
  host.serve(ChildProjection, (input, _principal, self2) => {
28035
- const revision = self2.state("revision", Type10.Integer(), 0);
28705
+ const revision = self2.state("revision", Type11.Integer(), 0);
28036
28706
  const reader = self2.resource("reader", {
28037
28707
  version: 1,
28038
28708
  async open() {
@@ -28073,7 +28743,7 @@ function installSubagentHost(host, runtime, context) {
28073
28743
  parentId: Number(input.sessionId),
28074
28744
  agentId: input.agentId ?? `round-${input.taskId}`
28075
28745
  });
28076
- const authorizedRevision = self2.state("authorizedRevision", Type10.Integer(), 0);
28746
+ const authorizedRevision = self2.state("authorizedRevision", Type11.Integer(), 0);
28077
28747
  self2.on("authorizedView", shared.revision, async (value, ctx) => {
28078
28748
  try {
28079
28749
  target.value;
@@ -28126,7 +28796,7 @@ function installSubagentHost(host, runtime, context) {
28126
28796
  }
28127
28797
 
28128
28798
  // packages/gateway/src/session-host.ts
28129
- var OwnerInput = Type11.Object({});
28799
+ var OwnerInput = Type12.Object({});
28130
28800
  var Owner = local2("neko.attachment-owner", {
28131
28801
  version: 1,
28132
28802
  input: OwnerInput,
@@ -28134,7 +28804,7 @@ var Owner = local2("neko.attachment-owner", {
28134
28804
  });
28135
28805
  var Projection = local2("neko.projection", {
28136
28806
  version: 1,
28137
- input: Type11.Object({ id: Type11.String() }),
28807
+ input: Type12.Object({ id: Type12.String() }),
28138
28808
  members: {
28139
28809
  view: ChatViewSchema,
28140
28810
  reader: opaque2("neko.projection-reader@1")
@@ -28174,7 +28844,8 @@ async function openProjection(runtime, session, context) {
28174
28844
  "app.neko.subagents",
28175
28845
  "app.neko.jobs",
28176
28846
  "app.neko.todos",
28177
- "app.neko.questions"
28847
+ "app.neko.questions",
28848
+ "app.neko.plan"
28178
28849
  ].includes(change.record.kind) && change.conversationId === session.id || change.record.kind === "pi.usage" && change.conversationId !== undefined && childIds.has(change.conversationId))))
28179
28850
  changed?.(null);
28180
28851
  });
@@ -28202,7 +28873,7 @@ async function openProjection(runtime, session, context) {
28202
28873
  }
28203
28874
  }, "", { models: runtime.listModels(), contextEntries: view.value.entries, compaction });
28204
28875
  const jobs = (await session.jobs()).map(clipJob);
28205
- const [todos, questions] = await Promise.all([session.todos(), session.questions()]);
28876
+ const [todos, questions, plan] = await Promise.all([session.todos(), session.questions(), session.plan()]);
28206
28877
  const changes = await session.changeSummary().catch(() => {
28207
28878
  return;
28208
28879
  });
@@ -28223,6 +28894,7 @@ async function openProjection(runtime, session, context) {
28223
28894
  jobs,
28224
28895
  todos,
28225
28896
  questions,
28897
+ plan,
28226
28898
  ...changes ? { changes } : {},
28227
28899
  agents: summarizeSubagents(subagents, (await runtime.harness.snapshot(SubagentsDoc, session.id, context))?.items),
28228
28900
  subagents: subagents.map(({ usageStart: _start, usageEnd: _end, ...entry }) => ({
@@ -28435,6 +29107,21 @@ function serveSessions(host, runtime, context) {
28435
29107
  throw new ChordError4("invalid", error instanceof Error ? error.message : "Cannot answer the question");
28436
29108
  }
28437
29109
  },
29110
+ planGet: async ({ version }) => await projection.value.session.planRecord(version) ?? null,
29111
+ planRespond: async ({ version, decision }) => {
29112
+ try {
29113
+ return await projection.value.session.respondPlan(version, decision);
29114
+ } catch (error) {
29115
+ throw new ChordError4("invalid", error instanceof Error ? error.message : "Cannot decide the plan");
29116
+ }
29117
+ },
29118
+ planMode: async ({ mode }) => {
29119
+ try {
29120
+ return await projection.value.session.setPlanMode(mode);
29121
+ } catch (error) {
29122
+ throw new ChordError4("invalid", error instanceof Error ? error.message : "Cannot change plan mode");
29123
+ }
29124
+ },
28438
29125
  skills: () => runtime.listSkills(projection.value.session.id),
28439
29126
  workspaceDir: async ({ path }, ctx) => {
28440
29127
  try {
@@ -28504,7 +29191,7 @@ function serveSessions(host, runtime, context) {
28504
29191
  }
28505
29192
 
28506
29193
  // packages/gateway/src/skill-market.ts
28507
- import { createHash as createHash6, randomUUID as randomUUID4 } from "crypto";
29194
+ import { createHash as createHash6, randomUUID as randomUUID5 } from "crypto";
28508
29195
  import { mkdir as mkdir4, readdir as readdir2, rm as rm4, stat as stat3, writeFile as writeFile4 } from "fs/promises";
28509
29196
  import { homedir as homedir6 } from "os";
28510
29197
  import { dirname as dirname6, join as join11 } from "path";
@@ -29096,7 +29783,7 @@ function createSkillMarketClient(options) {
29096
29783
  const skill = await detail(name);
29097
29784
  const files = unpack(skill, await download(skill));
29098
29785
  await sweepTempDirs(skillsDir);
29099
- const temp = join11(dirname6(skillsDir), `.tmp-${randomUUID4()}`);
29786
+ const temp = join11(dirname6(skillsDir), `.tmp-${randomUUID5()}`);
29100
29787
  try {
29101
29788
  for (const [relative, bytes] of files) {
29102
29789
  const dest = join11(temp, ...relative.split("/"));
@@ -29105,7 +29792,7 @@ function createSkillMarketClient(options) {
29105
29792
  }
29106
29793
  await mkdir4(skillsDir, { recursive: true });
29107
29794
  if (exists) {
29108
- const backup = join11(dirname6(skillsDir), `${BACKUP_PREFIX}${randomUUID4()}-${name}`);
29795
+ const backup = join11(dirname6(skillsDir), `${BACKUP_PREFIX}${randomUUID5()}-${name}`);
29109
29796
  await renameWithRetry(target, backup);
29110
29797
  try {
29111
29798
  await renameWithRetry(temp, target, placeRename ? { rename: placeRename } : {});
@@ -29229,7 +29916,7 @@ async function listDrives() {
29229
29916
  // packages/gateway/src/server-services.ts
29230
29917
  var ServerState = local3("neko.server-state", {
29231
29918
  version: 1,
29232
- input: Type12.Object({}),
29919
+ input: Type13.Object({}),
29233
29920
  members: {
29234
29921
  directory: DirectoryStateSchema,
29235
29922
  workspaces: WorkspaceStateSchema,
@@ -29237,10 +29924,10 @@ var ServerState = local3("neko.server-state", {
29237
29924
  searchSettings: SearchSettingsStateSchema,
29238
29925
  contextSettings: ContextSettingsStateSchema,
29239
29926
  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())
29927
+ refreshCredentials: method2(Type13.Null(), Type13.Null()),
29928
+ refreshSearchSettings: method2(Type13.Null(), Type13.Null()),
29929
+ refreshContextSettings: method2(Type13.Null(), Type13.Null()),
29930
+ refreshDefaultModel: method2(Type13.Null(), Type13.Null())
29244
29931
  }
29245
29932
  });
29246
29933
  async function createNekoServerServices(options) {
@@ -29320,7 +30007,8 @@ async function createNekoServerServices(options) {
29320
30007
  lastMessageAt: info.lastMessageAt,
29321
30008
  model: info.model,
29322
30009
  status: info.status,
29323
- awaitingAnswer: info.awaitingAnswer
30010
+ awaitingAnswer: info.awaitingAnswer,
30011
+ awaitingPlan: info.awaitingPlan
29324
30012
  }));
29325
30013
  const readStatuses = async () => {
29326
30014
  const enabled = new Set(runtime.enabledProviders());
@@ -29519,7 +30207,7 @@ async function createNekoServerServices(options) {
29519
30207
  if (!await runtime.getConversation(toConversationId(id)))
29520
30208
  throw new ChordError5("gone", `Unknown session: ${id}`);
29521
30209
  if (owner.owner.active?.sessionId !== id) {
29522
- owner.owner.active = { sessionId: id, attachmentId: randomUUID5() };
30210
+ owner.owner.active = { sessionId: id, attachmentId: randomUUID6() };
29523
30211
  await runtime.touchConversation(toConversationId(id));
29524
30212
  }
29525
30213
  return owner.owner.active;
@@ -29853,7 +30541,7 @@ async function bindGateway(runtime, options) {
29853
30541
  listeners.push(wsListener);
29854
30542
  }
29855
30543
  const acceptLocal = (link) => chord.accept(link, {
29856
- principal: { kind: "local", connectionId: randomUUID6() },
30544
+ principal: { kind: "local", connectionId: randomUUID7() },
29857
30545
  onProtocolMismatch: ({ peer }) => options.onError?.(new Error(`Rejected a client on Chord protocol ${peer ?? "unknown"}; restart or update it.`))
29858
30546
  });
29859
30547
  try {
@@ -29955,7 +30643,7 @@ async function loadOrCreateServerId(nekoDir) {
29955
30643
  if (existing !== "")
29956
30644
  return existing;
29957
30645
  } catch {}
29958
- const created = randomUUID6();
30646
+ const created = randomUUID7();
29959
30647
  await mkdir6(nekoDir, { recursive: true, mode: 448 });
29960
30648
  await writeFile5(file, `${created}
29961
30649
  `, { mode: 384 });