@kibitzsh/kibitz 0.0.3 → 0.0.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.
- package/README.md +2 -0
- package/dist/cli/index.js +341 -2609
- package/dist/core/commentary.js +602 -924
- package/dist/core/platform-support.js +155 -127
- package/dist/core/providers/anthropic.d.ts.map +1 -1
- package/dist/core/providers/anthropic.js +9 -2
- package/dist/core/providers/anthropic.js.map +1 -1
- package/dist/core/session-dispatch.js +379 -391
- package/dist/core/watcher.d.ts +1 -1
- package/dist/core/watcher.d.ts.map +1 -1
- package/dist/core/watcher.js +834 -795
- package/dist/core/watcher.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,453 +1,441 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
var
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
|
|
30
|
-
// src/core/session-dispatch.ts
|
|
31
|
-
var session_dispatch_exports = {};
|
|
32
|
-
__export(session_dispatch_exports, {
|
|
33
|
-
SessionDispatchService: () => SessionDispatchService,
|
|
34
|
-
buildExistingDispatchCommand: () => buildExistingDispatchCommand,
|
|
35
|
-
buildInteractiveDispatchCommand: () => buildInteractiveDispatchCommand,
|
|
36
|
-
resolveDispatchCommand: () => resolveDispatchCommand
|
|
37
|
-
});
|
|
38
|
-
module.exports = __toCommonJS(session_dispatch_exports);
|
|
39
|
-
var import_events = require("events");
|
|
40
|
-
var import_child_process2 = require("child_process");
|
|
41
|
-
var fs2 = __toESM(require("fs"));
|
|
42
|
-
var path2 = __toESM(require("path"));
|
|
43
|
-
|
|
44
|
-
// src/core/platform-support.ts
|
|
45
|
-
var import_child_process = require("child_process");
|
|
46
|
-
var fs = __toESM(require("fs"));
|
|
47
|
-
var path = __toESM(require("path"));
|
|
48
|
-
function getProviderCliCommand(provider, platform = process.platform) {
|
|
49
|
-
return platform === "win32" ? `${provider}.cmd` : provider;
|
|
50
|
-
}
|
|
51
|
-
function resolveCmdNodeScript(cmdPath) {
|
|
52
|
-
if (!String(cmdPath || "").toLowerCase().endsWith(".cmd")) return null;
|
|
53
|
-
try {
|
|
54
|
-
const content = fs.readFileSync(cmdPath, "utf8");
|
|
55
|
-
const match = content.match(/%dp0%\\(.+?\.js)/i);
|
|
56
|
-
if (!match) return null;
|
|
57
|
-
const scriptPath = path.join(path.dirname(cmdPath), match[1]);
|
|
58
|
-
return fs.existsSync(scriptPath) ? scriptPath : null;
|
|
59
|
-
} catch {
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
function findCommandPath(command, platform = process.platform) {
|
|
64
|
-
try {
|
|
65
|
-
if (platform === "win32") {
|
|
66
|
-
const out2 = (0, import_child_process.execSync)(`where ${command}`, {
|
|
67
|
-
encoding: "utf8",
|
|
68
|
-
timeout: 5e3,
|
|
69
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
70
|
-
}).trim();
|
|
71
|
-
const first = out2.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
72
|
-
return first || void 0;
|
|
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]; } };
|
|
73
7
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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.SessionDispatchService = void 0;
|
|
37
|
+
exports.buildExistingDispatchCommand = buildExistingDispatchCommand;
|
|
38
|
+
exports.buildInteractiveDispatchCommand = buildInteractiveDispatchCommand;
|
|
39
|
+
exports.resolveDispatchCommand = resolveDispatchCommand;
|
|
40
|
+
const events_1 = require("events");
|
|
41
|
+
const child_process_1 = require("child_process");
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const platform_support_1 = require("./platform-support");
|
|
45
|
+
class SessionDispatchService extends events_1.EventEmitter {
|
|
46
|
+
options;
|
|
47
|
+
constructor(options) {
|
|
48
|
+
super();
|
|
49
|
+
this.options = options;
|
|
96
50
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
51
|
+
async dispatch(request) {
|
|
52
|
+
const prompt = String(request.prompt || '').trim();
|
|
53
|
+
if (!prompt) {
|
|
54
|
+
this.emitStatus('failed', request.target, 'Prompt cannot be empty');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this.emitStatus('queued', request.target, `Queued for ${describeTarget(request.target)}`);
|
|
58
|
+
if (request.target.kind === 'existing') {
|
|
59
|
+
await this.dispatchExistingSession(request.target, prompt);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const provider = request.target.kind === 'new-codex' ? 'codex' : 'claude';
|
|
63
|
+
this.emitStatus('started', request.target, `Starting new ${provider} session`);
|
|
64
|
+
try {
|
|
65
|
+
if (request.origin === 'vscode') {
|
|
66
|
+
if (!this.options.launchInteractiveSession) {
|
|
67
|
+
throw new Error('Interactive launcher is not available in VS Code mode');
|
|
68
|
+
}
|
|
69
|
+
await this.options.launchInteractiveSession(provider, prompt);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
await runInteractiveInTerminal(provider, prompt);
|
|
73
|
+
}
|
|
74
|
+
this.emitStatus('sent', request.target, `Started new ${provider} session`);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
this.emitStatus('failed', request.target, normalizeError(error));
|
|
78
|
+
}
|
|
101
79
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
80
|
+
async dispatchExistingSession(target, prompt) {
|
|
81
|
+
const targetSessionId = String(target.sessionId || '').trim().toLowerCase();
|
|
82
|
+
const targetAgent = target.agent;
|
|
83
|
+
if (!targetSessionId || (targetAgent !== 'claude' && targetAgent !== 'codex')) {
|
|
84
|
+
this.emitStatus('failed', target, 'Missing target session or provider');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const active = this.options.getActiveSessions();
|
|
88
|
+
const activeMatch = active.find((session) => {
|
|
89
|
+
return session.agent === targetAgent && session.id.toLowerCase() === targetSessionId;
|
|
90
|
+
});
|
|
91
|
+
if (!activeMatch) {
|
|
92
|
+
this.emitStatus('failed', target, `Selected ${describeProvider(targetAgent)} session is not active`);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const targetLabel = describeTarget(target);
|
|
96
|
+
this.emitStatus('started', target, `Dispatching to ${targetLabel}`);
|
|
97
|
+
try {
|
|
98
|
+
const command = buildExistingDispatchCommand(target, prompt);
|
|
99
|
+
const dispatchCwd = deriveDispatchCwdForSession(activeMatch);
|
|
100
|
+
const beforeDispatch = captureSessionFileSnapshot(activeMatch.filePath);
|
|
101
|
+
const handle = await startBackgroundCommand(command, { cwd: dispatchCwd });
|
|
102
|
+
const dispatchOutcome = await runWithTimeout(waitForDispatchAcknowledgement(activeMatch.filePath, prompt, beforeDispatch, handle.completion), 20_000, 'Dispatch timed out waiting for target session update');
|
|
103
|
+
// If process exited before prompt was observed, enforce full verification.
|
|
104
|
+
if (dispatchOutcome === 'process-complete') {
|
|
105
|
+
verifyExistingDispatchDelivery(activeMatch.filePath, prompt, beforeDispatch);
|
|
106
|
+
}
|
|
107
|
+
// Keep completion promise observed to avoid unhandled rejections after early ack.
|
|
108
|
+
void handle.completion.catch(() => undefined);
|
|
109
|
+
this.emitStatus('sent', target, `Prompt sent to ${targetLabel}`);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
this.emitStatus('failed', target, normalizeError(error));
|
|
108
113
|
}
|
|
109
|
-
await this.options.launchInteractiveSession(provider, prompt);
|
|
110
|
-
} else {
|
|
111
|
-
await runInteractiveInTerminal(provider, prompt);
|
|
112
|
-
}
|
|
113
|
-
this.emitStatus("sent", request.target, `Started new ${provider} session`);
|
|
114
|
-
} catch (error) {
|
|
115
|
-
this.emitStatus("failed", request.target, normalizeError(error));
|
|
116
114
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
115
|
+
emitStatus(state, target, message) {
|
|
116
|
+
const status = {
|
|
117
|
+
state,
|
|
118
|
+
message,
|
|
119
|
+
target,
|
|
120
|
+
timestamp: Date.now(),
|
|
121
|
+
};
|
|
122
|
+
this.emit('status', status);
|
|
124
123
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
this.emitStatus("failed", target, `Selected ${describeProvider(targetAgent)} session is not active`);
|
|
131
|
-
return;
|
|
124
|
+
}
|
|
125
|
+
exports.SessionDispatchService = SessionDispatchService;
|
|
126
|
+
function buildExistingDispatchCommand(target, prompt, platform = process.platform) {
|
|
127
|
+
if (target.kind !== 'existing') {
|
|
128
|
+
throw new Error(`Expected existing target, got "${target.kind}"`);
|
|
132
129
|
}
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
beforeDispatch,
|
|
145
|
-
handle.completion
|
|
146
|
-
),
|
|
147
|
-
2e4,
|
|
148
|
-
"Dispatch timed out waiting for target session update"
|
|
149
|
-
);
|
|
150
|
-
if (dispatchOutcome === "process-complete") {
|
|
151
|
-
verifyExistingDispatchDelivery(activeMatch.filePath, prompt, beforeDispatch);
|
|
152
|
-
}
|
|
153
|
-
void handle.completion.catch(() => void 0);
|
|
154
|
-
this.emitStatus("sent", target, `Prompt sent to ${targetLabel}`);
|
|
155
|
-
} catch (error) {
|
|
156
|
-
this.emitStatus("failed", target, normalizeError(error));
|
|
130
|
+
const sessionId = String(target.sessionId || '').trim();
|
|
131
|
+
const agent = target.agent;
|
|
132
|
+
if (!sessionId || (agent !== 'claude' && agent !== 'codex')) {
|
|
133
|
+
throw new Error('Missing existing-session target details');
|
|
134
|
+
}
|
|
135
|
+
if (agent === 'codex') {
|
|
136
|
+
return {
|
|
137
|
+
provider: 'codex',
|
|
138
|
+
command: (0, platform_support_1.getProviderCliCommand)('codex', platform),
|
|
139
|
+
args: ['exec', 'resume', '--json', '--skip-git-repo-check', sessionId, prompt],
|
|
140
|
+
};
|
|
157
141
|
}
|
|
158
|
-
}
|
|
159
|
-
emitStatus(state, target, message) {
|
|
160
|
-
const status = {
|
|
161
|
-
state,
|
|
162
|
-
message,
|
|
163
|
-
target,
|
|
164
|
-
timestamp: Date.now()
|
|
165
|
-
};
|
|
166
|
-
this.emit("status", status);
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
function buildExistingDispatchCommand(target, prompt, platform = process.platform) {
|
|
170
|
-
if (target.kind !== "existing") {
|
|
171
|
-
throw new Error(`Expected existing target, got "${target.kind}"`);
|
|
172
|
-
}
|
|
173
|
-
const sessionId = String(target.sessionId || "").trim();
|
|
174
|
-
const agent = target.agent;
|
|
175
|
-
if (!sessionId || agent !== "claude" && agent !== "codex") {
|
|
176
|
-
throw new Error("Missing existing-session target details");
|
|
177
|
-
}
|
|
178
|
-
if (agent === "codex") {
|
|
179
142
|
return {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
143
|
+
provider: 'claude',
|
|
144
|
+
command: (0, platform_support_1.getProviderCliCommand)('claude', platform),
|
|
145
|
+
args: ['-p', prompt, '--verbose', '--output-format', 'stream-json', '--resume', sessionId],
|
|
183
146
|
};
|
|
184
|
-
}
|
|
185
|
-
return {
|
|
186
|
-
provider: "claude",
|
|
187
|
-
command: getProviderCliCommand("claude", platform),
|
|
188
|
-
args: ["-p", prompt, "--verbose", "--output-format", "stream-json", "--resume", sessionId]
|
|
189
|
-
};
|
|
190
147
|
}
|
|
191
148
|
function buildInteractiveDispatchCommand(provider, prompt, platform = process.platform) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
149
|
+
return {
|
|
150
|
+
provider,
|
|
151
|
+
command: (0, platform_support_1.getProviderCliCommand)(provider, platform),
|
|
152
|
+
args: [prompt],
|
|
153
|
+
};
|
|
197
154
|
}
|
|
198
155
|
function resolveDispatchCommand(command, platform = process.platform) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
156
|
+
const pathHint = (0, platform_support_1.findCommandPath)(command.command, platform);
|
|
157
|
+
if (!pathHint) {
|
|
158
|
+
throw new Error(`CLI not found: ${command.command}`);
|
|
159
|
+
}
|
|
160
|
+
if (platform === 'win32') {
|
|
161
|
+
const nodeScript = (0, platform_support_1.resolveCmdNodeScript)(pathHint);
|
|
162
|
+
if (nodeScript) {
|
|
163
|
+
return {
|
|
164
|
+
command: process.execPath,
|
|
165
|
+
args: [nodeScript, ...command.args],
|
|
166
|
+
shell: false,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (pathHint.toLowerCase().endsWith('.cmd')) {
|
|
170
|
+
return {
|
|
171
|
+
command: pathHint,
|
|
172
|
+
args: command.args,
|
|
173
|
+
shell: true,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
211
176
|
}
|
|
212
|
-
|
|
213
|
-
return {
|
|
177
|
+
return {
|
|
214
178
|
command: pathHint,
|
|
215
179
|
args: command.args,
|
|
216
|
-
shell:
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
return {
|
|
221
|
-
command: pathHint,
|
|
222
|
-
args: command.args,
|
|
223
|
-
shell: false
|
|
224
|
-
};
|
|
180
|
+
shell: false,
|
|
181
|
+
};
|
|
225
182
|
}
|
|
226
183
|
async function waitForDispatchAcknowledgement(filePath, prompt, before, completion) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
});
|
|
235
|
-
while (true) {
|
|
236
|
-
if (completionState === 2) {
|
|
237
|
-
throw completionError || new Error("Dispatch failed");
|
|
238
|
-
}
|
|
239
|
-
if (hasSessionUpdateWithPrompt(filePath, prompt, before)) {
|
|
240
|
-
return "prompt-observed";
|
|
241
|
-
}
|
|
242
|
-
if (completionState === 1) {
|
|
243
|
-
return "process-complete";
|
|
244
|
-
}
|
|
245
|
-
await sleepMs(120);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
async function startBackgroundCommand(command, options = {}) {
|
|
249
|
-
const resolved = resolveDispatchCommand(command);
|
|
250
|
-
return await new Promise((resolve, reject) => {
|
|
251
|
-
const child = (0, import_child_process2.spawn)(resolved.command, resolved.args, {
|
|
252
|
-
stdio: ["ignore", "ignore", "pipe"],
|
|
253
|
-
shell: resolved.shell,
|
|
254
|
-
windowsHide: true,
|
|
255
|
-
...options.cwd ? { cwd: options.cwd } : {}
|
|
184
|
+
let completionState = 0; // 0: running, 1: success, 2: failed
|
|
185
|
+
let completionError = null;
|
|
186
|
+
completion.then(() => {
|
|
187
|
+
completionState = 1;
|
|
188
|
+
}).catch((error) => {
|
|
189
|
+
completionState = 2;
|
|
190
|
+
completionError = error instanceof Error ? error : new Error(String(error || 'Dispatch failed'));
|
|
256
191
|
});
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const failLaunch = (error) => {
|
|
261
|
-
if (launchSettled) return;
|
|
262
|
-
launchSettled = true;
|
|
263
|
-
reject(error);
|
|
264
|
-
};
|
|
265
|
-
const completion = new Promise((resolveCompletion, rejectCompletion) => {
|
|
266
|
-
child.on("error", (error) => {
|
|
267
|
-
if (!spawned) {
|
|
268
|
-
failLaunch(error);
|
|
269
|
-
return;
|
|
192
|
+
while (true) {
|
|
193
|
+
if (completionState === 2) {
|
|
194
|
+
throw completionError || new Error('Dispatch failed');
|
|
270
195
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
child.on("close", (code) => {
|
|
274
|
-
if (code === 0) {
|
|
275
|
-
resolveCompletion();
|
|
276
|
-
return;
|
|
196
|
+
if (hasSessionUpdateWithPrompt(filePath, prompt, before)) {
|
|
197
|
+
return 'prompt-observed';
|
|
277
198
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
rejectCompletion(new Error("Provider CLI does not support required resume flags. Update the CLI version."));
|
|
281
|
-
return;
|
|
199
|
+
if (completionState === 1) {
|
|
200
|
+
return 'process-complete';
|
|
282
201
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
202
|
+
await sleepMs(120);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
async function startBackgroundCommand(command, options = {}) {
|
|
206
|
+
const resolved = resolveDispatchCommand(command);
|
|
207
|
+
return await new Promise((resolve, reject) => {
|
|
208
|
+
const child = (0, child_process_1.spawn)(resolved.command, resolved.args, {
|
|
209
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
210
|
+
shell: resolved.shell,
|
|
211
|
+
windowsHide: true,
|
|
212
|
+
...(options.cwd ? { cwd: options.cwd } : {}),
|
|
213
|
+
});
|
|
214
|
+
let stderr = '';
|
|
215
|
+
let spawned = false;
|
|
216
|
+
let launchSettled = false;
|
|
217
|
+
const failLaunch = (error) => {
|
|
218
|
+
if (launchSettled)
|
|
219
|
+
return;
|
|
220
|
+
launchSettled = true;
|
|
221
|
+
reject(error);
|
|
222
|
+
};
|
|
223
|
+
const completion = new Promise((resolveCompletion, rejectCompletion) => {
|
|
224
|
+
child.on('error', (error) => {
|
|
225
|
+
if (!spawned) {
|
|
226
|
+
failLaunch(error);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
rejectCompletion(error);
|
|
230
|
+
});
|
|
231
|
+
child.on('close', (code) => {
|
|
232
|
+
if (code === 0) {
|
|
233
|
+
resolveCompletion();
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const normalized = String(stderr || '').trim();
|
|
237
|
+
if (looksLikeUnsupportedFlags(normalized)) {
|
|
238
|
+
rejectCompletion(new Error('Provider CLI does not support required resume flags. Update the CLI version.'));
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
rejectCompletion(new Error(normalized || `Dispatch exited with code ${code}`));
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
child.stderr?.on('data', (data) => {
|
|
245
|
+
stderr += data.toString();
|
|
246
|
+
});
|
|
247
|
+
child.on('spawn', () => {
|
|
248
|
+
spawned = true;
|
|
249
|
+
if (launchSettled)
|
|
250
|
+
return;
|
|
251
|
+
launchSettled = true;
|
|
252
|
+
resolve({ completion });
|
|
253
|
+
});
|
|
294
254
|
});
|
|
295
|
-
});
|
|
296
255
|
}
|
|
297
256
|
async function runInteractiveInTerminal(provider, prompt) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
257
|
+
const command = buildInteractiveDispatchCommand(provider, prompt);
|
|
258
|
+
const resolved = resolveDispatchCommand(command);
|
|
259
|
+
await new Promise((resolve, reject) => {
|
|
260
|
+
const child = (0, child_process_1.spawn)(resolved.command, resolved.args, {
|
|
261
|
+
stdio: 'inherit',
|
|
262
|
+
shell: resolved.shell,
|
|
263
|
+
windowsHide: false,
|
|
264
|
+
});
|
|
265
|
+
child.on('error', (error) => reject(error));
|
|
266
|
+
child.on('close', (code) => {
|
|
267
|
+
if (code === 0)
|
|
268
|
+
resolve();
|
|
269
|
+
else
|
|
270
|
+
reject(new Error(`Interactive session exited with code ${code}`));
|
|
271
|
+
});
|
|
305
272
|
});
|
|
306
|
-
child.on("error", (error) => reject(error));
|
|
307
|
-
child.on("close", (code) => {
|
|
308
|
-
if (code === 0) resolve();
|
|
309
|
-
else reject(new Error(`Interactive session exited with code ${code}`));
|
|
310
|
-
});
|
|
311
|
-
});
|
|
312
273
|
}
|
|
313
274
|
function describeTarget(target) {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
275
|
+
if (target.kind === 'new-codex')
|
|
276
|
+
return 'new codex session';
|
|
277
|
+
if (target.kind === 'new-claude')
|
|
278
|
+
return 'new claude session';
|
|
279
|
+
const provider = describeProvider(target.agent);
|
|
280
|
+
const project = cleanTargetLabel(target.projectName, 24);
|
|
281
|
+
const sessionTitle = cleanTargetLabel(target.sessionTitle, 44);
|
|
282
|
+
if (project && sessionTitle)
|
|
283
|
+
return `${provider} session (${project} › ${sessionTitle})`;
|
|
284
|
+
if (sessionTitle)
|
|
285
|
+
return `${provider} session (${sessionTitle})`;
|
|
286
|
+
if (project)
|
|
287
|
+
return `${provider} session (${project})`;
|
|
288
|
+
return `${provider} session`;
|
|
323
289
|
}
|
|
324
290
|
function describeProvider(agent) {
|
|
325
|
-
|
|
291
|
+
return String(agent || '').toLowerCase() === 'claude' ? 'Claude' : 'Codex';
|
|
326
292
|
}
|
|
327
293
|
function cleanTargetLabel(value, max) {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
294
|
+
const text = String(value || '').replace(/\s+/g, ' ').trim();
|
|
295
|
+
if (!text)
|
|
296
|
+
return '';
|
|
297
|
+
if (/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i.test(text))
|
|
298
|
+
return '';
|
|
299
|
+
if (text.length <= max)
|
|
300
|
+
return text;
|
|
301
|
+
if (max <= 3)
|
|
302
|
+
return text.slice(0, max);
|
|
303
|
+
return `${text.slice(0, max - 3).trimEnd()}...`;
|
|
334
304
|
}
|
|
335
305
|
function looksLikeUnsupportedFlags(stderr) {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
306
|
+
const normalized = String(stderr || '').toLowerCase();
|
|
307
|
+
if (!normalized)
|
|
308
|
+
return false;
|
|
309
|
+
return normalized.includes('unknown option')
|
|
310
|
+
|| normalized.includes('unknown flag')
|
|
311
|
+
|| normalized.includes('unrecognized option')
|
|
312
|
+
|| normalized.includes('did you mean');
|
|
339
313
|
}
|
|
340
314
|
function normalizeError(error) {
|
|
341
|
-
|
|
342
|
-
|
|
315
|
+
if (error instanceof Error && error.message)
|
|
316
|
+
return error.message;
|
|
317
|
+
return String(error || 'Dispatch failed');
|
|
343
318
|
}
|
|
344
319
|
function captureSessionFileSnapshot(filePath) {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
320
|
+
try {
|
|
321
|
+
const stat = fs.statSync(filePath);
|
|
322
|
+
return {
|
|
323
|
+
exists: true,
|
|
324
|
+
size: stat.size,
|
|
325
|
+
mtimeMs: stat.mtimeMs,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
return {
|
|
330
|
+
exists: false,
|
|
331
|
+
size: 0,
|
|
332
|
+
mtimeMs: 0,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
359
335
|
}
|
|
360
336
|
function verifyExistingDispatchDelivery(filePath, prompt, before) {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
337
|
+
if (!hasSessionFileChanged(filePath, before)) {
|
|
338
|
+
throw new Error('Target session did not update after dispatch');
|
|
339
|
+
}
|
|
340
|
+
const promptSignature = firstPromptSignature(prompt);
|
|
341
|
+
if (promptSignature.length < 4)
|
|
342
|
+
return;
|
|
343
|
+
const tail = readSessionTailSinceOffset(filePath, before.size);
|
|
344
|
+
if (!tail) {
|
|
345
|
+
throw new Error('Target session updated but prompt text was not found');
|
|
346
|
+
}
|
|
347
|
+
if (!tail.toLowerCase().includes(promptSignature.toLowerCase())) {
|
|
348
|
+
throw new Error('Prompt text was not found in target session update');
|
|
349
|
+
}
|
|
373
350
|
}
|
|
374
351
|
function hasSessionUpdateWithPrompt(filePath, prompt, before) {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
352
|
+
if (!hasSessionFileChanged(filePath, before))
|
|
353
|
+
return false;
|
|
354
|
+
const promptSignature = firstPromptSignature(prompt);
|
|
355
|
+
if (promptSignature.length < 4)
|
|
356
|
+
return true;
|
|
357
|
+
const tail = readSessionTailSinceOffset(filePath, before.size);
|
|
358
|
+
if (!tail)
|
|
359
|
+
return false;
|
|
360
|
+
return tail.toLowerCase().includes(promptSignature.toLowerCase());
|
|
381
361
|
}
|
|
382
362
|
function hasSessionFileChanged(filePath, before) {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
363
|
+
const after = captureSessionFileSnapshot(filePath);
|
|
364
|
+
if (!after.exists) {
|
|
365
|
+
throw new Error('Target session file is not accessible after dispatch');
|
|
366
|
+
}
|
|
367
|
+
return after.size > before.size || after.mtimeMs > before.mtimeMs;
|
|
388
368
|
}
|
|
389
369
|
function firstPromptSignature(prompt) {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
370
|
+
const lines = String(prompt || '')
|
|
371
|
+
.split(/\r?\n/g)
|
|
372
|
+
.map((line) => line.trim())
|
|
373
|
+
.filter(Boolean);
|
|
374
|
+
const first = lines[0] || String(prompt || '').trim();
|
|
375
|
+
return first.slice(0, 160);
|
|
393
376
|
}
|
|
394
377
|
function readSessionTailSinceOffset(filePath, previousSize) {
|
|
395
|
-
try {
|
|
396
|
-
const stat = fs2.statSync(filePath);
|
|
397
|
-
const maxBytes = 1024 * 512;
|
|
398
|
-
const start = Math.max(0, previousSize - 2048);
|
|
399
|
-
const desiredStart = stat.size - start > maxBytes ? Math.max(0, stat.size - maxBytes) : start;
|
|
400
|
-
const length = Math.max(0, stat.size - desiredStart);
|
|
401
|
-
if (length === 0) return "";
|
|
402
|
-
const fd = fs2.openSync(filePath, "r");
|
|
403
378
|
try {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
379
|
+
const stat = fs.statSync(filePath);
|
|
380
|
+
const maxBytes = 1024 * 512;
|
|
381
|
+
const start = Math.max(0, previousSize - 2048);
|
|
382
|
+
const desiredStart = stat.size - start > maxBytes
|
|
383
|
+
? Math.max(0, stat.size - maxBytes)
|
|
384
|
+
: start;
|
|
385
|
+
const length = Math.max(0, stat.size - desiredStart);
|
|
386
|
+
if (length === 0)
|
|
387
|
+
return '';
|
|
388
|
+
const fd = fs.openSync(filePath, 'r');
|
|
389
|
+
try {
|
|
390
|
+
const buf = Buffer.alloc(length);
|
|
391
|
+
fs.readSync(fd, buf, 0, length, desiredStart);
|
|
392
|
+
return buf.toString('utf8');
|
|
393
|
+
}
|
|
394
|
+
finally {
|
|
395
|
+
fs.closeSync(fd);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
catch {
|
|
399
|
+
return '';
|
|
409
400
|
}
|
|
410
|
-
} catch {
|
|
411
|
-
return "";
|
|
412
|
-
}
|
|
413
401
|
}
|
|
414
402
|
async function runWithTimeout(promise, timeoutMs, message) {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
403
|
+
return await new Promise((resolve, reject) => {
|
|
404
|
+
const timer = setTimeout(() => reject(new Error(message)), timeoutMs);
|
|
405
|
+
promise.then((value) => {
|
|
406
|
+
clearTimeout(timer);
|
|
407
|
+
resolve(value);
|
|
408
|
+
}).catch((error) => {
|
|
409
|
+
clearTimeout(timer);
|
|
410
|
+
reject(error);
|
|
411
|
+
});
|
|
423
412
|
});
|
|
424
|
-
});
|
|
425
413
|
}
|
|
426
414
|
async function sleepMs(ms) {
|
|
427
|
-
|
|
415
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
428
416
|
}
|
|
429
417
|
function deriveDispatchCwdForSession(session) {
|
|
430
|
-
|
|
431
|
-
|
|
418
|
+
if (session.agent !== 'claude')
|
|
419
|
+
return undefined;
|
|
420
|
+
return decodeClaudeProjectPathFromSessionFile(session.filePath);
|
|
432
421
|
}
|
|
433
422
|
function decodeClaudeProjectPathFromSessionFile(filePath) {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
423
|
+
if (!filePath)
|
|
424
|
+
return undefined;
|
|
425
|
+
const projectDir = path.basename(path.dirname(filePath));
|
|
426
|
+
if (!projectDir)
|
|
427
|
+
return undefined;
|
|
428
|
+
const parts = projectDir.split('-').filter(Boolean);
|
|
429
|
+
if (parts.length === 0)
|
|
430
|
+
return undefined;
|
|
431
|
+
if (parts.length >= 2 && /^[A-Za-z]$/.test(parts[0])) {
|
|
432
|
+
const windowsPath = `${parts[0]}:\\${parts.slice(1).join('\\')}`;
|
|
433
|
+
if (fs.existsSync(windowsPath))
|
|
434
|
+
return windowsPath;
|
|
435
|
+
}
|
|
436
|
+
const unixPath = `/${parts.join('/')}`;
|
|
437
|
+
if (fs.existsSync(unixPath))
|
|
438
|
+
return unixPath;
|
|
439
|
+
return undefined;
|
|
446
440
|
}
|
|
447
|
-
|
|
448
|
-
0 && (module.exports = {
|
|
449
|
-
SessionDispatchService,
|
|
450
|
-
buildExistingDispatchCommand,
|
|
451
|
-
buildInteractiveDispatchCommand,
|
|
452
|
-
resolveDispatchCommand
|
|
453
|
-
});
|
|
441
|
+
//# sourceMappingURL=session-dispatch.js.map
|