agent-tempo 1.0.1 → 1.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.
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.__resetBgPreflightCacheForTests = __resetBgPreflightCacheForTests;
37
+ exports.bgPreflight = bgPreflight;
38
+ /**
39
+ * #596 / ADR 0016 — permission preflight for `claude --bg`.
40
+ *
41
+ * Anthropic's per-user supervisor refuses bypass modes
42
+ * (`--dangerously-skip-permissions`) that were never accepted interactively
43
+ * in the target cwd. The bypass-consent record is NOT transparently readable
44
+ * on disk (not in `~/.claude/settings*.json`, not in
45
+ * `~/.claude/projects/<encoded-cwd>/`), so a behavioral probe is the most
46
+ * robust signal.
47
+ *
48
+ * **Surprise observed during live E2E** (documented for ADR follow-up):
49
+ * `claude --bg --dangerously-skip-permissions --help` does NOT short-circuit
50
+ * on `--help` — the supervisor adopts the session FIRST then processes the
51
+ * help flag. So a "dry-run" probe actually creates a real supervisor job
52
+ * (prints `backgrounded · <shortId> (idle …)` and exits 0). The probe is
53
+ * still our most reliable acceptance signal — we just have to immediately
54
+ * `claude stop <shortId>` the session we created so the probe is truly
55
+ * side-effect-free from the operator's perspective. Cache hits skip
56
+ * probe+stop entirely.
57
+ *
58
+ * Result is cached per `(host, cwd)` in an in-process `Map` for the
59
+ * daemon's lifetime — once a daemon has confirmed a cwd is ok, subsequent
60
+ * recruits in that cwd skip the probe. On failure we surface an actionable
61
+ * error string the recruit activity logs and rethrows.
62
+ *
63
+ * Cache invalidates on daemon restart (the user might have accepted in the
64
+ * intervening time) — `__resetBgPreflightCacheForTests` exists for unit
65
+ * coverage. Production code never invalidates the cache directly.
66
+ */
67
+ const child_process_1 = require("child_process");
68
+ const os = __importStar(require("os"));
69
+ const spawn_1 = require("../spawn");
70
+ const log = (...args) => console.error('[agent-tempo:bg-preflight]', ...args);
71
+ const cache = new Map();
72
+ function cacheKey(k) {
73
+ return `${k.host}:::${k.cwd}`;
74
+ }
75
+ /**
76
+ * Test hook — never call from production code. Convention per
77
+ * `docs/adr/0006-test-hooks-naming.md`.
78
+ */
79
+ function __resetBgPreflightCacheForTests() {
80
+ cache.clear();
81
+ }
82
+ /**
83
+ * Probe whether `claude --bg` can spawn in the given cwd without prompting
84
+ * the operator for permission acceptance. First call probes; subsequent
85
+ * calls for the same `(host, cwd)` hit the in-process cache.
86
+ *
87
+ * Returns `{ ok: true }` when the dry-run succeeded (exit 0). On any other
88
+ * exit code (or spawn-side ENOENT), returns `{ ok: false, error: ... }`
89
+ * with an actionable message. Never throws — callers handle the result.
90
+ */
91
+ function bgPreflight(cwd, options) {
92
+ const host = options?.host ?? os.hostname();
93
+ const key = cacheKey({ host, cwd });
94
+ if (cache.has(key)) {
95
+ return { ok: true, cached: true };
96
+ }
97
+ const claudeBin = (0, spawn_1.resolveClaudePath)(options?.claudeBin);
98
+ // Dry-run: `--help` exits immediately without spawning a real session.
99
+ // Bypass-permissions flag triggers the supervisor's accept-consent check,
100
+ // which is the behavior we actually want to verify.
101
+ let result;
102
+ try {
103
+ result = (0, child_process_1.spawnSync)(claudeBin, ['--bg', '--dangerously-skip-permissions', '--help'], {
104
+ cwd,
105
+ encoding: 'utf8',
106
+ timeout: 15_000,
107
+ shell: process.platform === 'win32',
108
+ });
109
+ }
110
+ catch (err) {
111
+ const msg = err instanceof Error ? err.message : String(err);
112
+ const error = `claude --bg preflight failed to launch (${msg}). Run 'claude' once in ${cwd} and accept the permission dialog, then retry recruit.`;
113
+ log(error);
114
+ return { ok: false, error, cached: false };
115
+ }
116
+ if (result.error) {
117
+ const error = `claude --bg preflight failed to launch (${result.error.message}). Run 'claude' once in ${cwd} and accept the permission dialog, then retry recruit.`;
118
+ log(error);
119
+ return { ok: false, error, cached: false };
120
+ }
121
+ if (result.status === 0) {
122
+ // The probe spawned a real supervisor job — clean it up immediately so
123
+ // the operator never sees a leaked `idle` session in `claude agents`.
124
+ // Stdout format observed: `backgrounded · <shortId> (idle — send a prompt to start)`
125
+ const stdoutCombined = (result.stdout || '').toString();
126
+ const shortIdMatch = stdoutCombined.match(/backgrounded\s*[·•]\s*([0-9a-f]{8})/i);
127
+ if (shortIdMatch) {
128
+ const probeShortId = shortIdMatch[1];
129
+ try {
130
+ (0, child_process_1.spawnSync)(claudeBin, ['stop', probeShortId], {
131
+ cwd,
132
+ encoding: 'utf8',
133
+ timeout: 10_000,
134
+ shell: process.platform === 'win32',
135
+ });
136
+ log(`probe stopped its own session ${probeShortId} in ${cwd}`);
137
+ }
138
+ catch (err) {
139
+ log(`probe-cleanup warning: failed to stop ${probeShortId} (${err instanceof Error ? err.message : String(err)}); operator may see an idle 'agents' row`);
140
+ }
141
+ }
142
+ else {
143
+ log(`probe in ${cwd} exit 0 but no shortId in stdout — supervisor surface may have changed; not stopping anything`);
144
+ }
145
+ cache.set(key, true);
146
+ return { ok: true, cached: false };
147
+ }
148
+ const stderr = (result.stderr || '').trim();
149
+ const stdout = (result.stdout || '').trim();
150
+ const detail = stderr || stdout || `exit ${result.status}`;
151
+ const error = `claude --bg preflight rejected in ${cwd} (${detail}). Run 'claude' once in ${cwd} and accept the permission dialog, then retry recruit.`;
152
+ log(error);
153
+ return { ok: false, error, cached: false };
154
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-tempo",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "description": "Many agents, one tempo. Durable coordination for multi-agent work via Temporal.",
5
5
  "keywords": [
6
6
  "mcp",
@@ -92,6 +92,8 @@
92
92
  "@modelcontextprotocol/sdk": "~1.28.0",
93
93
  "@temporalio/activity": "~1.15.0",
94
94
  "@temporalio/client": "~1.15.0",
95
+ "@temporalio/common": "~1.15.0",
96
+ "@temporalio/proto": "~1.15.0",
95
97
  "@temporalio/worker": "~1.15.0",
96
98
  "@temporalio/workflow": "~1.15.0",
97
99
  "croner": "^10.0.1",
@@ -107,7 +109,6 @@
107
109
  },
108
110
  "devDependencies": {
109
111
  "@size-limit/preset-app": "^12.1.0",
110
- "@temporalio/common": "^1.15.0",
111
112
  "@temporalio/testing": "~1.15.0",
112
113
  "@types/chai": "^4.3.20",
113
114
  "@types/mocha": "^10.0.10",
@@ -160,7 +161,6 @@
160
161
  "engines": {
161
162
  "node": ">=20"
162
163
  },
163
- "packageManager": "npm@10.9.2",
164
164
  "license": "MIT",
165
165
  "trustedDependencies": [
166
166
  "@swc/core",
@@ -168,5 +168,6 @@
168
168
  ],
169
169
  "overrides": {
170
170
  "rxjs": "^7.8.1"
171
- }
171
+ },
172
+ "packageManager": "npm@10.9.2"
172
173
  }