pi-maestro-teammate 2.1.0 → 2.2.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 (45) hide show
  1. package/bin/pi-teammate-outbox.mjs +12 -0
  2. package/package.json +3 -2
  3. package/src/backends/acp-cli.ts +1 -0
  4. package/src/cli-tools/cli-tools-config.ts +31 -14
  5. package/src/completion-outbox/cli.ts +123 -0
  6. package/src/completion-outbox/file-store.ts +440 -46
  7. package/src/extension/index.ts +86 -51
  8. package/src/extension/mailbox/file-store.ts +4 -9
  9. package/src/extension/mailbox/host.ts +27 -10
  10. package/src/extension/mailbox/rollout.ts +3 -3
  11. package/src/extension/mailbox/router.ts +2 -0
  12. package/src/extension/mailbox/types.ts +3 -1
  13. package/src/extension/schemas.ts +13 -4
  14. package/src/extension/task-delegation.ts +2 -2
  15. package/src/extension/teammate-core.ts +14 -6
  16. package/src/extension/teammate-helpers.ts +13 -6
  17. package/src/extension/teammate-proxy.ts +3 -3
  18. package/src/public/v1/model-routing.ts +6 -0
  19. package/src/runs/execution-infra.ts +51 -7
  20. package/src/runs/execution.ts +15 -12
  21. package/src/runs/pi-subprocess-attempt.ts +2813 -2755
  22. package/src/runtime-broker/client.ts +4 -10
  23. package/src/runtime-broker/sqlite-store.ts +52 -2
  24. package/src/runtime-broker/transport.ts +2 -1
  25. package/src/sessions/session-core.ts +3 -3
  26. package/src/shared/turn-ledger.ts +30 -6
  27. package/src/shared/types.ts +1 -1
  28. package/src/tui/attach-overlay.ts +23 -14
  29. package/src/tui/session-send-overlay.ts +6 -1
  30. package/types/backends/acp-cli.d.ts +1 -0
  31. package/types/completion-outbox/cli.d.ts +1 -0
  32. package/types/completion-outbox/file-store.d.ts +18 -0
  33. package/types/extension/mailbox/host.d.ts +1 -1
  34. package/types/extension/mailbox/rollout.d.ts +3 -3
  35. package/types/extension/mailbox/types.d.ts +2 -2
  36. package/types/extension/schemas.d.ts +6 -5
  37. package/types/extension/teammate-core.d.ts +2 -2
  38. package/types/extension/teammate-proxy.d.ts +3 -3
  39. package/types/public/v1/model-routing.d.ts +1 -0
  40. package/types/runs/execution-infra.d.ts +17 -0
  41. package/types/runs/pi-subprocess-attempt.d.ts +1 -1
  42. package/types/runtime-broker/transport.d.ts +2 -2
  43. package/types/sessions/session-core.d.ts +2 -2
  44. package/types/shared/types.d.ts +1 -1
  45. package/types/tui/attach-overlay.d.ts +2 -0
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import { createJiti } from "jiti";
3
+
4
+ const jiti = createJiti(import.meta.url, { interopDefault: true });
5
+ const { main } = await jiti.import("../src/completion-outbox/cli.ts");
6
+
7
+ try {
8
+ process.exitCode = await main(process.argv.slice(2));
9
+ } catch (error) {
10
+ process.stderr.write(`pi-teammate-outbox: ${error instanceof Error ? error.message : String(error)}\n`);
11
+ process.exitCode = 1;
12
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-maestro-teammate",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Pi extension — teammate agent dispatch with DAG task graphs, RPC messaging, and compact TUI",
5
5
  "type": "module",
6
6
  "engines": {
@@ -9,6 +9,7 @@
9
9
  "bin": {
10
10
  "pi-teammate-broker": "./bin/pi-teammate-broker.mjs",
11
11
  "pi-teammate-models": "./bin/pi-teammate-models.mjs",
12
+ "pi-teammate-outbox": "./bin/pi-teammate-outbox.mjs",
12
13
  "pi-teammate-remote": "./bin/pi-teammate-remote.mjs"
13
14
  },
14
15
  "keywords": [
@@ -19,7 +20,7 @@
19
20
  "agents"
20
21
  ],
21
22
  "scripts": {
22
- "test": "node --experimental-transform-types --import ./test/setup.ts --test --test-concurrency=4 \"test/*.test.ts\"",
23
+ "test": "node --experimental-transform-types --import ./test/setup.ts --test --test-concurrency=2 \"test/*.test.ts\"",
23
24
  "typecheck": "tsc -p tsconfig.build.json",
24
25
  "build:declarations": "tsc -p tsconfig.declarations.json",
25
26
  "check:declarations": "node scripts/check-declarations.mjs",
@@ -796,6 +796,7 @@ export function createAcpCliBackend(
796
796
  * registration ship fields whose keys have no text.
797
797
  */
798
798
  export { ACP_CLI_SETTINGS_CATALOGS } from "./acp-cli-catalog.ts";
799
+ export { probeSshCliExecutable } from "../remote/ssh-exec.ts";
799
800
 
800
801
  /**
801
802
  * The registered instance.
@@ -17,7 +17,6 @@
17
17
  * ~/.maestro/cli-tools.json for pi-maestro-flow's original provider registration.
18
18
  */
19
19
 
20
- import { execFileSync } from "node:child_process";
21
20
  import * as fs from "node:fs";
22
21
  import * as os from "node:os";
23
22
  import * as path from "node:path";
@@ -82,7 +81,6 @@ export interface MaestroDelegateConfig {
82
81
 
83
82
  const GLOBAL_CONFIG_FILE = "teammate-cli-tools.json";
84
83
  const LEGACY_CONFIG_PATH = path.join(os.homedir(), ".maestro", "cli-tools.json");
85
- const PROBE_TIMEOUT_MS = 2_000;
86
84
  const PROBE_CACHE_TTL_MS = 30_000;
87
85
 
88
86
  export function getGlobalCliToolsConfigPath(): string {
@@ -326,6 +324,34 @@ function adapterInstallHint(command: string): string {
326
324
  return pkg === undefined ? "" : `; install it with: npm i -g ${pkg}`;
327
325
  }
328
326
 
327
+ function localExecutableCandidates(command: string): string[] {
328
+ const hasPathSeparator = command.includes("/") || command.includes("\\");
329
+ const directories = hasPathSeparator
330
+ ? [process.cwd()]
331
+ : (process.env.PATH ?? "").split(path.delimiter).map((entry) => {
332
+ const trimmed = entry.trim();
333
+ return trimmed.replace(/^"|"$/g, "") || process.cwd();
334
+ });
335
+ const extensions = process.platform === "win32" && path.extname(command) === ""
336
+ ? ["", ...(process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";")]
337
+ : [""];
338
+ return [...new Set(directories.flatMap((directory) =>
339
+ extensions.map((extension) => path.resolve(directory, `${command}${extension.toLowerCase()}`)),
340
+ ))];
341
+ }
342
+
343
+ function localExecutableExists(command: string): boolean {
344
+ return localExecutableCandidates(command).some((candidate) => {
345
+ try {
346
+ if (!fs.statSync(candidate).isFile()) return false;
347
+ fs.accessSync(candidate, process.platform === "win32" ? fs.constants.F_OK : fs.constants.X_OK);
348
+ return true;
349
+ } catch {
350
+ return false;
351
+ }
352
+ });
353
+ }
354
+
329
355
  function probeLocalExecutable(key: string, command: string): CliToolProbeResult {
330
356
  const quotedCommand = JSON.stringify(command);
331
357
  let result: CliToolProbeResult;
@@ -351,18 +377,9 @@ function probeLocalExecutable(key: string, command: string): CliToolProbeResult
351
377
  };
352
378
  }
353
379
  } else {
354
- try {
355
- const lookup = process.platform === "win32" ? "where" : "which";
356
- execFileSync(lookup, [command], {
357
- encoding: "utf8",
358
- timeout: PROBE_TIMEOUT_MS,
359
- stdio: ["ignore", "ignore", "pipe"],
360
- windowsHide: true,
361
- });
362
- result = { ok: true, command };
363
- } catch {
364
- result = { ok: false, command, error: `executable ${quotedCommand} not found on PATH${adapterInstallHint(command)}` };
365
- }
380
+ result = localExecutableExists(command)
381
+ ? { ok: true, command }
382
+ : { ok: false, command, error: `executable ${quotedCommand} not found on PATH${adapterInstallHint(command)}` };
366
383
  }
367
384
  probeCache.set(key, { result, at: Date.now() });
368
385
  return result;
@@ -0,0 +1,123 @@
1
+ import * as path from "node:path";
2
+ import { CompletionOutboxFileStore } from "./file-store.ts";
3
+ import { getRuntimeWorkspaceIdentity } from "../runtime-broker/private-state.ts";
4
+
5
+ interface ParsedOptions {
6
+ command?: "cleanup";
7
+ workspace: string;
8
+ rootDir?: string;
9
+ apply: boolean;
10
+ json: boolean;
11
+ maxEntries?: number;
12
+ help: boolean;
13
+ }
14
+
15
+ function usage(): string {
16
+ return [
17
+ "Usage: pi-teammate-outbox cleanup [options]",
18
+ "",
19
+ "Safely removes obsolete atomic-replacement remnants while preserving the",
20
+ "latest recoverable transaction for every canonical outbox file.",
21
+ "",
22
+ "Options:",
23
+ " --workspace <path> Workspace whose outbox is cleaned (default: cwd)",
24
+ " --root <path> Completion outbox root directory",
25
+ " --apply Delete candidates; without this flag cleanup is dry-run",
26
+ " --max-entries <n> Refuse scans larger than n entries (default: 100000)",
27
+ " --json Emit one JSON result",
28
+ " --help Show this help",
29
+ "",
30
+ "Exit codes: 0 success, 2 usage error, 3 workspace outbox is busy.",
31
+ ].join("\n");
32
+ }
33
+
34
+ function parsePositiveInteger(value: string | undefined, option: string): number {
35
+ if (!value || !/^\d+$/.test(value)) throw new Error(`${option} requires a positive integer`);
36
+ const parsed = Number(value);
37
+ if (!Number.isSafeInteger(parsed) || parsed < 1 || parsed > 1_000_000) {
38
+ throw new Error(`${option} must be between 1 and 1000000`);
39
+ }
40
+ return parsed;
41
+ }
42
+
43
+ function parseOptions(args: readonly string[]): ParsedOptions {
44
+ const first = args[0];
45
+ const command = first === "cleanup" ? first : undefined;
46
+ const help = args.includes("--help") || args.includes("-h");
47
+ const options: ParsedOptions = {
48
+ command,
49
+ workspace: process.cwd(),
50
+ apply: false,
51
+ json: false,
52
+ help,
53
+ };
54
+ for (let index = command ? 1 : (help ? 1 : 0); index < args.length; index += 1) {
55
+ const argument = args[index];
56
+ if (argument === "--help" || argument === "-h") continue;
57
+ if (argument === "--apply") {
58
+ options.apply = true;
59
+ continue;
60
+ }
61
+ if (argument === "--json") {
62
+ options.json = true;
63
+ continue;
64
+ }
65
+ const value = args[++index];
66
+ if (argument === "--workspace") {
67
+ if (!value) throw new Error("--workspace requires a path");
68
+ options.workspace = path.resolve(value);
69
+ } else if (argument === "--root") {
70
+ if (!value) throw new Error("--root requires a path");
71
+ options.rootDir = path.resolve(value);
72
+ } else if (argument === "--max-entries") {
73
+ options.maxEntries = parsePositiveInteger(value, argument);
74
+ } else {
75
+ throw new Error(`Unknown option: ${argument}`);
76
+ }
77
+ }
78
+ return options;
79
+ }
80
+
81
+ export async function main(args = process.argv.slice(2)): Promise<number> {
82
+ const options = parseOptions(args);
83
+ if (options.help || !options.command) {
84
+ process.stdout.write(`${usage()}\n`);
85
+ return options.help ? 0 : 2;
86
+ }
87
+
88
+ const identity = getRuntimeWorkspaceIdentity(options.workspace);
89
+ const store = new CompletionOutboxFileStore({
90
+ ...(options.rootDir === undefined ? {} : { rootDir: options.rootDir }),
91
+ });
92
+ const result = await store.cleanupRemnants(identity.workspaceId, {
93
+ apply: options.apply,
94
+ ...(options.maxEntries === undefined ? {} : { maxEntries: options.maxEntries }),
95
+ });
96
+ const output = {
97
+ workspace: identity.canonicalPath,
98
+ workspaceId: identity.workspaceId,
99
+ rootDir: store.rootDir,
100
+ ...result,
101
+ };
102
+
103
+ if (options.json) {
104
+ process.stdout.write(`${JSON.stringify(output)}\n`);
105
+ } else if (result.busy) {
106
+ process.stderr.write(`Completion outbox is busy for ${identity.canonicalPath}; nothing was removed.\n`);
107
+ } else {
108
+ process.stdout.write([
109
+ `Completion outbox cleanup: ${result.apply ? "applied" : "dry-run"}`,
110
+ `Workspace: ${identity.canonicalPath}`,
111
+ `Scanned: ${result.scannedEntries} entries (${result.replacementFiles} replacement remnants)`,
112
+ `Preserved: ${result.preservedFiles}`,
113
+ `Candidates: ${result.candidateFiles} files, ${result.candidateBytes} bytes`,
114
+ `Removed: ${result.removedFiles} files, ${result.removedBytes} bytes`,
115
+ ...(result.candidateSample.length === 0 ? [] : [
116
+ "Candidate sample:",
117
+ ...result.candidateSample.map((candidate) => ` ${candidate}`),
118
+ ]),
119
+ ...(!result.apply && result.candidateFiles > 0 ? ["Re-run with --apply to remove these candidates."] : []),
120
+ ].join("\n") + "\n");
121
+ }
122
+ return result.busy ? 3 : 0;
123
+ }