pi-tian-background-terminals 0.1.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/LICENSE +21 -0
- package/README.md +124 -0
- package/docs/implementation-guide.md +430 -0
- package/index.ts +584 -0
- package/package.json +59 -0
- package/src/domain.ts +102 -0
- package/src/manager.ts +1070 -0
- package/src/output.ts +163 -0
- package/src/prompt.ts +195 -0
- package/src/result-delivery.ts +27 -0
- package/src/runtime.ts +41 -0
- package/src/ui/output-view.ts +79 -0
- package/src/ui/ps.ts +624 -0
package/src/domain.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain model for background terminals.
|
|
3
|
+
*
|
|
4
|
+
* A "terminal" is one shell process started by the model. It receives no
|
|
5
|
+
* interactive stdin, captures stdout and stderr separately, and settles
|
|
6
|
+
* exactly once into a final state.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Data } from "effect";
|
|
10
|
+
|
|
11
|
+
export type TerminalStatus =
|
|
12
|
+
| "running"
|
|
13
|
+
| "done"
|
|
14
|
+
| "failed"
|
|
15
|
+
| "timed_out"
|
|
16
|
+
| "killed";
|
|
17
|
+
// "done" = exited with code 0
|
|
18
|
+
// "failed" = exited non-zero, or a spawn-level runtime error after start
|
|
19
|
+
// "timed_out" = exceeded the model-requested hard runtime timeout
|
|
20
|
+
// "killed" = terminated from /ps or during session teardown
|
|
21
|
+
|
|
22
|
+
/** Read-only view over one captured output stream (stdout or stderr). */
|
|
23
|
+
export interface OutputView {
|
|
24
|
+
/** Bounded head + omission marker + bounded tail, ready for the /ps viewer. */
|
|
25
|
+
readonly text: string;
|
|
26
|
+
/** Stable prefix retained in memory. */
|
|
27
|
+
readonly head: string;
|
|
28
|
+
/** Rolling suffix retained in memory. */
|
|
29
|
+
readonly tail: string;
|
|
30
|
+
/** True total bytes ever received on this stream. */
|
|
31
|
+
readonly totalBytes: number;
|
|
32
|
+
/** Bytes omitted from the middle of the in-memory view (0 = complete). */
|
|
33
|
+
readonly truncatedBytes: number;
|
|
34
|
+
/** On-disk full capture; always the complete stream when spilling works. */
|
|
35
|
+
readonly spillPath?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface TerminalSnapshot {
|
|
39
|
+
readonly id: string;
|
|
40
|
+
/** Exactly the command line the model asked to run. */
|
|
41
|
+
readonly command: string;
|
|
42
|
+
/** Short model-provided name, shown in listings and the UI. */
|
|
43
|
+
readonly title: string;
|
|
44
|
+
/** Resolved absolute cwd the process runs in. */
|
|
45
|
+
readonly cwd: string;
|
|
46
|
+
/** Undefined only if spawn itself failed before a pid was assigned. */
|
|
47
|
+
readonly pid?: number;
|
|
48
|
+
readonly status: TerminalStatus;
|
|
49
|
+
/** Date.now() at spawn. */
|
|
50
|
+
readonly createdAt: number;
|
|
51
|
+
/** Date.now() at settle (exit/kill). */
|
|
52
|
+
readonly settledAt?: number;
|
|
53
|
+
/** Optional hard runtime deadline requested by the bash call. */
|
|
54
|
+
readonly timeoutMs?: number;
|
|
55
|
+
/** Set when the process exited via exit code (exactly one of exitCode/signal). */
|
|
56
|
+
readonly exitCode?: number;
|
|
57
|
+
/** Set when the process was terminated by a signal, e.g. "SIGTERM". */
|
|
58
|
+
readonly signal?: string;
|
|
59
|
+
/** Spawn error / kill-escalation notes, bounded. */
|
|
60
|
+
readonly errorText?: string;
|
|
61
|
+
readonly stdout: OutputView;
|
|
62
|
+
readonly stderr: OutputView;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function formatElapsed(snap: TerminalSnapshot) {
|
|
66
|
+
const end = snap.settledAt ?? Date.now();
|
|
67
|
+
const totalSeconds = Math.max(0, Math.round((end - snap.createdAt) / 1000));
|
|
68
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
69
|
+
const seconds = totalSeconds % 60;
|
|
70
|
+
return minutes > 0
|
|
71
|
+
? `${minutes}m${seconds.toString().padStart(2, "0")}s`
|
|
72
|
+
: `${seconds}s`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** "exit 0", "exit 137", "SIGTERM", or "running". */
|
|
76
|
+
export function formatExit(snap: TerminalSnapshot) {
|
|
77
|
+
if (snap.status === "running") return "running";
|
|
78
|
+
if (snap.status === "timed_out") return "timed out";
|
|
79
|
+
if (snap.signal) return snap.signal;
|
|
80
|
+
if (snap.exitCode !== undefined) return `exit ${snap.exitCode}`;
|
|
81
|
+
return snap.status;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// --- Errors -------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
export class SpawnError extends Data.TaggedError("SpawnError")<{
|
|
87
|
+
readonly message: string;
|
|
88
|
+
/** True only when the manager can prove no child process was created. */
|
|
89
|
+
readonly fallbackSafe: boolean;
|
|
90
|
+
}> {}
|
|
91
|
+
|
|
92
|
+
export class ConcurrencyLimitError extends Data.TaggedError(
|
|
93
|
+
"ConcurrencyLimitError",
|
|
94
|
+
)<{
|
|
95
|
+
readonly message: string;
|
|
96
|
+
}> {}
|
|
97
|
+
|
|
98
|
+
export class UnknownTerminalError extends Data.TaggedError(
|
|
99
|
+
"UnknownTerminalError",
|
|
100
|
+
)<{
|
|
101
|
+
readonly message: string;
|
|
102
|
+
}> {}
|