@markusylisiurunen/tau 0.3.47 → 0.3.48

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 (73) hide show
  1. package/README.md +9 -8
  2. package/dist/code_mode/command.js +3 -1
  3. package/dist/code_mode/command.js.map +1 -1
  4. package/dist/code_mode/files.d.ts +47 -0
  5. package/dist/code_mode/files.js +296 -0
  6. package/dist/code_mode/files.js.map +1 -0
  7. package/dist/code_mode/index.d.ts +2 -0
  8. package/dist/code_mode/index.js +1 -0
  9. package/dist/code_mode/index.js.map +1 -1
  10. package/dist/code_mode/runtime.d.ts +4 -0
  11. package/dist/code_mode/runtime.js +58 -14
  12. package/dist/code_mode/runtime.js.map +1 -1
  13. package/dist/core/agent/agent_runtime.js +0 -10
  14. package/dist/core/agent/agent_runtime.js.map +1 -1
  15. package/dist/core/config/schema.js.map +1 -1
  16. package/dist/core/runtime/chat_runtime.js +5 -8
  17. package/dist/core/runtime/chat_runtime.js.map +1 -1
  18. package/dist/core/session/direct_bash.js +6 -1
  19. package/dist/core/session/direct_bash.js.map +1 -1
  20. package/dist/core/static/code_mode/nook/documentation.md +1 -1
  21. package/dist/core/static/code_mode/sandbox_runner.mjs +27 -22
  22. package/dist/core/telegram/config.js +34 -11
  23. package/dist/core/telegram/config.js.map +1 -1
  24. package/dist/core/telegram/session_manager.js +48 -14
  25. package/dist/core/telegram/session_manager.js.map +1 -1
  26. package/dist/core/telegram/workspace.js +24 -0
  27. package/dist/core/telegram/workspace.js.map +1 -1
  28. package/dist/core/tools/bash.js.map +1 -1
  29. package/dist/core/tools/code_mode.js +11 -0
  30. package/dist/core/tools/code_mode.js.map +1 -1
  31. package/dist/core/tools/history.js +2 -1
  32. package/dist/core/tools/history.js.map +1 -1
  33. package/dist/core/tools/nook.js +3 -2
  34. package/dist/core/tools/nook.js.map +1 -1
  35. package/dist/core/tools/presentation.js +36 -20
  36. package/dist/core/tools/presentation.js.map +1 -1
  37. package/dist/core/tools/send_input_to_agent.js +1 -1
  38. package/dist/core/tools/send_input_to_agent.js.map +1 -1
  39. package/dist/core/tools/spawn_agent.js +1 -1
  40. package/dist/core/tools/spawn_agent.js.map +1 -1
  41. package/dist/core/tools/subagent_ui.js +1 -1
  42. package/dist/core/tools/subagent_ui.js.map +1 -1
  43. package/dist/core/tools/web.js +4 -3
  44. package/dist/core/tools/web.js.map +1 -1
  45. package/dist/core/tools/write.js +6 -2
  46. package/dist/core/tools/write.js.map +1 -1
  47. package/dist/core/utils/spawn_capture.js +15 -16
  48. package/dist/core/utils/spawn_capture.js.map +1 -1
  49. package/dist/core/version.js +1 -1
  50. package/dist/host/client_tool_broker.js +9 -1
  51. package/dist/host/client_tool_broker.js.map +1 -1
  52. package/dist/host/local_session_host.js +3 -17
  53. package/dist/host/local_session_host.js.map +1 -1
  54. package/dist/nook/README.md +1 -1
  55. package/dist/protocol/session_protocol.d.ts +2 -1
  56. package/dist/protocol/session_protocol.js +2 -1
  57. package/dist/protocol/session_protocol.js.map +1 -1
  58. package/dist/sdk/client_tool_command.d.ts +24 -7
  59. package/dist/sdk/client_tool_command.js +142 -7
  60. package/dist/sdk/client_tool_command.js.map +1 -1
  61. package/dist/sdk/code_mode.js +3 -1
  62. package/dist/sdk/code_mode.js.map +1 -1
  63. package/dist/sdk/session.js +1 -0
  64. package/dist/sdk/session.js.map +1 -1
  65. package/dist/sdk/types.d.ts +1 -0
  66. package/dist/store/file_session_store.js +0 -8
  67. package/dist/store/file_session_store.js.map +1 -1
  68. package/dist/store/memory_session_store.js +0 -4
  69. package/dist/store/memory_session_store.js.map +1 -1
  70. package/dist/store/session_store.js.map +1 -1
  71. package/dist/tui/command_client_tools.js +17 -67
  72. package/dist/tui/command_client_tools.js.map +1 -1
  73. package/package.json +1 -1
package/README.md CHANGED
@@ -174,7 +174,7 @@ The runner keeps tool and lifecycle progress quiet: Telegram receives command ac
174
174
 
175
175
  Telegram DM input supports plain text, voice/audio transcription with the transcript echoed back for verification, and attachment queueing (`image/*`, PDF, `.txt`, `.md`, `.json`, `.csv`, `.yaml`, `.yml`). allowed groups are opt-in via `allowedChatIds`; non-triggering group text/captions, attachments, audio transcripts, and processing errors are buffered as sender-attributed context and the most recent 50 messages since the previous bot-triggering turn are included when a bot mention triggers a turn. group commands accept explicit bot mentions on or around the command, such as `/status@botusername`, `/status @botusername`, or `@botusername /status`.
176
176
 
177
- Telegram config defines `bots`, `projects`, `workspaceRoot`, optional `systemMessage`, and optional `maxSessions`. Projects may describe one repository or compose several repository projects under a generated multi-repo root. Repositories use persistent bare caches at `<workspaceRoot>-repo-cache/<projectId>.git`; active and failed session records and per-chat project preferences are persisted, workspaces survive runner restarts, and unreferenced workspace entries are removed on startup. Tau reconnects preserved active sessions to their corresponding snapshots and reconciles unresolved Telegram requests through the durable turn ledger. Accepted running turns remain interruptible after a lost response, while failed, blocked, or confirmed-unaccepted request notifications remain persisted until Telegram delivery succeeds. Terminal failed records retain the initiating submission diagnostic until the session is closed. When creating or reconstructing a workspace, Telegram starts an optional executable `.tau/scripts/provision` repository hook through the Tau session without blocking chat access; failures are reported while the session remains available. Preserved workspaces skip provisioning on restart. for config details, see [docs/telegram.md](docs/telegram.md).
177
+ Telegram config defines `bots`, `projects`, `workspaceRoot`, optional `systemMessage`, and optional `maxSessions`. Projects may describe one repository, reuse one existing persistent directory across sessions, or compose several repository projects under a generated multi-repo root. Persistent directories must already exist and are never created, provisioned, or removed by Tau. Repositories use persistent bare caches at `<workspaceRoot>-repo-cache/<projectId>.git`; active and failed session records and per-chat project preferences are persisted, workspaces survive runner restarts, and unreferenced managed workspace entries are removed on startup. Tau reconnects preserved active sessions to their corresponding snapshots and reconciles unresolved Telegram requests through the durable turn ledger. Accepted running turns remain interruptible after a lost response, while failed, blocked, or confirmed-unaccepted request notifications remain persisted until Telegram delivery succeeds. Terminal failed records retain the initiating submission diagnostic until the session is closed. When creating or reconstructing a repository workspace, Telegram starts an optional executable `.tau/scripts/provision` hook through the Tau session without blocking chat access; failures are reported while the session remains available. Preserved workspaces skip provisioning on restart. for config details, see [docs/telegram.md](docs/telegram.md).
178
178
 
179
179
  ## built-in tool commands
180
180
 
@@ -669,13 +669,14 @@ A project or nested directory can select a different exact set without defining
669
669
 
670
670
  The most specific `.tau/config.json` selection wins. Unknown names are skipped, and `enabledClientTools: []` disables every configured command client tool for that workspace.
671
671
 
672
- For every call, Tau validates the arguments against the configured schema, starts the command directly without a shell, and speaks a version 2 newline-delimited JSON protocol over stdin and stdout. The initial stdin frame contains the invocation:
672
+ For every call, Tau validates the arguments against the configured schema, starts the command directly without a shell, and speaks a version 3 newline-delimited JSON protocol over stdin and stdout. The initial stdin frame contains the invocation:
673
673
 
674
674
  ```json
675
675
  {
676
- "version": 2,
676
+ "version": 3,
677
677
  "type": "invoke",
678
678
  "sessionId": "session-id",
679
+ "agentId": "agent-id",
679
680
  "callId": "call-id",
680
681
  "arguments": { "message": "Build finished" }
681
682
  }
@@ -684,12 +685,12 @@ For every call, Tau validates the arguments against the configured schema, start
684
685
  Commands may send `exec` frames to run commands in the session execution environment. Tau returns the ordinary `session.exec` result in a matching `exec.result` stdin frame. The command finishes with one stdout result frame:
685
686
 
686
687
  ```json
687
- { "version": 2, "type": "result", "content": "Notification shown." }
688
+ { "version": 3, "type": "result", "content": "Notification shown." }
688
689
  ```
689
690
 
690
- Use `runTauClientToolCommand()` from `@markusylisiurunen/tau/sdk` or `@markusylisiurunen/tau/code-mode` to handle this framing and receive the same `{ sessionId, callId, signal, executionEnvironment }` context as an in-process SDK client tool. The command itself still runs on the TUI machine; only `context.executionEnvironment.exec()` runs through `session.exec` in the session execution environment.
691
+ Use `runTauClientToolCommand()` from `@markusylisiurunen/tau/sdk` or `@markusylisiurunen/tau/code-mode` to handle this framing and receive the same `{ sessionId, agentId, callId, signal, executionEnvironment }` context as an in-process SDK client tool. The command itself still runs on the TUI machine; only `context.executionEnvironment.exec()` runs through `session.exec` in the session execution environment.
691
692
 
692
- The result `content` becomes model-visible. A nonzero exit fails the tool using stderr, falling back to stdout when stderr is empty. Tau runs the command from the TUI's current working directory with the TUI process environment unchanged, limits each command-to-TUI protocol frame and total stdout and stderr to 1 MiB, allows at most eight execution requests to remain active while their responses are delivered with stdin backpressure, and terminates the command's process group on cancellation, terminal transport failure, timeout, or excess output. Closing protocol stdin aborts the command helper and its pending execution requests. Process-group termination escalates to `SIGKILL` after two seconds even if the command leader exits first. `executionTimeoutMs` defaults to 60 seconds.
693
+ The result `content` becomes model-visible. A nonzero exit fails the tool using stderr. Tau runs the command from the TUI's current working directory with the TUI process environment unchanged. Command stdout is decoded as a bounded NDJSON protocol stream with at most 512 frames and 192 MiB total per invocation; each frame is capped at 24 MiB, `exec` stdin retains the session protocol's 16 MiB decoded limit, and final result content plus stderr are each capped at 1 MiB. Tau allows at most eight execution requests to remain active while their responses are delivered with stdin backpressure, and terminates the command's process group on cancellation, terminal transport failure, timeout, or excess output. Closing protocol stdin aborts the command helper and its pending execution requests. Process-group termination escalates to `SIGKILL` after two seconds even if the command leader exits first. `executionTimeoutMs` defaults to 60 seconds.
693
694
 
694
695
  ```ts
695
696
  #!/usr/bin/env node
@@ -718,7 +719,7 @@ Tau's `@markusylisiurunen/tau/code-mode` export implements this command framing
718
719
  }
719
720
  ```
720
721
 
721
- The executable owns authenticated clients and explicitly exposes trusted handlers. Handler arguments and results cross a JSON boundary; generated code receives only the declared namespace plus `docs`, `console`, `Date`, and `Math`:
722
+ The executable owns authenticated clients and explicitly exposes trusted handlers. Handler arguments and results cross a JSON boundary; generated code receives the declared namespace, agent-scoped `files`, `docs`, `console`, `Date`, and `Math`:
722
723
 
723
724
  ```ts
724
725
  #!/usr/bin/env node
@@ -736,7 +737,7 @@ await runTauCodeModeCommand({
736
737
  });
737
738
  ```
738
739
 
739
- Trusted code-mode API handlers receive the command's execution-environment facade in their context and may intentionally expose narrower workspace operations through the declared API. Generated code does not receive an undeclared shell namespace. Tau prepends canonical runtime guidance to the executable's API documentation. The installed runtime therefore documents its actual capabilities, 60-second default timeout, 8,192-token output projection, 128-call total limit, and eight-call concurrency limit without duplicating those details in `config.json`. The optional `buildTauCodeModeToolDescription()` helper produces the recommended progressive-disclosure description, but descriptions are otherwise passed through unchanged.
740
+ Trusted code-mode API handlers receive the command's execution-environment facade in their context and may intentionally expose narrower workspace operations through the declared API. Generated code does not receive an undeclared shell namespace. Its `files.read()`, `files.write()`, `files.list()`, and `files.remove()` methods operate on UTF-8 files in an agent-scoped execution-environment temporary directory. The files are shared across code-mode tools and later calls for that agent, and returned absolute paths are usable by Bash. Names are basenames rather than paths; `files.write()` rejects changes above 128 regular files or 64 MiB total, while Bash-created over-limit contents remain visible and removable. The execution environment's temporary filesystem controls the lifetime, and no scratch metadata enters the session snapshot. Tau prepends canonical runtime guidance to the executable's API documentation. The installed runtime therefore documents its actual capabilities, 60-second default timeout, 8,192-token output projection, 128-call total limit, and eight-call concurrency limit without duplicating those details in `config.json`. The optional `buildTauCodeModeToolDescription()` helper produces the recommended progressive-disclosure description, but descriptions are otherwise passed through unchanged.
740
741
 
741
742
  Configured names must be unique and cannot replace built-in TUI or host tools. Only one observing client may advertise a given name for a session. `--no-client-tools` disables command client tools together with `diff_review` and `prefill_input`.
742
743
 
@@ -1,5 +1,5 @@
1
1
  import { runTauClientToolCommand } from "../sdk/client_tool_command.js";
2
- import { executeTauCodeMode, validateTauCodeModeDefinition, } from "./runtime.js";
2
+ import { createTauCodeModeExecutionEnvironmentFiles, executeTauCodeMode, validateTauCodeModeDefinition, } from "./runtime.js";
3
3
  export async function runTauCodeModeCommand(definition) {
4
4
  validateTauCodeModeDefinition(definition);
5
5
  await runTauClientToolCommand(async (args, context) => {
@@ -10,9 +10,11 @@ export async function runTauCodeModeCommand(definition) {
10
10
  signal: context.signal,
11
11
  invocation: {
12
12
  sessionId: context.sessionId,
13
+ agentId: context.agentId,
13
14
  callId: context.callId,
14
15
  },
15
16
  executionEnvironment: context.executionEnvironment,
17
+ files: createTauCodeModeExecutionEnvironmentFiles(context.agentId, context.executionEnvironment),
16
18
  });
17
19
  });
18
20
  }
@@ -1 +1 @@
1
- {"version":3,"file":"command.js","sourceRoot":"","sources":["../../src/code_mode/command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AACxE,OAAO,EACL,kBAAkB,EAElB,6BAA6B,GAC9B,MAAM,cAAc,CAAC;AAEtB,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,UAAiC;IAC3E,6BAA6B,CAAC,UAAU,CAAC,CAAC;IAC1C,MAAM,uBAAuB,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;QACpD,MAAM,IAAI,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;QACtC,OAAO,MAAM,kBAAkB,CAAC;YAC9B,GAAG,UAAU;YACb,IAAI;YACJ,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,UAAU,EAAE;gBACV,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,MAAM,EAAE,OAAO,CAAC,MAAM;aACvB;YACD,oBAAoB,EAAE,OAAO,CAAC,oBAAoB;SACnD,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAc;IACxC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACnE,CAAC;IACD,MAAM,IAAI,GAAG,KAAgC,CAAC;IAC9C,IACE,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ;QAC7B,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;QACjB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,MAAM,CAAC,EAC/C,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,8EAA8E,CAAC,CAAC;IAClG,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC;AACnB,CAAC"}
1
+ {"version":3,"file":"command.js","sourceRoot":"","sources":["../../src/code_mode/command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AACxE,OAAO,EACL,0CAA0C,EAC1C,kBAAkB,EAElB,6BAA6B,GAC9B,MAAM,cAAc,CAAC;AAEtB,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,UAAiC;IAC3E,6BAA6B,CAAC,UAAU,CAAC,CAAC;IAC1C,MAAM,uBAAuB,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;QACpD,MAAM,IAAI,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;QACtC,OAAO,MAAM,kBAAkB,CAAC;YAC9B,GAAG,UAAU;YACb,IAAI;YACJ,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,UAAU,EAAE;gBACV,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,MAAM,EAAE,OAAO,CAAC,MAAM;aACvB;YACD,oBAAoB,EAAE,OAAO,CAAC,oBAAoB;YAClD,KAAK,EAAE,0CAA0C,CAC/C,OAAO,CAAC,OAAO,EACf,OAAO,CAAC,oBAAoB,CAC7B;SACF,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAc;IACxC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACnE,CAAC;IACD,MAAM,IAAI,GAAG,KAAgC,CAAC;IAC9C,IACE,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ;QAC7B,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;QACjB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,MAAM,CAAC,EAC/C,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,8EAA8E,CAAC,CAAC;IAClG,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC;AACnB,CAAC"}
@@ -0,0 +1,47 @@
1
+ export declare const TAU_CODE_MODE_MAX_FILES = 128;
2
+ export declare const TAU_CODE_MODE_MAX_TOTAL_FILE_BYTES: number;
3
+ export type TauCodeModeFileAdapterResult = {
4
+ stdout: string;
5
+ stderr: string;
6
+ exitCode: number | null;
7
+ };
8
+ export type TauCodeModeFileAdapter = {
9
+ runNodeScript(script: string, options: {
10
+ input: string;
11
+ signal: AbortSignal;
12
+ maxCaptureBytes: number;
13
+ }): Promise<TauCodeModeFileAdapterResult>;
14
+ };
15
+ export type TauCodeModeFilesOptions = {
16
+ agentId: string;
17
+ adapter: TauCodeModeFileAdapter;
18
+ };
19
+ export type TauCodeModeFileMetadata = {
20
+ name: string;
21
+ path: string;
22
+ bytes: number;
23
+ };
24
+ export type TauCodeModeFileList = {
25
+ files: TauCodeModeFileMetadata[];
26
+ totalFiles: number;
27
+ totalBytes: number;
28
+ };
29
+ export declare function createTauCodeModeFilesApi(options: TauCodeModeFilesOptions): {
30
+ read: (args: unknown[], context: {
31
+ signal: AbortSignal;
32
+ }) => Promise<string>;
33
+ write: (args: unknown[], context: {
34
+ signal: AbortSignal;
35
+ }) => Promise<{
36
+ path: string;
37
+ bytes: number;
38
+ }>;
39
+ list: (args: unknown[], context: {
40
+ signal: AbortSignal;
41
+ }) => Promise<TauCodeModeFileList>;
42
+ remove: (args: unknown[], context: {
43
+ signal: AbortSignal;
44
+ }) => Promise<{
45
+ path: string;
46
+ }>;
47
+ };
@@ -0,0 +1,296 @@
1
+ import { CODE_MODE_MAX_BRIDGE_PAYLOAD_BYTES } from "../core/tools/code_mode_worker.js";
2
+ export const TAU_CODE_MODE_MAX_FILES = 128;
3
+ export const TAU_CODE_MODE_MAX_TOTAL_FILE_BYTES = 64 * 1024 * 1024;
4
+ const FILE_COMMAND_MAX_CAPTURE_BYTES = 8 * 1024 * 1024;
5
+ export function createTauCodeModeFilesApi(options) {
6
+ if (!options.agentId)
7
+ throw new Error("code-mode files agentId must not be empty");
8
+ let operationQueue = Promise.resolve();
9
+ const execute = (request, signal) => {
10
+ const operation = operationQueue.then(() => executeScratchFileRequest(options.adapter, request, signal));
11
+ operationQueue = operation.then(() => undefined, () => undefined);
12
+ return operation;
13
+ };
14
+ return {
15
+ read: async (args, context) => {
16
+ const [name] = parseArguments(args, ["string"], "files.read");
17
+ return await execute({ operation: "read", agentId: options.agentId, name }, context.signal);
18
+ },
19
+ write: async (args, context) => {
20
+ const [name, content] = parseArguments(args, ["string", "string"], "files.write");
21
+ return await execute({
22
+ operation: "write",
23
+ agentId: options.agentId,
24
+ name,
25
+ content,
26
+ }, context.signal);
27
+ },
28
+ list: async (args, context) => {
29
+ parseArguments(args, [], "files.list");
30
+ return await execute({ operation: "list", agentId: options.agentId }, context.signal);
31
+ },
32
+ remove: async (args, context) => {
33
+ const [name] = parseArguments(args, ["string"], "files.remove");
34
+ return await execute({
35
+ operation: "remove",
36
+ agentId: options.agentId,
37
+ name,
38
+ }, context.signal);
39
+ },
40
+ };
41
+ }
42
+ function parseArguments(args, types, method) {
43
+ if (args.length !== types.length || args.some((value) => typeof value !== "string")) {
44
+ const signature = types.map((type, index) => `${index === 0 ? "name" : "content"}: ${type}`);
45
+ throw new TypeError(`${method} expects (${signature.join(", ")})`);
46
+ }
47
+ return args;
48
+ }
49
+ async function executeScratchFileRequest(adapter, request, signal) {
50
+ const result = await adapter.runNodeScript(SCRATCH_FILE_SCRIPT, {
51
+ input: JSON.stringify(request),
52
+ signal,
53
+ maxCaptureBytes: FILE_COMMAND_MAX_CAPTURE_BYTES,
54
+ });
55
+ if (result.exitCode !== 0) {
56
+ throw new Error(result.stderr.trim() || "code-mode scratch file operation failed");
57
+ }
58
+ let response;
59
+ try {
60
+ response = JSON.parse(result.stdout);
61
+ }
62
+ catch {
63
+ throw new Error("code-mode scratch file operation returned invalid JSON");
64
+ }
65
+ if (!isScratchFileResponse(response)) {
66
+ throw new Error("code-mode scratch file operation returned an invalid response");
67
+ }
68
+ if (!response.ok) {
69
+ const error = new Error(response.error.message);
70
+ error.name = response.error.name;
71
+ throw error;
72
+ }
73
+ return response.value;
74
+ }
75
+ function isScratchFileResponse(value) {
76
+ if (typeof value !== "object" || value === null || !("ok" in value))
77
+ return false;
78
+ const response = value;
79
+ if (response.ok === true)
80
+ return "value" in response;
81
+ if (response.ok !== false || typeof response.error !== "object" || response.error === null) {
82
+ return false;
83
+ }
84
+ const error = response.error;
85
+ return typeof error.name === "string" && typeof error.message === "string";
86
+ }
87
+ const SCRATCH_FILE_SCRIPT = String.raw `
88
+ const {
89
+ closeSync,
90
+ constants,
91
+ fstatSync,
92
+ fsyncSync,
93
+ lstatSync,
94
+ mkdirSync,
95
+ openSync,
96
+ readFileSync,
97
+ readdirSync,
98
+ renameSync,
99
+ unlinkSync,
100
+ writeFileSync,
101
+ } = require("node:fs");
102
+ const { createHash, randomUUID } = require("node:crypto");
103
+ const { tmpdir } = require("node:os");
104
+ const { join } = require("node:path");
105
+ const { TextDecoder } = require("node:util");
106
+
107
+ const MAX_NAME_BYTES = 255;
108
+ const MAX_OPERATION_BYTES = ${CODE_MODE_MAX_BRIDGE_PAYLOAD_BYTES};
109
+ const MAX_FILES = ${TAU_CODE_MODE_MAX_FILES};
110
+ const MAX_TOTAL_BYTES = ${TAU_CODE_MODE_MAX_TOTAL_FILE_BYTES};
111
+
112
+ function fail(message) {
113
+ throw new Error(message);
114
+ }
115
+
116
+ function validateName(name) {
117
+ const bytes = Buffer.byteLength(name, "utf8");
118
+ if (
119
+ bytes === 0 ||
120
+ bytes > MAX_NAME_BYTES ||
121
+ name === "." ||
122
+ name === ".." ||
123
+ name.includes("/") ||
124
+ name.includes("\\") ||
125
+ name.includes("\0")
126
+ ) {
127
+ fail("scratch file name must be a UTF-8 basename between 1 and 255 bytes");
128
+ }
129
+ }
130
+
131
+ function ensureOwnedDirectory(path, label) {
132
+ try {
133
+ mkdirSync(path, { mode: 0o700 });
134
+ } catch (error) {
135
+ if (error?.code !== "EEXIST") throw error;
136
+ }
137
+ const stats = lstatSync(path);
138
+ if (stats.isSymbolicLink() || !stats.isDirectory()) {
139
+ fail(label + " path is not a directory");
140
+ }
141
+ if (typeof process.getuid !== "function" || stats.uid !== process.getuid()) {
142
+ fail(label + " directory is not owned by the current user");
143
+ }
144
+ if ((stats.mode & 0o077) !== 0) {
145
+ fail(label + " directory permissions must not allow group or other access");
146
+ }
147
+ return path;
148
+ }
149
+
150
+ function getRoot(agentId) {
151
+ if (typeof agentId !== "string" || agentId.length === 0) fail("agentId must not be empty");
152
+ const scope = createHash("sha256").update(agentId).digest("hex").slice(0, 32);
153
+ return ensureOwnedDirectory(join(tmpdir(), "tau-code-mode-files-" + scope), "code-mode scratch");
154
+ }
155
+
156
+ function getStagingRoot(root) {
157
+ return ensureOwnedDirectory(root + "-staging", "code-mode scratch staging");
158
+ }
159
+
160
+ function getFilePath(root, name) {
161
+ validateName(name);
162
+ return join(root, name);
163
+ }
164
+
165
+ function getExistingFile(path) {
166
+ let stats;
167
+ try {
168
+ stats = lstatSync(path);
169
+ } catch (error) {
170
+ if (error?.code === "ENOENT") return undefined;
171
+ throw error;
172
+ }
173
+ if (stats.isSymbolicLink() || !stats.isFile()) fail("scratch path is not a regular file");
174
+ return stats;
175
+ }
176
+
177
+ function listFiles(root) {
178
+ const files = [];
179
+ for (const name of readdirSync(root)) {
180
+ const path = join(root, name);
181
+ const stats = lstatSync(path);
182
+ if (stats.isSymbolicLink() || !stats.isFile()) continue;
183
+ files.push({ name, path, bytes: stats.size });
184
+ }
185
+ files.sort((left, right) => left.name.localeCompare(right.name));
186
+ return files;
187
+ }
188
+
189
+ function read(root, name) {
190
+ const path = getFilePath(root, name);
191
+ const descriptor = openSync(
192
+ path,
193
+ constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK,
194
+ );
195
+ try {
196
+ const stats = fstatSync(descriptor);
197
+ if (!stats.isFile()) fail("scratch path is not a regular file");
198
+ if (stats.size > MAX_OPERATION_BYTES) {
199
+ fail("scratch file exceeds the 1 MiB read limit");
200
+ }
201
+ const content = readFileSync(descriptor);
202
+ return new TextDecoder("utf-8", { fatal: true }).decode(content);
203
+ } finally {
204
+ closeSync(descriptor);
205
+ }
206
+ }
207
+
208
+ function write(root, name, content) {
209
+ if (typeof content !== "string") fail("scratch file content must be a string");
210
+ const bytes = Buffer.byteLength(content, "utf8");
211
+ if (bytes > MAX_OPERATION_BYTES) fail("scratch file exceeds the 1 MiB write limit");
212
+
213
+ const path = getFilePath(root, name);
214
+ const existing = getExistingFile(path);
215
+ const files = listFiles(root);
216
+ const totalBytes = files.reduce((total, file) => total + file.bytes, 0);
217
+ const resultingFiles = files.length + (existing ? 0 : 1);
218
+ const resultingBytes = totalBytes - (existing?.size ?? 0) + bytes;
219
+ if (resultingFiles > MAX_FILES) fail("scratch directory exceeds the 128-file limit");
220
+ if (resultingBytes > MAX_TOTAL_BYTES) fail("scratch directory exceeds the 64 MiB total limit");
221
+
222
+ const temporaryPath = join(getStagingRoot(root), ".tau-write-" + randomUUID());
223
+ let descriptor;
224
+ try {
225
+ descriptor = openSync(
226
+ temporaryPath,
227
+ constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW,
228
+ 0o600,
229
+ );
230
+ writeFileSync(descriptor, content, "utf8");
231
+ fsyncSync(descriptor);
232
+ closeSync(descriptor);
233
+ descriptor = undefined;
234
+ renameSync(temporaryPath, path);
235
+ } finally {
236
+ if (descriptor !== undefined) closeSync(descriptor);
237
+ try {
238
+ unlinkSync(temporaryPath);
239
+ } catch (error) {
240
+ if (error?.code !== "ENOENT") throw error;
241
+ }
242
+ }
243
+ return { path, bytes };
244
+ }
245
+
246
+ function list(root) {
247
+ const allFiles = listFiles(root);
248
+ return {
249
+ files: allFiles,
250
+ totalFiles: allFiles.length,
251
+ totalBytes: allFiles.reduce((total, file) => total + file.bytes, 0),
252
+ };
253
+ }
254
+
255
+ function remove(root, name) {
256
+ const path = getFilePath(root, name);
257
+ if (!getExistingFile(path)) fail("scratch file does not exist");
258
+ unlinkSync(path);
259
+ return { path };
260
+ }
261
+
262
+ function execute(request) {
263
+ if (typeof request !== "object" || request === null || typeof request.operation !== "string") {
264
+ fail("invalid scratch file request");
265
+ }
266
+ const root = getRoot(request.agentId);
267
+ switch (request.operation) {
268
+ case "read":
269
+ return read(root, request.name);
270
+ case "write":
271
+ return write(root, request.name, request.content);
272
+ case "list":
273
+ return list(root);
274
+ case "remove":
275
+ return remove(root, request.name);
276
+ default:
277
+ fail("unsupported scratch file operation");
278
+ }
279
+ }
280
+
281
+ try {
282
+ const request = JSON.parse(readFileSync(0, "utf8"));
283
+ process.stdout.write(JSON.stringify({ ok: true, value: execute(request) }));
284
+ } catch (error) {
285
+ process.stdout.write(
286
+ JSON.stringify({
287
+ ok: false,
288
+ error: {
289
+ name: error instanceof Error ? error.name : "Error",
290
+ message: error instanceof Error ? error.message : String(error),
291
+ },
292
+ }),
293
+ );
294
+ }
295
+ `;
296
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.js","sourceRoot":"","sources":["../../src/code_mode/files.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,6 @@
1
1
  export { runTauClientToolCommand, TAU_CLIENT_TOOL_COMMAND_PROTOCOL_VERSION, type TauClientToolCommandHandler, } from "../sdk/client_tool_command.js";
2
2
  export { runTauCodeModeCommand } from "./command.js";
3
+ export type { TauCodeModeFileAdapter, TauCodeModeFileAdapterResult, TauCodeModeFileList, TauCodeModeFileMetadata, TauCodeModeFilesOptions, } from "./files.js";
4
+ export { TAU_CODE_MODE_MAX_FILES, TAU_CODE_MODE_MAX_TOTAL_FILE_BYTES, } from "./files.js";
3
5
  export type { BuildTauCodeModeToolDescriptionOptions, ExecuteTauCodeModeOptions, TauCodeModeApi, TauCodeModeDefinition, TauCodeModeExecutionStatus, TauCodeModeHandler, TauCodeModeHandlerContext, TauCodeModeInvocation, TauCodeModeJsonValue, TauCodeModePersistOutput, TauCodeModeResult, } from "./runtime.js";
4
6
  export { buildTauCodeModeToolDescription, executeTauCodeMode, TAU_CODE_MODE_DEFAULT_TIMEOUT_MS, TAU_CODE_MODE_MAX_OUTPUT_TOKENS, } from "./runtime.js";
@@ -1,4 +1,5 @@
1
1
  export { runTauClientToolCommand, TAU_CLIENT_TOOL_COMMAND_PROTOCOL_VERSION, } from "../sdk/client_tool_command.js";
2
2
  export { runTauCodeModeCommand } from "./command.js";
3
+ export { TAU_CODE_MODE_MAX_FILES, TAU_CODE_MODE_MAX_TOTAL_FILE_BYTES, } from "./files.js";
3
4
  export { buildTauCodeModeToolDescription, executeTauCodeMode, TAU_CODE_MODE_DEFAULT_TIMEOUT_MS, TAU_CODE_MODE_MAX_OUTPUT_TOKENS, } from "./runtime.js";
4
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/code_mode/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,wCAAwC,GAEzC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAcrD,OAAO,EACL,+BAA+B,EAC/B,kBAAkB,EAClB,gCAAgC,EAChC,+BAA+B,GAChC,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/code_mode/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,wCAAwC,GAEzC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAQrD,OAAO,EACL,uBAAuB,EACvB,kCAAkC,GACnC,MAAM,YAAY,CAAC;AAcpB,OAAO,EACL,+BAA+B,EAC/B,kBAAkB,EAClB,gCAAgC,EAChC,+BAA+B,GAChC,MAAM,cAAc,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import type { TauSdkClientToolExecutionEnvironment } from "../sdk/types.js";
2
+ import { type TauCodeModeFilesOptions } from "./files.js";
2
3
  export declare const TAU_CODE_MODE_DEFAULT_TIMEOUT_MS = 60000;
3
4
  export declare const TAU_CODE_MODE_MAX_OUTPUT_TOKENS = 8192;
4
5
  export type TauCodeModeJsonValue = null | boolean | number | string | TauCodeModeJsonValue[] | {
@@ -6,6 +7,7 @@ export type TauCodeModeJsonValue = null | boolean | number | string | TauCodeMod
6
7
  };
7
8
  export type TauCodeModeInvocation = {
8
9
  sessionId: string;
10
+ agentId: string;
9
11
  callId: string;
10
12
  };
11
13
  export type TauCodeModeHandlerContext = {
@@ -59,6 +61,7 @@ export type ExecuteTauCodeModeOptions = TauCodeModeDefinition & {
59
61
  signal?: AbortSignal;
60
62
  invocation?: TauCodeModeInvocation | null;
61
63
  executionEnvironment?: TauSdkClientToolExecutionEnvironment | null;
64
+ files?: TauCodeModeFilesOptions;
62
65
  };
63
66
  export type TauCodeModeResult = {
64
67
  content: string;
@@ -67,6 +70,7 @@ export type BuildTauCodeModeToolDescriptionOptions = {
67
70
  name: string;
68
71
  description: string;
69
72
  };
73
+ export declare function createTauCodeModeExecutionEnvironmentFiles(agentId: string, executionEnvironment: TauSdkClientToolExecutionEnvironment): TauCodeModeFilesOptions;
70
74
  export type TauCodeModeRuntimeResult = {
71
75
  result: TauCodeModeResult;
72
76
  status: TauCodeModeExecutionStatus;
@@ -2,12 +2,26 @@ import stripAnsi from "strip-ansi";
2
2
  import { CODE_MODE_MAX_BRIDGE_PAYLOAD_BYTES, CODE_MODE_MAX_BRIDGE_REQUESTS, CODE_MODE_MAX_CONCURRENT_BRIDGE_REQUESTS, executeCodeModeWorker, } from "../core/tools/code_mode_worker.js";
3
3
  import { bytesToTokens } from "../core/utils/token.js";
4
4
  import { formatBytes, truncateForTokens } from "../core/utils/truncate.js";
5
+ import { createTauCodeModeFilesApi, TAU_CODE_MODE_MAX_FILES, TAU_CODE_MODE_MAX_TOTAL_FILE_BYTES, } from "./files.js";
5
6
  export const TAU_CODE_MODE_DEFAULT_TIMEOUT_MS = 60_000;
6
7
  export const TAU_CODE_MODE_MAX_OUTPUT_TOKENS = 8_192;
7
8
  const sandboxRunnerUrl = new URL("../core/static/code_mode/sandbox_runner.mjs", import.meta.url);
8
9
  const javascriptIdentifierPattern = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
9
- const reservedNames = new Set([...Object.getOwnPropertyNames(globalThis), "docs"]);
10
+ const reservedNames = new Set([...Object.getOwnPropertyNames(globalThis), "docs", "files"]);
10
11
  const unsafeApiKeys = new Set(["__proto__", "constructor", "prototype"]);
12
+ export function createTauCodeModeExecutionEnvironmentFiles(agentId, executionEnvironment) {
13
+ return {
14
+ agentId,
15
+ adapter: {
16
+ runNodeScript: async (script, options) => await executionEnvironment.exec('exec "$0" "$@"', {
17
+ args: ["node", "-e", script],
18
+ stdin: Buffer.from(options.input),
19
+ signal: options.signal,
20
+ maxCaptureBytes: options.maxCaptureBytes,
21
+ }),
22
+ },
23
+ };
24
+ }
11
25
  export function buildTauCodeModeToolDescription({ name, description, }) {
12
26
  validateName(name);
13
27
  const trimmedDescription = description.trim();
@@ -26,7 +40,7 @@ export function validateTauCodeModeDefinition(definition) {
26
40
  throw new Error("code-mode documentation must not be empty");
27
41
  }
28
42
  validateTimeout(definition.timeoutMs);
29
- registerMethods(definition.api);
43
+ registerMethods(definition.api, definition.name);
30
44
  }
31
45
  export async function executeTauCodeMode(options) {
32
46
  const runtime = await runTauCodeMode(options);
@@ -43,12 +57,20 @@ export async function runTauCodeMode(options) {
43
57
  throw new Error("code-mode documentation must not be empty");
44
58
  }
45
59
  validateTimeout(options.timeoutMs);
46
- const methods = registerMethods(options.api);
60
+ const apiMethods = registerMethods(options.api, options.name);
61
+ const filesMethods = options.files
62
+ ? registerMethods(createTauCodeModeFilesApi(options.files), "files", apiMethods.length)
63
+ : [];
64
+ const apis = [
65
+ { name: options.name, methods: apiMethods },
66
+ ...(filesMethods.length > 0 ? [{ name: "files", methods: filesMethods }] : []),
67
+ ];
68
+ const methods = apis.flatMap((api) => api.methods);
47
69
  const timeoutMs = options.timeoutMs ?? TAU_CODE_MODE_DEFAULT_TIMEOUT_MS;
48
70
  const invocation = options.invocation ?? null;
49
71
  const executionEnvironment = options.executionEnvironment ?? null;
50
72
  const signal = options.signal ?? new AbortController().signal;
51
- const docs = buildRuntimeDocumentation(options.name, options.documentation, timeoutMs);
73
+ const docs = buildRuntimeDocumentation(options.name, options.documentation, timeoutMs, filesMethods.length > 0);
52
74
  const startedAt = Date.now();
53
75
  let execution;
54
76
  try {
@@ -57,22 +79,24 @@ export async function runTauCodeMode(options) {
57
79
  workerData: {
58
80
  code: options.code,
59
81
  docs,
60
- name: options.name,
61
- methods: methods.map(({ id, path }) => ({ id, path })),
82
+ apis: apis.map((api) => ({
83
+ name: api.name,
84
+ methods: api.methods.map(({ id, path }) => ({ id, path })),
85
+ })),
62
86
  },
63
87
  signal,
64
88
  timeoutMs,
65
89
  handleRequest: async (request, requestSignal) => {
66
90
  const method = methods[request.methodId];
67
91
  if (!method)
68
- throw new Error(`unsupported ${options.name} API method`);
69
- const args = parseBridgeArguments(request.argsJson, options.name, method.path);
92
+ throw new Error("unsupported code-mode API method");
93
+ const args = parseBridgeArguments(request.argsJson, method.apiName, method.path);
70
94
  const value = await method.handler(args, {
71
95
  signal: requestSignal,
72
96
  invocation,
73
97
  executionEnvironment,
74
98
  });
75
- return serializeBridgeResult(value, options.name, method.path);
99
+ return serializeBridgeResult(value, method.apiName, method.path);
76
100
  },
77
101
  });
78
102
  }
@@ -142,7 +166,7 @@ function validateTimeout(timeoutMs) {
142
166
  throw new Error("code-mode timeoutMs must be a positive integer");
143
167
  }
144
168
  }
145
- function registerMethods(api) {
169
+ function registerMethods(api, apiName, firstId = 0) {
146
170
  const methods = [];
147
171
  const visit = (value, path) => {
148
172
  if (!isPlainObject(value)) {
@@ -158,7 +182,7 @@ function registerMethods(api) {
158
182
  throw new Error(`code-mode API key '${formatPath(childPath)}' is not allowed`);
159
183
  }
160
184
  if (typeof child === "function") {
161
- methods.push({ id: methods.length, path: childPath, handler: child });
185
+ methods.push({ id: firstId + methods.length, apiName, path: childPath, handler: child });
162
186
  }
163
187
  else {
164
188
  visit(child, childPath);
@@ -220,26 +244,46 @@ function serializeBridgeResult(value, name, path) {
220
244
  }
221
245
  return json;
222
246
  }
223
- function buildRuntimeDocumentation(name, documentation, timeoutMs) {
247
+ function buildRuntimeDocumentation(name, documentation, timeoutMs, hasFiles) {
224
248
  return [
225
249
  "# Code-mode runtime",
226
250
  "",
227
251
  "## Available globals",
228
252
  "",
229
253
  `- \`${name}\`: the explicitly exposed API documented below.`,
254
+ ...(hasFiles
255
+ ? ["- `files`: shared UTF-8 scratch files for this agent, documented below."]
256
+ : []),
230
257
  "- `docs`: this document.",
231
258
  "- `console`: program output through `debug`, `error`, `info`, `log`, and `warn`.",
232
259
  "- `Date`: standard date handling with live current-time access.",
233
260
  "- `Math`: standard math operations, including `Math.random()`.",
234
261
  "",
235
262
  "Top-level `await` is supported. The program return value is ignored; only console output is returned.",
236
- "Generated code has no direct filesystem, process, environment, network, credential, import, timer, or `fetch` access.",
263
+ hasFiles
264
+ ? "Generated code has no direct process, environment, network, credential, import, timer, or `fetch` access. Filesystem access is limited to the `files` scratch API."
265
+ : "Generated code has no direct filesystem, process, environment, network, credential, import, timer, or `fetch` access.",
237
266
  "",
238
267
  "## API boundary",
239
268
  "",
240
- `Arguments passed to \`${name}\` methods and their results cross a JSON serialization boundary with a ${formatBytes(CODE_MODE_MAX_BRIDGE_PAYLOAD_BYTES)} limit per request or response.`,
269
+ `Arguments and results cross a JSON serialization boundary with a ${formatBytes(CODE_MODE_MAX_BRIDGE_PAYLOAD_BYTES)} limit per request or response.`,
241
270
  `A program may make at most ${CODE_MODE_MAX_BRIDGE_REQUESTS} API calls, with at most ${CODE_MODE_MAX_CONCURRENT_BRIDGE_REQUESTS} unresolved calls concurrently. Exceeding any bridge limit fails the program.`,
242
271
  `The program must finish within ${formatDuration(timeoutMs)}.`,
272
+ ...(hasFiles
273
+ ? [
274
+ "",
275
+ "## Scratch files",
276
+ "",
277
+ "Scratch files are real files in an execution-environment temporary directory shared by every code-mode tool for this agent. Returned absolute paths can be used by Bash and other tools. The execution environment controls their lifetime; they are not stored in the session snapshot.",
278
+ "",
279
+ "- `await files.write(name, content)` atomically writes UTF-8 text and returns `{ path, bytes }`.",
280
+ "- `await files.read(name)` reads the current file as UTF-8 text.",
281
+ "- `await files.list()` returns `{ files, totalFiles, totalBytes }`; each file has `{ name, path, bytes }`.",
282
+ "- `await files.remove(name)` removes a file and returns `{ path }`.",
283
+ "",
284
+ `Names must be single UTF-8 basenames of at most 255 bytes. \`files.write()\` rejects changes above ${TAU_CODE_MODE_MAX_FILES} regular files or ${TAU_CODE_MODE_MAX_TOTAL_FILE_BYTES / (1024 * 1024)} MiB total; Bash-created over-limit contents remain visible and removable.`,
285
+ ]
286
+ : []),
243
287
  "",
244
288
  "## Output",
245
289
  "",