@corenel/term 0.1.0 → 0.2.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.
Files changed (55) hide show
  1. package/dist/chunk-2D7G4YW2.js +97 -0
  2. package/dist/chunk-CB5RFNB4.js +33 -0
  3. package/dist/chunk-F4ENQXQQ.js +909 -0
  4. package/dist/chunk-I44FC7PE.js +142 -0
  5. package/dist/chunk-IK4ZANNW.js +95 -0
  6. package/dist/chunk-M44CET5L.js +40 -0
  7. package/dist/chunk-OSJFMQCW.js +44 -0
  8. package/dist/{chunk-R6AXTD55.js → chunk-VTJEWHM3.js} +19 -1
  9. package/dist/commands.d.ts +14 -0
  10. package/dist/commands.d.ts.map +1 -1
  11. package/dist/commands.js +1 -1
  12. package/dist/describe.d.ts +23 -0
  13. package/dist/describe.d.ts.map +1 -1
  14. package/dist/describe.js +6 -1
  15. package/dist/diff.d.ts +100 -0
  16. package/dist/diff.d.ts.map +1 -0
  17. package/dist/diff.js +18 -0
  18. package/dist/ink/App.d.ts +12 -0
  19. package/dist/ink/App.d.ts.map +1 -1
  20. package/dist/ink/Modal.d.ts.map +1 -1
  21. package/dist/ink/TodoBlock.d.ts +10 -0
  22. package/dist/ink/TodoBlock.d.ts.map +1 -0
  23. package/dist/ink/banner.d.ts +36 -0
  24. package/dist/ink/banner.d.ts.map +1 -0
  25. package/dist/ink/index.d.ts.map +1 -1
  26. package/dist/ink/index.js +300 -140
  27. package/dist/ink/items.d.ts.map +1 -1
  28. package/dist/ink/scroll.d.ts +14 -2
  29. package/dist/ink/scroll.d.ts.map +1 -1
  30. package/dist/ink/status.d.ts.map +1 -1
  31. package/dist/ink/theme.d.ts +9 -0
  32. package/dist/ink/theme.d.ts.map +1 -1
  33. package/dist/ink/todos.d.ts +20 -0
  34. package/dist/ink/todos.d.ts.map +1 -0
  35. package/dist/markdown.d.ts.map +1 -1
  36. package/dist/persist.d.ts +27 -4
  37. package/dist/persist.d.ts.map +1 -1
  38. package/dist/persist.js +3 -1
  39. package/dist/policySource.d.ts +26 -0
  40. package/dist/policySource.d.ts.map +1 -0
  41. package/dist/policySource.js +8 -0
  42. package/dist/session.d.ts +199 -13
  43. package/dist/session.d.ts.map +1 -1
  44. package/dist/session.js +10 -5
  45. package/dist/state.d.ts +84 -0
  46. package/dist/state.d.ts.map +1 -1
  47. package/dist/state.js +5 -3
  48. package/dist/writePreview.d.ts +58 -0
  49. package/dist/writePreview.d.ts.map +1 -0
  50. package/dist/writePreview.js +9 -0
  51. package/package.json +19 -6
  52. package/dist/chunk-47IH6X5H.js +0 -20
  53. package/dist/chunk-HAQGHLW6.js +0 -235
  54. package/dist/chunk-HYWLODZT.js +0 -1230
  55. 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
@@ -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;AAE5D,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,CAAA;CAAE,GAC/C;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,CAAA;CAAE,GAC5G;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;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,YAAY,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;CAChB;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,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;CACtB;AAED,wBAAgB,UAAU,IAAI,QAAQ,CAcrC"}
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"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"}
package/dist/state.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import {
2
- emptyState
3
- } from "./chunk-47IH6X5H.js";
2
+ emptyState,
3
+ parseTodos
4
+ } from "./chunk-OSJFMQCW.js";
4
5
  export {
5
- emptyState
6
+ emptyState,
7
+ parseTodos
6
8
  };
@@ -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"}
@@ -0,0 +1,9 @@
1
+ import {
2
+ WRITE_TOOL_NAMES,
3
+ buildWritePreview
4
+ } from "./chunk-M44CET5L.js";
5
+ import "./chunk-I44FC7PE.js";
6
+ export {
7
+ WRITE_TOOL_NAMES,
8
+ buildWritePreview
9
+ };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@corenel/term",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
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), so @corenel/harness and @corenel/protocol are inlined at build time and are NOT runtime dependencies; they are needed only to consume the published .d.ts.",
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.0",
50
+ "@corenel/protocol": "0.4.0"
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/harness": "0.3.2",
45
- "@corenel/protocol": "0.3.2",
46
- "@corenel/tools-node": "0.2.0"
58
+ "@corenel/tools-node": "0.2.1"
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
  }
@@ -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
- };
@@ -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
- };