@sma1lboy/kobe 0.8.69 → 0.8.71
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/dist/cli/index.js +429 -423
- package/dist/cli/pty-host-node.mjs +120 -28
- package/dist/skills/kobe/SKILL.md +2 -2
- package/package.json +1 -1
|
@@ -18,7 +18,7 @@ function bunTerminalDriver(spawn) {
|
|
|
18
18
|
});
|
|
19
19
|
return {
|
|
20
20
|
pid: proc.pid,
|
|
21
|
-
exited: proc.exited,
|
|
21
|
+
exited: proc.exited.then(() => ({ code: proc.exitCode ?? null, signal: proc.signalCode ?? null }), () => ({ code: null, signal: null })),
|
|
22
22
|
write: (data) => proc.terminal?.write(data),
|
|
23
23
|
resize: (cols, rows) => proc.terminal?.resize(cols, rows),
|
|
24
24
|
close: () => proc.terminal?.close(),
|
|
@@ -42,7 +42,7 @@ async function nodePtyDriver(spawn) {
|
|
|
42
42
|
settle = resolve;
|
|
43
43
|
});
|
|
44
44
|
child.onData((data) => request.onData(data));
|
|
45
|
-
child.onExit(({ exitCode }) => settle(exitCode));
|
|
45
|
+
child.onExit(({ exitCode }) => settle({ code: exitCode, signal: null }));
|
|
46
46
|
return {
|
|
47
47
|
pid: child.pid,
|
|
48
48
|
exited,
|
|
@@ -57,7 +57,7 @@ async function nodePtyDriver(spawn) {
|
|
|
57
57
|
// ../kobe-daemon/src/daemon/pty-server.ts
|
|
58
58
|
import { mkdir, unlink, writeFile } from "node:fs/promises";
|
|
59
59
|
import { createServer } from "node:net";
|
|
60
|
-
import { dirname } from "node:path";
|
|
60
|
+
import { dirname as dirname2 } from "node:path";
|
|
61
61
|
import { StringDecoder as StringDecoder2 } from "node:string_decoder";
|
|
62
62
|
|
|
63
63
|
// ../kobe-daemon/src/daemon/client-writer.ts
|
|
@@ -236,6 +236,9 @@ function defaultPtyHostPidPath(homeDir = process.env.KOBE_HOME_DIR ?? homedir())
|
|
|
236
236
|
return override;
|
|
237
237
|
return join(homeDir, ".kobe", "pty.pid");
|
|
238
238
|
}
|
|
239
|
+
function defaultPtyExitsPath(homeDir = process.env.KOBE_HOME_DIR ?? homedir()) {
|
|
240
|
+
return join(homeDir, ".kobe", "pty-exits.json");
|
|
241
|
+
}
|
|
239
242
|
// ../kobe-plugin-sdk/src/contract.ts
|
|
240
243
|
var DAEMON_CHANNELS = [
|
|
241
244
|
"task.snapshot",
|
|
@@ -269,6 +272,51 @@ function frameToLine(frame) {
|
|
|
269
272
|
`;
|
|
270
273
|
}
|
|
271
274
|
|
|
275
|
+
// ../kobe-daemon/src/daemon/pty-exit-store.ts
|
|
276
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
277
|
+
import { dirname } from "node:path";
|
|
278
|
+
var MAX_RECORDS = 50;
|
|
279
|
+
var TAIL_LINES = 40;
|
|
280
|
+
var TAIL_LINE_CHARS = 500;
|
|
281
|
+
var ANSI_RE = /\x1b\[[0-9;?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)?|\x1b[@-_]/g;
|
|
282
|
+
function plainTail(raw) {
|
|
283
|
+
const plain = raw.replace(ANSI_RE, "").replace(/\r\n/g, `
|
|
284
|
+
`);
|
|
285
|
+
const lines = plain.split(`
|
|
286
|
+
`).map((line) => (line.split("\r").pop() ?? "").slice(0, TAIL_LINE_CHARS));
|
|
287
|
+
while (lines.length > 0 && (lines[lines.length - 1] ?? "").trim() === "")
|
|
288
|
+
lines.pop();
|
|
289
|
+
return lines.slice(-TAIL_LINES);
|
|
290
|
+
}
|
|
291
|
+
function readPtyExitRecords(path = defaultPtyExitsPath()) {
|
|
292
|
+
try {
|
|
293
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
294
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
295
|
+
return {};
|
|
296
|
+
return parsed;
|
|
297
|
+
} catch {
|
|
298
|
+
return {};
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function recordPtyExit(info, path = defaultPtyExitsPath()) {
|
|
302
|
+
if (info.key.startsWith("::"))
|
|
303
|
+
return;
|
|
304
|
+
if (info.exit.code === 0 && info.exit.signal === null)
|
|
305
|
+
return;
|
|
306
|
+
const records = readPtyExitRecords(path);
|
|
307
|
+
records[info.key] = {
|
|
308
|
+
key: info.key,
|
|
309
|
+
pid: info.pid,
|
|
310
|
+
code: info.exit.code,
|
|
311
|
+
signal: info.exit.signal,
|
|
312
|
+
at: info.exit.at,
|
|
313
|
+
tail: plainTail(info.tail)
|
|
314
|
+
};
|
|
315
|
+
const newest = Object.values(records).sort((a, b) => a.at < b.at ? 1 : -1).slice(0, MAX_RECORDS);
|
|
316
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
317
|
+
writeFileSync(path, JSON.stringify(Object.fromEntries(newest.map((r) => [r.key, r])), null, 2), "utf8");
|
|
318
|
+
}
|
|
319
|
+
|
|
272
320
|
// ../kobe-daemon/src/daemon/pty-host.ts
|
|
273
321
|
import { StringDecoder } from "node:string_decoder";
|
|
274
322
|
|
|
@@ -344,6 +392,24 @@ function embeddedTerminalEnv(base, overrides = {}) {
|
|
|
344
392
|
}
|
|
345
393
|
|
|
346
394
|
// ../kobe-daemon/src/daemon/pty-observability.ts
|
|
395
|
+
function describeExit(exit) {
|
|
396
|
+
if (exit?.signal)
|
|
397
|
+
return ` (signal ${exit.signal})`;
|
|
398
|
+
if (exit && exit.code !== null)
|
|
399
|
+
return ` (code ${exit.code})`;
|
|
400
|
+
return " (cause unknown)";
|
|
401
|
+
}
|
|
402
|
+
function ringTail(chunks, bytes, maxBytes) {
|
|
403
|
+
const skip = Math.max(0, bytes - maxBytes);
|
|
404
|
+
let seen = 0;
|
|
405
|
+
const parts = [];
|
|
406
|
+
for (const chunk of chunks) {
|
|
407
|
+
if (seen + chunk.byteLength > skip)
|
|
408
|
+
parts.push(seen >= skip ? chunk : chunk.subarray(skip - seen));
|
|
409
|
+
seen += chunk.byteLength;
|
|
410
|
+
}
|
|
411
|
+
return Buffer.concat(parts).toString("utf8");
|
|
412
|
+
}
|
|
347
413
|
var OSC_TITLE_RE = /\x1b\][02];([^\x07\x1b]*)(?:\x07|\x1b\\)/g;
|
|
348
414
|
var TITLE_CARRY_CAP = 1024;
|
|
349
415
|
function titleCarryFrom(rest) {
|
|
@@ -370,8 +436,9 @@ function scanOscTitle(session, buf) {
|
|
|
370
436
|
session.titleCarry = carry;
|
|
371
437
|
}
|
|
372
438
|
function peekRing(session, sinceOffset) {
|
|
373
|
-
if (!session)
|
|
374
|
-
return { exists: false, alive: false, pid: null, offset: 0, data: "", sinceValid: false };
|
|
439
|
+
if (!session) {
|
|
440
|
+
return { exists: false, alive: false, pid: null, offset: 0, data: "", sinceValid: false, exit: null };
|
|
441
|
+
}
|
|
375
442
|
const windowStart = session.totalBytes - session.bytes;
|
|
376
443
|
let buf = Buffer.concat(session.chunks);
|
|
377
444
|
let sinceValid = false;
|
|
@@ -385,7 +452,8 @@ function peekRing(session, sinceOffset) {
|
|
|
385
452
|
pid: session.proc?.pid ?? null,
|
|
386
453
|
offset: session.totalBytes,
|
|
387
454
|
data: buf.toString("base64"),
|
|
388
|
-
sinceValid
|
|
455
|
+
sinceValid,
|
|
456
|
+
exit: session.exit ?? null
|
|
389
457
|
};
|
|
390
458
|
}
|
|
391
459
|
|
|
@@ -417,6 +485,7 @@ function signalProcessGroup(pid, signal, fallback, platform = process.platform)
|
|
|
417
485
|
// ../kobe-daemon/src/daemon/pty-host.ts
|
|
418
486
|
var DEFAULT_SCROLLBACK_CAP = 512 * 1024;
|
|
419
487
|
var TERMINATION_GRACE_MS = 500;
|
|
488
|
+
var EXIT_TAIL_BYTES = 16 * 1024;
|
|
420
489
|
|
|
421
490
|
class PtyHost {
|
|
422
491
|
sessions = new Map;
|
|
@@ -434,7 +503,7 @@ class PtyHost {
|
|
|
434
503
|
created = true;
|
|
435
504
|
session = this.adoptSpare(key, spec) ?? this.spawn(key, spec);
|
|
436
505
|
this.sessions.set(key, session);
|
|
437
|
-
} else if (session.alive && (session.cols !== spec.cols || session.rows !== spec.rows)) {
|
|
506
|
+
} else if (session.alive && spec.cols !== undefined && spec.rows !== undefined && (session.cols !== spec.cols || session.rows !== spec.rows)) {
|
|
438
507
|
this.resize(key, spec.cols, spec.rows);
|
|
439
508
|
}
|
|
440
509
|
session.sinks.set(token, sink);
|
|
@@ -482,11 +551,13 @@ class PtyHost {
|
|
|
482
551
|
return null;
|
|
483
552
|
this.spare = null;
|
|
484
553
|
spare.key = key;
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
554
|
+
const cols = spec.cols ?? spare.cols;
|
|
555
|
+
const rows = spec.rows ?? spare.rows;
|
|
556
|
+
if (spare.cols !== cols || spare.rows !== rows) {
|
|
557
|
+
spare.cols = cols;
|
|
558
|
+
spare.rows = rows;
|
|
488
559
|
try {
|
|
489
|
-
spare.proc?.resize(
|
|
560
|
+
spare.proc?.resize(cols, rows);
|
|
490
561
|
} catch {
|
|
491
562
|
this.markExited(spare);
|
|
492
563
|
return null;
|
|
@@ -494,7 +565,7 @@ class PtyHost {
|
|
|
494
565
|
}
|
|
495
566
|
this.opts.log?.("pty", `adopted warm shell for ${key} (pid ${spare.proc?.pid})`);
|
|
496
567
|
this.opts.onSessionStart?.();
|
|
497
|
-
this.warm(spec.cwd, spare.command[0],
|
|
568
|
+
this.warm(spec.cwd, spare.command[0], cols, rows);
|
|
498
569
|
return spare;
|
|
499
570
|
}
|
|
500
571
|
write(key, data) {
|
|
@@ -548,8 +619,10 @@ class PtyHost {
|
|
|
548
619
|
pid: s.proc?.pid ?? null,
|
|
549
620
|
command: s.command,
|
|
550
621
|
title: s.title,
|
|
622
|
+
totalBytes: s.totalBytes,
|
|
551
623
|
parked: s.parked,
|
|
552
|
-
parkedScreenBytes: s.parkedScreenBytes
|
|
624
|
+
parkedScreenBytes: s.parkedScreenBytes,
|
|
625
|
+
exit: s.exit
|
|
553
626
|
}));
|
|
554
627
|
}
|
|
555
628
|
peek(key, sinceOffset) {
|
|
@@ -599,6 +672,8 @@ class PtyHost {
|
|
|
599
672
|
}
|
|
600
673
|
spawn(key, spec, spare = false) {
|
|
601
674
|
const argv = spec.command && spec.command.length > 0 ? [...spec.command] : [spec.shell ?? resolveLoginShell()];
|
|
675
|
+
const cols = spec.cols ?? 80;
|
|
676
|
+
const rows = spec.rows ?? 24;
|
|
602
677
|
const session = {
|
|
603
678
|
key,
|
|
604
679
|
cwd: spec.cwd,
|
|
@@ -607,15 +682,16 @@ class PtyHost {
|
|
|
607
682
|
chunks: [],
|
|
608
683
|
bytes: 0,
|
|
609
684
|
totalBytes: 0,
|
|
610
|
-
cols
|
|
611
|
-
rows
|
|
685
|
+
cols,
|
|
686
|
+
rows,
|
|
612
687
|
command: argv,
|
|
613
688
|
title: "",
|
|
614
689
|
titleCarry: "",
|
|
615
690
|
titleDecoder: new StringDecoder("utf8"),
|
|
616
691
|
sinks: new Map,
|
|
617
692
|
parked: false,
|
|
618
|
-
parkedScreenBytes: 0
|
|
693
|
+
parkedScreenBytes: 0,
|
|
694
|
+
exit: null
|
|
619
695
|
};
|
|
620
696
|
try {
|
|
621
697
|
session.proc = (this.opts.driver ?? bunTerminalDriver())({
|
|
@@ -623,16 +699,16 @@ class PtyHost {
|
|
|
623
699
|
cwd: spec.cwd,
|
|
624
700
|
env: embeddedTerminalEnv(process.env, {
|
|
625
701
|
TERM: "xterm-256color",
|
|
626
|
-
COLUMNS: String(
|
|
627
|
-
LINES: String(
|
|
702
|
+
COLUMNS: String(cols),
|
|
703
|
+
LINES: String(rows),
|
|
628
704
|
BASH_SILENCE_DEPRECATION_WARNING: "1",
|
|
629
705
|
KOBE_TERMINAL_PTY: "1"
|
|
630
706
|
}),
|
|
631
|
-
cols
|
|
632
|
-
rows
|
|
707
|
+
cols,
|
|
708
|
+
rows,
|
|
633
709
|
onData: (data) => this.onData(session, data)
|
|
634
710
|
});
|
|
635
|
-
session.proc.exited.then(() => this.markExited(session), () => this.markExited(session));
|
|
711
|
+
session.proc.exited.then((exit) => this.markExited(session, exit), () => this.markExited(session));
|
|
636
712
|
this.opts.log?.("pty", `spawned ${argv[0]} for ${key} (pid ${session.proc.pid})`);
|
|
637
713
|
if (!spare)
|
|
638
714
|
this.opts.onSessionStart?.();
|
|
@@ -663,21 +739,30 @@ class PtyHost {
|
|
|
663
739
|
for (const sink of session.sinks.values())
|
|
664
740
|
sink(frame);
|
|
665
741
|
}
|
|
666
|
-
markExited(session) {
|
|
742
|
+
markExited(session, exit) {
|
|
667
743
|
if (!session.alive)
|
|
668
744
|
return;
|
|
669
745
|
session.alive = false;
|
|
746
|
+
session.exit = { code: exit?.code ?? null, signal: exit?.signal ?? null, at: new Date().toISOString() };
|
|
670
747
|
try {
|
|
671
748
|
session.proc?.close();
|
|
672
749
|
} catch {}
|
|
673
750
|
const frame = {
|
|
674
751
|
type: "event",
|
|
675
752
|
name: "pty.exit",
|
|
676
|
-
payload: { key: session.key, pid: session.proc?.pid ?? null }
|
|
753
|
+
payload: { key: session.key, pid: session.proc?.pid ?? null, ...session.exit }
|
|
677
754
|
};
|
|
678
755
|
for (const sink of session.sinks.values())
|
|
679
756
|
sink(frame);
|
|
680
|
-
this.opts.log?.("pty", `session ${session.key} exited`);
|
|
757
|
+
this.opts.log?.("pty", `session ${session.key} exited${describeExit(session.exit)}`);
|
|
758
|
+
try {
|
|
759
|
+
this.opts.onSessionExit?.({
|
|
760
|
+
key: session.key,
|
|
761
|
+
pid: session.proc?.pid ?? null,
|
|
762
|
+
exit: session.exit,
|
|
763
|
+
tail: ringTail(session.chunks, session.bytes, EXIT_TAIL_BYTES)
|
|
764
|
+
});
|
|
765
|
+
} catch {}
|
|
681
766
|
this.opts.onSessionEnd?.();
|
|
682
767
|
}
|
|
683
768
|
async endChild(session) {
|
|
@@ -736,13 +821,20 @@ async function startPtyHostServer(options = {}) {
|
|
|
736
821
|
if (ptys.liveCount() === 0)
|
|
737
822
|
armIdle();
|
|
738
823
|
},
|
|
824
|
+
onSessionExit: (info) => {
|
|
825
|
+
try {
|
|
826
|
+
recordPtyExit(info);
|
|
827
|
+
} catch (err) {
|
|
828
|
+
log("pty", `exit record write failed for ${info.key}: ${err instanceof Error ? err.message : String(err)}`);
|
|
829
|
+
}
|
|
830
|
+
},
|
|
739
831
|
driver: options.driver,
|
|
740
832
|
log
|
|
741
833
|
});
|
|
742
834
|
const pipeSocket = isWindowsPipePath(socketPath);
|
|
743
835
|
if (!pipeSocket)
|
|
744
|
-
await mkdir(
|
|
745
|
-
await mkdir(
|
|
836
|
+
await mkdir(dirname2(socketPath), { recursive: true });
|
|
837
|
+
await mkdir(dirname2(pidPath), { recursive: true });
|
|
746
838
|
const server = createServer((socket) => {
|
|
747
839
|
const client = {
|
|
748
840
|
socket,
|
|
@@ -797,8 +889,8 @@ async function startPtyHostServer(options = {}) {
|
|
|
797
889
|
cwd: requireString(payload, "cwd"),
|
|
798
890
|
command: Array.isArray(payload.command) ? payload.command.filter((c) => typeof c === "string") : undefined,
|
|
799
891
|
shell: typeof payload.shell === "string" ? payload.shell : undefined,
|
|
800
|
-
cols: typeof payload.cols === "number" ? payload.cols :
|
|
801
|
-
rows: typeof payload.rows === "number" ? payload.rows :
|
|
892
|
+
cols: typeof payload.cols === "number" ? payload.cols : undefined,
|
|
893
|
+
rows: typeof payload.rows === "number" ? payload.rows : undefined
|
|
802
894
|
}, client, (frame) => writeFrame(client, frame), typeof payload.sinceOffset === "number" ? payload.sinceOffset : undefined, typeof payload.sincePid === "number" ? payload.sincePid : undefined);
|
|
803
895
|
}
|
|
804
896
|
case "pty.write": {
|
|
@@ -3,7 +3,7 @@ name: kobe
|
|
|
3
3
|
description: Use when controlling kobe tasks, parallel coding attempts, hosted agent sessions, task lifecycle, or the daemon-owned issue tracker from a shell.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<!-- kobe-skill-version:
|
|
6
|
+
<!-- kobe-skill-version: 14 — bump in lockstep with KOBE_SKILL_VERSION (src/lib/skill-install.ts). -->
|
|
7
7
|
|
|
8
8
|
# kobe shell control
|
|
9
9
|
|
|
@@ -140,7 +140,7 @@ logs, dashboards), don't scatter panes for work `add`/`fan-out` should own.
|
|
|
140
140
|
| `pin --task-id ID [--pinned=false]` | Pin/unpin |
|
|
141
141
|
| `set-active --task-id ID` / `--none` | Change shared active task |
|
|
142
142
|
| `ensure-worktree --task-id ID` | Materialize without starting an engine |
|
|
143
|
-
| `land --task-id ID [--strategy merge\|squash] [--delete-branch] [--then-archive]` | Merge the task's branch into the base repo's current branch |
|
|
143
|
+
| `land --task-id ID [--strategy merge\|squash] [--delete-branch] [--then-archive] [--remove-worktree]` | Merge the task's branch into the base repo's current branch; `--remove-worktree` cleans up the Worktree after (branch stays; dirty/self/base refused, outcome in the result's `worktree` field) |
|
|
144
144
|
| `delete --task-id ID [--force]` | Destructive task + Worktree removal |
|
|
145
145
|
| `discover-adoptable --repo PATH` | Find untracked Worktrees |
|
|
146
146
|
| `adopt --repo PATH --worktree PATH` | Import a Worktree |
|
package/package.json
CHANGED