@tekmidian/pai 0.59.0 → 0.60.1
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-CGyAIHo4.mjs} +4 -4
- package/dist/{chain-Cltx0NjY.mjs.map → chain-CGyAIHo4.mjs.map} +1 -1
- package/dist/cli/index.mjs +12 -12
- package/dist/cli/program.mjs +12 -12
- package/dist/{config-D1omG_UU.mjs → config-CQCmH_gV.mjs} +17 -4
- package/dist/{config-D1omG_UU.mjs.map → config-CQCmH_gV.mjs.map} +1 -1
- package/dist/{config-DCYWFaEi.mjs → config-DjXq_W42.mjs} +1 -1
- package/dist/{context-handover-cache-Dt4HW1Ro.mjs → context-handover-cache-BzM7Qudn.mjs} +4 -4
- package/dist/{context-handover-cache-Dt4HW1Ro.mjs.map → context-handover-cache-BzM7Qudn.mjs.map} +1 -1
- package/dist/daemon/index.mjs +12 -12
- package/dist/{daemon-CmqJfeb9.mjs → daemon-B_TodsIS.mjs} +97 -23
- package/dist/daemon-B_TodsIS.mjs.map +1 -0
- package/dist/daemon-jviL3VVJ.mjs +19 -0
- package/dist/daemon-mcp/index.mjs +9 -9
- package/dist/daemon-mcp/index.mjs.map +1 -1
- package/dist/{factory-C9wGUe30.mjs → factory-DW7BCJJT.mjs} +1 -1
- package/dist/{factory-DiuntvwG.mjs → factory-EUUhSSoq.mjs} +7 -8
- package/dist/factory-EUUhSSoq.mjs.map +1 -0
- package/dist/{fallback-B_2ozQHw.mjs → fallback-CjdakQVu.mjs} +80 -9
- package/dist/fallback-CjdakQVu.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 +5 -4
- package/dist/hooks/route-agents-to-worker.mjs.map +3 -3
- package/dist/hooks/security-validator.mjs +5 -4
- 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 +5 -4
- package/dist/hooks/whisper-rules.mjs.map +3 -3
- package/dist/hooks/worker-proxy.mjs +5 -4
- package/dist/hooks/worker-proxy.mjs.map +3 -3
- package/dist/hooks/worker-status-line.mjs +10 -7
- package/dist/hooks/worker-status-line.mjs.map +3 -3
- package/dist/hooks/worker-supervision.mjs +5 -4
- package/dist/hooks/worker-supervision.mjs.map +3 -3
- package/dist/{main-resolver-DYNxxNwJ.mjs → main-resolver-BFQfU5xo.mjs} +100 -21
- package/dist/main-resolver-BFQfU5xo.mjs.map +1 -0
- package/dist/main-resolver-VMBENifT.mjs +7 -0
- package/dist/{planner-DsZZEZCA.mjs → planner-DN967Lvm.mjs} +83 -30
- package/dist/planner-DN967Lvm.mjs.map +1 -0
- package/dist/{program-B5miXqQa.mjs → program-BnsjB57Q.mjs} +278 -92
- package/dist/program-BnsjB57Q.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-FeM1_UVa.mjs} +102 -18
- package/dist/run-FeM1_UVa.mjs.map +1 -0
- package/dist/{run-env-DyTWgZC9.mjs → run-env-CEHTeIUM.mjs} +2 -2
- package/dist/{run-env-DyTWgZC9.mjs.map → run-env-CEHTeIUM.mjs.map} +1 -1
- package/dist/{server-CzqHGbKt.mjs → server-CekGOJcp.mjs} +2 -2
- package/dist/{server-CzqHGbKt.mjs.map → server-CekGOJcp.mjs.map} +1 -1
- package/dist/{session-keepalive-DDsEo81Q.mjs → session-keepalive-Bc9aT678.mjs} +5 -5
- package/dist/{session-keepalive-DDsEo81Q.mjs.map → session-keepalive-Bc9aT678.mjs.map} +1 -1
- package/dist/{tools-Diro2s8J.mjs → tools-qXyHFwNj.mjs} +2 -2
- package/dist/{tools-Diro2s8J.mjs.map → tools-qXyHFwNj.mjs.map} +1 -1
- package/dist/{tools-KgL1rkPD.mjs → tools-r-7ZG8B5.mjs} +3 -3
- package/dist/{wakeup-D0uvqLsE.mjs → wakeup-C48hcN6O.mjs} +2 -2
- package/dist/wakeup-C48hcN6O.mjs.map +1 -0
- package/dist/{work-queue-worker-DL9lu8sN.mjs → work-queue-worker-BL4dq5P7.mjs} +2 -2
- package/dist/{work-queue-worker-DL9lu8sN.mjs.map → work-queue-worker-BL4dq5P7.mjs.map} +1 -1
- package/dist/work-queue-worker-DVOVcEnO.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/daemon-B6hx1Ysz.mjs +0 -19
- package/dist/daemon-CmqJfeb9.mjs.map +0 -1
- package/dist/factory-DiuntvwG.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-EUUhSSoq.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-CEHTeIUM.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-CekGOJcp.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". */
|
|
@@ -636,7 +639,8 @@ function isGitRepo(cwd) {
|
|
|
636
639
|
function promptLooksReadonly(prompt) {
|
|
637
640
|
const p = prompt.trim();
|
|
638
641
|
if (!p) return true;
|
|
639
|
-
|
|
642
|
+
const firstLine = p.split("\n", 1)[0];
|
|
643
|
+
if (/\b(read[- ]only|do not (modify|change|edit|write)|don'?t (modify|change|edit|write)|no changes)\b/i.test(firstLine)) return true;
|
|
640
644
|
return /^(review|read|analy[sz]e|research|summar[iy]|inspect|investigate|spotcheck|report|find|list|check|verify|describe|explain|show)\b/i.test(p);
|
|
641
645
|
}
|
|
642
646
|
/** The classes whose runs write files and therefore default to a worktree. */
|
|
@@ -1294,6 +1298,75 @@ function itermForClaudeSession(logDir, claudeSession, now = Date.now()) {
|
|
|
1294
1298
|
return term ? itermUuid(term) : null;
|
|
1295
1299
|
}
|
|
1296
1300
|
|
|
1301
|
+
//#endregion
|
|
1302
|
+
//#region src/workers/controls.ts
|
|
1303
|
+
/**
|
|
1304
|
+
* controls.ts — the clickr argv for handing desktop control to a worker or
|
|
1305
|
+
* taking it back, shared by `pai worker controls` and the follow pane's
|
|
1306
|
+
* "your controls" / "my controls" chat action, so both go through clickr the
|
|
1307
|
+
* same way.
|
|
1308
|
+
*
|
|
1309
|
+
* The grant is exclusive to one worker at a time: clickr's MCP server checks
|
|
1310
|
+
* PAI_WORKER_ID/pid against the `--agent <id> --pid <pid>` it was handed
|
|
1311
|
+
* control for. Crash paths are covered by clickr's own pid check; a clean
|
|
1312
|
+
* exit is covered by returnControlsIfHeld below, called from every exit path
|
|
1313
|
+
* of run.ts's runWorker.
|
|
1314
|
+
*/
|
|
1315
|
+
/** clickr argv for `who`: `you` binds the grant to `id` (and its runner pid, when known); `me` has no target. */
|
|
1316
|
+
function clickrControlsArgv(id, who, pid) {
|
|
1317
|
+
if (who === "me") return ["controls", "me"];
|
|
1318
|
+
const argv = [
|
|
1319
|
+
"controls",
|
|
1320
|
+
"you",
|
|
1321
|
+
"--agent",
|
|
1322
|
+
id
|
|
1323
|
+
];
|
|
1324
|
+
if (pid !== void 0) argv.push("--pid", String(pid));
|
|
1325
|
+
return argv;
|
|
1326
|
+
}
|
|
1327
|
+
/** clickr argv to hand a worker's grant back on its exit. */
|
|
1328
|
+
function clickrControlsReturnArgv(id) {
|
|
1329
|
+
return [
|
|
1330
|
+
"controls",
|
|
1331
|
+
"return",
|
|
1332
|
+
"--agent",
|
|
1333
|
+
id
|
|
1334
|
+
];
|
|
1335
|
+
}
|
|
1336
|
+
/** Record that `id` was granted clickr desktop controls (`who: "you"` succeeded). */
|
|
1337
|
+
function markControlsHeld(logDir, id) {
|
|
1338
|
+
const status = loadStatus(logDir, id);
|
|
1339
|
+
if (!status) return;
|
|
1340
|
+
status.controlsHeld = true;
|
|
1341
|
+
saveStatus(logDir, status);
|
|
1342
|
+
}
|
|
1343
|
+
/**
|
|
1344
|
+
* Best-effort hand desktop controls back on a worker's exit, when it ever
|
|
1345
|
+
* held them. Never throws and never hangs past clickr's own exit or a 3s
|
|
1346
|
+
* cap — a missing clickr binary or a stuck process must not hold up the
|
|
1347
|
+
* worker's own exit.
|
|
1348
|
+
*/
|
|
1349
|
+
async function returnControlsIfHeld(logDir, id) {
|
|
1350
|
+
if (!id) return;
|
|
1351
|
+
if (!loadStatus(logDir, id)?.controlsHeld) return;
|
|
1352
|
+
await new Promise((resolve) => {
|
|
1353
|
+
let done = false;
|
|
1354
|
+
const finish = () => {
|
|
1355
|
+
if (done) return;
|
|
1356
|
+
done = true;
|
|
1357
|
+
resolve();
|
|
1358
|
+
};
|
|
1359
|
+
try {
|
|
1360
|
+
const proc = spawn("clickr", clickrControlsReturnArgv(id), { stdio: "ignore" });
|
|
1361
|
+
proc.on("close", finish);
|
|
1362
|
+
proc.on("error", finish);
|
|
1363
|
+
} catch {
|
|
1364
|
+
finish();
|
|
1365
|
+
}
|
|
1366
|
+
setTimeout(finish, 3e3).unref();
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1297
1370
|
//#endregion
|
|
1298
1371
|
//#region src/workers/engines/image.ts
|
|
1299
1372
|
/**
|
|
@@ -2428,9 +2501,11 @@ function fullContractPrompt(format) {
|
|
|
2428
2501
|
"will pick something up later, never leave a background command as your final action. Finish, then",
|
|
2429
2502
|
"report.",
|
|
2430
2503
|
"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
|
-
"
|
|
2504
|
+
"Never sleep-poll: no `sleep N` over 60 seconds, anywhere in a command — standalone, after `&&`/`||`/",
|
|
2505
|
+
"`;`/`|`, or inside a loop or subshell. Ending your turn ends this process, so a background job gets",
|
|
2506
|
+
"no later wake-up here: run the long command in the FOREGROUND with a Bash timeout up to 600000 ms;",
|
|
2507
|
+
"if it can run longer than that, start it in the background and wait with a foreground loop that ends",
|
|
2508
|
+
"when the job ends: `while kill -0 <pid> 2>/dev/null; do sleep 15; done`.",
|
|
2434
2509
|
...fileNotInlineBlock(),
|
|
2435
2510
|
"",
|
|
2436
2511
|
`Operator messages: a user turn starting with ${OPERATOR_MARK} was typed by the operator while you`,
|
|
@@ -3041,7 +3116,7 @@ async function runWorker(opts) {
|
|
|
3041
3116
|
const logDir = workersLogDir(config);
|
|
3042
3117
|
mkdirSync(logDir, { recursive: true });
|
|
3043
3118
|
if (opts.className === "plan" && !opts._planner) {
|
|
3044
|
-
const { runPlanner } = await import("./planner-
|
|
3119
|
+
const { runPlanner } = await import("./planner-DN967Lvm.mjs");
|
|
3045
3120
|
return runPlanner(opts);
|
|
3046
3121
|
}
|
|
3047
3122
|
const parent = launchParent(opts.parent);
|
|
@@ -3066,8 +3141,14 @@ async function runWorker(opts) {
|
|
|
3066
3141
|
const label = opts.label ?? shortText(parsed.prompt ?? UNLABELED, 70);
|
|
3067
3142
|
const model = capabilityResolution ? capabilityResolution.model : resolveRunModel(target, opts.className, opts.modelFlag);
|
|
3068
3143
|
const reportFormat = resolveReportFormat(opts.reportFormatFlag);
|
|
3144
|
+
let workerId = opts.id;
|
|
3145
|
+
const trackStart = (wid) => {
|
|
3146
|
+
workerId = wid;
|
|
3147
|
+
opts.onWorkerStart?.(wid);
|
|
3148
|
+
};
|
|
3069
3149
|
try {
|
|
3070
|
-
|
|
3150
|
+
let rc;
|
|
3151
|
+
if (target.provider.engine === "image") rc = await executeImageRun({
|
|
3071
3152
|
config,
|
|
3072
3153
|
logDir,
|
|
3073
3154
|
target,
|
|
@@ -3083,10 +3164,10 @@ async function runWorker(opts) {
|
|
|
3083
3164
|
parent: parent ?? void 0,
|
|
3084
3165
|
stage: opts.stage,
|
|
3085
3166
|
quiet: opts.quiet,
|
|
3086
|
-
onWorkerStart:
|
|
3167
|
+
onWorkerStart: trackStart,
|
|
3087
3168
|
id: opts.id
|
|
3088
3169
|
});
|
|
3089
|
-
if (target.provider.engine === "codex")
|
|
3170
|
+
else if (target.provider.engine === "codex") rc = await executeCodexRun({
|
|
3090
3171
|
config,
|
|
3091
3172
|
logDir,
|
|
3092
3173
|
target,
|
|
@@ -3100,14 +3181,14 @@ async function runWorker(opts) {
|
|
|
3100
3181
|
parent: parent ?? void 0,
|
|
3101
3182
|
stage: opts.stage,
|
|
3102
3183
|
quiet: opts.quiet,
|
|
3103
|
-
onWorkerStart:
|
|
3184
|
+
onWorkerStart: trackStart,
|
|
3104
3185
|
id: opts.id,
|
|
3105
3186
|
worktreeFlag: opts.worktreeFlag,
|
|
3106
3187
|
className: opts.className,
|
|
3107
3188
|
reportFormat,
|
|
3108
3189
|
capability: capability ?? void 0
|
|
3109
3190
|
});
|
|
3110
|
-
|
|
3191
|
+
else rc = await executeRun({
|
|
3111
3192
|
config,
|
|
3112
3193
|
logDir,
|
|
3113
3194
|
target,
|
|
@@ -3122,7 +3203,7 @@ async function runWorker(opts) {
|
|
|
3122
3203
|
parent: parent ?? void 0,
|
|
3123
3204
|
stage: opts.stage,
|
|
3124
3205
|
quiet: opts.quiet,
|
|
3125
|
-
onWorkerStart:
|
|
3206
|
+
onWorkerStart: trackStart,
|
|
3126
3207
|
id: opts.id,
|
|
3127
3208
|
capability: capability ?? void 0,
|
|
3128
3209
|
worktreeFlag: opts.worktreeFlag,
|
|
@@ -3132,7 +3213,10 @@ async function runWorker(opts) {
|
|
|
3132
3213
|
reportFormat,
|
|
3133
3214
|
noReportRetry: opts.noReportRetry ?? false
|
|
3134
3215
|
});
|
|
3216
|
+
await returnControlsIfHeld(logDir, workerId);
|
|
3217
|
+
return rc;
|
|
3135
3218
|
} catch (e) {
|
|
3219
|
+
await returnControlsIfHeld(logDir, workerId);
|
|
3136
3220
|
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
3221
|
throw e;
|
|
3138
3222
|
}
|
|
@@ -4129,5 +4213,5 @@ function resultFromOutput(out) {
|
|
|
4129
4213
|
}
|
|
4130
4214
|
|
|
4131
4215
|
//#endregion
|
|
4132
|
-
export {
|
|
4133
|
-
//# sourceMappingURL=run-
|
|
4216
|
+
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 };
|
|
4217
|
+
//# sourceMappingURL=run-FeM1_UVa.mjs.map
|