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/README.md +192 -47
- package/dist/client/assets/{index-DYhp2f2g.js → index-vrkP1dHF.js} +57 -57
- package/dist/client/assets/index-wmHY1s4T.css +2 -0
- package/dist/client/index.html +2 -2
- package/dist/gateway.js +1880 -1192
- package/dist/neko.js +2558 -1137
- package/package.json +1 -1
- package/dist/client/assets/index-Cdi87SA6.css +0 -2
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
19072
|
+
defineDoc as defineDoc8,
|
|
18988
19073
|
Harness,
|
|
18989
|
-
InboxDoc as
|
|
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/
|
|
19789
|
-
import {
|
|
19790
|
-
|
|
19791
|
-
|
|
19792
|
-
|
|
19793
|
-
|
|
19794
|
-
|
|
19795
|
-
|
|
19796
|
-
|
|
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/
|
|
19799
|
-
|
|
19800
|
-
|
|
19801
|
-
|
|
19802
|
-
|
|
19803
|
-
|
|
19804
|
-
|
|
19805
|
-
|
|
19806
|
-
|
|
19807
|
-
|
|
19808
|
-
|
|
19809
|
-
|
|
19810
|
-
|
|
19811
|
-
|
|
19812
|
-
|
|
19813
|
-
|
|
19814
|
-
|
|
19815
|
-
|
|
19816
|
-
|
|
19817
|
-
|
|
19818
|
-
|
|
19819
|
-
|
|
19820
|
-
|
|
19821
|
-
|
|
19822
|
-
|
|
19823
|
-
|
|
19824
|
-
|
|
19825
|
-
|
|
19826
|
-
|
|
19827
|
-
|
|
19828
|
-
|
|
19829
|
-
|
|
19830
|
-
|
|
19831
|
-
|
|
19832
|
-
}
|
|
19833
|
-
|
|
19834
|
-
|
|
19835
|
-
|
|
19836
|
-
|
|
19837
|
-
|
|
19838
|
-
|
|
19839
|
-
|
|
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
|
-
|
|
19842
|
-
|
|
19843
|
-
|
|
19844
|
-
|
|
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
|
-
|
|
19851
|
-
|
|
19852
|
-
|
|
19853
|
-
|
|
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
|
-
|
|
19857
|
-
|
|
19858
|
-
|
|
19859
|
-
|
|
19860
|
-
|
|
19861
|
-
|
|
19862
|
-
|
|
19863
|
-
|
|
19864
|
-
|
|
19865
|
-
|
|
19866
|
-
|
|
19867
|
-
|
|
19868
|
-
|
|
19869
|
-
|
|
19870
|
-
|
|
19871
|
-
|
|
19872
|
-
|
|
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
|
-
|
|
19915
|
-
References are relative to ${dirname4(skill.filePath)}.
|
|
19916
|
-
|
|
19917
|
-
${body}
|
|
19918
|
-
</skill>`;
|
|
19919
|
-
return args ? `${block}
|
|
19989
|
+
}
|
|
19920
19990
|
|
|
19921
|
-
|
|
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
|
-
|
|
19925
|
-
|
|
19926
|
-
|
|
19927
|
-
|
|
19928
|
-
|
|
19929
|
-
|
|
19930
|
-
|
|
19931
|
-
|
|
19932
|
-
|
|
19933
|
-
|
|
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
|
-
|
|
20012
|
-
|
|
20013
|
-
|
|
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
|
-
|
|
20035
|
-
|
|
20036
|
-
|
|
20037
|
-
|
|
20038
|
-
|
|
20039
|
-
|
|
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
|
-
|
|
20042
|
-
|
|
20043
|
-
|
|
20044
|
-
|
|
20045
|
-
|
|
20046
|
-
|
|
20047
|
-
|
|
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
|
-
|
|
20054
|
-
|
|
20055
|
-
if (
|
|
20056
|
-
|
|
20057
|
-
|
|
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
|
-
|
|
20072
|
-
|
|
20073
|
-
|
|
20074
|
-
|
|
20075
|
-
|
|
20076
|
-
|
|
20077
|
-
|
|
20078
|
-
|
|
20079
|
-
|
|
20080
|
-
|
|
20081
|
-
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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
|
-
|
|
20468
|
-
|
|
20469
|
-
|
|
20470
|
-
|
|
20471
|
-
|
|
20472
|
-
|
|
20473
|
-
|
|
20474
|
-
|
|
20475
|
-
|
|
20476
|
-
|
|
20477
|
-
|
|
20478
|
-
|
|
20479
|
-
|
|
20480
|
-
|
|
20481
|
-
|
|
20482
|
-
|
|
20483
|
-
|
|
20484
|
-
|
|
20485
|
-
|
|
20486
|
-
|
|
20487
|
-
|
|
20488
|
-
|
|
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
|
-
|
|
20491
|
-
|
|
20492
|
-
|
|
20493
|
-
|
|
20494
|
-
|
|
20495
|
-
|
|
20496
|
-
|
|
20497
|
-
|
|
20498
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
|
20838
|
-
import { Type as
|
|
20839
|
-
var TodosDoc =
|
|
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 =
|
|
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 =
|
|
20854
|
-
id:
|
|
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:
|
|
20860
|
-
status:
|
|
21332
|
+
text: Type6.String({ minLength: 1, maxLength: TODO_LIMITS.textChars }),
|
|
21333
|
+
status: Type6.String({ enum: [...TODO_STATUSES] })
|
|
20861
21334
|
}, { additionalProperties: false });
|
|
20862
|
-
var
|
|
20863
|
-
action:
|
|
20864
|
-
items:
|
|
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 =
|
|
20870
|
-
var
|
|
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
|
|
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:
|
|
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 ?
|
|
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(
|
|
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:
|
|
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:
|
|
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 = () =>
|
|
21404
|
+
var createTodoExtension = () => defineExtension9({ name: "neko-todos", tools: [createTodoTool()] });
|
|
20932
21405
|
|
|
20933
21406
|
// packages/core/src/tool-approvals.ts
|
|
20934
|
-
import { randomUUID as
|
|
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
|
|
20939
|
-
defineExtension as
|
|
20940
|
-
hook as
|
|
20941
|
-
ToolTask as
|
|
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 =
|
|
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:
|
|
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
|
|
21526
|
+
return defineExtension10({
|
|
21054
21527
|
name: "neko-tool-approvals",
|
|
21055
21528
|
hooks: [
|
|
21056
|
-
|
|
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
|
-
|
|
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"),
|
|
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
|
|
21338
|
-
import { defineExtension as
|
|
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
|
|
21829
|
+
return defineExtension11({
|
|
21357
21830
|
name: "neko-web-search",
|
|
21358
21831
|
tools: [
|
|
21359
|
-
|
|
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:
|
|
21363
|
-
query:
|
|
21364
|
-
maxResults:
|
|
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
|
-
|
|
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:
|
|
21394
|
-
urls:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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 =
|
|
23007
|
-
var num =
|
|
23008
|
-
var bool =
|
|
23009
|
-
var nil =
|
|
23010
|
-
var integer =
|
|
23011
|
-
var nullableString =
|
|
23012
|
-
var phase =
|
|
23013
|
-
var level =
|
|
23014
|
-
var thinking =
|
|
23015
|
-
var model2 =
|
|
23016
|
-
var change =
|
|
23017
|
-
var usage =
|
|
23018
|
-
var image =
|
|
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 =
|
|
23028
|
-
var call =
|
|
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:
|
|
23037
|
-
diffRemoved:
|
|
23038
|
-
diffTruncated:
|
|
23580
|
+
diffAdded: Type8.Union([num, nil]),
|
|
23581
|
+
diffRemoved: Type8.Union([num, nil]),
|
|
23582
|
+
diffTruncated: Type8.Optional(bool)
|
|
23039
23583
|
});
|
|
23040
|
-
var DelegationSchema =
|
|
23584
|
+
var DelegationSchema = Type8.Object({
|
|
23041
23585
|
parentId: integer,
|
|
23042
|
-
agentId:
|
|
23043
|
-
round:
|
|
23044
|
-
runnerId:
|
|
23045
|
-
inputEntryId:
|
|
23046
|
-
answerEntryId:
|
|
23047
|
-
roundUsage:
|
|
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:
|
|
23595
|
+
role: Type8.Enum(["explore", "plan", "coder"]),
|
|
23052
23596
|
title: str,
|
|
23053
23597
|
createdAt: num,
|
|
23054
|
-
startedAt:
|
|
23055
|
-
endedAt:
|
|
23056
|
-
status:
|
|
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:
|
|
23059
|
-
historyThrough:
|
|
23060
|
-
historyUsage:
|
|
23602
|
+
approvalCount: Type8.Optional(integer),
|
|
23603
|
+
historyThrough: Type8.Optional(integer),
|
|
23604
|
+
historyUsage: Type8.Optional(usage)
|
|
23061
23605
|
});
|
|
23062
|
-
var SubagentSummarySchema =
|
|
23606
|
+
var SubagentSummarySchema = Type8.Object({
|
|
23063
23607
|
agentId: str,
|
|
23064
23608
|
parentId: integer,
|
|
23065
23609
|
childId: integer,
|
|
23066
|
-
role:
|
|
23610
|
+
role: Type8.Enum(["explore", "plan", "coder"]),
|
|
23067
23611
|
title: str,
|
|
23068
|
-
status:
|
|
23069
|
-
activeRoundId:
|
|
23612
|
+
status: Type8.Enum(["idle", "queued", "running"]),
|
|
23613
|
+
activeRoundId: Type8.Union([integer, nil]),
|
|
23070
23614
|
lastRoundId: integer,
|
|
23071
|
-
lastReport:
|
|
23615
|
+
lastReport: Type8.Optional(nullableString),
|
|
23072
23616
|
roundCount: integer,
|
|
23073
23617
|
taskTitle: str,
|
|
23074
23618
|
approvalCount: integer,
|
|
23075
23619
|
createdAt: num,
|
|
23076
|
-
historyThrough:
|
|
23077
|
-
historyUsage:
|
|
23620
|
+
historyThrough: Type8.Optional(integer),
|
|
23621
|
+
historyUsage: Type8.Optional(usage)
|
|
23078
23622
|
});
|
|
23079
|
-
var jobStatus =
|
|
23080
|
-
var JobSummarySchema =
|
|
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:
|
|
23088
|
-
exitCode:
|
|
23089
|
-
reason:
|
|
23090
|
-
timeoutSeconds:
|
|
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 =
|
|
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 =
|
|
23644
|
+
var QueuedInputSchema = Type8.Object({
|
|
23101
23645
|
id: integer,
|
|
23102
|
-
mode:
|
|
23646
|
+
mode: Type8.Enum(["steer", "followUp"]),
|
|
23103
23647
|
preview: str,
|
|
23104
23648
|
images: integer,
|
|
23105
|
-
skill:
|
|
23649
|
+
skill: Type8.Optional(Type8.Object({ name: str, args: str }))
|
|
23106
23650
|
});
|
|
23107
|
-
var WithdrawResultSchema =
|
|
23108
|
-
result:
|
|
23109
|
-
text:
|
|
23110
|
-
images:
|
|
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 =
|
|
23113
|
-
result:
|
|
23114
|
-
id:
|
|
23115
|
-
text:
|
|
23116
|
-
images:
|
|
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 =
|
|
23119
|
-
id:
|
|
23120
|
-
text:
|
|
23121
|
-
status:
|
|
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 =
|
|
23667
|
+
var TodoListSchema = Type8.Object({
|
|
23124
23668
|
revision: integer,
|
|
23125
|
-
items:
|
|
23669
|
+
items: Type8.Array(TodoItemSchema, { maxItems: TODO_LIMITS.maxItems })
|
|
23126
23670
|
});
|
|
23127
|
-
var QuestionOptionSchema =
|
|
23128
|
-
id:
|
|
23129
|
-
label:
|
|
23130
|
-
description:
|
|
23131
|
-
recommended:
|
|
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 =
|
|
23134
|
-
var QuestionSummarySchema =
|
|
23677
|
+
var questionStatus = Type8.Enum(["pending", "answered", "skipped", "cancelled"]);
|
|
23678
|
+
var QuestionSummarySchema = Type8.Object({
|
|
23135
23679
|
id: integer,
|
|
23136
23680
|
version: str,
|
|
23137
|
-
preview:
|
|
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 =
|
|
23686
|
+
var WorkspaceEntrySchema = Type8.Object({
|
|
23143
23687
|
name: str,
|
|
23144
23688
|
path: str,
|
|
23145
|
-
kind:
|
|
23146
|
-
protected:
|
|
23689
|
+
kind: Type8.Enum(["file", "directory", "symlink"]),
|
|
23690
|
+
protected: Type8.Optional(Type8.Literal(true))
|
|
23147
23691
|
}, { additionalProperties: false });
|
|
23148
|
-
var WorkspaceDirSchema =
|
|
23692
|
+
var WorkspaceDirSchema = Type8.Object({
|
|
23149
23693
|
path: str,
|
|
23150
|
-
entries:
|
|
23694
|
+
entries: Type8.Array(WorkspaceEntrySchema, { maxItems: 1000 }),
|
|
23151
23695
|
limited: bool,
|
|
23152
23696
|
incomplete: bool
|
|
23153
23697
|
});
|
|
23154
|
-
var WorkspaceFileSchema =
|
|
23698
|
+
var WorkspaceFileSchema = Type8.Object({
|
|
23155
23699
|
path: str,
|
|
23156
23700
|
size: num,
|
|
23157
23701
|
mtimeMs: num,
|
|
23158
|
-
kind:
|
|
23159
|
-
content:
|
|
23702
|
+
kind: Type8.Enum(["text", "binary", "protected"]),
|
|
23703
|
+
content: Type8.Optional(str),
|
|
23160
23704
|
truncated: bool
|
|
23161
23705
|
});
|
|
23162
|
-
var ChangeSummarySchema =
|
|
23163
|
-
var ChangedFileSchema =
|
|
23706
|
+
var ChangeSummarySchema = Type8.Object({ revision: integer, files: integer, added: integer, removed: integer });
|
|
23707
|
+
var ChangedFileSchema = Type8.Object({
|
|
23164
23708
|
path: str,
|
|
23165
|
-
outside:
|
|
23709
|
+
outside: Type8.Optional(Type8.Literal(true)),
|
|
23166
23710
|
ops: integer,
|
|
23167
23711
|
added: integer,
|
|
23168
23712
|
removed: integer,
|
|
23169
|
-
agents:
|
|
23713
|
+
agents: Type8.Array(str, { maxItems: 64 }),
|
|
23170
23714
|
lastSeq: integer
|
|
23171
23715
|
}, { additionalProperties: false });
|
|
23172
|
-
var ChangeListSchema =
|
|
23716
|
+
var ChangeListSchema = Type8.Object({
|
|
23173
23717
|
revision: integer,
|
|
23174
|
-
files:
|
|
23718
|
+
files: Type8.Array(ChangedFileSchema, { maxItems: 500 }),
|
|
23175
23719
|
truncated: bool
|
|
23176
23720
|
});
|
|
23177
|
-
var ChangeOpSchema =
|
|
23721
|
+
var ChangeOpSchema = Type8.Object({
|
|
23178
23722
|
seq: integer,
|
|
23179
23723
|
agent: str,
|
|
23180
|
-
tool:
|
|
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 =
|
|
23730
|
+
var ChangeDetailSchema = Type8.Object({
|
|
23187
23731
|
path: str,
|
|
23188
|
-
outside:
|
|
23189
|
-
ops:
|
|
23732
|
+
outside: Type8.Optional(Type8.Literal(true)),
|
|
23733
|
+
ops: Type8.Array(ChangeOpSchema, { maxItems: 50 }),
|
|
23190
23734
|
truncated: bool
|
|
23191
23735
|
});
|
|
23192
|
-
var QuestionListSchema =
|
|
23736
|
+
var QuestionListSchema = Type8.Object({
|
|
23193
23737
|
revision: integer,
|
|
23194
|
-
pending:
|
|
23738
|
+
pending: Type8.Array(QuestionSummarySchema, { maxItems: QUESTION_LIMITS.maxPending })
|
|
23195
23739
|
});
|
|
23196
|
-
var QuestionRecordSchema =
|
|
23740
|
+
var QuestionRecordSchema = Type8.Object({
|
|
23197
23741
|
id: integer,
|
|
23198
23742
|
callId: str,
|
|
23199
23743
|
version: str,
|
|
23200
|
-
question:
|
|
23744
|
+
question: Type8.String({ maxLength: QUESTION_LIMITS.questionChars }),
|
|
23201
23745
|
multiple: bool,
|
|
23202
|
-
options:
|
|
23746
|
+
options: Type8.Array(QuestionOptionSchema, { maxItems: QUESTION_LIMITS.maxOptions }),
|
|
23203
23747
|
status: questionStatus,
|
|
23204
|
-
answer:
|
|
23205
|
-
|
|
23206
|
-
optionIds:
|
|
23207
|
-
text:
|
|
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:
|
|
23756
|
+
settledAt: Type8.Union([num, nil])
|
|
23213
23757
|
});
|
|
23214
|
-
var QuestionResponseSchema =
|
|
23215
|
-
|
|
23216
|
-
kind:
|
|
23217
|
-
optionIds:
|
|
23218
|
-
text:
|
|
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
|
-
|
|
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 =
|
|
23223
|
-
|
|
23224
|
-
kind:
|
|
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:
|
|
23791
|
+
options: Type8.Array(Type8.Object({ id: str, label: str, recommended: Type8.Optional(bool) })),
|
|
23229
23792
|
status: questionStatus,
|
|
23230
|
-
optionIds:
|
|
23231
|
-
labels:
|
|
23232
|
-
text:
|
|
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
|
-
|
|
23235
|
-
kind:
|
|
23236
|
-
action:
|
|
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 =
|
|
23243
|
-
historyBefore:
|
|
23244
|
-
agents:
|
|
23245
|
-
jobs:
|
|
23246
|
-
queued:
|
|
23247
|
-
todos:
|
|
23248
|
-
questions:
|
|
23249
|
-
|
|
23250
|
-
|
|
23251
|
-
|
|
23252
|
-
|
|
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:
|
|
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:
|
|
23260
|
-
compaction:
|
|
23261
|
-
failure:
|
|
23262
|
-
active:
|
|
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:
|
|
23835
|
+
reason: Type8.Enum(["manual", "threshold", "overflow"]),
|
|
23265
23836
|
blocking: bool,
|
|
23266
23837
|
attempt: integer,
|
|
23267
|
-
retry:
|
|
23838
|
+
retry: Type8.Optional(Type8.Object({ at: num, error: str }))
|
|
23268
23839
|
})),
|
|
23269
|
-
latest:
|
|
23840
|
+
latest: Type8.Union([
|
|
23270
23841
|
nil,
|
|
23271
|
-
|
|
23842
|
+
Type8.Object({
|
|
23272
23843
|
taskId: integer,
|
|
23273
|
-
phase:
|
|
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:
|
|
23279
|
-
|
|
23280
|
-
type:
|
|
23281
|
-
order:
|
|
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:
|
|
23286
|
-
skill:
|
|
23287
|
-
defaultImagePrompt:
|
|
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
|
-
|
|
23290
|
-
type:
|
|
23291
|
-
order:
|
|
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
|
-
|
|
23301
|
-
type:
|
|
23302
|
-
order:
|
|
23871
|
+
Type8.Object({
|
|
23872
|
+
type: Type8.Literal("tool"),
|
|
23873
|
+
order: Type8.Optional(num),
|
|
23303
23874
|
key: str,
|
|
23304
|
-
images:
|
|
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:
|
|
23884
|
+
durationMs: Type8.Union([num, nil]),
|
|
23314
23885
|
note: nullableString,
|
|
23315
23886
|
diff: nullableString,
|
|
23316
|
-
diffAdded:
|
|
23317
|
-
diffRemoved:
|
|
23318
|
-
diffTruncated:
|
|
23319
|
-
interaction:
|
|
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
|
-
|
|
23322
|
-
type:
|
|
23323
|
-
order:
|
|
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:
|
|
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:
|
|
23902
|
+
streaming: Type8.Union([nil, Type8.Object({ thinking: str, text: str, toolCalls: Type8.Array(call) })])
|
|
23331
23903
|
});
|
|
23332
|
-
var SessionSummarySchema =
|
|
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:
|
|
23339
|
-
model:
|
|
23340
|
-
status:
|
|
23341
|
-
awaitingAnswer:
|
|
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 =
|
|
23345
|
-
var WorkspaceSummarySchema =
|
|
23346
|
-
var WorkspaceStateSchema =
|
|
23347
|
-
var credentialStatus =
|
|
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:
|
|
23351
|
-
enabled:
|
|
23923
|
+
source: Type8.Enum(["saved", "environment", "runtime", "keyless", "none"]),
|
|
23924
|
+
enabled: Type8.Optional(bool)
|
|
23352
23925
|
});
|
|
23353
|
-
var CredentialStateSchema =
|
|
23926
|
+
var CredentialStateSchema = Type8.Object({
|
|
23354
23927
|
revision: integer,
|
|
23355
|
-
statuses:
|
|
23928
|
+
statuses: Type8.Record(str, credentialStatus)
|
|
23356
23929
|
});
|
|
23357
|
-
var AttachmentSchema =
|
|
23358
|
-
var chatInput =
|
|
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:
|
|
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:
|
|
23939
|
+
input: Type8.Object({}, { additionalProperties: false }),
|
|
23367
23940
|
variants: {
|
|
23368
23941
|
ready: {
|
|
23369
23942
|
methods: {
|
|
23370
|
-
create: rpc(
|
|
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 =
|
|
23380
|
-
maxWidth:
|
|
23381
|
-
maxHeight:
|
|
23382
|
-
maxBytes:
|
|
23383
|
-
jpegQuality:
|
|
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 =
|
|
23386
|
-
maxRequestBytes:
|
|
23387
|
-
images:
|
|
23388
|
-
resize:
|
|
23389
|
-
maxPerMessage:
|
|
23390
|
-
maxPerRequest:
|
|
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 =
|
|
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:
|
|
23400
|
-
inputLimits:
|
|
23972
|
+
input: Type8.Array(str),
|
|
23973
|
+
inputLimits: Type8.Optional(inputLimits),
|
|
23401
23974
|
reasoning: bool,
|
|
23402
|
-
supportedThinkingLevels:
|
|
23403
|
-
thinkingLevelAdjustments:
|
|
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:
|
|
23416
|
-
variants: { ready: { methods: { list: rpc(nil,
|
|
23988
|
+
input: Type8.Object({}, { additionalProperties: false }),
|
|
23989
|
+
variants: { ready: { methods: { list: rpc(nil, Type8.Array(modelInfo)) } } }
|
|
23417
23990
|
});
|
|
23418
|
-
var imageChunk =
|
|
23991
|
+
var imageChunk = Type8.Object({ data: str, total: integer, mimeType: str });
|
|
23419
23992
|
var NekoSubagent = contract("neko.subagent", {
|
|
23420
23993
|
version: 1,
|
|
23421
|
-
input:
|
|
23994
|
+
input: Type8.Object({
|
|
23422
23995
|
sessionId: str,
|
|
23423
23996
|
attachmentId: str,
|
|
23424
|
-
taskId:
|
|
23425
|
-
agentId:
|
|
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(
|
|
23432
|
-
kind:
|
|
23433
|
-
before:
|
|
23434
|
-
token:
|
|
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
|
-
}),
|
|
23437
|
-
getImage: rpc(
|
|
23438
|
-
decideApproval: rpc(
|
|
23439
|
-
stop: rpc(
|
|
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(
|
|
24024
|
+
submit: rpc(Type8.Object({
|
|
23452
24025
|
content: str,
|
|
23453
|
-
images:
|
|
23454
|
-
requestId:
|
|
23455
|
-
whenBusy:
|
|
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(
|
|
23459
|
-
steer: rpc(
|
|
23460
|
-
questionGet: rpc(
|
|
23461
|
-
questionRespond: rpc(
|
|
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(
|
|
23464
|
-
workspaceFile: rpc(
|
|
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(
|
|
23467
|
-
skills: rpc(nil,
|
|
23468
|
-
jobOutput: rpc(
|
|
23469
|
-
jobStop: rpc(
|
|
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(
|
|
23472
|
-
importImage: rpc(str,
|
|
23473
|
-
draftImage: rpc(
|
|
23474
|
-
beginImage: rpc(
|
|
23475
|
-
appendImage: rpc(
|
|
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(
|
|
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 =
|
|
23484
|
-
var page =
|
|
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:
|
|
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:
|
|
24068
|
+
input: Type8.Object({}, { additionalProperties: false }),
|
|
23493
24069
|
variants: {
|
|
23494
24070
|
ready: {
|
|
23495
24071
|
state: { state: WorkspaceStateSchema },
|
|
23496
24072
|
methods: {
|
|
23497
|
-
add: rpc(
|
|
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 =
|
|
24079
|
+
var provider = Type8.String({ minLength: 1, maxLength: 64 });
|
|
23504
24080
|
var NekoCredentials = contract("neko.credentials", {
|
|
23505
24081
|
version: 2,
|
|
23506
|
-
input:
|
|
24082
|
+
input: Type8.Object({}, { additionalProperties: false }),
|
|
23507
24083
|
variants: {
|
|
23508
24084
|
ready: {
|
|
23509
24085
|
state: { state: CredentialStateSchema },
|
|
23510
24086
|
methods: {
|
|
23511
|
-
save: rpc(
|
|
23512
|
-
remove: rpc(
|
|
23513
|
-
setEnabled: rpc(
|
|
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 =
|
|
24094
|
+
var SearchSettingsStateSchema = Type8.Object({
|
|
23519
24095
|
revision: integer,
|
|
23520
|
-
selected:
|
|
24096
|
+
selected: Type8.Enum(["tavily", "deepseek"])
|
|
23521
24097
|
});
|
|
23522
24098
|
var NekoSearchSettings = contract("neko.search-settings", {
|
|
23523
24099
|
version: 1,
|
|
23524
|
-
input:
|
|
24100
|
+
input: Type8.Object({}, { additionalProperties: false }),
|
|
23525
24101
|
variants: {
|
|
23526
24102
|
ready: {
|
|
23527
24103
|
state: { state: SearchSettingsStateSchema },
|
|
23528
24104
|
methods: {
|
|
23529
|
-
set: rpc(
|
|
24105
|
+
set: rpc(Type8.Object({ provider: Type8.Enum(["tavily", "deepseek"]) }, { additionalProperties: false }), nil)
|
|
23530
24106
|
}
|
|
23531
24107
|
}
|
|
23532
24108
|
}
|
|
23533
24109
|
});
|
|
23534
|
-
var contextSource =
|
|
23535
|
-
var ContextSettingsStateSchema =
|
|
24110
|
+
var contextSource = Type8.Enum(["default", "config", "saved"]);
|
|
24111
|
+
var ContextSettingsStateSchema = Type8.Object({
|
|
23536
24112
|
revision: integer,
|
|
23537
|
-
effective:
|
|
23538
|
-
compaction:
|
|
24113
|
+
effective: Type8.Object({
|
|
24114
|
+
compaction: Type8.Object({
|
|
23539
24115
|
enabled: bool,
|
|
23540
|
-
triggerPercent:
|
|
24116
|
+
triggerPercent: Type8.Union([integer, nil]),
|
|
23541
24117
|
keepRecentTokens: integer
|
|
23542
24118
|
}),
|
|
23543
|
-
pruning:
|
|
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:
|
|
23552
|
-
compaction:
|
|
23553
|
-
pruning:
|
|
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) =>
|
|
23563
|
-
var ContextSettingsPatchSchema =
|
|
23564
|
-
compaction:
|
|
23565
|
-
enabled:
|
|
23566
|
-
triggerPercent:
|
|
23567
|
-
keepRecentTokens:
|
|
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:
|
|
23570
|
-
enabled:
|
|
23571
|
-
triggerPercent:
|
|
23572
|
-
keepRecent:
|
|
23573
|
-
minChars:
|
|
23574
|
-
minSavePercent:
|
|
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:
|
|
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(
|
|
24161
|
+
reset: rpc(Type8.Object({}, { additionalProperties: false }), nil)
|
|
23586
24162
|
}
|
|
23587
24163
|
}
|
|
23588
24164
|
}
|
|
23589
24165
|
});
|
|
23590
|
-
var defaultModelSource =
|
|
23591
|
-
var DefaultModelStateSchema =
|
|
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:
|
|
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 =
|
|
23609
|
-
entries:
|
|
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:
|
|
24191
|
+
input: Type8.Object({}, { additionalProperties: false }),
|
|
23616
24192
|
variants: {
|
|
23617
24193
|
ready: {
|
|
23618
24194
|
methods: {
|
|
23619
|
-
search: rpc(
|
|
24195
|
+
search: rpc(Type8.Object({ workspaceRoot: str, query: str }, { additionalProperties: false }), FileCandidatesSchema)
|
|
23620
24196
|
}
|
|
23621
24197
|
}
|
|
23622
24198
|
}
|
|
23623
24199
|
});
|
|
23624
|
-
var marketSkill =
|
|
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:
|
|
23635
|
-
activeLevel:
|
|
24210
|
+
installed: Type8.Object({ project: bool, user: bool, builtin: bool }),
|
|
24211
|
+
activeLevel: Type8.Union([level, nil])
|
|
23636
24212
|
});
|
|
23637
|
-
var preview =
|
|
23638
|
-
var scope =
|
|
23639
|
-
var installResult =
|
|
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:
|
|
24218
|
+
input: Type8.Object({}, { additionalProperties: false }),
|
|
23643
24219
|
variants: {
|
|
23644
24220
|
ready: {
|
|
23645
24221
|
methods: {
|
|
23646
|
-
list: rpc(
|
|
23647
|
-
detail: rpc(
|
|
23648
|
-
preview: rpc(
|
|
23649
|
-
install: rpc(
|
|
23650
|
-
uninstall: rpc(
|
|
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 =
|
|
23656
|
-
|
|
23657
|
-
|
|
23658
|
-
status:
|
|
23659
|
-
reason:
|
|
23660
|
-
detail:
|
|
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 =
|
|
24239
|
+
var ApplicationInfoSchema = Type8.Object({
|
|
23664
24240
|
runningVersion: str,
|
|
23665
24241
|
installedVersion: nullableString,
|
|
23666
|
-
protocolVersion:
|
|
24242
|
+
protocolVersion: Type8.Integer({ minimum: 1 }),
|
|
23667
24243
|
latestVersion: nullableString,
|
|
23668
|
-
checkedAt:
|
|
23669
|
-
checkState:
|
|
24244
|
+
checkedAt: Type8.Union([integer, nil]),
|
|
24245
|
+
checkState: Type8.Enum(["unknown", "checking", "ok", "failed", "development"]),
|
|
23670
24246
|
serverId: str,
|
|
23671
|
-
shell:
|
|
24247
|
+
shell: Type8.Optional(ShellStatusSchema)
|
|
23672
24248
|
});
|
|
23673
24249
|
var NekoApplicationInfo = contract("neko.application-info", {
|
|
23674
24250
|
version: 1,
|
|
23675
|
-
input:
|
|
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
|
|
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 =
|
|
24019
|
-
|
|
24020
|
-
|
|
24021
|
-
|
|
24022
|
-
status:
|
|
24023
|
-
address:
|
|
24024
|
-
port:
|
|
24025
|
-
bindAddress:
|
|
24026
|
-
mode:
|
|
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
|
-
|
|
24029
|
-
|
|
24604
|
+
Type9.Object({ status: Type9.Literal("error"), error: Type9.String() }),
|
|
24605
|
+
Type9.Object({ status: Type9.Literal("stopping") })
|
|
24030
24606
|
]);
|
|
24031
|
-
var adminDeviceSchema =
|
|
24032
|
-
deviceId:
|
|
24033
|
-
name:
|
|
24034
|
-
pairedAt:
|
|
24035
|
-
lastSeenAt:
|
|
24036
|
-
online:
|
|
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:
|
|
24616
|
+
input: Type9.Object({ token: Type9.String() }),
|
|
24041
24617
|
variants: {
|
|
24042
24618
|
admin: {
|
|
24043
24619
|
methods: {
|
|
24044
|
-
state: rpc(
|
|
24620
|
+
state: rpc(Type9.Null(), Type9.Object({
|
|
24045
24621
|
state: listenerStateSchema,
|
|
24046
|
-
candidates:
|
|
24047
|
-
port:
|
|
24048
|
-
certificate:
|
|
24049
|
-
|
|
24050
|
-
|
|
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:
|
|
24053
|
-
lastConnectedAddress:
|
|
24054
|
-
wsl:
|
|
24055
|
-
|
|
24056
|
-
|
|
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(
|
|
24060
|
-
address:
|
|
24061
|
-
port:
|
|
24062
|
-
}),
|
|
24063
|
-
status:
|
|
24064
|
-
address:
|
|
24065
|
-
port:
|
|
24066
|
-
bindAddress:
|
|
24067
|
-
mode:
|
|
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(
|
|
24070
|
-
createPairing: rpc(
|
|
24071
|
-
cancelPairing: rpc(
|
|
24072
|
-
revoke: rpc(
|
|
24073
|
-
regenerateCertificate: rpc(
|
|
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
|
|
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.
|
|
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
|
|
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 =
|
|
24428
|
-
apiVersions:
|
|
24429
|
-
app:
|
|
24430
|
-
version:
|
|
24431
|
-
platform:
|
|
24432
|
-
build:
|
|
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 =
|
|
24473
|
-
var str2 =
|
|
24474
|
-
var int =
|
|
24475
|
-
var nullable2 = (schema) =>
|
|
24476
|
-
var MobileContentRefSchema =
|
|
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 =
|
|
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
|
|
24494
|
-
|
|
24495
|
-
|
|
24496
|
-
|
|
24497
|
-
|
|
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
|
|
24500
|
-
|
|
24501
|
-
|
|
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:
|
|
24505
|
-
options:
|
|
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(
|
|
24508
|
-
labels: opt(
|
|
25091
|
+
optionIds: opt(Type10.Array(str2)),
|
|
25092
|
+
labels: opt(Type10.Array(str2)),
|
|
24509
25093
|
text: opt(nullable2(str2))
|
|
24510
25094
|
}),
|
|
24511
|
-
|
|
24512
|
-
kind:
|
|
24513
|
-
action:
|
|
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 =
|
|
25110
|
+
var MobileToolItemSchema = Type10.Object({
|
|
24520
25111
|
callId: str2,
|
|
24521
25112
|
name: str2,
|
|
24522
25113
|
summary: str2,
|
|
24523
|
-
status:
|
|
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(
|
|
24529
|
-
diffRemoved: opt(
|
|
24530
|
-
diffTruncated: opt(
|
|
24531
|
-
durationMs: opt(
|
|
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 =
|
|
25126
|
+
var MobileChatItemSchema = Type10.Object({
|
|
24536
25127
|
key: str2,
|
|
24537
|
-
type:
|
|
24538
|
-
|
|
24539
|
-
|
|
24540
|
-
|
|
24541
|
-
|
|
24542
|
-
|
|
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(
|
|
24552
|
-
refs: opt(
|
|
25142
|
+
images: opt(Type10.Array(MobileImageRefSchema)),
|
|
25143
|
+
refs: opt(Type10.Array(MobileContentRefSchema))
|
|
24553
25144
|
});
|
|
24554
|
-
var MobileStreamingSchema =
|
|
25145
|
+
var MobileStreamingSchema = Type10.Object({
|
|
24555
25146
|
text: str2,
|
|
24556
25147
|
thinking: str2,
|
|
24557
25148
|
textOmitted: int,
|
|
24558
25149
|
thinkingOmitted: int,
|
|
24559
|
-
toolCalls:
|
|
25150
|
+
toolCalls: Type10.Array(Type10.Object({
|
|
24560
25151
|
callId: str2,
|
|
24561
25152
|
name: str2,
|
|
24562
25153
|
summary: str2,
|
|
24563
|
-
status:
|
|
25154
|
+
status: Type10.Union([Type10.Literal("running"), Type10.Literal("done")])
|
|
24564
25155
|
}))
|
|
24565
25156
|
});
|
|
24566
|
-
var MobileUsageSchema =
|
|
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:
|
|
25163
|
+
cost: Type10.Number({ minimum: 0 })
|
|
24573
25164
|
});
|
|
24574
|
-
var MobileSubagentRoundSchema =
|
|
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:
|
|
24582
|
-
|
|
24583
|
-
|
|
24584
|
-
|
|
24585
|
-
|
|
24586
|
-
|
|
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:
|
|
24590
|
-
createdAt:
|
|
24591
|
-
endedAt: nullable2(
|
|
25180
|
+
readonly: Type10.Boolean(),
|
|
25181
|
+
createdAt: Type10.Number(),
|
|
25182
|
+
endedAt: nullable2(Type10.Number()),
|
|
24592
25183
|
usage: opt(MobileUsageSchema)
|
|
24593
25184
|
});
|
|
24594
|
-
var MobileAgentSchema =
|
|
25185
|
+
var MobileAgentSchema = Type10.Object({
|
|
24595
25186
|
agentId: str2,
|
|
24596
25187
|
role: str2,
|
|
24597
25188
|
title: str2,
|
|
24598
|
-
status:
|
|
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 =
|
|
25195
|
+
var MobileQueuedSchema = Type10.Object({
|
|
24605
25196
|
id: int,
|
|
24606
|
-
mode:
|
|
24607
|
-
preview:
|
|
25197
|
+
mode: Type10.Union([Type10.Literal("steer"), Type10.Literal("followUp")]),
|
|
25198
|
+
preview: Type10.String(),
|
|
24608
25199
|
images: int
|
|
24609
25200
|
});
|
|
24610
|
-
var MobileChatStatusSchema =
|
|
24611
|
-
busy:
|
|
24612
|
-
queued: opt(
|
|
24613
|
-
todos: opt(
|
|
24614
|
-
questions: opt(
|
|
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(
|
|
24619
|
-
agent:
|
|
24620
|
-
subagents:
|
|
24621
|
-
agents:
|
|
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(
|
|
24624
|
-
readonly: opt(
|
|
25215
|
+
compaction: opt(Type10.Object({ phase: str2, at: Type10.Number() })),
|
|
25216
|
+
readonly: opt(Type10.Boolean())
|
|
24625
25217
|
});
|
|
24626
|
-
var MobileChatSnapshotSchema =
|
|
25218
|
+
var MobileChatSnapshotSchema = Type10.Object({
|
|
24627
25219
|
...MobileChatStatusSchema.properties,
|
|
24628
|
-
items:
|
|
25220
|
+
items: Type10.Array(MobileChatItemSchema),
|
|
24629
25221
|
historyBefore: nullable2(str2),
|
|
24630
25222
|
streaming: nullable2(MobileStreamingSchema)
|
|
24631
25223
|
});
|
|
24632
|
-
var MobileWorkspaceSchema =
|
|
24633
|
-
var MobileSessionSummarySchema =
|
|
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:
|
|
24639
|
-
lastMessageAt:
|
|
24640
|
-
archived:
|
|
24641
|
-
awaitingAnswer: opt(
|
|
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 =
|
|
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(
|
|
24652
|
-
requestedAt:
|
|
25244
|
+
timeoutSeconds: opt(Type10.Number({ minimum: 0 })),
|
|
25245
|
+
requestedAt: Type10.Number()
|
|
24653
25246
|
});
|
|
24654
|
-
var MobileTodoItemSchema =
|
|
24655
|
-
id:
|
|
24656
|
-
text:
|
|
24657
|
-
status:
|
|
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 =
|
|
25252
|
+
var MobileTodoListSchema = Type10.Object({
|
|
24660
25253
|
revision: int,
|
|
24661
|
-
items:
|
|
25254
|
+
items: Type10.Array(MobileTodoItemSchema, { maxItems: TODO_LIMITS.maxItems })
|
|
24662
25255
|
});
|
|
24663
|
-
var MobileQuestionOptionSchema =
|
|
24664
|
-
id:
|
|
24665
|
-
label:
|
|
24666
|
-
description: opt(
|
|
24667
|
-
recommended: opt(
|
|
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 =
|
|
25262
|
+
var MobileQuestionSummarySchema = Type10.Object({
|
|
24670
25263
|
id: int,
|
|
24671
25264
|
version: str2,
|
|
24672
|
-
preview:
|
|
24673
|
-
multiple:
|
|
25265
|
+
preview: Type10.String({ maxLength: QUESTION_LIMITS.previewChars }),
|
|
25266
|
+
multiple: Type10.Boolean(),
|
|
24674
25267
|
optionCount: int,
|
|
24675
|
-
createdAt:
|
|
25268
|
+
createdAt: Type10.Number()
|
|
24676
25269
|
});
|
|
24677
|
-
var MobileQuestionListSchema =
|
|
25270
|
+
var MobileQuestionListSchema = Type10.Object({
|
|
24678
25271
|
revision: int,
|
|
24679
|
-
pending:
|
|
25272
|
+
pending: Type10.Array(MobileQuestionSummarySchema, { maxItems: QUESTION_LIMITS.maxPending })
|
|
24680
25273
|
});
|
|
24681
|
-
var MobileQuestionSchema =
|
|
25274
|
+
var MobileQuestionSchema = Type10.Object({
|
|
24682
25275
|
id: int,
|
|
24683
25276
|
callId: str2,
|
|
24684
25277
|
version: str2,
|
|
24685
|
-
question:
|
|
24686
|
-
multiple:
|
|
24687
|
-
options:
|
|
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(
|
|
24690
|
-
optionIds:
|
|
24691
|
-
text: nullable2(
|
|
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:
|
|
24694
|
-
settledAt: nullable2(
|
|
25286
|
+
createdAt: Type10.Number(),
|
|
25287
|
+
settledAt: nullable2(Type10.Number())
|
|
24695
25288
|
});
|
|
24696
|
-
var MobileQuestionResponseSchema =
|
|
24697
|
-
|
|
24698
|
-
kind:
|
|
24699
|
-
optionIds: opt(
|
|
24700
|
-
text: opt(
|
|
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
|
-
|
|
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 =
|
|
25311
|
+
var sessionListParams = Type10.Object({
|
|
24705
25312
|
workspace: str2,
|
|
24706
25313
|
query: opt(str2),
|
|
24707
25314
|
cursor: opt(str2)
|
|
24708
25315
|
});
|
|
24709
|
-
var sessionListResult =
|
|
24710
|
-
sessions:
|
|
25316
|
+
var sessionListResult = Type10.Object({
|
|
25317
|
+
sessions: Type10.Array(MobileSessionSummarySchema),
|
|
24711
25318
|
nextCursor: opt(str2)
|
|
24712
25319
|
});
|
|
24713
|
-
var subscription = { subscriptionId:
|
|
24714
|
-
var pageResult =
|
|
24715
|
-
var pageParams =
|
|
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:
|
|
24718
|
-
limit: opt(
|
|
25324
|
+
before: Type10.String({ minLength: 1 }),
|
|
25325
|
+
limit: opt(Type10.Integer({ minimum: 1, maximum: 50 }))
|
|
24719
25326
|
});
|
|
24720
|
-
var chunkResult =
|
|
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:
|
|
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:
|
|
25332
|
+
"sessions.create": { params: Type10.Object({ workspace: str2 }), result: MobileSessionSummarySchema },
|
|
24726
25333
|
"chat.attach": {
|
|
24727
|
-
params:
|
|
24728
|
-
result:
|
|
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:
|
|
25337
|
+
"chat.detach": { params: Type10.Object(subscription), result: Type10.Null() },
|
|
24731
25338
|
"chat.history": { params: pageParams, result: pageResult },
|
|
24732
25339
|
"chat.content": {
|
|
24733
|
-
params:
|
|
25340
|
+
params: Type10.Object({ ref: MobileContentRefSchema, offset: int }),
|
|
24734
25341
|
result: chunkResult
|
|
24735
25342
|
},
|
|
24736
25343
|
"chat.submit": {
|
|
24737
|
-
params:
|
|
25344
|
+
params: Type10.Object({
|
|
24738
25345
|
...subscription,
|
|
24739
|
-
requestId:
|
|
24740
|
-
text:
|
|
24741
|
-
mode: opt(
|
|
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:
|
|
25350
|
+
result: Type10.Object({ accepted: Type10.Literal(true), queued: Type10.Boolean() })
|
|
24744
25351
|
},
|
|
24745
25352
|
"chat.submissionStatus": {
|
|
24746
|
-
params:
|
|
24747
|
-
result:
|
|
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:
|
|
24751
|
-
result:
|
|
24752
|
-
result:
|
|
24753
|
-
|
|
24754
|
-
|
|
24755
|
-
|
|
24756
|
-
|
|
24757
|
-
|
|
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(
|
|
25367
|
+
text: opt(Type10.String()),
|
|
24761
25368
|
images: opt(int)
|
|
24762
25369
|
})
|
|
24763
25370
|
},
|
|
24764
25371
|
"chat.withdraw": {
|
|
24765
|
-
params:
|
|
24766
|
-
result:
|
|
24767
|
-
result:
|
|
24768
|
-
|
|
24769
|
-
|
|
24770
|
-
|
|
24771
|
-
|
|
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(
|
|
25380
|
+
text: opt(Type10.String()),
|
|
24774
25381
|
images: opt(int)
|
|
24775
25382
|
})
|
|
24776
25383
|
},
|
|
24777
|
-
"chat.abort": { params:
|
|
25384
|
+
"chat.abort": { params: Type10.Object(subscription), result: Type10.Object({ accepted: Type10.Boolean() }) },
|
|
24778
25385
|
"subagents.open": {
|
|
24779
|
-
params:
|
|
24780
|
-
result:
|
|
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:
|
|
24788
|
-
subagentSubscriptionId:
|
|
24789
|
-
before:
|
|
24790
|
-
limit: opt(
|
|
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:
|
|
24796
|
-
result:
|
|
25402
|
+
params: Type10.Object({ subagentSubscriptionId: Type10.String({ minLength: 1 }) }),
|
|
25403
|
+
result: Type10.Null()
|
|
24797
25404
|
},
|
|
24798
25405
|
"subagents.stop": {
|
|
24799
|
-
params:
|
|
24800
|
-
result:
|
|
25406
|
+
params: Type10.Object({ ...subscription, taskId: int }),
|
|
25407
|
+
result: Type10.Object({ stopped: Type10.Boolean() })
|
|
24801
25408
|
},
|
|
24802
25409
|
"approvals.list": {
|
|
24803
|
-
params:
|
|
24804
|
-
result:
|
|
25410
|
+
params: Type10.Object(subscription),
|
|
25411
|
+
result: Type10.Object({ approvals: Type10.Array(MobileApprovalSchema) })
|
|
24805
25412
|
},
|
|
24806
|
-
"approvals.get": { params:
|
|
25413
|
+
"approvals.get": { params: Type10.Object({ ...subscription, id: int }), result: MobileApprovalSchema },
|
|
24807
25414
|
"approvals.decide": {
|
|
24808
|
-
params:
|
|
25415
|
+
params: Type10.Object({
|
|
24809
25416
|
...subscription,
|
|
24810
25417
|
taskId: int,
|
|
24811
25418
|
id: int,
|
|
24812
|
-
version:
|
|
24813
|
-
decision:
|
|
25419
|
+
version: Type10.String({ minLength: 1 }),
|
|
25420
|
+
decision: Type10.Union([Type10.Literal("allow"), Type10.Literal("deny")])
|
|
24814
25421
|
}),
|
|
24815
|
-
result:
|
|
24816
|
-
status:
|
|
24817
|
-
|
|
24818
|
-
|
|
24819
|
-
|
|
24820
|
-
|
|
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:
|
|
24825
|
-
"questions.list": { params:
|
|
24826
|
-
"questions.get": { params:
|
|
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:
|
|
25435
|
+
params: Type10.Object({
|
|
24829
25436
|
...subscription,
|
|
24830
25437
|
id: int,
|
|
24831
|
-
version:
|
|
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:
|
|
25460
|
+
params: Type10.Object({
|
|
24838
25461
|
...subscription,
|
|
24839
|
-
agentId: opt(
|
|
25462
|
+
agentId: opt(Type10.String({ minLength: 1 })),
|
|
24840
25463
|
entryId: int,
|
|
24841
25464
|
contentIndex: int,
|
|
24842
25465
|
offset: int
|
|
24843
25466
|
}),
|
|
24844
|
-
result:
|
|
25467
|
+
result: Type10.Object({ ...chunkResult.properties, mimeType: str2 })
|
|
24845
25468
|
}
|
|
24846
25469
|
};
|
|
24847
25470
|
function buildHelloResultSchema() {
|
|
24848
|
-
return
|
|
24849
|
-
apiVersion:
|
|
25471
|
+
return Type10.Object({
|
|
25472
|
+
apiVersion: Type10.Integer({ minimum: 1 }),
|
|
24850
25473
|
serverId: str2,
|
|
24851
25474
|
serverName: str2,
|
|
24852
25475
|
gatewayVersion: str2,
|
|
24853
|
-
capabilities:
|
|
24854
|
-
limits:
|
|
24855
|
-
maxFrameBytes:
|
|
24856
|
-
maxPayloadBytes:
|
|
24857
|
-
pageItems:
|
|
24858
|
-
chunkBytes:
|
|
24859
|
-
maxConcurrentRequests:
|
|
24860
|
-
maxSendBufferBytes:
|
|
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:
|
|
24866
|
-
upserts:
|
|
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(
|
|
25492
|
+
streamingAppend: opt(Type10.Object({ text: opt(str2), thinking: opt(str2) }))
|
|
24870
25493
|
};
|
|
24871
25494
|
var MOBILE_API_V1_NOTIFICATIONS = {
|
|
24872
|
-
"workspaces.changed":
|
|
24873
|
-
"sessions.changed":
|
|
24874
|
-
"chat.patch":
|
|
24875
|
-
"chat.reset":
|
|
24876
|
-
"subagents.patch":
|
|
24877
|
-
"subagents.reset":
|
|
24878
|
-
"approvals.changed":
|
|
24879
|
-
"todos.changed":
|
|
24880
|
-
"questions.changed":
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
28697
|
+
input: Type11.Object({ parentId: Type11.Integer(), agentId: Type11.String() }),
|
|
28028
28698
|
members: {
|
|
28029
|
-
revision:
|
|
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",
|
|
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",
|
|
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 =
|
|
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:
|
|
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
|
|
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-${
|
|
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}${
|
|
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:
|
|
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(
|
|
29241
|
-
refreshSearchSettings: method2(
|
|
29242
|
-
refreshContextSettings: method2(
|
|
29243
|
-
refreshDefaultModel: method2(
|
|
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:
|
|
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:
|
|
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 =
|
|
30646
|
+
const created = randomUUID7();
|
|
29959
30647
|
await mkdir6(nekoDir, { recursive: true, mode: 448 });
|
|
29960
30648
|
await writeFile5(file, `${created}
|
|
29961
30649
|
`, { mode: 384 });
|