@zocomputer/agent-sdk 0.5.0

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.
Files changed (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +673 -0
  3. package/dist/attachments.js +52 -0
  4. package/dist/gateway-fetch.js +67 -0
  5. package/dist/index.js +4169 -0
  6. package/dist/initiator-auth.js +49 -0
  7. package/dist/platform/agent-sandbox/index.js +691 -0
  8. package/dist/platform/cloud-tools/image.js +498 -0
  9. package/dist/platform/cloud-tools/index.js +507 -0
  10. package/dist/platform/cloud-tools/web-search.js +87 -0
  11. package/dist/platform/runtime-ai/gateway.js +87 -0
  12. package/dist/platform/runtime-ai/index.js +91 -0
  13. package/dist/platform/runtime-ai/register.js +88 -0
  14. package/dist/platform/runtime-ai/session-fetch.js +54 -0
  15. package/dist/platform/runtime-auth/index.js +141 -0
  16. package/dist/state-files.js +287 -0
  17. package/dist/state-sandbox.js +383 -0
  18. package/dist/state.js +29 -0
  19. package/dist/steer-inbox.js +84 -0
  20. package/dist/steer.js +83 -0
  21. package/package.json +143 -0
  22. package/platform/agent-sandbox/api-client.ts +196 -0
  23. package/platform/agent-sandbox/index.ts +27 -0
  24. package/platform/agent-sandbox/pure.ts +83 -0
  25. package/platform/agent-sandbox/sftp.ts +141 -0
  26. package/platform/agent-sandbox/ssh-connection.ts +165 -0
  27. package/platform/agent-sandbox/ssh-exec.ts +98 -0
  28. package/platform/agent-sandbox/ssh-session.ts +487 -0
  29. package/platform/agent-sandbox/zo-backend.ts +88 -0
  30. package/platform/agent-sandbox/zo-sandbox.ts +39 -0
  31. package/platform/cloud-tools/image-path.ts +44 -0
  32. package/platform/cloud-tools/image.ts +225 -0
  33. package/platform/cloud-tools/index.ts +22 -0
  34. package/platform/cloud-tools/state-files.ts +368 -0
  35. package/platform/cloud-tools/tool-meta.ts +32 -0
  36. package/platform/cloud-tools/web-search.ts +15 -0
  37. package/platform/runtime-ai/gateway.ts +76 -0
  38. package/platform/runtime-ai/index.ts +20 -0
  39. package/platform/runtime-ai/register.ts +26 -0
  40. package/platform/runtime-ai/session-fetch.ts +124 -0
  41. package/platform/runtime-auth/index.ts +331 -0
  42. package/src/async-tasks.ts +273 -0
  43. package/src/attachments.ts +109 -0
  44. package/src/backgroundable.ts +88 -0
  45. package/src/bounded-output.ts +159 -0
  46. package/src/dir-conventions.ts +238 -0
  47. package/src/extract/cache.ts +40 -0
  48. package/src/extract/docx.ts +18 -0
  49. package/src/extract/pdf.ts +54 -0
  50. package/src/extract/sheet.ts +56 -0
  51. package/src/file-kind.ts +258 -0
  52. package/src/file-view.ts +80 -0
  53. package/src/gateway-fetch.ts +115 -0
  54. package/src/glob-match.ts +13 -0
  55. package/src/hooks.ts +213 -0
  56. package/src/index.ts +419 -0
  57. package/src/initiator-auth.ts +81 -0
  58. package/src/instructions.ts +224 -0
  59. package/src/list-files.ts +41 -0
  60. package/src/mock-model.ts +572 -0
  61. package/src/park-delivery.ts +247 -0
  62. package/src/path-locks.ts +52 -0
  63. package/src/read-file-content.ts +142 -0
  64. package/src/read-text.ts +40 -0
  65. package/src/redeliver.ts +155 -0
  66. package/src/run.ts +159 -0
  67. package/src/sandbox-io.ts +414 -0
  68. package/src/state-files.ts +460 -0
  69. package/src/state-sandbox.ts +710 -0
  70. package/src/state.ts +96 -0
  71. package/src/steer-inbox.ts +105 -0
  72. package/src/steer-tool.ts +83 -0
  73. package/src/steer.ts +146 -0
  74. package/src/task.ts +320 -0
  75. package/src/tools/bash.ts +143 -0
  76. package/src/tools/edit.ts +58 -0
  77. package/src/tools/glob.ts +56 -0
  78. package/src/tools/grep.ts +188 -0
  79. package/src/tools/read.ts +319 -0
  80. package/src/tools/tasks.ts +241 -0
  81. package/src/tools/webfetch.ts +368 -0
  82. package/src/tools/write.ts +42 -0
  83. package/src/walk.ts +112 -0
  84. package/src/watch-output.ts +104 -0
  85. package/src/web-fetch.ts +324 -0
  86. package/src/web-page.ts +179 -0
  87. package/src/workspace-io.ts +225 -0
  88. package/src/workspace.ts +41 -0
@@ -0,0 +1,224 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { defineDynamic, defineInstructions } from "eve/instructions";
4
+
5
+ // eve ingests no AGENTS.md at runtime, so an eve agent working in a real repo
6
+ // needs the root conventions injected (every other harness — Cursor, Claude
7
+ // Code — reads them natively). The factory returns a dynamic instruction an
8
+ // agent re-exports from `agent/instructions/<name>.ts`; it rebuilds on
9
+ // "session.started" so edits take effect on the next chat, never mid-session
10
+ // (prompt-cache safe: the prompt is stable for a session's lifetime).
11
+
12
+ /** Pure markdown for the root-AGENTS.md section; "" when the file is absent. */
13
+ export function buildRepoConventionsMarkdown(workspaceRoot: string): string {
14
+ let agents = "";
15
+ try {
16
+ agents = readFileSync(resolve(workspaceRoot, "AGENTS.md"), "utf8").trim();
17
+ } catch {
18
+ // No root AGENTS.md (e.g. the agent pointed at a non-repo dir) — inject nothing.
19
+ }
20
+ if (!agents) return "";
21
+ return `## Repository conventions (root AGENTS.md)
22
+
23
+ These repo-wide conventions always apply. Nested directories add their own \`AGENTS.md\` — read those for the code you touch.
24
+
25
+ <root-agents-md>
26
+ ${agents}
27
+ </root-agents-md>`;
28
+ }
29
+
30
+ /**
31
+ * Inject the workspace's root AGENTS.md as a system-prompt section. Nested
32
+ * per-directory AGENTS.md files stay the model's job to read — this covers
33
+ * the root conventions.
34
+ */
35
+ export function createRepoConventionsInstruction(opts: { workspaceRoot: string }) {
36
+ const { workspaceRoot } = opts;
37
+ return defineDynamic({
38
+ events: {
39
+ "session.started": () =>
40
+ defineInstructions({ markdown: buildRepoConventionsMarkdown(workspaceRoot) }),
41
+ },
42
+ });
43
+ }
44
+
45
+ /**
46
+ * The workflow guidance for the stdlib's async tools (bash auto-backgrounding,
47
+ * run_async/check_tasks/await_task). Static by design: dynamic instructions
48
+ * are system messages — part of the cached prompt prefix — so live task state
49
+ * belongs in tool results (check_tasks), never re-rendered here. See
50
+ * journal/ben/rib/2026-07-01-prompt-cache-as-economic-constraint.md.
51
+ */
52
+ export function createParallelToolsInstruction() {
53
+ const instruction = defineInstructions({
54
+ markdown: `## Parallel tool calls
55
+
56
+ Long-running work can continue in the **background** instead of blocking the turn. The \`bash\` tool does this automatically: if a command is still running after its short foreground wait, it returns a \`task_id\` and keeps the process alive. You can also use \`run_async\` when you already know the work should start in the background. After you start background work, decide whether your *next* action depends on its output:
57
+
58
+ - **Independent?** Keep working — read files, make edits, start other tasks — then \`check_tasks\` (non-blocking status + live output preview) or \`await_task\` (blocks for the result) when it's convenient.
59
+ - **Dependent?** Call \`await_task\` right away; treat it like a normal blocking call.
60
+
61
+ Guidance:
62
+ - Prefer plain \`bash\` for shell commands even when they might run long; it auto-returns a task handle if needed. Use \`run_async\` when you already know a command should start in the background and want to skip the foreground wait.
63
+ - You can have several tasks in flight at once. Each \`run_async\` returns a \`task_id\`; keep track of them.
64
+ - \`check_tasks\` shows status and live output previews for tasks that support progress. \`await_task\` returns the final output.
65
+ - For a long job where you only care about a specific signal — a failure line, a "listening on" banner — pass \`notify\` (\`{ pattern, reason }\`) to \`bash\` or \`run_async\` instead of polling: matching output is delivered to you as a message while you're idle. \`run_async\`'s \`notify_on_complete\` does the same when the task settles.
66
+ - When you do poll on wall-clock time (waiting on CI, a review, a deploy), keep any single blocking call under ~4 minutes — one sleep+check per call, not a whole retry loop in one call. Provider prompt caches expire after ~5 minutes of model inactivity, so one long silent call re-prices your entire context on the next step; returning between polls keeps it warm.
67
+ - Background task metadata and completed results persist across agent restarts. A task still running during a restart is reported as \`lost\`; start it again if its result still matters.
68
+ - Before finishing your turn, make sure any background task whose result matters has been awaited — don't end while relevant work is still running. If you're unsure what's still in flight, call \`check_tasks\`. A task you set a \`notify\` watcher on may keep running — its matches will reach you as messages.`,
69
+ });
70
+
71
+ return defineDynamic({
72
+ events: {
73
+ "session.started": () => instruction,
74
+ },
75
+ });
76
+ }
77
+
78
+ /** Pure markdown for the how-to-work contract; see createWorkflowInstruction. */
79
+ export function buildWorkflowMarkdown(opts?: {
80
+ workspaceNoun?: string;
81
+ verifyCommandHint?: string | undefined;
82
+ }): string {
83
+ const noun = opts?.workspaceNoun ?? "workspace";
84
+ const verify = opts?.verifyCommandHint
85
+ ? ` (e.g. \`${opts.verifyCommandHint}\`)`
86
+ : "";
87
+ return `## How to work
88
+
89
+ 1. **Explore before you edit.** Find the relevant code with \`glob\`/\`grep\` and \`read\` it — match the ${noun}'s existing patterns instead of guessing.
90
+ 2. **Read a file before editing it**, so your edits target the current text. Prefer \`edit\` for targeted changes; use \`write\` for new files or full rewrites.
91
+ 3. **Follow the surrounding conventions.** Match the style, structure, and idioms of the code around your change rather than imposing your own.
92
+ 4. **Verify your work.** After changing code, run the relevant checks${verify} and fix what you broke. Leave the ${noun} in a working state.
93
+ 5. **Track multi-step work** with \`todo\`, and keep it current as you finish each step.
94
+ 6. **Finish the job before ending your turn.** Reread your final message: if it promises work ("I'll…"), lays out next steps you could take now, or asks a question you could answer yourself with a tool call, do that work instead of stopping. End your turn only when the task is complete or you're blocked on something only the user can provide.`;
95
+ }
96
+
97
+ /**
98
+ * The how-to-work contract: explore→read→edit→verify, todo tracking, and the
99
+ * end-of-turn completeness check. Static markdown, session-stable
100
+ * (prompt-cache safe); the verify hint interpolates once at build time.
101
+ */
102
+ export function createWorkflowInstruction(opts?: {
103
+ workspaceNoun?: string;
104
+ verifyCommandHint?: string | undefined;
105
+ }) {
106
+ const instruction = defineInstructions({ markdown: buildWorkflowMarkdown(opts) });
107
+ return defineDynamic({
108
+ events: {
109
+ "session.started": () => instruction,
110
+ },
111
+ });
112
+ }
113
+
114
+ /** Pure markdown for the reporting contract; see createCommunicationInstruction. */
115
+ export function buildCommunicationMarkdown(): string {
116
+ return `## Communicating
117
+
118
+ - **Lead with the outcome.** The first sentence of your final message answers "what happened" — what changed, what you found, whether it worked. Supporting detail and reasoning come after, for readers who want them.
119
+ - **Readable beats brief.** Shorten by dropping detail that doesn't change what the reader does next — not by compressing prose into fragments, arrow chains, or bare jargon. Write complete sentences and name the specific thing (the actual file, function, or command), not "the relevant helper".
120
+ - **Report, don't fix, when the user is diagnosing.** If they're describing a problem or asking a question, the deliverable is your assessment: investigate and report. Apply a fix only when they ask for one.
121
+ - **Act within scope without asking.** For reversible actions that follow from the task, decide and proceed — asking "Should I…?" stalls the work. Stop to ask only for destructive or hard-to-reverse actions, or genuine scope changes the user must decide.
122
+ - **Report outcomes faithfully.** If a check fails, say so and include the output; if you skipped a step, say that; when something is done and verified, state it plainly without hedging.`;
123
+ }
124
+
125
+ /**
126
+ * The reporting contract: lead with the outcome, keep prose readable,
127
+ * assess-don't-fix when the user is diagnosing, act without permission-seeking
128
+ * inside the task's scope. Static and session-stable (prompt-cache safe).
129
+ */
130
+ export function createCommunicationInstruction() {
131
+ const instruction = defineInstructions({ markdown: buildCommunicationMarkdown() });
132
+ return defineDynamic({
133
+ events: {
134
+ "session.started": () => instruction,
135
+ },
136
+ });
137
+ }
138
+
139
+ /** Pure markdown for the ask_question playbook; see createHitlInstruction. */
140
+ export function buildHitlMarkdown(): string {
141
+ return `## Asking the user (ask_question)
142
+
143
+ Call \`ask_question\` only when you're genuinely blocked on a choice that is the user's to make — not for permission to proceed with a reasonable default you can pick yourself. When you do ask:
144
+
145
+ - **Offer \`options\` when the choices are enumerable** instead of asking open-ended; each option is \`{ id, label, description?, style? }\` and the user answers with one click.
146
+ - **Put your recommended option first** and mark it \`style: "primary"\`. Use \`style: "danger"\` for destructive or hard-to-reverse choices.
147
+ - **Use each option's \`description\`** for the trade-off the label can't carry.
148
+ - **Keep free text open** (\`allowFreeform: true\`) unless the answer must be exactly one of the options.
149
+ - **Ask independent questions together**: emit several \`ask_question\` calls in one response — they collect into a single prompt and you get all the answers at once, instead of making the user answer serial round-trips.`;
150
+ }
151
+
152
+ /**
153
+ * The ask_question playbook for eve's built-in HITL tool. The framework ships
154
+ * the tool with a one-line description and no guidance on options, styles, or
155
+ * when to ask; models under-use the structured surface without this. Static
156
+ * and session-stable (prompt-cache safe).
157
+ */
158
+ export function createHitlInstruction() {
159
+ const instruction = defineInstructions({ markdown: buildHitlMarkdown() });
160
+ return defineDynamic({
161
+ events: {
162
+ "session.started": () => instruction,
163
+ },
164
+ });
165
+ }
166
+
167
+ /** One declared subagent the delegation playbook should route work to. */
168
+ export interface SubagentRosterEntry {
169
+ /** The subagent's tool name (its `agent/subagents/<id>/` directory name). */
170
+ readonly name: string;
171
+ /** When the parent should pick it, e.g. "read-only codebase questions". */
172
+ readonly when: string;
173
+ }
174
+
175
+ /** Pure markdown for the subagent delegation playbook. */
176
+ export function buildSubagentMarkdown(
177
+ workspaceNoun = "workspace",
178
+ roster?: readonly SubagentRosterEntry[],
179
+ ): string {
180
+ const noun = workspaceNoun;
181
+ const rosterSection =
182
+ roster && roster.length > 0
183
+ ? `
184
+
185
+ ### Choosing a subagent
186
+
187
+ Beyond the clone, you have declared specialists — each is its own tool with the same \`{ message, outputSchema? }\` input:
188
+
189
+ ${roster.map((entry) => `- **\`${entry.name}\`** — ${entry.when}.`).join("\n")}
190
+
191
+ Prefer a specialist when its purpose or model tier matches the subtask; use the clone \`agent\` when none fits. A specialist that can edit shares the non-overlapping write-scope rule above; one that cannot write is safe to fan out freely.`
192
+ : "";
193
+ return `## Delegating with the agent tool
194
+
195
+ \`agent\` runs a focused subtask in a **fresh copy of yourself** — same tools and instructions, same ${noun}, but a **blank conversation**: the child sees only the \`message\` you send, none of your history. It's how you parallelize.
196
+
197
+ - **Pack the message with everything the child needs**: the exact deliverable, relevant paths, constraints, and any context it can't discover cheaply. A vague delegation wastes the whole child run.
198
+ - **Fan out independent subtasks in parallel**: emit several \`agent\` calls in one response — they run concurrently and all results return before you continue. Fan out only work that's genuinely independent.
199
+ - **Give parallel children non-overlapping write scopes** (different files or directories). They share your ${noun} and see each other's writes; overlapping edits clobber.
200
+ - **Don't delegate trivia.** A subtask that one or two direct tool calls would answer is faster done yourself; delegation pays off for self-contained work with real depth (multi-file exploration, an isolated fix + verify, a report).
201
+ - Set \`outputSchema\` when you need structured output back instead of prose.${rosterSection}`;
202
+ }
203
+
204
+ /**
205
+ * Delegation guidance for eve's built-in `agent` tool (a clone of the calling
206
+ * agent) and, when `roster` names declared subagents, the routing guidance
207
+ * between them. eve ships the tools but no playbook, and models under-use
208
+ * them or pack children with too little context without one. Static markdown,
209
+ * session-stable (prompt-cache safe), parameterized only at build time.
210
+ */
211
+ export function createSubagentInstruction(opts?: {
212
+ workspaceNoun?: string;
213
+ roster?: readonly SubagentRosterEntry[] | undefined;
214
+ }) {
215
+ const instruction = defineInstructions({
216
+ markdown: buildSubagentMarkdown(opts?.workspaceNoun, opts?.roster),
217
+ });
218
+
219
+ return defineDynamic({
220
+ events: {
221
+ "session.started": () => instruction,
222
+ },
223
+ });
224
+ }
@@ -0,0 +1,41 @@
1
+ import { spawnSync } from "node:child_process";
2
+
3
+ // Comfortably holds even a huge repo's NUL-delimited path list.
4
+ const MAX_BUFFER = 64 * 1024 * 1024;
5
+
6
+ function gitPaths(root: string, args: string[]): string[] | null {
7
+ const res = spawnSync("git", args, { cwd: root, encoding: "utf8", maxBuffer: MAX_BUFFER });
8
+ if (res.error || res.status !== 0) return null;
9
+ return res.stdout.split("\0").filter((path) => path.length > 0);
10
+ }
11
+
12
+ /**
13
+ * Candidate file list for glob/grep: one `git ls-files` spawn (tens of ms)
14
+ * with exact .gitignore semantics, instead of a hand-rolled walk that has to
15
+ * keep its own ignore list in sync (and used to read 2.6 GB of Rust build
16
+ * output per unscoped grep). Returns repo-root-relative, forward-slash paths —
17
+ * git's native output shape. `scope` (a repo-relative directory) narrows the
18
+ * listing via a git pathspec. Returns null when git can't answer (not a repo,
19
+ * git missing), so callers fall back to `walkFiles`.
20
+ */
21
+ export function listGitFiles(root: string, scope?: string): string[] | null {
22
+ const spec = scope !== undefined && scope !== "." ? ["--", scope] : [];
23
+ const files = gitPaths(root, [
24
+ "ls-files",
25
+ "--cached",
26
+ "--others",
27
+ "--exclude-standard",
28
+ "-z",
29
+ ...spec,
30
+ ]);
31
+ if (files === null) return null;
32
+ // `--cached` still lists a tracked file after an un-staged `rm`; subtract
33
+ // those so we never hand back a path that isn't on disk. If this follow-up
34
+ // fails (it shouldn't — the primary listing just proved git works here),
35
+ // return the unfiltered list rather than null: a few stale just-deleted
36
+ // paths beat throwing away a valid answer and re-walking the whole tree.
37
+ const deleted = gitPaths(root, ["ls-files", "--deleted", "-z", ...spec]);
38
+ if (deleted === null || deleted.length === 0) return files;
39
+ const gone = new Set(deleted);
40
+ return files.filter((path) => !gone.has(path));
41
+ }