tau-all-agent 0.1.2 → 0.1.4

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 (37) hide show
  1. package/README.md +23 -22
  2. package/extensions/answer.ts +4 -4
  3. package/extensions/branch-term.ts +3 -3
  4. package/extensions/btw.ts +4 -4
  5. package/extensions/ghostty.ts +1 -1
  6. package/extensions/git-diff-stats.ts +1 -1
  7. package/extensions/git-pr-status.ts +1 -1
  8. package/extensions/insights.ts +3 -3
  9. package/extensions/interlude.ts +1 -1
  10. package/extensions/loop.ts +3 -3
  11. package/extensions/memory.ts +8 -2
  12. package/extensions/notify.ts +1 -1
  13. package/extensions/openai-fast.ts +1 -1
  14. package/extensions/openai-verbosity.ts +1 -1
  15. package/extensions/review.ts +111 -38
  16. package/extensions/sandbox/index.ts +84 -8
  17. package/extensions/telegram/daemon.mjs +148 -7
  18. package/extensions/telegram/index.ts +1 -1
  19. package/extensions/tool-display-mode.ts +544 -0
  20. package/extensions/usage/anthropic.ts +1 -1
  21. package/extensions/usage/github-copilot.ts +1 -1
  22. package/extensions/usage/index.ts +3 -3
  23. package/extensions/usage/minimax.ts +1 -1
  24. package/extensions/usage/openai-codex.ts +1 -1
  25. package/extensions/usage/openrouter.ts +1 -1
  26. package/extensions/usage/providers.ts +0 -2
  27. package/extensions/usage/types.ts +1 -2
  28. package/extensions/usage/zai.ts +1 -1
  29. package/extensions/websearch/index.ts +3 -3
  30. package/extensions/websearch/providers/anthropic.pi.ts +1 -1
  31. package/extensions/websearch/providers/gemini.pi.ts +1 -1
  32. package/extensions/websearch/providers/openai-codex.pi.ts +1 -1
  33. package/extensions/websearch/providers/pi-model.shared.ts +2 -2
  34. package/extensions/worktree.ts +3 -3
  35. package/package.json +23 -8
  36. package/skills/oracle/scripts/oracle +0 -1
  37. package/extensions/usage/google-gemini-cli.ts +0 -232
@@ -25,8 +25,9 @@
25
25
  * },
26
26
  * "filesystem": {
27
27
  * "denyRead": ["~/.ssh", "~/.aws"],
28
- * "allowWrite": [".", "/tmp"],
28
+ * "allowWrite": ["."],
29
29
  * "denyWrite": [".env"],
30
+ * "allowTempDirs": true,
30
31
  * "allowGitCommonDir": true
31
32
  * }
32
33
  * }
@@ -47,7 +48,7 @@
47
48
 
48
49
  import { spawn, spawnSync } from "node:child_process";
49
50
  import { existsSync, readFileSync } from "node:fs";
50
- import { homedir } from "node:os";
51
+ import { homedir, tmpdir } from "node:os";
51
52
  import { join, resolve } from "node:path";
52
53
  import {
53
54
  SandboxManager,
@@ -62,8 +63,8 @@ import {
62
63
  globToRegex,
63
64
  normalizePathForSandbox,
64
65
  } from "@anthropic-ai/sandbox-runtime/dist/sandbox/sandbox-utils.js";
65
- import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
66
- import { type BashOperations, createBashTool } from "@mariozechner/pi-coding-agent";
66
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
67
+ import { type BashOperations, createBashTool } from "@earendil-works/pi-coding-agent";
67
68
 
68
69
  // --- Constants ---
69
70
 
@@ -74,24 +75,69 @@ const DEFAULT_CONFIG: SandboxConfig = {
74
75
  mode: DEFAULT_PROMPT_MODE,
75
76
  network: {
76
77
  allowedDomains: [
78
+ "localhost",
79
+ "127.0.0.1",
77
80
  "npmjs.org",
78
81
  "*.npmjs.org",
79
82
  "registry.npmjs.org",
80
83
  "registry.yarnpkg.com",
81
84
  "pypi.org",
82
85
  "*.pypi.org",
86
+ "pythonhosted.org",
87
+ "*.pythonhosted.org",
88
+ "crates.io",
89
+ "*.crates.io",
90
+ "proxy.golang.org",
91
+ "sum.golang.org",
92
+ "go.dev",
93
+ "golang.org",
94
+ "rubygems.org",
95
+ "*.rubygems.org",
83
96
  "github.com",
84
97
  "*.github.com",
98
+ "githubusercontent.com",
99
+ "*.githubusercontent.com",
85
100
  "api.github.com",
86
101
  "raw.githubusercontent.com",
102
+ "ghcr.io",
103
+ "docker.io",
104
+ "*.docker.io",
105
+ "production.cloudflare.docker.com",
106
+ "sentry.io",
107
+ "*.sentry.io",
108
+ "datadoghq.com",
109
+ "*.datadoghq.com",
110
+ "datadoghq.eu",
111
+ "*.datadoghq.eu",
112
+ "anthropic.com",
113
+ "*.anthropic.com",
114
+ "claude.ai",
115
+ "*.claude.ai",
116
+ "openai.com",
117
+ "*.openai.com",
118
+ "chatgpt.com",
119
+ "*.chatgpt.com",
120
+ "google.com",
121
+ "*.google.com",
122
+ "googleapis.com",
123
+ "*.googleapis.com",
124
+ "todoist.com",
125
+ "*.todoist.com",
126
+ "twist.com",
127
+ "*.twist.com",
128
+ "doist.com",
129
+ "*.doist.com",
87
130
  ],
88
131
  deniedDomains: [],
132
+ allowLocalBinding: true,
89
133
  },
90
134
  filesystem: {
91
135
  denyRead: ["~/.ssh", "~/.aws", "~/.gnupg"],
92
- allowWrite: [".", "/tmp"],
136
+ allowWrite: [".", "~/.cache", "~/.npm"],
93
137
  denyWrite: [".env", ".env.*", "*.pem", "*.key"],
94
- allowGitCommonDir: false,
138
+ allowTempDirs: true,
139
+ allowGitConfig: true,
140
+ allowGitCommonDir: true,
95
141
  },
96
142
  };
97
143
 
@@ -120,6 +166,7 @@ type SandboxConfig = Omit<SandboxRuntimeConfig, "filesystem"> & {
120
166
  enabled?: boolean;
121
167
  mode?: PromptMode;
122
168
  filesystem: SandboxRuntimeConfig["filesystem"] & {
169
+ allowTempDirs?: boolean;
123
170
  allowGitCommonDir?: boolean;
124
171
  };
125
172
  };
@@ -582,6 +629,10 @@ function finalizeConfig(config: SandboxConfig): SandboxConfig {
582
629
  DEFAULT_CONFIG.filesystem.denyWrite,
583
630
  "filesystem.denyWrite",
584
631
  ),
632
+ allowTempDirs:
633
+ typeof config.filesystem?.allowTempDirs === "boolean"
634
+ ? config.filesystem.allowTempDirs
635
+ : DEFAULT_CONFIG.filesystem.allowTempDirs,
585
636
  allowGitCommonDir:
586
637
  typeof config.filesystem?.allowGitCommonDir === "boolean"
587
638
  ? config.filesystem.allowGitCommonDir
@@ -705,12 +756,32 @@ function deepMerge(base: SandboxConfig, overrides: Partial<SandboxConfig>): Sand
705
756
  return result;
706
757
  }
707
758
 
759
+ let cachedTemporaryWritePaths: string[] | undefined;
760
+
761
+ function getTemporaryWritePaths(): string[] {
762
+ if (cachedTemporaryWritePaths) return cachedTemporaryWritePaths;
763
+
764
+ // allowTempDirs always includes the conventional shared temp path, even when
765
+ // os.tmpdir() points at a platform-specific per-user directory.
766
+ const currentTmpDir = tmpdir();
767
+ const paths = ["/tmp", currentTmpDir, normalizePathForSandbox(currentTmpDir)];
768
+ if (process.platform === "darwin") paths.push("/private/tmp");
769
+
770
+ cachedTemporaryWritePaths = Array.from(
771
+ new Set(paths.map((path) => path.replace(/\/+$/, "") || "/")),
772
+ );
773
+ return cachedTemporaryWritePaths;
774
+ }
775
+
708
776
  function toRuntimeConfig(config: SandboxConfig): SandboxRuntimeConfig {
709
- const { allowGitCommonDir: _allowGitCommonDir, ...filesystem } = config.filesystem;
777
+ const { allowGitCommonDir: _allowGitCommonDir, allowTempDirs, ...filesystem } = config.filesystem;
778
+ const allowWrite = allowTempDirs
779
+ ? Array.from(new Set([...filesystem.allowWrite, ...getTemporaryWritePaths()]))
780
+ : filesystem.allowWrite;
710
781
 
711
782
  return {
712
783
  network: config.network,
713
- filesystem,
784
+ filesystem: { ...filesystem, allowWrite },
714
785
  ignoreViolations: config.ignoreViolations,
715
786
  enableWeakerNestedSandbox: config.enableWeakerNestedSandbox,
716
787
  enableWeakerNetworkIsolation: config.enableWeakerNetworkIsolation,
@@ -2455,11 +2526,16 @@ export default function (pi: ExtensionAPI) {
2455
2526
  " Network:",
2456
2527
  ` Allowed: ${runtimeConfig.network.allowedDomains.join(", ") || "(none)"}`,
2457
2528
  ` Denied: ${runtimeConfig.network.deniedDomains.join(", ") || "(none)"}`,
2529
+ ` allowLocalBinding: ${runtimeConfig.network.allowLocalBinding ? "true" : "false"}`,
2530
+ ` allowAllUnixSockets: ${runtimeConfig.network.allowAllUnixSockets ? "true" : "false"}`,
2531
+ ` allowUnixSockets: ${runtimeConfig.network.allowUnixSockets?.join(", ") || "(none)"}`,
2458
2532
  "",
2459
2533
  " Filesystem:",
2460
2534
  ` Deny Read: ${runtimeConfig.filesystem.denyRead.join(", ") || "(none)"}`,
2461
2535
  ` Allow Write: ${runtimeConfig.filesystem.allowWrite.join(", ") || "(none)"}`,
2462
2536
  ` Deny Write: ${runtimeConfig.filesystem.denyWrite.join(", ") || "(none)"}`,
2537
+ ` allowTempDirs: ${sandboxConfig?.filesystem.allowTempDirs ? "true" : "false"}`,
2538
+ ` allowGitConfig: ${runtimeConfig.filesystem.allowGitConfig ? "true" : "false"}`,
2463
2539
  ` allowGitCommonDir: ${sandboxConfig?.filesystem.allowGitCommonDir ? "true" : "false"}`,
2464
2540
  "",
2465
2541
  " Advanced:",
@@ -6,7 +6,7 @@ import net from "node:net";
6
6
  import process from "node:process";
7
7
  import { execFile, spawn } from "node:child_process";
8
8
  import { randomUUID } from "node:crypto";
9
- import TelegramBot from "node-telegram-bot-api";
9
+ import { EventEmitter } from "node:events";
10
10
  import { extractTextFromMessage } from "./message-text.mjs";
11
11
 
12
12
  const AGENT_DIR = path.join(os.homedir(), ".pi", "agent");
@@ -26,7 +26,8 @@ const TELEGRAM_COMMANDS = [
26
26
  { command: "help", description: "Show available commands" },
27
27
  ];
28
28
 
29
- const TELEGRAM_POLL_TIMEOUT_SECONDS = 30;
29
+ const TELEGRAM_POLL_TIMEOUT_SEC = 30;
30
+ const TELEGRAM_POLL_RETRY_MS = 1_000;
30
31
  const TELEGRAM_HTTP_TIMEOUT_MS = 35_000;
31
32
  const POLLING_STOP_TIMEOUT_MS = 4_000;
32
33
  const ACTIVITY_NOTICE_COOLDOWN_MS = 60 * 60 * 1000;
@@ -44,6 +45,146 @@ const PI_ENTRYPOINT = process.env.PI_TELEGRAM_PI_ENTRYPOINT?.trim() || undefined
44
45
  const RESOLVED_TMPDIR = await fsp.realpath(os.tmpdir()).catch(() => os.tmpdir());
45
46
  const HEADLESS_SESSION_PATH_ERROR = "Path must start with / or ~ and refer to a directory.";
46
47
 
48
+ class TelegramApiError extends Error {
49
+ constructor(method, status, description) {
50
+ const suffix = description ? `: ${description}` : "";
51
+ super(`Telegram ${method} failed (${status})${suffix}`);
52
+ this.name = "TelegramApiError";
53
+ this.status = status;
54
+ this.description = description;
55
+ }
56
+ }
57
+
58
+ class TelegramPollingBot extends EventEmitter {
59
+ constructor(token, options = {}) {
60
+ super();
61
+ this.baseUrl = `https://api.telegram.org/bot${token}`;
62
+ this.pollTimeoutSeconds = options.polling?.params?.timeout ?? TELEGRAM_POLL_TIMEOUT_SEC;
63
+ this.httpTimeoutMs = options.request?.timeout ?? TELEGRAM_HTTP_TIMEOUT_MS;
64
+ this.offset = 0;
65
+ this.stopped = false;
66
+ this.pollController = new AbortController();
67
+ this.pollPromise = this.pollLoop();
68
+ }
69
+
70
+ async sendChatAction(chatId, action) {
71
+ return this.call("sendChatAction", { chat_id: chatId, action });
72
+ }
73
+
74
+ async sendMessage(chatId, text, options = {}) {
75
+ return this.call("sendMessage", { chat_id: chatId, text, ...options });
76
+ }
77
+
78
+ async setMyCommands(commands) {
79
+ return this.call("setMyCommands", { commands });
80
+ }
81
+
82
+ async answerCallbackQuery(callbackQueryId, options = {}) {
83
+ return this.call("answerCallbackQuery", { callback_query_id: callbackQueryId, ...options });
84
+ }
85
+
86
+ async stopPolling() {
87
+ if (this.stopped) return;
88
+ this.stopped = true;
89
+ this.pollController.abort();
90
+ await this.pollPromise.catch(() => {});
91
+ }
92
+
93
+ async pollLoop() {
94
+ while (!this.stopped) {
95
+ try {
96
+ const updates = await this.call(
97
+ "getUpdates",
98
+ {
99
+ offset: this.offset,
100
+ timeout: this.pollTimeoutSeconds,
101
+ allowed_updates: ["message", "callback_query"],
102
+ },
103
+ { signal: this.pollController.signal },
104
+ );
105
+
106
+ for (const update of Array.isArray(updates) ? updates : []) {
107
+ if (Number.isInteger(update.update_id)) {
108
+ this.offset = Math.max(this.offset, update.update_id + 1);
109
+ }
110
+ if (update.message) this.emit("message", update.message);
111
+ if (update.callback_query) this.emit("callback_query", update.callback_query);
112
+ }
113
+ } catch (error) {
114
+ if (this.stopped) return;
115
+ if (this.isAbortError(error)) continue;
116
+ this.emit("polling_error", error);
117
+ await sleep(TELEGRAM_POLL_RETRY_MS);
118
+ }
119
+ }
120
+ }
121
+
122
+ async call(method, payload, options = {}) {
123
+ const response = await this.fetchJson(method, payload, options);
124
+ if (!response || response.ok !== true) {
125
+ throw new TelegramApiError(
126
+ method,
127
+ response?.error_code ?? 200,
128
+ response?.description || "Unexpected response",
129
+ );
130
+ }
131
+ return response.result;
132
+ }
133
+
134
+ async fetchJson(method, payload, options = {}) {
135
+ const controller = new AbortController();
136
+ const timeoutId = setTimeout(() => controller.abort(), this.httpTimeoutMs);
137
+ const onAbort = () => controller.abort();
138
+ if (options.signal?.aborted) {
139
+ clearTimeout(timeoutId);
140
+ throw new DOMException("Aborted", "AbortError");
141
+ }
142
+ options.signal?.addEventListener("abort", onAbort, { once: true });
143
+
144
+ try {
145
+ const response = await fetch(`${this.baseUrl}/${method}`, {
146
+ method: "POST",
147
+ headers: { "Content-Type": "application/json" },
148
+ body: JSON.stringify(payload),
149
+ signal: controller.signal,
150
+ });
151
+
152
+ let body;
153
+ try {
154
+ body = await response.json();
155
+ } catch {
156
+ body = undefined;
157
+ }
158
+
159
+ if (!response.ok) {
160
+ throw new TelegramApiError(method, response.status, body?.description);
161
+ }
162
+
163
+ return body;
164
+ } finally {
165
+ clearTimeout(timeoutId);
166
+ options.signal?.removeEventListener("abort", onAbort);
167
+ }
168
+ }
169
+
170
+ emit(eventName, ...args) {
171
+ let handled = false;
172
+ for (const listener of this.listeners(eventName)) {
173
+ handled = true;
174
+ try {
175
+ listener(...args);
176
+ } catch (error) {
177
+ console.error(`[telegram] ${eventName} handler error`, error);
178
+ }
179
+ }
180
+ return handled;
181
+ }
182
+
183
+ isAbortError(error) {
184
+ return error instanceof Error && error.name === "AbortError";
185
+ }
186
+ }
187
+
47
188
  async function loadConfig() {
48
189
  try {
49
190
  const raw = await fsp.readFile(CONFIG_PATH, "utf8");
@@ -1857,10 +1998,10 @@ async function startServer() {
1857
1998
 
1858
1999
  server = await startServer();
1859
2000
 
1860
- bot = new TelegramBot(botTokenInfo.token, {
2001
+ bot = new TelegramPollingBot(botTokenInfo.token, {
1861
2002
  polling: {
1862
2003
  params: {
1863
- timeout: TELEGRAM_POLL_TIMEOUT_SECONDS,
2004
+ timeout: TELEGRAM_POLL_TIMEOUT_SEC,
1864
2005
  },
1865
2006
  },
1866
2007
  request: {
@@ -1869,9 +2010,9 @@ bot = new TelegramBot(botTokenInfo.token, {
1869
2010
  });
1870
2011
 
1871
2012
  void syncBotCommands();
1872
- // node-telegram-bot-api already retries failed polls. Avoid stop/start
1873
- // recovery here: cancelling an in-flight poll can spin up overlapping
1874
- // pollers and replay the same Telegram update multiple times.
2013
+ // The polling client retries failed polls internally. Avoid stop/start recovery here:
2014
+ // cancelling an in-flight poll can spin up overlapping pollers and replay the same
2015
+ // Telegram update multiple times.
1875
2016
  bot.on("polling_error", (error) => {
1876
2017
  const message = errorMessage(error);
1877
2018
  const kind = isLikelyNetworkPollingError(error) ? "polling_error (network)" : "polling_error";
@@ -3,7 +3,7 @@ import {
3
3
  type ExtensionAPI,
4
4
  type ExtensionContext,
5
5
  type ExtensionCommandContext,
6
- } from "@mariozechner/pi-coding-agent";
6
+ } from "@earendil-works/pi-coding-agent";
7
7
  import net from "node:net";
8
8
  import os from "node:os";
9
9
  import path from "node:path";