@tekmidian/pai 0.59.0 → 0.60.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.
- package/.claude-plugin/plugin.json +8 -33
- package/PLUGIN-ARCHITECTURE.md +4 -6
- package/dist/{aibroker-client-B8Wtasez.mjs → aibroker-client-BYG-GtAg.mjs} +1 -1
- package/dist/{chain-Cltx0NjY.mjs → chain-DFlzAfDE.mjs} +4 -4
- package/dist/{chain-Cltx0NjY.mjs.map → chain-DFlzAfDE.mjs.map} +1 -1
- package/dist/cli/index.mjs +12 -12
- package/dist/cli/program.mjs +12 -12
- package/dist/{config-DCYWFaEi.mjs → config-GceCMUxa.mjs} +1 -1
- package/dist/{config-D1omG_UU.mjs → config-msER8xj_.mjs} +16 -4
- package/dist/config-msER8xj_.mjs.map +1 -0
- package/dist/{context-handover-cache-Dt4HW1Ro.mjs → context-handover-cache-DF0xWVB0.mjs} +4 -4
- package/dist/{context-handover-cache-Dt4HW1Ro.mjs.map → context-handover-cache-DF0xWVB0.mjs.map} +1 -1
- package/dist/daemon/index.mjs +12 -12
- package/dist/daemon-BppwJtrH.mjs +19 -0
- package/dist/{daemon-CmqJfeb9.mjs → daemon-DArRKBlk.mjs} +97 -23
- package/dist/daemon-DArRKBlk.mjs.map +1 -0
- package/dist/daemon-mcp/index.mjs +9 -9
- package/dist/daemon-mcp/index.mjs.map +1 -1
- package/dist/{factory-DiuntvwG.mjs → factory-ChfAvCzO.mjs} +7 -7
- package/dist/{factory-DiuntvwG.mjs.map → factory-ChfAvCzO.mjs.map} +1 -1
- package/dist/{factory-C9wGUe30.mjs → factory-D6I2jmjQ.mjs} +1 -1
- package/dist/{fallback-B_2ozQHw.mjs → fallback-B7VEMMRn.mjs} +80 -9
- package/dist/fallback-B7VEMMRn.mjs.map +1 -0
- package/dist/hooks/block-sleep-poll.mjs +7590 -0
- package/dist/hooks/block-sleep-poll.mjs.map +7 -0
- package/dist/hooks/capture-all-events.mjs +8 -4
- package/dist/hooks/capture-all-events.mjs.map +2 -2
- package/dist/hooks/capture-session-summary.mjs.map +1 -1
- package/dist/hooks/capture-tool-output.mjs.map +1 -1
- package/dist/hooks/cleanup-session-files.mjs.map +1 -1
- package/dist/hooks/context-compression-hook.mjs +6 -6
- package/dist/hooks/context-compression-hook.mjs.map +3 -3
- package/dist/hooks/initialize-session.mjs.map +1 -1
- package/dist/hooks/load-core-context.mjs.map +1 -1
- package/dist/hooks/load-project-context.mjs.map +2 -2
- package/dist/hooks/post-compact-inject.mjs +1 -1
- package/dist/hooks/post-compact-inject.mjs.map +2 -2
- package/dist/hooks/route-agents-to-worker.mjs +4 -3
- package/dist/hooks/route-agents-to-worker.mjs.map +3 -3
- package/dist/hooks/security-validator.mjs +4 -3
- package/dist/hooks/security-validator.mjs.map +3 -3
- package/dist/hooks/stop-hook.mjs.map +1 -1
- package/dist/hooks/sync-todo-to-md.mjs.map +1 -1
- package/dist/hooks/whisper-rules.mjs +4 -3
- package/dist/hooks/whisper-rules.mjs.map +3 -3
- package/dist/hooks/worker-proxy.mjs +4 -3
- package/dist/hooks/worker-proxy.mjs.map +3 -3
- package/dist/hooks/worker-status-line.mjs +9 -6
- package/dist/hooks/worker-status-line.mjs.map +3 -3
- package/dist/hooks/worker-supervision.mjs +4 -3
- package/dist/hooks/worker-supervision.mjs.map +3 -3
- package/dist/{main-resolver-DYNxxNwJ.mjs → main-resolver-BqTjt-GL.mjs} +100 -21
- package/dist/main-resolver-BqTjt-GL.mjs.map +1 -0
- package/dist/main-resolver-CCojcYr2.mjs +7 -0
- package/dist/{planner-DsZZEZCA.mjs → planner-DbpsMZqY.mjs} +83 -30
- package/dist/planner-DbpsMZqY.mjs.map +1 -0
- package/dist/{program-B5miXqQa.mjs → program-QSuci3RH.mjs} +278 -92
- package/dist/program-QSuci3RH.mjs.map +1 -0
- package/dist/router-C5hPk9YP.mjs +3 -0
- package/dist/{router-BdxdsAvs.mjs → router-DjFZIoDl.mjs} +2 -1
- package/dist/router-DjFZIoDl.mjs.map +1 -0
- package/dist/{run-XaA-UPdW.mjs → run-BFxuNnIJ.mjs} +100 -17
- package/dist/run-BFxuNnIJ.mjs.map +1 -0
- package/dist/{run-env-DyTWgZC9.mjs → run-env-kbWVwUvU.mjs} +2 -2
- package/dist/{run-env-DyTWgZC9.mjs.map → run-env-kbWVwUvU.mjs.map} +1 -1
- package/dist/{server-CzqHGbKt.mjs → server-B0vBmfJt.mjs} +2 -2
- package/dist/{server-CzqHGbKt.mjs.map → server-B0vBmfJt.mjs.map} +1 -1
- package/dist/{session-keepalive-DDsEo81Q.mjs → session-keepalive-CUcFa8ld.mjs} +5 -5
- package/dist/{session-keepalive-DDsEo81Q.mjs.map → session-keepalive-CUcFa8ld.mjs.map} +1 -1
- package/dist/{tools-Diro2s8J.mjs → tools-CaTZnIau.mjs} +2 -2
- package/dist/{tools-Diro2s8J.mjs.map → tools-CaTZnIau.mjs.map} +1 -1
- package/dist/{tools-KgL1rkPD.mjs → tools-_7MF8Ebb.mjs} +3 -3
- package/dist/{wakeup-D0uvqLsE.mjs → wakeup-CTX3rlz-.mjs} +2 -2
- package/dist/wakeup-CTX3rlz-.mjs.map +1 -0
- package/dist/{work-queue-worker-DL9lu8sN.mjs → work-queue-worker-B4eZGy5n.mjs} +2 -2
- package/dist/{work-queue-worker-DL9lu8sN.mjs.map → work-queue-worker-B4eZGy5n.mjs.map} +1 -1
- package/dist/work-queue-worker-DuhZmfDB.mjs +11 -0
- package/docker/init.sql +1 -1
- package/docs/commands/README.md +4 -4
- package/docs/commands/daemon.md +6 -6
- package/docs/commands/session.md +2 -2
- package/docs/commands/worker.md +5 -2
- package/docs/workers-config.md +5 -1
- package/package.json +1 -1
- package/plugins/context-preservation/hooks/hooks.json +38 -31
- package/plugins/context-preservation/plugin.json +1 -4
- package/plugins/core/hooks/hooks.json +51 -46
- package/plugins/core/plugin.json +1 -4
- package/plugins/creative/plugin.json +1 -5
- package/plugins/observability/hooks/hooks.json +87 -72
- package/plugins/observability/plugin.json +1 -5
- package/plugins/productivity/hooks/hooks.json +22 -19
- package/plugins/productivity/plugin.json +2 -5
- package/plugins/semantic-search/plugin.json +1 -16
- package/plugins/ui/hooks/hooks.json +17 -14
- package/plugins/ui/plugin.json +1 -5
- package/plugins/zettelkasten/plugin.json +1 -14
- package/src/hooks/session-autosave.sh +4 -2
- package/src/hooks/session-stop.sh +2 -2
- package/src/hooks/ts/capture-all-events.ts +3 -3
- package/src/hooks/ts/lib/metadata-extraction.ts +3 -2
- package/src/hooks/ts/lib/pai-paths.test.ts +1 -1
- package/src/hooks/ts/lib/sleep-poll-gate.test.ts +62 -0
- package/src/hooks/ts/lib/sleep-poll-gate.ts +77 -0
- package/src/hooks/ts/post-tool-use/capture-tool-output.ts +1 -1
- package/src/hooks/ts/post-tool-use/sync-todo-to-md.ts +1 -1
- package/src/hooks/ts/pre-compact/context-compression-hook.ts +1 -1
- package/src/hooks/ts/pre-tool-use/block-sleep-poll.test.ts +44 -0
- package/src/hooks/ts/pre-tool-use/block-sleep-poll.ts +50 -0
- package/src/hooks/ts/session-end/capture-session-summary.ts +1 -1
- package/src/hooks/ts/session-start/initialize-session.ts +2 -2
- package/src/hooks/ts/session-start/load-core-context.ts +2 -2
- package/src/hooks/ts/session-start/load-project-context.ts +1 -1
- package/src/hooks/ts/stop/stop-hook.ts +1 -1
- package/src/hooks/ts/user-prompt/cleanup-session-files.ts +1 -1
- package/statusline-command.sh +26 -11
- package/dist/config-D1omG_UU.mjs.map +0 -1
- package/dist/daemon-B6hx1Ysz.mjs +0 -19
- package/dist/daemon-CmqJfeb9.mjs.map +0 -1
- package/dist/fallback-B_2ozQHw.mjs.map +0 -1
- package/dist/main-resolver-DYNxxNwJ.mjs.map +0 -1
- package/dist/main-resolver-Df65bPAi.mjs +0 -7
- package/dist/planner-DsZZEZCA.mjs.map +0 -1
- package/dist/program-B5miXqQa.mjs.map +0 -1
- package/dist/router-BXOf_e9e.mjs +0 -3
- package/dist/router-BdxdsAvs.mjs.map +0 -1
- package/dist/run-XaA-UPdW.mjs.map +0 -1
- package/dist/wakeup-D0uvqLsE.mjs.map +0 -1
- package/dist/work-queue-worker-BY7xn4pw.mjs +0 -11
|
@@ -131,6 +131,7 @@ var MacOsProvider = class {
|
|
|
131
131
|
channelId = "macos";
|
|
132
132
|
async send(payload, config) {
|
|
133
133
|
if (!config.channels.macos.enabled) return false;
|
|
134
|
+
if (process.platform !== "darwin") return false;
|
|
134
135
|
try {
|
|
135
136
|
const safeTitle = (payload.title ?? "PAI").replace(/'/g, "\\'");
|
|
136
137
|
const script = `display notification "${payload.message.replace(/'/g, "\\'")}" with title "${safeTitle}"`;
|
|
@@ -246,4 +247,4 @@ async function routeNotification(payload, config) {
|
|
|
246
247
|
|
|
247
248
|
//#endregion
|
|
248
249
|
export { routeNotification as t };
|
|
249
|
-
//# sourceMappingURL=router-
|
|
250
|
+
//# sourceMappingURL=router-DjFZIoDl.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router-DjFZIoDl.mjs","names":[],"sources":["../src/notifications/providers/ntfy.ts","../src/notifications/providers/whatsapp.ts","../src/notifications/providers/macos.ts","../src/notifications/providers/cli.ts","../src/notifications/router.ts"],"sourcesContent":["/**\n * ntfy.ts — ntfy.sh notification provider\n *\n * Sends notifications to a configured ntfy.sh topic via HTTP.\n */\n\nimport type {\n NotificationProvider,\n NotificationPayload,\n NotificationConfig,\n} from \"../types.js\";\n\nexport class NtfyProvider implements NotificationProvider {\n readonly channelId = \"ntfy\" as const;\n\n async send(\n payload: NotificationPayload,\n config: NotificationConfig\n ): Promise<boolean> {\n const cfg = config.channels.ntfy;\n if (!cfg.enabled || !cfg.url) return false;\n\n try {\n const headers: Record<string, string> = {\n \"Content-Type\": \"text/plain; charset=utf-8\",\n };\n\n if (payload.title) {\n headers[\"Title\"] = payload.title;\n }\n\n if (cfg.priority && cfg.priority !== \"default\") {\n headers[\"Priority\"] = cfg.priority;\n }\n\n const response = await fetch(cfg.url, {\n method: \"POST\",\n headers,\n body: payload.message,\n });\n\n return response.ok;\n } catch {\n return false;\n }\n }\n}\n","/**\n * whatsapp.ts — WhatsApp notification provider (via the AIBroker hub)\n *\n * Sends through AIBroker's hub socket, which proxies to the whazaa adapter.\n *\n * It used to dial /tmp/whazaa.sock and call a method named `whatsapp_send`.\n * Both were wrong, and had been since AIBroker became the runtime hub and\n * adapters became thin transports: Whazaa no longer owns an IPC socket of its\n * own (it registers with the hub, currently at /tmp/whazaa-watcher.sock, which\n * is the hub's business and not ours), and `whatsapp_send` is an MCP TOOL name\n * — the adapter itself takes `send` and `tts`.\n *\n * So every WhatsApp notification failed. Silently, because a failed channel\n * writes one line to stderr and the router has no fallback: on 2026-08-04 four\n * task-bus escalations about a job that had not run for nine hours reached\n * /tmp/pai-scheduler.log and nowhere else, while the user had no idea.\n *\n * Routing through the hub rather than at the adapter directly is deliberate:\n * the hub knows where its adapters are, and that is exactly the knowledge whose\n * absence broke this. Connect-per-call, no persistent state.\n */\n\nimport { connect } from \"node:net\";\nimport { randomUUID } from \"node:crypto\";\nimport type {\n NotificationProvider,\n NotificationPayload,\n NotificationConfig,\n} from \"../types.js\";\n\n/**\n * AIBroker's hub socket.\n *\n * Hardcoded rather than imported: PAI must not depend on AIBroker. This is a\n * protocol constant between them, like the agent mark in tasks/poller.ts.\n */\nconst HUB_SOCKET = \"/tmp/aibroker.sock\";\nconst HUB_TIMEOUT_MS = 10_000;\n\n/**\n * Send a single IPC call to the hub.\n * Returns true on success, false if the hub is not available or errors.\n */\nfunction callHub(\n method: string,\n params: Record<string, unknown>\n): Promise<boolean> {\n return new Promise((resolve) => {\n let done = false;\n let buffer = \"\";\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n function finish(ok: boolean): void {\n if (done) return;\n done = true;\n if (timer) { clearTimeout(timer); timer = null; }\n try { socket?.destroy(); } catch { /* ignore */ }\n resolve(ok);\n }\n\n const socket = connect(HUB_SOCKET, () => {\n const request = {\n jsonrpc: \"2.0\",\n id: randomUUID(),\n method,\n params,\n };\n socket.write(JSON.stringify(request) + \"\\n\");\n });\n\n socket.on(\"data\", (chunk: Buffer) => {\n buffer += chunk.toString();\n const nl = buffer.indexOf(\"\\n\");\n if (nl === -1) return;\n try {\n const resp = JSON.parse(buffer.slice(0, nl)) as { error?: unknown; ok?: boolean };\n // The hub reports failure as {ok:false,error}; JSON-RPC reports {error}.\n // Checking only `error` counted an {ok:false} reply as a delivered\n // notification, which is the one mistake this file must never make.\n finish(!resp.error && resp.ok !== false);\n } catch {\n finish(false);\n }\n });\n\n socket.on(\"error\", () => finish(false));\n socket.on(\"end\", () => finish(false));\n\n timer = setTimeout(() => finish(false), HUB_TIMEOUT_MS);\n });\n}\n\nexport class WhatsAppProvider implements NotificationProvider {\n readonly channelId = \"whatsapp\" as const;\n\n async send(\n payload: NotificationPayload,\n config: NotificationConfig\n ): Promise<boolean> {\n const cfg = config.channels.whatsapp;\n if (!cfg.enabled) return false;\n\n const isVoiceMode = config.mode === \"voice\" || config.channels.voice.enabled;\n const asVoice = isVoiceMode && config.mode === \"voice\";\n\n // The adapter's own vocabulary is `send` and `tts`, reached through the\n // hub's `adapter_call`. `whatsapp_send` — what this used to ask for — is the\n // name of the MCP TOOL that wraps it, and the adapter has never answered to\n // it. The two vocabularies are easy to confuse because the MCP tool exists\n // and works; it just is not this interface.\n const inner: Record<string, unknown> = asVoice\n ? { text: payload.message, voice: config.channels.voice.voiceName ?? \"bm_george\" }\n : { message: payload.message };\n\n if (cfg.recipient) {\n // `send` addresses by `recipient`, `tts` by `jid` — same destination,\n // different key, per the adapter's interface.\n inner[asVoice ? \"jid\" : \"recipient\"] = cfg.recipient;\n }\n\n return callHub(\"adapter_call\", {\n adapter: \"whazaa\",\n method: asVoice ? \"tts\" : \"send\",\n params: inner,\n });\n }\n}\n","/**\n * macos.ts — macOS notification provider\n *\n * Uses the `osascript` command to display a macOS system notification.\n * Non-blocking: spawns the process and returns success without waiting.\n */\n\nimport { spawn } from \"node:child_process\";\nimport type {\n NotificationProvider,\n NotificationPayload,\n NotificationConfig,\n} from \"../types.js\";\n\nexport class MacOsProvider implements NotificationProvider {\n readonly channelId = \"macos\" as const;\n\n async send(\n payload: NotificationPayload,\n config: NotificationConfig\n ): Promise<boolean> {\n const cfg = config.channels.macos;\n if (!cfg.enabled) return false;\n if (process.platform !== \"darwin\") return false;\n\n try {\n const title = payload.title ?? \"PAI\";\n // Escape single quotes in title and message for AppleScript\n const safeTitle = title.replace(/'/g, \"\\\\'\");\n const safeMessage = payload.message.replace(/'/g, \"\\\\'\");\n\n const script = `display notification \"${safeMessage}\" with title \"${safeTitle}\"`;\n\n return new Promise((resolve) => {\n const child = spawn(\"osascript\", [\"-e\", script], {\n detached: true,\n stdio: \"ignore\",\n });\n child.unref();\n\n // Give the process a moment to start, then assume success.\n // osascript is always present on macOS.\n child.on(\"error\", () => resolve(false));\n\n // Resolve after a short timeout — osascript exits quickly\n setTimeout(() => resolve(true), 200);\n });\n } catch {\n return false;\n }\n }\n}\n","/**\n * cli.ts — CLI notification provider\n *\n * Writes notifications to the PAI daemon log (stderr).\n * Always succeeds — it's the fallback channel.\n */\n\nimport type {\n NotificationProvider,\n NotificationPayload,\n NotificationConfig,\n} from \"../types.js\";\n\nexport class CliProvider implements NotificationProvider {\n readonly channelId = \"cli\" as const;\n\n async send(\n payload: NotificationPayload,\n _config: NotificationConfig\n ): Promise<boolean> {\n const prefix = `[pai-notify:${payload.event}]`;\n const title = payload.title ? ` ${payload.title}:` : \"\";\n process.stderr.write(`${prefix}${title} ${payload.message}\\n`);\n return true;\n }\n}\n","/**\n * router.ts — Notification router\n *\n * Routes notification events to the appropriate channels based on the\n * current mode and per-event routing config.\n *\n * Channel providers are instantiated lazily and cached.\n */\n\nimport type {\n NotificationPayload,\n NotificationConfig,\n NotificationProvider,\n ChannelId,\n SendResult,\n NotificationMode,\n} from \"./types.js\";\nimport { NtfyProvider } from \"./providers/ntfy.js\";\nimport { WhatsAppProvider } from \"./providers/whatsapp.js\";\nimport { MacOsProvider } from \"./providers/macos.js\";\nimport { CliProvider } from \"./providers/cli.js\";\n\n// ---------------------------------------------------------------------------\n// Provider registry (singletons — stateless, safe to reuse)\n// ---------------------------------------------------------------------------\n\nconst PROVIDERS: Record<ChannelId, NotificationProvider> = {\n ntfy: new NtfyProvider(),\n whatsapp: new WhatsAppProvider(),\n macos: new MacOsProvider(),\n voice: new WhatsAppProvider(), // Voice uses WhatsApp TTS; handled in WhatsAppProvider\n cli: new CliProvider(),\n};\n\n// ---------------------------------------------------------------------------\n// Channel resolution\n// ---------------------------------------------------------------------------\n\n/**\n * Given the current config, resolve which channels should receive a\n * notification for the given event type.\n *\n * Mode overrides:\n * \"off\" → no channels\n * \"auto\" → use routing table, filtered by enabled channels\n * \"voice\" → whatsapp (TTS enabled in provider)\n * \"whatsapp\" → whatsapp\n * \"ntfy\" → ntfy\n * \"macos\" → macos\n * \"cli\" → cli\n */\nfunction resolveChannels(\n config: NotificationConfig,\n event: NotificationPayload[\"event\"]\n): ChannelId[] {\n const { mode, channels, routing } = config;\n\n if (mode === \"off\") return [];\n\n // Non-auto modes: force a single channel\n const modeToChannel: Partial<Record<NotificationMode, ChannelId>> = {\n voice: \"whatsapp\", // WhatsAppProvider checks mode === \"voice\" for TTS\n whatsapp: \"whatsapp\",\n ntfy: \"ntfy\",\n macos: \"macos\",\n cli: \"cli\",\n };\n\n if (mode !== \"auto\") {\n const ch = modeToChannel[mode];\n if (!ch) return [];\n // Check the channel is enabled\n const cfg = channels[ch];\n if (cfg && !cfg.enabled) return [ch]; // Still send — mode override bypasses enabled check\n return [ch];\n }\n\n // Auto mode: use routing table, filter to enabled channels\n const candidates = routing[event] ?? [];\n return candidates.filter((ch) => {\n const cfg = channels[ch];\n // \"voice\" channel is virtual — it overlaps with whatsapp.\n // Skip \"voice\" as an independent channel; voice is handled by checking config.mode.\n if (ch === \"voice\") return false;\n return cfg?.enabled === true;\n });\n}\n\n// ---------------------------------------------------------------------------\n// Router\n// ---------------------------------------------------------------------------\n\n/**\n * Route a notification to the appropriate channels.\n *\n * Sends to all resolved channels in parallel.\n * Individual channel failures are non-fatal and logged to stderr.\n *\n * @param payload The notification to send\n * @param config The current notification config (from daemon state)\n */\nexport async function routeNotification(\n payload: NotificationPayload,\n config: NotificationConfig\n): Promise<SendResult> {\n const channels = resolveChannels(config, payload.event);\n\n if (channels.length === 0) {\n return {\n channelsAttempted: [],\n channelsSucceeded: [],\n channelsFailed: [],\n mode: config.mode,\n };\n }\n\n const results = await Promise.allSettled(\n channels.map(async (ch) => {\n const provider = PROVIDERS[ch];\n const ok = await provider.send(payload, config);\n if (!ok) {\n process.stderr.write(\n `[pai-notify] Channel ${ch} failed for event ${payload.event}\\n`\n );\n }\n return { ch, ok };\n })\n );\n\n const succeeded: ChannelId[] = [];\n const failed: ChannelId[] = [];\n\n for (const r of results) {\n if (r.status === \"fulfilled\") {\n if (r.value.ok) {\n succeeded.push(r.value.ch);\n } else {\n failed.push(r.value.ch);\n }\n } else {\n // Provider threw — treat as failure\n failed.push(channels[results.indexOf(r)]);\n }\n }\n\n return {\n channelsAttempted: channels,\n channelsSucceeded: succeeded,\n channelsFailed: failed,\n mode: config.mode,\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { i as getRegistryBackend } from "./factory-
|
|
2
|
-
import { D as resolveModelCapability, O as resolveProviderKey, T as readWorkersSection, h as classModelCapability, m as assertProviderRunnable, n as claudeCommand, p as WorkersConfigError, t as buildRunEnv, w as providerKeyPath, y as isModelCapability } from "./run-env-
|
|
3
|
-
import { _ as statusPath, d as resolveTarget, f as setCooldown, g as panesDir, h as ledgerPath, i as capabilityForRun, m as eventsPath, n as ensureProxyRunning, o as isQuotaFailure, p as ensureNoMcpConfig, s as nextAutoProvider, t as DEFAULT_PROXY_PORT, u as resolveCapabilityRun, v as workersLogDir } from "./server-
|
|
1
|
+
import { i as getRegistryBackend } from "./factory-ChfAvCzO.mjs";
|
|
2
|
+
import { D as resolveModelCapability, O as resolveProviderKey, T as readWorkersSection, h as classModelCapability, m as assertProviderRunnable, n as claudeCommand, p as WorkersConfigError, t as buildRunEnv, w as providerKeyPath, y as isModelCapability } from "./run-env-kbWVwUvU.mjs";
|
|
3
|
+
import { _ as statusPath, d as resolveTarget, f as setCooldown, g as panesDir, h as ledgerPath, i as capabilityForRun, m as eventsPath, n as ensureProxyRunning, o as isQuotaFailure, p as ensureNoMcpConfig, s as nextAutoProvider, t as DEFAULT_PROXY_PORT, u as resolveCapabilityRun, v as workersLogDir } from "./server-B0vBmfJt.mjs";
|
|
4
4
|
import { appendFileSync, closeSync, existsSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
|
|
5
5
|
import { homedir, tmpdir } from "node:os";
|
|
6
6
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
@@ -469,6 +469,10 @@ function elapsedOf(ts, now = /* @__PURE__ */ new Date()) {
|
|
|
469
469
|
/** Shorter sleeps are legitimate polling — not worth a supervision event. */
|
|
470
470
|
const SLEEP_FLOOR_SECS = 60;
|
|
471
471
|
const SLEEP_RE = /^\s*sleep\s+([0-9]+(?:\.[0-9]+)?)([smhd]?)\s*(?:$|[;&|])/;
|
|
472
|
+
/** s/m/h/d suffix to seconds multiplier, shared by every `sleep N<unit>` parse below. */
|
|
473
|
+
function sleepUnitMultiplier(unit) {
|
|
474
|
+
return unit === "m" ? 60 : unit === "h" ? 3600 : unit === "d" ? 86400 : 1;
|
|
475
|
+
}
|
|
472
476
|
/**
|
|
473
477
|
* Seconds of a `sleep N` (N plain or with an s/m/h/d suffix) that stands alone
|
|
474
478
|
* or leads a compound command (`sleep 150; cat f`, `sleep 2m && tail f`,
|
|
@@ -480,8 +484,7 @@ const SLEEP_RE = /^\s*sleep\s+([0-9]+(?:\.[0-9]+)?)([smhd]?)\s*(?:$|[;&|])/;
|
|
|
480
484
|
function parseSleepSecs(command) {
|
|
481
485
|
const m = command.match(SLEEP_RE);
|
|
482
486
|
if (!m) return null;
|
|
483
|
-
const
|
|
484
|
-
const secs = Number(m[1]) * mult;
|
|
487
|
+
const secs = Number(m[1]) * sleepUnitMultiplier(m[2]);
|
|
485
488
|
return Number.isFinite(secs) && secs >= SLEEP_FLOOR_SECS ? secs : null;
|
|
486
489
|
}
|
|
487
490
|
/** One-line description of a tool call, e.g. "Bash: npm test". */
|
|
@@ -1294,6 +1297,75 @@ function itermForClaudeSession(logDir, claudeSession, now = Date.now()) {
|
|
|
1294
1297
|
return term ? itermUuid(term) : null;
|
|
1295
1298
|
}
|
|
1296
1299
|
|
|
1300
|
+
//#endregion
|
|
1301
|
+
//#region src/workers/controls.ts
|
|
1302
|
+
/**
|
|
1303
|
+
* controls.ts — the clickr argv for handing desktop control to a worker or
|
|
1304
|
+
* taking it back, shared by `pai worker controls` and the follow pane's
|
|
1305
|
+
* "your controls" / "my controls" chat action, so both go through clickr the
|
|
1306
|
+
* same way.
|
|
1307
|
+
*
|
|
1308
|
+
* The grant is exclusive to one worker at a time: clickr's MCP server checks
|
|
1309
|
+
* PAI_WORKER_ID/pid against the `--agent <id> --pid <pid>` it was handed
|
|
1310
|
+
* control for. Crash paths are covered by clickr's own pid check; a clean
|
|
1311
|
+
* exit is covered by returnControlsIfHeld below, called from every exit path
|
|
1312
|
+
* of run.ts's runWorker.
|
|
1313
|
+
*/
|
|
1314
|
+
/** clickr argv for `who`: `you` binds the grant to `id` (and its runner pid, when known); `me` has no target. */
|
|
1315
|
+
function clickrControlsArgv(id, who, pid) {
|
|
1316
|
+
if (who === "me") return ["controls", "me"];
|
|
1317
|
+
const argv = [
|
|
1318
|
+
"controls",
|
|
1319
|
+
"you",
|
|
1320
|
+
"--agent",
|
|
1321
|
+
id
|
|
1322
|
+
];
|
|
1323
|
+
if (pid !== void 0) argv.push("--pid", String(pid));
|
|
1324
|
+
return argv;
|
|
1325
|
+
}
|
|
1326
|
+
/** clickr argv to hand a worker's grant back on its exit. */
|
|
1327
|
+
function clickrControlsReturnArgv(id) {
|
|
1328
|
+
return [
|
|
1329
|
+
"controls",
|
|
1330
|
+
"return",
|
|
1331
|
+
"--agent",
|
|
1332
|
+
id
|
|
1333
|
+
];
|
|
1334
|
+
}
|
|
1335
|
+
/** Record that `id` was granted clickr desktop controls (`who: "you"` succeeded). */
|
|
1336
|
+
function markControlsHeld(logDir, id) {
|
|
1337
|
+
const status = loadStatus(logDir, id);
|
|
1338
|
+
if (!status) return;
|
|
1339
|
+
status.controlsHeld = true;
|
|
1340
|
+
saveStatus(logDir, status);
|
|
1341
|
+
}
|
|
1342
|
+
/**
|
|
1343
|
+
* Best-effort hand desktop controls back on a worker's exit, when it ever
|
|
1344
|
+
* held them. Never throws and never hangs past clickr's own exit or a 3s
|
|
1345
|
+
* cap — a missing clickr binary or a stuck process must not hold up the
|
|
1346
|
+
* worker's own exit.
|
|
1347
|
+
*/
|
|
1348
|
+
async function returnControlsIfHeld(logDir, id) {
|
|
1349
|
+
if (!id) return;
|
|
1350
|
+
if (!loadStatus(logDir, id)?.controlsHeld) return;
|
|
1351
|
+
await new Promise((resolve) => {
|
|
1352
|
+
let done = false;
|
|
1353
|
+
const finish = () => {
|
|
1354
|
+
if (done) return;
|
|
1355
|
+
done = true;
|
|
1356
|
+
resolve();
|
|
1357
|
+
};
|
|
1358
|
+
try {
|
|
1359
|
+
const proc = spawn("clickr", clickrControlsReturnArgv(id), { stdio: "ignore" });
|
|
1360
|
+
proc.on("close", finish);
|
|
1361
|
+
proc.on("error", finish);
|
|
1362
|
+
} catch {
|
|
1363
|
+
finish();
|
|
1364
|
+
}
|
|
1365
|
+
setTimeout(finish, 3e3).unref();
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1297
1369
|
//#endregion
|
|
1298
1370
|
//#region src/workers/engines/image.ts
|
|
1299
1371
|
/**
|
|
@@ -2428,9 +2500,11 @@ function fullContractPrompt(format) {
|
|
|
2428
2500
|
"will pick something up later, never leave a background command as your final action. Finish, then",
|
|
2429
2501
|
"report.",
|
|
2430
2502
|
"No narration, no timestamps, no greetings, no summaries of what you read; act, verify, then stop.",
|
|
2431
|
-
"Never sleep-poll: no `sleep N` over 60 seconds,
|
|
2432
|
-
"
|
|
2433
|
-
"
|
|
2503
|
+
"Never sleep-poll: no `sleep N` over 60 seconds, anywhere in a command — standalone, after `&&`/`||`/",
|
|
2504
|
+
"`;`/`|`, or inside a loop or subshell. Ending your turn ends this process, so a background job gets",
|
|
2505
|
+
"no later wake-up here: run the long command in the FOREGROUND with a Bash timeout up to 600000 ms;",
|
|
2506
|
+
"if it can run longer than that, start it in the background and wait with a foreground loop that ends",
|
|
2507
|
+
"when the job ends: `while kill -0 <pid> 2>/dev/null; do sleep 15; done`.",
|
|
2434
2508
|
...fileNotInlineBlock(),
|
|
2435
2509
|
"",
|
|
2436
2510
|
`Operator messages: a user turn starting with ${OPERATOR_MARK} was typed by the operator while you`,
|
|
@@ -3041,7 +3115,7 @@ async function runWorker(opts) {
|
|
|
3041
3115
|
const logDir = workersLogDir(config);
|
|
3042
3116
|
mkdirSync(logDir, { recursive: true });
|
|
3043
3117
|
if (opts.className === "plan" && !opts._planner) {
|
|
3044
|
-
const { runPlanner } = await import("./planner-
|
|
3118
|
+
const { runPlanner } = await import("./planner-DbpsMZqY.mjs");
|
|
3045
3119
|
return runPlanner(opts);
|
|
3046
3120
|
}
|
|
3047
3121
|
const parent = launchParent(opts.parent);
|
|
@@ -3066,8 +3140,14 @@ async function runWorker(opts) {
|
|
|
3066
3140
|
const label = opts.label ?? shortText(parsed.prompt ?? UNLABELED, 70);
|
|
3067
3141
|
const model = capabilityResolution ? capabilityResolution.model : resolveRunModel(target, opts.className, opts.modelFlag);
|
|
3068
3142
|
const reportFormat = resolveReportFormat(opts.reportFormatFlag);
|
|
3143
|
+
let workerId = opts.id;
|
|
3144
|
+
const trackStart = (wid) => {
|
|
3145
|
+
workerId = wid;
|
|
3146
|
+
opts.onWorkerStart?.(wid);
|
|
3147
|
+
};
|
|
3069
3148
|
try {
|
|
3070
|
-
|
|
3149
|
+
let rc;
|
|
3150
|
+
if (target.provider.engine === "image") rc = await executeImageRun({
|
|
3071
3151
|
config,
|
|
3072
3152
|
logDir,
|
|
3073
3153
|
target,
|
|
@@ -3083,10 +3163,10 @@ async function runWorker(opts) {
|
|
|
3083
3163
|
parent: parent ?? void 0,
|
|
3084
3164
|
stage: opts.stage,
|
|
3085
3165
|
quiet: opts.quiet,
|
|
3086
|
-
onWorkerStart:
|
|
3166
|
+
onWorkerStart: trackStart,
|
|
3087
3167
|
id: opts.id
|
|
3088
3168
|
});
|
|
3089
|
-
if (target.provider.engine === "codex")
|
|
3169
|
+
else if (target.provider.engine === "codex") rc = await executeCodexRun({
|
|
3090
3170
|
config,
|
|
3091
3171
|
logDir,
|
|
3092
3172
|
target,
|
|
@@ -3100,14 +3180,14 @@ async function runWorker(opts) {
|
|
|
3100
3180
|
parent: parent ?? void 0,
|
|
3101
3181
|
stage: opts.stage,
|
|
3102
3182
|
quiet: opts.quiet,
|
|
3103
|
-
onWorkerStart:
|
|
3183
|
+
onWorkerStart: trackStart,
|
|
3104
3184
|
id: opts.id,
|
|
3105
3185
|
worktreeFlag: opts.worktreeFlag,
|
|
3106
3186
|
className: opts.className,
|
|
3107
3187
|
reportFormat,
|
|
3108
3188
|
capability: capability ?? void 0
|
|
3109
3189
|
});
|
|
3110
|
-
|
|
3190
|
+
else rc = await executeRun({
|
|
3111
3191
|
config,
|
|
3112
3192
|
logDir,
|
|
3113
3193
|
target,
|
|
@@ -3122,7 +3202,7 @@ async function runWorker(opts) {
|
|
|
3122
3202
|
parent: parent ?? void 0,
|
|
3123
3203
|
stage: opts.stage,
|
|
3124
3204
|
quiet: opts.quiet,
|
|
3125
|
-
onWorkerStart:
|
|
3205
|
+
onWorkerStart: trackStart,
|
|
3126
3206
|
id: opts.id,
|
|
3127
3207
|
capability: capability ?? void 0,
|
|
3128
3208
|
worktreeFlag: opts.worktreeFlag,
|
|
@@ -3132,7 +3212,10 @@ async function runWorker(opts) {
|
|
|
3132
3212
|
reportFormat,
|
|
3133
3213
|
noReportRetry: opts.noReportRetry ?? false
|
|
3134
3214
|
});
|
|
3215
|
+
await returnControlsIfHeld(logDir, workerId);
|
|
3216
|
+
return rc;
|
|
3135
3217
|
} catch (e) {
|
|
3218
|
+
await returnControlsIfHeld(logDir, workerId);
|
|
3136
3219
|
if (e instanceof Error && e.message.startsWith("key file")) throw new Error(`provider "${target.providerName}": ${e.message}\nPut the token in that file (chmod 600) or point keyFile elsewhere.`);
|
|
3137
3220
|
throw e;
|
|
3138
3221
|
}
|
|
@@ -4129,5 +4212,5 @@ function resultFromOutput(out) {
|
|
|
4129
4212
|
}
|
|
4130
4213
|
|
|
4131
4214
|
//#endregion
|
|
4132
|
-
export {
|
|
4133
|
-
//# sourceMappingURL=run-
|
|
4215
|
+
export { UNLABELED as A, newWorkerId as B, workerInScope as C, worktreesDir as D, mergeWorker as E, elapsedOf as F, waitForTerminalStatus as G, ownsPid as H, isChatPane as I, shortText as J, longInlinePromptHint as K, isLive as L, alive as M, contextLabel as N, appendLedger as O, contextPercent as P, loadStatus as R, sessionTag as S, git as T, saveStatus as U, nowStamp as V, setWorkerLabel as W, expandMcpNames as X, describeMcp as Y, currentTabKey as _, projectLaunchConfig as a, resolveSession as b, handoffFromInside as c, workerDepth as d, checkPaneForWorker as f, markControlsHeld as g, clickrControlsArgv as h, usageContextTokens as i, ageOf as j, ledgerSummary as k, readInbox as l, openPaneForWorker as m, runWorker as n, parseWorkerReport as o, openFollowPane as p, parseRunnerArgs as q, testProvider as r, renderReport as s, printResult as t, sayToWorker as u, itermForClaudeSession as v, discardWorker as w, sessionMapPath as x, itermUuid as y, loadStatuses as z };
|
|
4216
|
+
//# sourceMappingURL=run-BFxuNnIJ.mjs.map
|