shraga 0.1.80 → 0.1.81
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shraga",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.81",
|
|
4
4
|
"description": "The teammate you delegate coding to — a self-hostable, multi-user AI coding agent web UI (Claude Code, with a pluggable engine seam).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -231,7 +231,15 @@ export async function startJob(owner: JobOwner, command: string): Promise<string
|
|
|
231
231
|
// stdin is /dev/null so a child can never block on (or steal) input; stdout+stderr go straight
|
|
232
232
|
// to the fd — nothing is buffered in this process, so a chatty job costs disk, not memory.
|
|
233
233
|
const proc = spawn('/bin/sh', ['-c', buildShell(id, cmd)], {
|
|
234
|
-
cwd: owner.cwd,
|
|
234
|
+
cwd: owner.cwd,
|
|
235
|
+
env: {
|
|
236
|
+
...childEnv(),
|
|
237
|
+
...owner.env,
|
|
238
|
+
// A long-running launcher can gate on these: only a REGISTERED job gets a completion wake,
|
|
239
|
+
// so one started in the foreground orphans silently when its 60s tool call is killed.
|
|
240
|
+
SHRAGA_JOB_ID: id,
|
|
241
|
+
SHRAGA_BG_JOB: '1',
|
|
242
|
+
},
|
|
235
243
|
detached: true, stdio: ['ignore', fd, fd],
|
|
236
244
|
});
|
|
237
245
|
j.pid = proc.pid;
|
|
@@ -251,6 +259,47 @@ export async function startJob(owner: JobOwner, command: string): Promise<string
|
|
|
251
259
|
return id;
|
|
252
260
|
}
|
|
253
261
|
|
|
262
|
+
/** The little of a spawned child this module needs to adopt one (node's ChildProcess satisfies it). */
|
|
263
|
+
type AdoptableProcess = {
|
|
264
|
+
pid?: number | null;
|
|
265
|
+
stdout?: { on(ev: 'data', cb: (chunk: unknown) => void): void } | null;
|
|
266
|
+
stderr?: { on(ev: 'data', cb: (chunk: unknown) => void): void } | null;
|
|
267
|
+
on(ev: 'close' | 'error', cb: (arg: never) => void): void;
|
|
268
|
+
unref?: () => void;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Register an ALREADY-RUNNING child as a background job. The Shell tool hands a foreground command
|
|
273
|
+
* over at its deadline instead of killing it: the work continues, and from here it is an ordinary job
|
|
274
|
+
* — polled with ShellOutput, and reported back to the session when it ends. Without this the deadline
|
|
275
|
+
* stays a kill, which is what abandoned a scheduled run midway.
|
|
276
|
+
*/
|
|
277
|
+
export function adoptJob(owner: JobOwner, command: string, proc: AdoptableProcess, seed = ''): string {
|
|
278
|
+
const id = `job-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
279
|
+
const j: JobRecord = {
|
|
280
|
+
id, sessionId: owner.sessionId, uid: owner.uid, userEmail: owner.userEmail,
|
|
281
|
+
command: command.trim(), cwd: owner.cwd, startedAt: Date.now(), status: 'running',
|
|
282
|
+
pid: proc.pid ?? undefined,
|
|
283
|
+
};
|
|
284
|
+
// The child was spawned with pipes by the caller, so output is appended here rather than dup'd to
|
|
285
|
+
// an fd the way a job we spawn ourselves does it.
|
|
286
|
+
const append = (chunk: unknown) => {
|
|
287
|
+
const text = typeof chunk === 'string' ? chunk : String(chunk ?? '');
|
|
288
|
+
if (!text) return;
|
|
289
|
+
try { writeFileSync(logFile(id), text, { flag: 'a' }); } catch { /* the job outlives its log */ }
|
|
290
|
+
};
|
|
291
|
+
if (seed) append(seed);
|
|
292
|
+
proc.stdout?.on('data', append);
|
|
293
|
+
proc.stderr?.on('data', append);
|
|
294
|
+
proc.on('error', ((err: Error) => finish(id, 'error', undefined, `adopted job error: ${err?.message ?? err}`)) as never);
|
|
295
|
+
proc.on('close', ((code: number | null) => finish(id, 'exited', code ?? undefined)) as never);
|
|
296
|
+
proc.unref?.();
|
|
297
|
+
records.set(id, j);
|
|
298
|
+
save(j);
|
|
299
|
+
console.log(`${PREFIX} adopted ${id} pid=${j.pid} session=${j.sessionId.slice(0, 8)} cmd=${j.command.slice(0, 120)}`);
|
|
300
|
+
return id;
|
|
301
|
+
}
|
|
302
|
+
|
|
254
303
|
/** Record a terminal state exactly once, then schedule the follow-up. */
|
|
255
304
|
function finish(id: string, status: JobStatus, exitCode?: number, note?: string): void {
|
|
256
305
|
const j = records.get(id);
|
|
@@ -479,6 +528,7 @@ export function sessionJobRegistry(owner: JobOwner) {
|
|
|
479
528
|
};
|
|
480
529
|
return {
|
|
481
530
|
start: (command: string) => startJob(owner, command),
|
|
531
|
+
adopt: (command: string, proc: AdoptableProcess, seed?: string) => adoptJob(owner, command, proc, seed),
|
|
482
532
|
output(id: string): string | null {
|
|
483
533
|
const j = mine(id);
|
|
484
534
|
if (!j) return null;
|