@corenel/term 0.1.0 → 0.2.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/LICENSE +93 -0
- package/dist/chunk-2D7G4YW2.js +97 -0
- package/dist/chunk-CB5RFNB4.js +33 -0
- package/dist/chunk-F4ENQXQQ.js +909 -0
- package/dist/chunk-I44FC7PE.js +142 -0
- package/dist/chunk-IK4ZANNW.js +95 -0
- package/dist/chunk-M44CET5L.js +40 -0
- package/dist/chunk-OSJFMQCW.js +44 -0
- package/dist/{chunk-R6AXTD55.js → chunk-VTJEWHM3.js} +19 -1
- package/dist/commands.d.ts +14 -0
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +1 -1
- package/dist/describe.d.ts +23 -0
- package/dist/describe.d.ts.map +1 -1
- package/dist/describe.js +6 -1
- package/dist/diff.d.ts +100 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +18 -0
- package/dist/ink/App.d.ts +12 -0
- package/dist/ink/App.d.ts.map +1 -1
- package/dist/ink/Modal.d.ts.map +1 -1
- package/dist/ink/TodoBlock.d.ts +10 -0
- package/dist/ink/TodoBlock.d.ts.map +1 -0
- package/dist/ink/banner.d.ts +36 -0
- package/dist/ink/banner.d.ts.map +1 -0
- package/dist/ink/index.d.ts.map +1 -1
- package/dist/ink/index.js +300 -140
- package/dist/ink/items.d.ts.map +1 -1
- package/dist/ink/scroll.d.ts +14 -2
- package/dist/ink/scroll.d.ts.map +1 -1
- package/dist/ink/status.d.ts.map +1 -1
- package/dist/ink/theme.d.ts +9 -0
- package/dist/ink/theme.d.ts.map +1 -1
- package/dist/ink/todos.d.ts +20 -0
- package/dist/ink/todos.d.ts.map +1 -0
- package/dist/markdown.d.ts.map +1 -1
- package/dist/persist.d.ts +27 -4
- package/dist/persist.d.ts.map +1 -1
- package/dist/persist.js +3 -1
- package/dist/policySource.d.ts +26 -0
- package/dist/policySource.d.ts.map +1 -0
- package/dist/policySource.js +8 -0
- package/dist/session.d.ts +199 -13
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +10 -5
- package/dist/state.d.ts +84 -0
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +5 -3
- package/dist/writePreview.d.ts +58 -0
- package/dist/writePreview.d.ts.map +1 -0
- package/dist/writePreview.js +9 -0
- package/package.json +19 -6
- package/dist/chunk-47IH6X5H.js +0 -20
- package/dist/chunk-HAQGHLW6.js +0 -235
- package/dist/chunk-HYWLODZT.js +0 -1230
- package/dist/chunk-TCVDVYMF.js +0 -40
package/dist/state.d.ts
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import type { AskQuestion, Usage } from '@corenel/protocol';
|
|
2
|
+
import type { WritePreview } from './writePreview';
|
|
3
|
+
/** One selectable id for a host-supplied capability (`/mode`, `/persona`,
|
|
4
|
+
* `/skill`, `/strategy`) -- a mode/persona/skill/strategy as DATA, never as a
|
|
5
|
+
* file path or a config-workspace detail, so this stays meaningful to a host
|
|
6
|
+
* that has neither (see TermSessionDeps.listModes et al. in ./session). */
|
|
7
|
+
export interface PaletteEntry {
|
|
8
|
+
id: string;
|
|
9
|
+
label: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
}
|
|
2
12
|
/** An item that can no longer change. Rendered ONCE, then owned by the terminal. */
|
|
3
13
|
export type CommittedItem = {
|
|
4
14
|
kind: 'user';
|
|
@@ -8,6 +18,28 @@ export type CommittedItem = {
|
|
|
8
18
|
kind: 'assistant';
|
|
9
19
|
id: string;
|
|
10
20
|
text: string;
|
|
21
|
+
/** True iff a face should print a blank line before this block: either
|
|
22
|
+
* it is the FIRST committed block of a reply (separating it from the
|
|
23
|
+
* user's line, or from the previous reply -- always true for a
|
|
24
|
+
* restored item, since history rebuilds a whole message as one item;
|
|
25
|
+
* see persist.ts), or the model itself wrote a blank line immediately
|
|
26
|
+
* before it. Never true for any other block, so a multi-block reply
|
|
27
|
+
* gets exactly the blank lines the model wrote, not one per block.
|
|
28
|
+
*
|
|
29
|
+
* A blank span itself is NOT committed as its own item (see
|
|
30
|
+
* TermSession.commitCompletedBlocks) -- Ink's `<Static>` writes each
|
|
31
|
+
* commit as its own flushed chunk as a real turn streams in, and a
|
|
32
|
+
* chunk whose only content is a blank line has its trailing blank
|
|
33
|
+
* trimmed by that flush, silently eating the separator. Folding a
|
|
34
|
+
* blank line into THIS flag on the block that follows it survives that
|
|
35
|
+
* trim, because the flag lands on a chunk that also carries real text.
|
|
36
|
+
*
|
|
37
|
+
* Set at commit time by the controller (or by persist.ts for a
|
|
38
|
+
* restored item) rather than inferred in the view by looking at
|
|
39
|
+
* neighbours, because `<Static>` renders each committed item
|
|
40
|
+
* independently and a view-side lookback breaks on resume and after
|
|
41
|
+
* `/clear`. */
|
|
42
|
+
leadingBlank?: true;
|
|
11
43
|
} | {
|
|
12
44
|
kind: 'tool';
|
|
13
45
|
id: string;
|
|
@@ -20,6 +52,13 @@ export type CommittedItem = {
|
|
|
20
52
|
durationMs?: number;
|
|
21
53
|
offloadedBytes?: number;
|
|
22
54
|
offloadRefId?: string;
|
|
55
|
+
/** True for a row rebuilt from a resumed conversation rather than one this
|
|
56
|
+
* process just ran. History never records success/failure (a `role: 'tool'`
|
|
57
|
+
* message carries no such flag) or duration, so a restored row's `isError`
|
|
58
|
+
* is always `false` and `durationMs` is always absent — this flag tells a
|
|
59
|
+
* face to render a neutral marker instead of reading `isError` as a real
|
|
60
|
+
* outcome. See @corenel/term's persist.ts. */
|
|
61
|
+
restored?: true;
|
|
23
62
|
} | {
|
|
24
63
|
kind: 'note';
|
|
25
64
|
id: string;
|
|
@@ -33,18 +72,43 @@ export interface LiveTool {
|
|
|
33
72
|
ord: number;
|
|
34
73
|
name: string;
|
|
35
74
|
subject: string;
|
|
75
|
+
/** Output streamed via `ctx.stream` (see 'tool-delta' in AgentEvent) while
|
|
76
|
+
* this call is still in flight -- e.g. a dev server's stdout. Bounded (see
|
|
77
|
+
* TermSession's TOOL_DELTA_CAP): this is a LIVE row Ink repaints every
|
|
78
|
+
* frame, so it must never hold more than a small, single-line-safe tail --
|
|
79
|
+
* the same reason streamed assistant text is committed block by block
|
|
80
|
+
* instead of growing `live.streaming` without bound (see session.ts's
|
|
81
|
+
* module comment). Absent means no delta has arrived for this call yet. */
|
|
82
|
+
delta?: string;
|
|
36
83
|
}
|
|
37
84
|
export interface PermissionAsk {
|
|
38
85
|
kind: 'permission';
|
|
39
86
|
tool: string;
|
|
40
87
|
/** The command or path being approved — never just the tool name. */
|
|
41
88
|
detail: string;
|
|
89
|
+
/** For a write/create call whose existing (or absent) content could be read:
|
|
90
|
+
* the diff to show instead of a bare path. Omitted for every other tool, and
|
|
91
|
+
* degrades to omitted when the existing file couldn't be read -- see
|
|
92
|
+
* `buildWritePreview` in ./writePreview. */
|
|
93
|
+
diff?: WritePreview;
|
|
42
94
|
}
|
|
43
95
|
export interface UserAsk {
|
|
44
96
|
kind: 'user';
|
|
45
97
|
questions: AskQuestion[];
|
|
46
98
|
}
|
|
47
99
|
export type Modal = PermissionAsk | UserAsk;
|
|
100
|
+
export type TodoStatus = 'pending' | 'in_progress' | 'completed' | 'blocked';
|
|
101
|
+
/** A local mirror of the harness's `todo_write` tool contract (see
|
|
102
|
+
* packages/harness/tools/todo.ts) — kept local rather than imported so this
|
|
103
|
+
* headless half of the package never pulls in anything from a tool
|
|
104
|
+
* definition, and stays free to add fields (or drop them) without coupling
|
|
105
|
+
* to the tool's JSON schema. */
|
|
106
|
+
export interface TodoItem {
|
|
107
|
+
id: string;
|
|
108
|
+
title: string;
|
|
109
|
+
status: TodoStatus;
|
|
110
|
+
note?: string;
|
|
111
|
+
}
|
|
48
112
|
export interface TuiState {
|
|
49
113
|
/** Bumped on every change so subscribers using useSyncExternalStore refresh. */
|
|
50
114
|
seq: number;
|
|
@@ -70,6 +134,26 @@ export interface TuiState {
|
|
|
70
134
|
} | null;
|
|
71
135
|
status: 'idle' | 'running' | 'aborting';
|
|
72
136
|
error: string | null;
|
|
137
|
+
/** The agent's task list, as last replaced by `todo_write` (a full rewrite
|
|
138
|
+
* each call -- see packages/harness/tools/todo.ts). Persists across turns
|
|
139
|
+
* within a conversation; cleared by `/clear`. */
|
|
140
|
+
todos: TodoItem[];
|
|
141
|
+
/** The label of a skill armed by `/skill <name>`, or null. Armed for the
|
|
142
|
+
* NEXT message only -- a face renders this as a visible "armed" indicator
|
|
143
|
+
* so the effect is never silent, and it is cleared the moment that message
|
|
144
|
+
* is sent (see TermSession.runTurn), not when the turn finishes. */
|
|
145
|
+
pendingSkill: string | null;
|
|
73
146
|
}
|
|
74
147
|
export declare function emptyState(): TuiState;
|
|
148
|
+
/** Parse a `todo_write` call's `args` into the list this face renders.
|
|
149
|
+
*
|
|
150
|
+
* This is MODEL OUTPUT, not a value our own code produced, so it is parsed
|
|
151
|
+
* defensively: anything that doesn't fit the shape is dropped rather than
|
|
152
|
+
* thrown on, because a malformed call here must never take down the turn it
|
|
153
|
+
* arrived in (see session.ts's `tool-call` handling). A missing `todos`, a
|
|
154
|
+
* non-array, a non-object item, or a missing/blank `title` drops that item
|
|
155
|
+
* (or the whole call); an unknown `status` string coerces to `pending`
|
|
156
|
+
* rather than being dropped, since the item itself is still real work the
|
|
157
|
+
* agent wants tracked. */
|
|
158
|
+
export declare function parseTodos(args: unknown): TodoItem[];
|
|
75
159
|
//# sourceMappingURL=state.d.ts.map
|
package/dist/state.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD;;;2EAG2E;AAC3E,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,oFAAoF;AACpF,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAC5C;;;;;;;;;;;;;;;;;;;;mBAoBe;IACf,YAAY,CAAC,EAAE,IAAI,CAAA;CAAE,GACvB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IACrF,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IACzG;;;;;kDAK8C;IAC9C,QAAQ,CAAC,EAAE,IAAI,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAElG,kDAAkD;AAClD,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;;+EAM2E;IAC3E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,YAAY,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf;;;gDAG4C;IAC5C,IAAI,CAAC,EAAE,YAAY,CAAC;CACrB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,WAAW,EAAE,CAAC;CAC1B;AAED,MAAM,MAAM,KAAK,GAAG,aAAa,GAAG,OAAO,CAAC;AAE5C,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,aAAa,GAAG,WAAW,GAAG,SAAS,CAAC;AAE7E;;;;gCAIgC;AAChC,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,QAAQ;IACvB,gFAAgF;IAChF,GAAG,EAAE,MAAM,CAAC;IASZ,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,QAAQ,EAAE,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACpE,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,KAAK,CAAC;IACb;;;;+EAI2E;IAC3E,SAAS,EAAE,KAAK,CAAC;IACjB,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC9C,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,CAAC;IACxC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB;;qDAEiD;IACjD,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB;;;wEAGoE;IACpE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,wBAAgB,UAAU,IAAI,QAAQ,CAgBrC;AAQD;;;;;;;;;0BAS0B;AAC1B,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,EAAE,CAiBpD"}
|
package/dist/state.js
CHANGED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type DiffRow, type LineEndingChange } from './diff';
|
|
2
|
+
/** The write tools whose `input` this can preview -- see
|
|
3
|
+
* packages/harness/tools/files.ts's `writeFileTool`/`createFileTool`. Both take
|
|
4
|
+
* `{ path: string; content?: string }`. Every OTHER tool (delete_file,
|
|
5
|
+
* rename_file, shell, ...) has no "pending file content" to diff, so
|
|
6
|
+
* `buildWritePreview` returns null for them and the caller falls back to the
|
|
7
|
+
* existing bare-path prompt. */
|
|
8
|
+
export declare const WRITE_TOOL_NAMES: ReadonlySet<string>;
|
|
9
|
+
export interface WritePreview {
|
|
10
|
+
path: string;
|
|
11
|
+
/** True when nothing on disk exists at `path` today (a create, or a
|
|
12
|
+
* write_file to a path that doesn't exist yet). */
|
|
13
|
+
isNewFile: boolean;
|
|
14
|
+
/** True when the file exists and its content is byte-identical to what
|
|
15
|
+
* would be written -- a real write call with nothing to show. */
|
|
16
|
+
noop: boolean;
|
|
17
|
+
/** Set when the dominant line ending differs between what's on disk and
|
|
18
|
+
* what would be written (see `lineEndingChange`) -- a fact about the write
|
|
19
|
+
* itself, rendered SEPARATELY from `rows` (never subject to the cap, and
|
|
20
|
+
* never absent just because the content diff is empty): a content-identical
|
|
21
|
+
* CRLF -> LF rewrite must not look like an empty no-op diff, since the file
|
|
22
|
+
* itself is genuinely about to change. */
|
|
23
|
+
endingChange: LineEndingChange | null;
|
|
24
|
+
/** CATCH-ALL invariant: true when the raw bytes genuinely differ
|
|
25
|
+
* (`before !== content`, and this is not a brand-new file) but neither
|
|
26
|
+
* `endingChange` nor `rows` (`added`/`removed` both zero) would show
|
|
27
|
+
* anything -- e.g. a trailing-newline-only change, which the line-level
|
|
28
|
+
* diff can't see (see splitLines's phantom-trailing-line fix: the same
|
|
29
|
+
* correction that stops a false blank-line diff also makes a REAL
|
|
30
|
+
* trailing-newline-only change invisible to `lineDiff`). Whatever the
|
|
31
|
+
* actual reason turns out to be, the rule this exists to enforce is
|
|
32
|
+
* simple: if the bytes on disk are about to change, the preview must
|
|
33
|
+
* never look like nothing is changing. Rendered as an explicit,
|
|
34
|
+
* unconditional row, same as `endingChange` -- never subject to the cap. */
|
|
35
|
+
unexplainedChange: boolean;
|
|
36
|
+
/** Capped diff rows to render (empty when `noop`) -- diff lines interleaved
|
|
37
|
+
* with `skip` markers for whatever the cap elided. See `capDiffLines`. */
|
|
38
|
+
rows: DiffRow[];
|
|
39
|
+
/** Length of the full (uncapped) diff, for a "N lines total" caller might want. */
|
|
40
|
+
totalLines: number;
|
|
41
|
+
added: number;
|
|
42
|
+
removed: number;
|
|
43
|
+
}
|
|
44
|
+
/** Reads the CURRENT text at `path`, or resolves `null` when nothing exists
|
|
45
|
+
* there. Must REJECT (not resolve null) for a real read failure -- an
|
|
46
|
+
* existing-but-unreadable file (permission error, binary content, a symlink
|
|
47
|
+
* escape) -- so `buildWritePreview` can tell "new file" apart from "could not
|
|
48
|
+
* read the old one" and degrade the latter to the path-only prompt rather
|
|
49
|
+
* than claiming a file that exists is new. */
|
|
50
|
+
export type ReadExisting = (path: string) => Promise<string | null>;
|
|
51
|
+
/**
|
|
52
|
+
* Build the diff to show for a pending write/create call, or `null` when this
|
|
53
|
+
* tool call has nothing previewable (not a write tool, no usable `path`, or
|
|
54
|
+
* the existing file could not be read) -- callers must treat `null` as "fall
|
|
55
|
+
* back to the tool's bare-path prompt", not as an error.
|
|
56
|
+
*/
|
|
57
|
+
export declare function buildWritePreview(tool: string, input: unknown, readExisting: ReadExisting, maxLines?: number): Promise<WritePreview | null>;
|
|
58
|
+
//# sourceMappingURL=writePreview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writePreview.d.ts","sourceRoot":"","sources":["../src/writePreview.ts"],"names":[],"mappings":"AASA,OAAO,EAAsD,KAAK,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAEjH;;;;;gCAKgC;AAChC,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAA0C,CAAC;AAE5F,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb;uDACmD;IACnD,SAAS,EAAE,OAAO,CAAC;IACnB;qEACiE;IACjE,IAAI,EAAE,OAAO,CAAC;IACd;;;;;8CAK0C;IAC1C,YAAY,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACtC;;;;;;;;;;gFAU4E;IAC5E,iBAAiB,EAAE,OAAO,CAAC;IAC3B;8EAC0E;IAC1E,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,mFAAmF;IACnF,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAID;;;;;8CAK8C;AAC9C,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAEpE;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,OAAO,EACd,YAAY,EAAE,YAAY,EAC1B,QAAQ,GAAE,MAA0B,GACnC,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAiC9B"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@corenel/term",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Corenel's terminal face — a headless chat controller plus its Ink views. `./session` is ink-free and shared with the CLI's readline fallback; only `./ink` pulls React. dist/ is bundled (see build.mjs),
|
|
5
|
+
"description": "Corenel's terminal face — a headless chat controller plus its Ink views. `./session` is ink-free and shared with the CLI's readline fallback; only `./ink` pulls React. dist/ is bundled (see build.mjs), but @corenel/harness and @corenel/protocol stay EXTERNAL to that bundle — they are real runtime dependencies, declared below, so the CLI and term share exactly one harness module instance (see build.mjs for why two instances is a governance hole, not a cosmetic bug).",
|
|
6
6
|
"exports": {
|
|
7
7
|
"./session": {
|
|
8
8
|
"types": "./dist/session.d.ts",
|
|
@@ -20,10 +20,22 @@
|
|
|
20
20
|
"types": "./dist/describe.d.ts",
|
|
21
21
|
"default": "./dist/describe.js"
|
|
22
22
|
},
|
|
23
|
+
"./diff": {
|
|
24
|
+
"types": "./dist/diff.d.ts",
|
|
25
|
+
"default": "./dist/diff.js"
|
|
26
|
+
},
|
|
27
|
+
"./writePreview": {
|
|
28
|
+
"types": "./dist/writePreview.d.ts",
|
|
29
|
+
"default": "./dist/writePreview.js"
|
|
30
|
+
},
|
|
23
31
|
"./persist": {
|
|
24
32
|
"types": "./dist/persist.d.ts",
|
|
25
33
|
"default": "./dist/persist.js"
|
|
26
34
|
},
|
|
35
|
+
"./policySource": {
|
|
36
|
+
"types": "./dist/policySource.d.ts",
|
|
37
|
+
"default": "./dist/policySource.js"
|
|
38
|
+
},
|
|
27
39
|
"./ink": {
|
|
28
40
|
"types": "./dist/ink/index.d.ts",
|
|
29
41
|
"default": "./dist/ink/index.js"
|
|
@@ -33,7 +45,9 @@
|
|
|
33
45
|
"ink": "^5.2.1",
|
|
34
46
|
"ink-text-input": "^6.0.0",
|
|
35
47
|
"openai": "^4.77.0",
|
|
36
|
-
"react": "^18.3.1"
|
|
48
|
+
"react": "^18.3.1",
|
|
49
|
+
"@corenel/harness": "0.4.1",
|
|
50
|
+
"@corenel/protocol": "0.4.1"
|
|
37
51
|
},
|
|
38
52
|
"devDependencies": {
|
|
39
53
|
"@types/node": "^24.13.1",
|
|
@@ -41,9 +55,7 @@
|
|
|
41
55
|
"esbuild": "^0.21.5",
|
|
42
56
|
"ink-testing-library": "^4.0.0",
|
|
43
57
|
"typescript": "^5.9.3",
|
|
44
|
-
"@corenel/
|
|
45
|
-
"@corenel/protocol": "0.3.2",
|
|
46
|
-
"@corenel/tools-node": "0.2.0"
|
|
58
|
+
"@corenel/tools-node": "0.2.2"
|
|
47
59
|
},
|
|
48
60
|
"license": "Elastic-2.0",
|
|
49
61
|
"engines": {
|
|
@@ -58,6 +70,7 @@
|
|
|
58
70
|
},
|
|
59
71
|
"scripts": {
|
|
60
72
|
"typecheck": "tsc --noEmit",
|
|
73
|
+
"prebuild": "pnpm --filter @corenel/protocol build && pnpm --filter @corenel/harness build",
|
|
61
74
|
"build": "node build.mjs && tsc -p tsconfig.build.json"
|
|
62
75
|
}
|
|
63
76
|
}
|
package/dist/chunk-47IH6X5H.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
// src/state.ts
|
|
2
|
-
function emptyState() {
|
|
3
|
-
return {
|
|
4
|
-
seq: 0,
|
|
5
|
-
transcriptGen: 0,
|
|
6
|
-
committed: [],
|
|
7
|
-
live: null,
|
|
8
|
-
queued: [],
|
|
9
|
-
modal: null,
|
|
10
|
-
usage: { promptTokens: 0, completionTokens: 0 },
|
|
11
|
-
turnUsage: { promptTokens: 0, completionTokens: 0 },
|
|
12
|
-
session: null,
|
|
13
|
-
status: "idle",
|
|
14
|
-
error: null
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export {
|
|
19
|
-
emptyState
|
|
20
|
-
};
|
package/dist/chunk-HAQGHLW6.js
DELETED
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
// ../protocol/files.ts
|
|
2
|
-
function flattenFiles(nodes, out = []) {
|
|
3
|
-
for (const n of nodes) {
|
|
4
|
-
if (n.kind === "file") out.push(n);
|
|
5
|
-
if (n.children?.length) flattenFiles(n.children, out);
|
|
6
|
-
}
|
|
7
|
-
return out;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
// ../protocol/stateDir.ts
|
|
11
|
-
var DEFAULT_STATE_DIR = ".corenel";
|
|
12
|
-
var dirName = DEFAULT_STATE_DIR;
|
|
13
|
-
function sessionsRoot() {
|
|
14
|
-
return `${dirName}/sessions`;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// ../harness/sessions/jsonl.ts
|
|
18
|
-
function encodeLine(line) {
|
|
19
|
-
return JSON.stringify(line) + "\n";
|
|
20
|
-
}
|
|
21
|
-
function foldSession(text) {
|
|
22
|
-
let meta = null;
|
|
23
|
-
const turns = [];
|
|
24
|
-
let title = "";
|
|
25
|
-
let titleLocked;
|
|
26
|
-
let updatedAt = 0;
|
|
27
|
-
for (const raw of text.split("\n")) {
|
|
28
|
-
const s = raw.trim();
|
|
29
|
-
if (!s) continue;
|
|
30
|
-
let line;
|
|
31
|
-
try {
|
|
32
|
-
line = JSON.parse(s);
|
|
33
|
-
} catch {
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
if (line.k === "meta" && !meta && typeof line.id === "string" && typeof line.createdAt === "number") {
|
|
37
|
-
meta = line;
|
|
38
|
-
title = typeof line.title === "string" ? line.title : "";
|
|
39
|
-
updatedAt = Math.max(updatedAt, line.createdAt);
|
|
40
|
-
} else if (line.k === "meta-update" && typeof line.at === "number") {
|
|
41
|
-
if (typeof line.title === "string") title = line.title;
|
|
42
|
-
if (typeof line.titleLocked === "boolean") titleLocked = line.titleLocked;
|
|
43
|
-
updatedAt = Math.max(updatedAt, line.at);
|
|
44
|
-
} else if (line.k === "turn" && Number.isInteger(line.i) && line.i >= 0 && typeof line.at === "number") {
|
|
45
|
-
turns[line.i] = line.m;
|
|
46
|
-
updatedAt = Math.max(updatedAt, line.at);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
if (!meta) return null;
|
|
50
|
-
return {
|
|
51
|
-
id: meta.id,
|
|
52
|
-
title,
|
|
53
|
-
createdAt: meta.createdAt,
|
|
54
|
-
updatedAt: updatedAt || meta.createdAt,
|
|
55
|
-
messages: turns.filter((t) => t !== void 0),
|
|
56
|
-
...meta.surface ? { surface: meta.surface } : {},
|
|
57
|
-
...titleLocked !== void 0 ? { titleLocked } : {}
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ../harness/sessions/fileSessionStore.ts
|
|
62
|
-
function uid(prefix) {
|
|
63
|
-
return `${prefix}_${Date.now().toString(36)}${Math.floor(Math.random() * 1e6).toString(36)}`;
|
|
64
|
-
}
|
|
65
|
-
var lastTs = 0;
|
|
66
|
-
function now() {
|
|
67
|
-
const t = Date.now();
|
|
68
|
-
lastTs = t > lastTs ? t : lastTs + 1;
|
|
69
|
-
return lastTs;
|
|
70
|
-
}
|
|
71
|
-
var JsonlSessionStore = class {
|
|
72
|
-
constructor(getFiles, root = sessionsRoot) {
|
|
73
|
-
this.getFiles = getFiles;
|
|
74
|
-
this.root = root;
|
|
75
|
-
}
|
|
76
|
-
cursors = /* @__PURE__ */ new Map();
|
|
77
|
-
async files() {
|
|
78
|
-
const f = await this.getFiles();
|
|
79
|
-
if (!f) throw new Error("No file workspace is connected \u2014 sessions are unavailable.");
|
|
80
|
-
return f;
|
|
81
|
-
}
|
|
82
|
-
path(id) {
|
|
83
|
-
return `${this.root()}/${id}/SESSION.jsonl`;
|
|
84
|
-
}
|
|
85
|
-
async create(title, surface) {
|
|
86
|
-
const createdAt = now();
|
|
87
|
-
const id = uid("ses");
|
|
88
|
-
const meta = { k: "meta", id, title, createdAt, ...surface ? { surface } : {} };
|
|
89
|
-
const text = encodeLine(meta);
|
|
90
|
-
await (await this.files()).write(this.path(id), text);
|
|
91
|
-
this.cursors.set(id, { text, turnJson: [], title });
|
|
92
|
-
return { id, title, createdAt, updatedAt: createdAt, messages: [], ...surface ? { surface } : {} };
|
|
93
|
-
}
|
|
94
|
-
async get(id) {
|
|
95
|
-
const text = await (await this.files()).read(this.path(id)).catch(() => null);
|
|
96
|
-
if (text == null) return null;
|
|
97
|
-
const ses = foldSession(text);
|
|
98
|
-
if (ses) {
|
|
99
|
-
this.cursors.set(id, {
|
|
100
|
-
text,
|
|
101
|
-
turnJson: ses.messages.map((m) => JSON.stringify(m)),
|
|
102
|
-
title: ses.title,
|
|
103
|
-
titleLocked: ses.titleLocked
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
return ses;
|
|
107
|
-
}
|
|
108
|
-
async list(surface) {
|
|
109
|
-
const files = await this.files();
|
|
110
|
-
const root = this.root();
|
|
111
|
-
const nodes = await files.list(root).catch(() => []);
|
|
112
|
-
const sessionFiles = flattenFiles(nodes).filter((f) => f.name === "SESSION.jsonl" && f.path.startsWith(root + "/"));
|
|
113
|
-
const out = [];
|
|
114
|
-
for (const f of sessionFiles) {
|
|
115
|
-
const text = await files.read(f.path).catch(() => null);
|
|
116
|
-
const ses = text != null ? foldSession(text) : null;
|
|
117
|
-
if (ses) out.push(ses);
|
|
118
|
-
}
|
|
119
|
-
const scoped = surface ? out.filter((s) => s.surface === surface || surface === "agent" && !s.surface) : out;
|
|
120
|
-
return scoped.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
121
|
-
}
|
|
122
|
-
async save(session) {
|
|
123
|
-
const at = now();
|
|
124
|
-
const cur = this.cursors.get(session.id) ?? await this.seed(session);
|
|
125
|
-
const turnJson = session.messages.map((m) => JSON.stringify(m));
|
|
126
|
-
const appended = [];
|
|
127
|
-
for (let i = 0; i < turnJson.length; i++) {
|
|
128
|
-
if (cur.turnJson[i] !== turnJson[i]) {
|
|
129
|
-
appended.push(encodeLine({ k: "turn", i, at, m: session.messages[i] }));
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
if (session.title !== cur.title || session.titleLocked !== cur.titleLocked) {
|
|
133
|
-
appended.push(encodeLine({
|
|
134
|
-
k: "meta-update",
|
|
135
|
-
at,
|
|
136
|
-
title: session.title,
|
|
137
|
-
...session.titleLocked !== void 0 ? { titleLocked: session.titleLocked } : {}
|
|
138
|
-
}));
|
|
139
|
-
}
|
|
140
|
-
if (!appended.length) return;
|
|
141
|
-
session.updatedAt = at;
|
|
142
|
-
const text = cur.text + appended.join("");
|
|
143
|
-
await (await this.files()).write(this.path(session.id), text);
|
|
144
|
-
this.cursors.set(session.id, { text, turnJson, title: session.title, titleLocked: session.titleLocked });
|
|
145
|
-
}
|
|
146
|
-
/** Cursor for a session this instance hasn't touched (e.g. saved after a
|
|
147
|
-
* reload): fold the existing file, or start a fresh log from the session's
|
|
148
|
-
* own meta. */
|
|
149
|
-
async seed(session) {
|
|
150
|
-
const text = await (await this.files()).read(this.path(session.id)).catch(() => null);
|
|
151
|
-
if (text != null) {
|
|
152
|
-
const ses = foldSession(text);
|
|
153
|
-
if (ses) {
|
|
154
|
-
return { text, turnJson: ses.messages.map((m) => JSON.stringify(m)), title: ses.title, titleLocked: ses.titleLocked };
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
const meta = encodeLine({
|
|
158
|
-
k: "meta",
|
|
159
|
-
id: session.id,
|
|
160
|
-
title: session.title,
|
|
161
|
-
createdAt: session.createdAt,
|
|
162
|
-
...session.surface ? { surface: session.surface } : {}
|
|
163
|
-
});
|
|
164
|
-
return { text: meta, turnJson: [], title: session.title, titleLocked: void 0 };
|
|
165
|
-
}
|
|
166
|
-
async rename(id, title) {
|
|
167
|
-
const cur = this.cursors.get(id);
|
|
168
|
-
const text = cur?.text ?? await (await this.files()).read(this.path(id)).catch(() => null);
|
|
169
|
-
if (text == null) return;
|
|
170
|
-
const next = text + encodeLine({ k: "meta-update", at: now(), title, titleLocked: true });
|
|
171
|
-
await (await this.files()).write(this.path(id), next);
|
|
172
|
-
const folded = foldSession(next);
|
|
173
|
-
if (folded) {
|
|
174
|
-
this.cursors.set(id, { text: next, turnJson: folded.messages.map((m) => JSON.stringify(m)), title, titleLocked: true });
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
async delete(id) {
|
|
178
|
-
this.cursors.delete(id);
|
|
179
|
-
await (await this.files()).remove(`${this.root()}/${id}`).catch(() => {
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
// src/persist.ts
|
|
185
|
-
function createSessionPersistence(files) {
|
|
186
|
-
const store = new JsonlSessionStore(() => files, () => sessionsRoot());
|
|
187
|
-
return {
|
|
188
|
-
async save(input) {
|
|
189
|
-
const session = input.id ? await store.get(input.id) : await store.create(input.title, "agent");
|
|
190
|
-
if (!session) {
|
|
191
|
-
const fresh = await store.create(input.title, "agent");
|
|
192
|
-
fresh.messages = input.messages;
|
|
193
|
-
await store.save(fresh);
|
|
194
|
-
return { id: fresh.id };
|
|
195
|
-
}
|
|
196
|
-
session.messages = input.messages;
|
|
197
|
-
await store.save(session);
|
|
198
|
-
return { id: session.id };
|
|
199
|
-
},
|
|
200
|
-
async load(id) {
|
|
201
|
-
if (id) {
|
|
202
|
-
const s = await store.get(id);
|
|
203
|
-
return s ? { id: s.id, title: s.title, messages: s.messages } : null;
|
|
204
|
-
}
|
|
205
|
-
const all = await store.list("agent");
|
|
206
|
-
const newest = [...all].sort((a, b) => b.updatedAt - a.updatedAt)[0];
|
|
207
|
-
return newest ? { id: newest.id, title: newest.title, messages: newest.messages } : null;
|
|
208
|
-
},
|
|
209
|
-
list: () => store.list("agent")
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
function committedFromMessages(messages) {
|
|
213
|
-
const items = [];
|
|
214
|
-
let n = 0;
|
|
215
|
-
for (const m of messages) {
|
|
216
|
-
if (m.role === "user" && typeof m.content === "string") {
|
|
217
|
-
items.push({ kind: "user", id: `h${n++}`, text: m.content });
|
|
218
|
-
} else if (m.role === "assistant" && typeof m.content === "string" && m.content.trim()) {
|
|
219
|
-
items.push({ kind: "assistant", id: `h${n++}`, text: m.content });
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
return items;
|
|
223
|
-
}
|
|
224
|
-
function titleFrom(messages) {
|
|
225
|
-
const first = messages.find((m) => m.role === "user");
|
|
226
|
-
const text = typeof first?.content === "string" ? first.content : "";
|
|
227
|
-
const line = text.split("\n")[0].trim();
|
|
228
|
-
return line.length > 60 ? `${line.slice(0, 59)}\u2026` : line || "conversation";
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
export {
|
|
232
|
-
createSessionPersistence,
|
|
233
|
-
committedFromMessages,
|
|
234
|
-
titleFrom
|
|
235
|
-
};
|