@north-light/crouter 0.3.200 → 0.3.202
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/api/client.d.ts +2 -1
- package/dist/api/client.js +3 -0
- package/dist/api/dto/inbox.d.ts +1 -1
- package/dist/api/dto/nodes.d.ts +16 -0
- package/dist/builtin-memory/insights/capture.md +1 -1
- package/dist/clients/attach/ansi-cells.d.ts +13 -4
- package/dist/clients/attach/ansi-cells.js +16 -10
- package/dist/clients/attach/overlays/help.js +6 -0
- package/dist/clients/attach/render/transcript-search.d.ts +28 -0
- package/dist/clients/attach/render/transcript-search.js +153 -0
- package/dist/clients/attach/render/viewport.d.ts +10 -1
- package/dist/clients/attach/render/viewport.js +27 -2
- package/dist/clients/attach/session/frame.d.ts +2 -1
- package/dist/clients/attach/session/frame.js +9 -0
- package/dist/clients/attach/session/keys.d.ts +5 -0
- package/dist/clients/attach/session/keys.js +3 -0
- package/dist/clients/attach/session/layout.d.ts +2 -2
- package/dist/clients/attach/session/layout.js +3 -2
- package/dist/clients/attach/session/transcript-search.d.ts +20 -0
- package/dist/clients/attach/session/transcript-search.js +151 -0
- package/dist/clients/attach/viewer.js +712 -710
- package/dist/commands/memory/delete.js +14 -3
- package/dist/commands/memory/edit.d.ts +1 -0
- package/dist/commands/memory/edit.js +166 -0
- package/dist/commands/memory/history.d.ts +1 -0
- package/dist/commands/memory/history.js +179 -0
- package/dist/commands/memory/list.js +1 -1
- package/dist/commands/memory/read.js +2 -2
- package/dist/commands/memory/shared.d.ts +27 -0
- package/dist/commands/memory/shared.js +69 -0
- package/dist/commands/memory/write.js +54 -61
- package/dist/commands/memory.js +6 -3
- package/dist/core/__tests__/seam/dormancy-release.test.js +2 -0
- package/dist/core/human/scan.d.ts +1 -3
- package/dist/core/human/scan.js +3 -3
- package/dist/core/human/types.d.ts +1 -0
- package/dist/core/keybindings/attach-control.d.ts +3 -0
- package/dist/core/keybindings/attach-control.js +1 -0
- package/dist/core/keybindings/catalog.d.ts +2 -2
- package/dist/core/keybindings/catalog.js +6 -0
- package/dist/core/memory/history.d.ts +54 -0
- package/dist/core/memory/history.js +202 -0
- package/dist/core/memory-resolver.d.ts +21 -0
- package/dist/core/memory-resolver.js +47 -16
- package/dist/core/preview-registry.js +2 -0
- package/dist/core/runtime/node-read.d.ts +11 -0
- package/dist/core/runtime/node-read.js +107 -41
- package/dist/daemon/api/handlers/inbox.js +1 -0
- package/dist/daemon/api/handlers/nodes.js +34 -1
- package/package.json +1 -1
- package/runtime.lock.json +2 -2
|
@@ -1,44 +1,45 @@
|
|
|
1
1
|
import { defineLeaf } from '../../core/command.js';
|
|
2
2
|
import { usage } from '../../core/errors.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { writeText, pathExists } from '../../core/fs-utils.js';
|
|
4
|
+
import { appendHistoryRecord, buildHistoryRecord, historyLogPathFor, } from '../../core/memory/history.js';
|
|
5
|
+
import { DOC_RATIONALE_CONSTRAINT, GUIDE_DOC_LINKS, GUIDE_PREDICATE_VOCABULARY, GUIDE_ROUTING_LINE, GUIDE_VISIBILITY_RUNGS, MEMORY_SCOPES, resolveWriteTarget, memoryFilePath, buildOrigin, coerceGate, coerceAppliesTo, coerceReadWhen, overlayParam, serializeMemoryDoc, } from './shared.js';
|
|
6
6
|
// A topic's memory set stays usable only when authors treat cohesion as a creation
|
|
7
7
|
// gate; otherwise small corrections accumulate as overlapping leaves whose routing
|
|
8
8
|
// lines cannot tell a future reader which one owns the topic.
|
|
9
9
|
export const writeLeaf = defineLeaf({
|
|
10
10
|
name: 'write',
|
|
11
|
-
description: 'create
|
|
12
|
-
whenToUse: 'you are recording a new knowledge document or preference
|
|
11
|
+
description: 'create a memory document',
|
|
12
|
+
whenToUse: 'you are recording a new knowledge document or preference that does not exist yet. Creation only — a name already taken at the resolved scope is an error, because revising a stored document goes through `crtr memory edit`, which requires a rationale and records the revision.',
|
|
13
13
|
help: {
|
|
14
14
|
name: 'memory write',
|
|
15
|
-
summary: 'create
|
|
15
|
+
summary: 'create memory/<name>.md at the resolved scope from frontmatter flags + a stdin body',
|
|
16
16
|
guide: 'Every frontmatter flag decides who sees this doc, when, and at what context cost. Each rung up is paid by every future agent at every boot or read, so default each rung down.\n\n' +
|
|
17
17
|
'Pick the kind. knowledge is consulted for facts or procedures; preference directs behavior. The kind choice is about how the doc is used, not about how long it is.\n\n' +
|
|
18
18
|
'Store reusable current truth, not session notes. Useful memories are non-obvious procedures, gotchas, durable preferences, and cross-repo conventions. Do not store chat summaries, implementation history, or facts already recorded in the repo.\n\n' +
|
|
19
|
-
|
|
19
|
+
GUIDE_VISIBILITY_RUNGS + '\n\n' +
|
|
20
20
|
'Choose the scope. `project` is for facts any agent in one repo needs. `user` is for person-wide facts and preferences that should follow the user everywhere. `profile` is for the profile’s bundle of dirs: cross-repo conventions, how the pieces relate, or the user’s stance toward that body of work. `node` is scratch memory only this running node sees; it rides into this node’s knowledge block and dies with the node. When unsure, choose the narrowest scope that will still reach the next agent who needs it.\n\n' +
|
|
21
21
|
'A project root INDEX is the workspace front door. Name the physical doc INDEX, target the exact project with --dir, and use kind knowledge, system-prompt-visibility none, file-read-visibility content, and applies-to ".". Its envelope name comes from the owning project directory unless the document already carries an explicit name. Keep only the project constraints, key commands, architecture orientation, and conventions that differ from defaults. `crtr memory lint` validates this exact contract for every project managed by the selected profile.\n\n' +
|
|
22
22
|
'Choose the hook — boot vs file context. System-prompt visibility is the boot catalog; file-read visibility fires on the applies-to routes. Put code-specific knowledge in the owning project store, give it the narrowest real file glob, and keep it out of boot when the file read is the useful trigger. Knowledge about a person or process usually has no file boundary, so set file-read-visibility none and route it through boot instead.\n\n' +
|
|
23
|
-
'Write the routing line (--when-and-why-to-read) first, before storing anything.
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
'Write the routing line (--when-and-why-to-read) first, before storing anything. ' + GUIDE_ROUTING_LINE + '\n\n' +
|
|
24
|
+
GUIDE_PREDICATE_VOCABULARY + '\n\n' +
|
|
25
|
+
GUIDE_DOC_LINKS + '\n\n' +
|
|
26
26
|
'When a doc grows long or information-rich, nest it into a graph instead of letting it become a scroll. The main doc at the topic’s path keeps the high-level, most load-bearing information, most important first; depth splits into reference docs under the topic’s directory (`area/topic/...`), each pointed at with a `[[link]]`. Split by subject: a leaf earns its link by covering a different subject a task might need on its own; a leaf of offloaded “further evidence”, examples, or references is never followed, so supporting material either sits in the main doc next to the point it supports or gets cut. The main doc is the entry point a reader can act from alone; a reference leaf is loaded only when the task needs that depth. Save reference leaves at `none` visibility on both axes — the link from the main doc is how they are found, so any higher rung just double-charges every boot or read for depth the graph already routes. Keep every doc as short as its job allows; `crtr memory lint` caps body length by rung and its findings carry the split guidance.\n\n' +
|
|
27
27
|
'A directory INDEX earns existence only where the aggregate view beats the sum of per-file rungs. Aggregated child routing is automatic: `crtr memory read` of a directory INDEX appends every member’s routing line (`[[name]]: <when-and-why-to-read>`), so never author that list in the body. Two shapes pass: synthesis — an operating guide or the cluster’s mechanics, ordering, conditions, and relationships, content no single child can carry — and a pure gate — one preview line covering a cluster with one shared routing condition, the body adding nothing the automatic list does not. Otherwise skip the INDEX and let each doc’s own rung route it: frontmatter routing lines are self-maintaining, while an INDEX body describing its children is a hand-maintained copy that drifts. Never write an INDEX body that restates child names, paraphrases their routing lines, fronts a small directory of sharply named docs, or summarizes children a reader should open.\n\n' +
|
|
28
|
-
'Find before write. Prefer slightly expanding an existing document
|
|
29
|
-
'--rationale is the gap this doc exists to close — the observed agent failure that prompted it, captured from user signal (a correction, a mistake you watched happen) rather than inferred from the doc’s own content. If the rationale is guessable from reading the doc, it is not the real one — a guessable gap is one agents do not actually fall into. Omit the flag when you have no observed gap to record
|
|
28
|
+
'Find before write. Prefer slightly expanding an existing document with `crtr memory edit`, nesting genuinely separate depth under its topic, and updating the existing `when-and-why-to-read` (plus its INDEX router when present) over creating another similar memory. A new document earns its own identity only when it has a distinct read trigger and a coherent body whose merge into the existing document would make it harder to route or use. Group related docs with path names (area/topic). Provenance is stamped here and preserved by every later revision. Run `crtr memory lint` after authoring.\n\n' +
|
|
29
|
+
'--rationale is the gap this doc exists to close — the observed agent failure that prompted it, captured from user signal (a correction, a mistake you watched happen) rather than inferred from the doc’s own content. If the rationale is guessable from reading the doc, it is not the real one — a guessable gap is one agents do not actually fall into. Omit the flag when you have no observed gap to record.\n\n' +
|
|
30
|
+
'Revise an existing doc with `crtr memory edit`.',
|
|
30
31
|
params: [
|
|
31
|
-
{ kind: 'positional', name: 'name', required: true, constraint: 'Path-derived identity: one segment, or several joined with `/` to nest the document under a directory and group it with related docs → memory/<name>.md at the resolved scope.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
{ kind: 'flag', name: 'rationale', type: 'string', required: false, constraint:
|
|
32
|
+
{ kind: 'positional', name: 'name', required: true, constraint: 'Path-derived identity: one segment, or several joined with `/` to nest the document under a directory and group it with related docs → memory/<name>.md at the resolved scope. The name must be free at that scope: an existing one is an error naming `crtr memory edit`.' },
|
|
33
|
+
overlayParam('kind', { required: true }),
|
|
34
|
+
overlayParam('when-and-why-to-read', { required: true }),
|
|
35
|
+
overlayParam('short-form'),
|
|
36
|
+
overlayParam('system-prompt-visibility', { required: true }, 'There is no kind default; pick a rung explicitly.'),
|
|
37
|
+
overlayParam('file-read-visibility', { required: true }, 'There is no kind default; pick a rung explicitly.'),
|
|
38
|
+
overlayParam('gate'),
|
|
39
|
+
overlayParam('applies-to'),
|
|
40
|
+
overlayParam('read-when'),
|
|
41
|
+
overlayParam('slash'),
|
|
42
|
+
{ kind: 'flag', name: 'rationale', type: 'string', required: false, constraint: DOC_RATIONALE_CONSTRAINT },
|
|
42
43
|
{ kind: 'flag', name: 'scope', type: 'enum', choices: [...MEMORY_SCOPES], required: false, constraint: 'Target scope. Default: project when inside a project, else user. `project` resolves to the NEAREST ancestor `.crouter/` walking up from cwd — in a nested workspace that can be a parent’s store, not the dir you are standing in; pass --dir to pin the exact project directory. `profile` requires a selected profile (CRTR_PROFILE_ID) or an explicit --profile. `node` writes to the this-node store (`nodes/<CRTR_NODE_ID>/context/memory/`) — the nearest scope, seen only by this running node, requires a node context.' },
|
|
43
44
|
{ kind: 'flag', name: 'dir', type: 'string', required: false, constraint: 'Exact project directory to write under — targets `<dir>/.crouter/memory/` regardless of cwd or ancestor stores, scaffolding `.crouter/` there if absent. THE way to place a doc in a specific project’s store (e.g. another project in your profile’s purview) without cd’ing there, and the only way to target a dir shadowed by an ancestor store. Implies --scope project; rejects --scope user/profile.' },
|
|
44
45
|
{ kind: 'flag', name: 'profile', type: 'string', required: false, constraint: 'Profile id or name to write under, for --scope profile. Default: the process CRTR_PROFILE_ID (the node\u2019s selected profile). Resolved through the same profile lookup as `crtr profile show`. Rejected (usage error) unless --scope profile is also selected \u2014 never silently ignored for another scope.' },
|
|
@@ -48,14 +49,15 @@ export const writeLeaf = defineLeaf({
|
|
|
48
49
|
{ name: 'name', type: 'string', required: true, constraint: 'The path-derived document name written.' },
|
|
49
50
|
{ name: 'kind', type: 'string', required: true, constraint: 'Kind recorded in frontmatter.' },
|
|
50
51
|
{ name: 'scope', type: 'string', required: true, constraint: 'Scope the document was written to: user, project, profile, or node.' },
|
|
51
|
-
{ name: 'path', type: 'string', required: true, constraint: 'Absolute path to the written document
|
|
52
|
-
{ name: 'created', type: 'boolean', required: true, constraint: 'true
|
|
53
|
-
{ name: 'frontmatter', type: 'object[]', required: true, constraint: 'The frontmatter options selected by this invocation, in document field order. Each: {key, value}.
|
|
54
|
-
{ name: '
|
|
52
|
+
{ name: 'path', type: 'string', required: true, constraint: 'Absolute path to the written document.' },
|
|
53
|
+
{ name: 'created', type: 'boolean', required: true, constraint: 'Always true on success — this leaf only creates. A name already taken at the resolved scope fails with a usage error naming `crtr memory edit`.' },
|
|
54
|
+
{ name: 'frontmatter', type: 'object[]', required: true, constraint: 'The frontmatter options selected by this invocation, in document field order. Each: {key, value}.' },
|
|
55
|
+
{ name: 'log_path', type: 'string', required: true, constraint: 'Absolute path to the document’s append-only revision log, opened here with a `create` record.' },
|
|
56
|
+
{ name: 'follow_up', type: 'string', required: true, constraint: 'Concrete next commands — read it back, revise it, or list the inventory.' },
|
|
55
57
|
],
|
|
56
58
|
outputKind: 'object',
|
|
57
59
|
effects: [
|
|
58
|
-
'Creates
|
|
60
|
+
'Creates memory/<name>.md at the resolved scope with the given frontmatter fields + stdin body, and opens memory/.history/<name>.jsonl with a `create` record.',
|
|
59
61
|
],
|
|
60
62
|
},
|
|
61
63
|
run: async (input) => {
|
|
@@ -67,35 +69,20 @@ export const writeLeaf = defineLeaf({
|
|
|
67
69
|
const dirArg = input['dir'];
|
|
68
70
|
const { scope, memoryDir } = resolveWriteTarget(scopeArg, profileArg, dirArg);
|
|
69
71
|
const path = memoryFilePath(memoryDir, name);
|
|
70
|
-
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
if (
|
|
74
|
-
throw usage(
|
|
72
|
+
// Create-only. Revision goes through `edit`, which requires a rationale and
|
|
73
|
+
// records the change — a write that still overwrote would be a standing
|
|
74
|
+
// bypass of the audit trail. The error is the migration path.
|
|
75
|
+
if (pathExists(path)) {
|
|
76
|
+
throw usage(`${name} already exists at ${scope} scope — revise it with \`crtr memory edit ${name} --rationale "<why this revision>"\`, which records the change. \`write\` only creates.`, { memory: name, scope, path });
|
|
75
77
|
}
|
|
76
|
-
|
|
77
|
-
// there is no kind default to lean on. UPDATE inherits whatever the existing
|
|
78
|
-
// doc already carries.
|
|
79
|
-
if (created && input['systemPromptVisibility'] === undefined) {
|
|
80
|
-
throw usage(`creating ${name} requires --system-prompt-visibility: pick a rung explicitly (none|name|preview|content) — there is no kind default. See \`crtr memory write -h\` for what each rung means.`);
|
|
81
|
-
}
|
|
82
|
-
if (created && input['fileReadVisibility'] === undefined) {
|
|
83
|
-
throw usage(`creating ${name} requires --file-read-visibility: pick a rung explicitly (none|name|preview|content) — there is no kind default. See \`crtr memory write -h\` for what each rung means.`);
|
|
84
|
-
}
|
|
85
|
-
// In-place update: start from the existing frontmatter (preserving fields
|
|
86
|
-
// not passed this time), then overlay the provided ones. Create: start clean.
|
|
87
|
-
const frontmatter = created
|
|
88
|
-
? {}
|
|
89
|
-
: { ...(parseFrontmatterGeneric(readText(path)).data ?? {}) };
|
|
90
|
-
// kind is required, always set. Optionals only overlay when provided so an
|
|
91
|
-
// update never erases a field the caller did not mention.
|
|
78
|
+
const frontmatter = {};
|
|
92
79
|
frontmatter['kind'] = kind;
|
|
93
|
-
// Provenance is runtime-stamped ONCE,
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
80
|
+
// Provenance is runtime-stamped ONCE, here — who/when/where authored this
|
|
81
|
+
// doc. Every later revision preserves it, so it always points at the
|
|
82
|
+
// conversation that created the doc, not the last one to touch it.
|
|
83
|
+
// `crtr memory origin <name>` derefs it.
|
|
84
|
+
const origin = buildOrigin();
|
|
85
|
+
frontmatter['origin'] = origin;
|
|
99
86
|
const setIf = (key, value) => {
|
|
100
87
|
if (value !== undefined)
|
|
101
88
|
frontmatter[key] = value;
|
|
@@ -115,6 +102,8 @@ export const writeLeaf = defineLeaf({
|
|
|
115
102
|
if (input['slash'] === true)
|
|
116
103
|
frontmatter['slash'] = true;
|
|
117
104
|
setIf('rationale', input['rationale']);
|
|
105
|
+
// One field consumers read for recency, live from the first byte.
|
|
106
|
+
frontmatter['last-updated'] = origin['created'];
|
|
118
107
|
const applies = frontmatter['applies-to'];
|
|
119
108
|
const routes = typeof applies === 'string'
|
|
120
109
|
? [applies]
|
|
@@ -127,10 +116,13 @@ export const writeLeaf = defineLeaf({
|
|
|
127
116
|
if (scope !== 'project' && routes.some((route) => route.trim() === '.')) {
|
|
128
117
|
throw usage('applies-to "." is a project-store workspace mount route; use a real file glob or choose file-read-visibility none for user, profile, and node memory');
|
|
129
118
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
119
|
+
const after = serializeMemoryDoc(frontmatter, body);
|
|
120
|
+
writeText(path, after);
|
|
121
|
+
const logPath = historyLogPathFor(memoryDir, path);
|
|
122
|
+
appendHistoryRecord(logPath, buildHistoryRecord({ op: 'create', before: '', after }));
|
|
123
|
+
// Confirm exactly the frontmatter choices made by this invocation — the
|
|
124
|
+
// runtime-stamped fields do not masquerade as selected options in the
|
|
125
|
+
// result or its collapsed viewer preview.
|
|
134
126
|
const selectedKeys = [
|
|
135
127
|
'kind',
|
|
136
128
|
...(input['whenAndWhyToRead'] !== undefined ? ['when-and-why-to-read'] : []),
|
|
@@ -149,9 +141,10 @@ export const writeLeaf = defineLeaf({
|
|
|
149
141
|
kind,
|
|
150
142
|
scope,
|
|
151
143
|
path,
|
|
152
|
-
created,
|
|
144
|
+
created: true,
|
|
153
145
|
frontmatter: selectedFrontmatter,
|
|
154
|
-
|
|
146
|
+
log_path: logPath,
|
|
147
|
+
follow_up: `Read it back with \`crtr memory read ${name}\`, revise it later with \`crtr memory edit ${name} --rationale "<why>"\`, or browse the inventory with \`crtr memory list\`.`,
|
|
155
148
|
};
|
|
156
149
|
},
|
|
157
150
|
});
|
package/dist/commands/memory.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
// `crtr memory` subtree — the document substrate (knowledge and preferences)
|
|
2
|
-
// accessed via the CLI. Flat leaves: list, read, find, write,
|
|
2
|
+
// accessed via the CLI. Flat leaves: list, read, find, write, edit, history,
|
|
3
|
+
// delete, origin, lint.
|
|
3
4
|
import { defineBranch } from '../core/command.js';
|
|
4
5
|
import { listLeaf } from './memory/list.js';
|
|
5
6
|
import { readLeaf } from './memory/read.js';
|
|
6
7
|
import { findLeaf } from './memory/find.js';
|
|
7
8
|
import { writeLeaf } from './memory/write.js';
|
|
9
|
+
import { editLeaf } from './memory/edit.js';
|
|
10
|
+
import { historyLeaf } from './memory/history.js';
|
|
8
11
|
import { deleteLeaf } from './memory/delete.js';
|
|
9
12
|
import { originLeaf } from './memory/origin.js';
|
|
10
13
|
import { lintLeaf } from './memory/lint.js';
|
|
@@ -19,8 +22,8 @@ export function registerMemory() {
|
|
|
19
22
|
help: {
|
|
20
23
|
name: 'memory',
|
|
21
24
|
summary: 'list, read, search, and write memory documents — knowledge and preferences',
|
|
22
|
-
model: 'Documents have path-derived identities and resolve across layered scopes in precedence order: node > project stack > profile > user > builtin. Browse the inventory with `list` to see what is stored; address a document directly with `read
|
|
25
|
+
model: 'Documents have path-derived identities and resolve across layered scopes in precedence order: node > project stack > profile > user > builtin. Browse the inventory with `list` to see what is stored; address a document directly with `read` once you know its name. One verb per operation: `write` creates, `edit` revises (every revision carries a rationale and is recorded), `delete` removes, and `history` reads how a document reached its current state. A directory may carry an `INDEX.md` with the same frontmatter schema as any doc — it renders as one boot-catalog entry whose system-prompt rung caps that subtree. File context is separate and explicit: `applies-to: "."` opens project context with the workspace; other globs fire after matching reads.',
|
|
23
26
|
},
|
|
24
|
-
children: [listLeaf, readLeaf, findLeaf, writeLeaf, deleteLeaf, originLeaf, lintLeaf],
|
|
27
|
+
children: [listLeaf, readLeaf, findLeaf, writeLeaf, editLeaf, historyLeaf, deleteLeaf, originLeaf, lintLeaf],
|
|
25
28
|
});
|
|
26
29
|
}
|
|
@@ -93,6 +93,7 @@ test('idle-release settles its real watcher before exit; daemon revives exactly
|
|
|
93
93
|
// settles its own prompt lifecycle: watcher → engine → stophook → settle →
|
|
94
94
|
// broker exit. No test-driven turn or settlement dispatch is involved.
|
|
95
95
|
await awaitReleased(manager);
|
|
96
|
+
await h.awaitFleetExit(manager);
|
|
96
97
|
assert.equal(readCursor(manager), first.entry_id, 'the exact handed-off row commits before clean shutdown');
|
|
97
98
|
const cyclesAfterRelease = h.node(manager).cycles ?? 0;
|
|
98
99
|
await h.tick();
|
|
@@ -112,6 +113,7 @@ test('idle-release settles its real watcher before exit; daemon revives exactly
|
|
|
112
113
|
await h.awaitBoot(manager, { minCount: 2 });
|
|
113
114
|
await h.awaitWake(manager, { sinceCount: injectedBeforeSecond, match: /second completed/ });
|
|
114
115
|
await awaitReleased(manager);
|
|
116
|
+
await h.awaitFleetExit(manager);
|
|
115
117
|
assert.equal(readCursor(manager), second.entry_id, 'the later row commits at its own natural idle release');
|
|
116
118
|
await h.tick();
|
|
117
119
|
assert.equal(h.node(manager).cycles ?? 0, cyclesAfterRelease + 1, 'the settled later row does not cause a duplicate revival');
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import type { PageTicketSummary, TicketSummary } from './types.js';
|
|
2
2
|
/** Read all unresolved page/review tickets, newest first. Progress never affects visibility. */
|
|
3
3
|
export declare function scanInbox(): TicketSummary[];
|
|
4
|
-
export
|
|
5
|
-
state: 'pending' | 'resolved' | 'canceled' | 'passive';
|
|
6
|
-
}
|
|
4
|
+
export type PageHistoryEntry = PageTicketSummary;
|
|
7
5
|
/**
|
|
8
6
|
* Every page ticket raised by one node — pending, resolved or canceled — oldest
|
|
9
7
|
* first. This is the durable discovery seam for transcript placement, so it is a
|
package/dist/core/human/scan.js
CHANGED
|
@@ -56,6 +56,7 @@ function pageSummary(dir, id) {
|
|
|
56
56
|
awaitsResponse: manifest.delivery.reply,
|
|
57
57
|
source: manifest.source ?? {},
|
|
58
58
|
emittedAt,
|
|
59
|
+
state: pageTicketState(dir),
|
|
59
60
|
claim: claimSummary(dir),
|
|
60
61
|
};
|
|
61
62
|
}
|
|
@@ -112,9 +113,8 @@ export function scanPageHistory(nodeId) {
|
|
|
112
113
|
const summary = pageSummary(dir, basename(dir));
|
|
113
114
|
if (summary === null || summary.source.nodeId !== nodeId)
|
|
114
115
|
continue;
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
items.push({ ...summary, state, ...(result?.kind === 'page' ? { answerDigest: result.summary.replace(/\s+/g, ' ').trim() } : {}) });
|
|
116
|
+
const result = summary.state === 'resolved' ? readTicketResult(dir) : null;
|
|
117
|
+
items.push({ ...summary, ...(result?.kind === 'page' ? { answerDigest: result.summary.replace(/\s+/g, ' ').trim() } : {}) });
|
|
118
118
|
}
|
|
119
119
|
return items.sort((a, b) => Date.parse(a.emittedAt) - Date.parse(b.emittedAt) || a.id.localeCompare(b.id));
|
|
120
120
|
}
|
|
@@ -65,6 +65,7 @@ export interface PageTicketSummary {
|
|
|
65
65
|
steps: number;
|
|
66
66
|
slotKinds: string[];
|
|
67
67
|
awaitsResponse: boolean;
|
|
68
|
+
state: 'pending' | 'resolved' | 'canceled' | 'passive';
|
|
68
69
|
}
|
|
69
70
|
/** The only pending-ticket shape scanners expose. */
|
|
70
71
|
export type TicketSummary = ReviewTicketSummary | PageTicketSummary;
|
|
@@ -28,6 +28,9 @@ export declare const ATTACH_CONTROL_BINDINGS: readonly [{
|
|
|
28
28
|
}, {
|
|
29
29
|
readonly menuId: "crtr.tmux.menu.attach.profile-files";
|
|
30
30
|
readonly actionId: "crtr.attach.profile-files";
|
|
31
|
+
}, {
|
|
32
|
+
readonly menuId: "crtr.tmux.menu.attach.search";
|
|
33
|
+
readonly actionId: "crtr.attach.search";
|
|
31
34
|
}, {
|
|
32
35
|
readonly menuId: "crtr.tmux.menu.attach.whip";
|
|
33
36
|
readonly actionId: "crtr.attach.whip";
|
|
@@ -9,6 +9,7 @@ export const ATTACH_CONTROL_BINDINGS = [
|
|
|
9
9
|
{ menuId: 'crtr.tmux.menu.attach.command.inspect', actionId: 'crtr.attach.command.inspect' },
|
|
10
10
|
{ menuId: 'crtr.tmux.menu.attach.file-review', actionId: 'crtr.attach.file-review' },
|
|
11
11
|
{ menuId: 'crtr.tmux.menu.attach.profile-files', actionId: 'crtr.attach.profile-files' },
|
|
12
|
+
{ menuId: 'crtr.tmux.menu.attach.search', actionId: 'crtr.attach.search' },
|
|
12
13
|
{ menuId: 'crtr.tmux.menu.attach.whip', actionId: 'crtr.attach.whip' },
|
|
13
14
|
];
|
|
14
15
|
const ATTACH_CONTROL_PREFIX = '\x1b_crtr;';
|
|
@@ -8,8 +8,8 @@ type ProfileMenuAction = 'up' | 'down' | 'select' | 'accept-default' | 'abort' |
|
|
|
8
8
|
type SetupAction = 'cancel' | 'tab-next' | 'tab-previous' | 'open' | 'list-down' | 'list-up' | 'toggle' | 'details' | 'advance' | 'cell-previous' | 'cell-next';
|
|
9
9
|
type SetupKeybindingAction = 'edit' | 'commit-edit' | 'cancel-edit' | 'unbind' | 'reset-row' | 'reset-all' | 'save' | 'discard' | 'page-up' | 'page-down' | 'filter' | 'filter-delete' | 'filter-clear';
|
|
10
10
|
type SetupModelsAction = 'open' | 'commit' | 'cancel' | 'scope-next';
|
|
11
|
-
export type BindingId = `crtr.tmux.menu.${TmuxMenuAction}` | `crtr.tmux.menu.issue.${'idea' | 'bug'}` | AttachMenuBindingId | `crtr.tmux.menu-global.${TmuxMenuEntryAction}` | `crtr.tmux.node.${'next' | 'previous'}` | 'crtr.tmux.inbox.toggle' | `crtr.attach.scroll.${'up' | 'down' | 'page-up' | 'page-down' | 'live'}` | 'crtr.attach.mouse.toggle' | 'crtr.attach.copy.message' | 'crtr.attach.help.toggle' | AttachBindingId | `crtr.graph.${GraphAction}` | `crtr.graph.confirm.${'accept' | 'cancel'}` | `crtr.inspector.${'force-quit' | 'down' | 'up' | 'section-next' | 'section-prev' | 'page-down' | 'page-up' | 'toggle-detail' | 'cancel-item' | 'open-log' | 'confirm' | 'dismiss' | 'retarget' | 'refresh' | 'quit'}` | `crtr.browse.${BrowseAction}` | `crtr.browse.search.${'cancel' | 'commit' | 'delete' | 'quit'}` | `crtr.profile.menu.${ProfileMenuAction}` | `crtr.profile.search.${'up' | 'down' | 'pick' | 'back' | 'delete' | 'abort'}` | `crtr.setup.${SetupAction}` | `crtr.setup.confirm.${'accept' | 'back'}` | `crtr.setup.keybindings.${SetupKeybindingAction}` | `crtr.setup.models.${SetupModelsAction}`;
|
|
12
|
-
export declare const BINDING_CATALOG: readonly [BindingDefinition<"crtr.tmux.menu.open">, BindingDefinition<"crtr.tmux.menu.attach">, ...(BindingDefinition<BindingId> | BindingDefinition<"crtr.tmux.node.next"> | BindingDefinition<"crtr.tmux.node.previous"> | BindingDefinition<"crtr.tmux.inbox.toggle"> | BindingDefinition<"crtr.attach.scroll.up"> | BindingDefinition<"crtr.attach.scroll.down"> | BindingDefinition<"crtr.attach.scroll.page-up"> | BindingDefinition<"crtr.attach.scroll.page-down"> | BindingDefinition<"crtr.attach.scroll.live"> | BindingDefinition<"crtr.attach.mouse.toggle"> | BindingDefinition<"crtr.attach.copy.message"> | BindingDefinition<"crtr.graph.down"> | BindingDefinition<"crtr.graph.up"> | BindingDefinition<"crtr.graph.first"> | BindingDefinition<"crtr.graph.last"> | BindingDefinition<"crtr.graph.collapse-or-manager"> | BindingDefinition<"crtr.graph.expand-or-child"> | BindingDefinition<"crtr.graph.focus"> | BindingDefinition<"crtr.graph.revive"> | BindingDefinition<"crtr.graph.focus-manager"> | BindingDefinition<"crtr.graph.close-subtree"> | BindingDefinition<"crtr.graph.dismiss"> | BindingDefinition<"crtr.graph.confirm.accept"> | BindingDefinition<"crtr.graph.confirm.cancel"> | BindingDefinition<"crtr.inspector.force-quit"> | BindingDefinition<"crtr.inspector.down"> | BindingDefinition<"crtr.inspector.up"> | BindingDefinition<"crtr.inspector.section-next"> | BindingDefinition<"crtr.inspector.section-prev"> | BindingDefinition<"crtr.inspector.page-down"> | BindingDefinition<"crtr.inspector.page-up"> | BindingDefinition<"crtr.inspector.toggle-detail"> | BindingDefinition<"crtr.inspector.cancel-item"> | BindingDefinition<"crtr.inspector.open-log"> | BindingDefinition<"crtr.inspector.confirm"> | BindingDefinition<"crtr.inspector.dismiss"> | BindingDefinition<"crtr.inspector.retarget"> | BindingDefinition<"crtr.inspector.refresh"> | BindingDefinition<"crtr.inspector.quit"> | BindingDefinition<"crtr.browse.quit"> | BindingDefinition<"crtr.browse.down"> | BindingDefinition<"crtr.browse.up"> | BindingDefinition<"crtr.browse.first"> | BindingDefinition<"crtr.browse.last"> | BindingDefinition<"crtr.browse.expand-or-child"> | BindingDefinition<"crtr.browse.collapse-or-parent"> | BindingDefinition<"crtr.browse.toggle-fold"> | BindingDefinition<"crtr.browse.tab-next"> | BindingDefinition<"crtr.browse.tab-previous"> | BindingDefinition<"crtr.browse.tab.2" | "crtr.browse.tab.1" | "crtr.browse.tab.3" | "crtr.browse.tab.4" | "crtr.browse.tab.5"> | BindingDefinition<"crtr.browse.close-subtree"> | BindingDefinition<"crtr.browse.confirm-close"> | BindingDefinition<"crtr.browse.sort"> | BindingDefinition<"crtr.browse.scope"> | BindingDefinition<"crtr.browse.profile"> | BindingDefinition<"crtr.browse.residents"> | BindingDefinition<"crtr.browse.preview"> | BindingDefinition<"crtr.browse.search"> | BindingDefinition<"crtr.browse.revive-now"> | BindingDefinition<"crtr.browse.focus"> | BindingDefinition<"crtr.browse.focus-background"> | BindingDefinition<"crtr.browse.yank-id"> | BindingDefinition<"crtr.browse.search.cancel"> | BindingDefinition<"crtr.browse.search.commit"> | BindingDefinition<"crtr.browse.search.delete"> | BindingDefinition<"crtr.browse.search.quit"> | BindingDefinition<"crtr.profile.menu.up"> | BindingDefinition<"crtr.profile.menu.down"> | BindingDefinition<"crtr.profile.menu.select"> | BindingDefinition<"crtr.profile.menu.accept-default"> | BindingDefinition<"crtr.profile.menu.abort"> | BindingDefinition<"crtr.profile.menu.slot.2" | "crtr.profile.menu.slot.1" | "crtr.profile.menu.slot.3" | "crtr.profile.menu.slot.4" | "crtr.profile.menu.slot.5" | "crtr.profile.menu.slot.6" | "crtr.profile.menu.slot.7" | "crtr.profile.menu.slot.8" | "crtr.profile.menu.slot.9">)[], BindingDefinition<"crtr.profile.menu.create">, BindingDefinition<"crtr.profile.menu.search-existing">, BindingDefinition<"crtr.profile.menu.use-root">, BindingDefinition<"crtr.profile.menu.set-default">, BindingDefinition<"crtr.profile.menu.add-directory">, BindingDefinition<"crtr.profile.menu.unpin">, BindingDefinition<"crtr.profile.search.up">, BindingDefinition<"crtr.profile.search.down">, BindingDefinition<"crtr.profile.search.pick">, BindingDefinition<"crtr.profile.search.back">, BindingDefinition<"crtr.profile.search.delete">, BindingDefinition<"crtr.profile.search.abort">, BindingDefinition<"crtr.setup.cancel">, BindingDefinition<"crtr.setup.tab-next">, BindingDefinition<"crtr.setup.tab-previous">, BindingDefinition<"crtr.setup.open">, BindingDefinition<"crtr.setup.list-down">, BindingDefinition<"crtr.setup.list-up">, BindingDefinition<"crtr.setup.toggle">, BindingDefinition<"crtr.setup.details">, BindingDefinition<"crtr.setup.advance">, BindingDefinition<"crtr.setup.confirm.accept">, BindingDefinition<"crtr.setup.confirm.back">, BindingDefinition<"crtr.setup.keybindings.edit">, BindingDefinition<"crtr.setup.keybindings.commit-edit">, BindingDefinition<"crtr.setup.keybindings.cancel-edit">, BindingDefinition<"crtr.setup.keybindings.unbind">, BindingDefinition<"crtr.setup.keybindings.reset-row">, BindingDefinition<"crtr.setup.keybindings.reset-all">, BindingDefinition<"crtr.setup.keybindings.save">, BindingDefinition<"crtr.setup.keybindings.discard">, BindingDefinition<"crtr.setup.keybindings.page-up">, BindingDefinition<"crtr.setup.keybindings.page-down">, BindingDefinition<"crtr.setup.keybindings.filter">, BindingDefinition<"crtr.setup.keybindings.filter-delete">, BindingDefinition<"crtr.setup.keybindings.filter-clear">, BindingDefinition<"crtr.setup.cell-previous">, BindingDefinition<"crtr.setup.cell-next">, BindingDefinition<"crtr.setup.models.open">, BindingDefinition<"crtr.setup.models.scope-next">, BindingDefinition<"crtr.setup.models.commit">, BindingDefinition<"crtr.setup.models.cancel">];
|
|
11
|
+
export type BindingId = `crtr.tmux.menu.${TmuxMenuAction}` | `crtr.tmux.menu.issue.${'idea' | 'bug'}` | AttachMenuBindingId | `crtr.tmux.menu-global.${TmuxMenuEntryAction}` | `crtr.tmux.node.${'next' | 'previous'}` | 'crtr.tmux.inbox.toggle' | `crtr.attach.scroll.${'up' | 'down' | 'page-up' | 'page-down' | 'live'}` | 'crtr.attach.mouse.toggle' | 'crtr.attach.copy.message' | 'crtr.attach.help.toggle' | `crtr.attach.search.${'next' | 'previous' | 'edit' | 'close'}` | AttachBindingId | `crtr.graph.${GraphAction}` | `crtr.graph.confirm.${'accept' | 'cancel'}` | `crtr.inspector.${'force-quit' | 'down' | 'up' | 'section-next' | 'section-prev' | 'page-down' | 'page-up' | 'toggle-detail' | 'cancel-item' | 'open-log' | 'confirm' | 'dismiss' | 'retarget' | 'refresh' | 'quit'}` | `crtr.browse.${BrowseAction}` | `crtr.browse.search.${'cancel' | 'commit' | 'delete' | 'quit'}` | `crtr.profile.menu.${ProfileMenuAction}` | `crtr.profile.search.${'up' | 'down' | 'pick' | 'back' | 'delete' | 'abort'}` | `crtr.setup.${SetupAction}` | `crtr.setup.confirm.${'accept' | 'back'}` | `crtr.setup.keybindings.${SetupKeybindingAction}` | `crtr.setup.models.${SetupModelsAction}`;
|
|
12
|
+
export declare const BINDING_CATALOG: readonly [BindingDefinition<"crtr.tmux.menu.open">, BindingDefinition<"crtr.tmux.menu.attach">, ...(BindingDefinition<BindingId> | BindingDefinition<"crtr.tmux.node.next"> | BindingDefinition<"crtr.tmux.node.previous"> | BindingDefinition<"crtr.tmux.inbox.toggle"> | BindingDefinition<"crtr.attach.scroll.up"> | BindingDefinition<"crtr.attach.scroll.down"> | BindingDefinition<"crtr.attach.scroll.page-up"> | BindingDefinition<"crtr.attach.scroll.page-down"> | BindingDefinition<"crtr.attach.scroll.live"> | BindingDefinition<"crtr.attach.mouse.toggle"> | BindingDefinition<"crtr.attach.copy.message"> | BindingDefinition<"crtr.attach.search.next"> | BindingDefinition<"crtr.attach.search.previous"> | BindingDefinition<"crtr.attach.search.edit"> | BindingDefinition<"crtr.attach.search.close"> | BindingDefinition<"crtr.graph.down"> | BindingDefinition<"crtr.graph.up"> | BindingDefinition<"crtr.graph.first"> | BindingDefinition<"crtr.graph.last"> | BindingDefinition<"crtr.graph.collapse-or-manager"> | BindingDefinition<"crtr.graph.expand-or-child"> | BindingDefinition<"crtr.graph.focus"> | BindingDefinition<"crtr.graph.revive"> | BindingDefinition<"crtr.graph.focus-manager"> | BindingDefinition<"crtr.graph.close-subtree"> | BindingDefinition<"crtr.graph.dismiss"> | BindingDefinition<"crtr.graph.confirm.accept"> | BindingDefinition<"crtr.graph.confirm.cancel"> | BindingDefinition<"crtr.inspector.force-quit"> | BindingDefinition<"crtr.inspector.down"> | BindingDefinition<"crtr.inspector.up"> | BindingDefinition<"crtr.inspector.section-next"> | BindingDefinition<"crtr.inspector.section-prev"> | BindingDefinition<"crtr.inspector.page-down"> | BindingDefinition<"crtr.inspector.page-up"> | BindingDefinition<"crtr.inspector.toggle-detail"> | BindingDefinition<"crtr.inspector.cancel-item"> | BindingDefinition<"crtr.inspector.open-log"> | BindingDefinition<"crtr.inspector.confirm"> | BindingDefinition<"crtr.inspector.dismiss"> | BindingDefinition<"crtr.inspector.retarget"> | BindingDefinition<"crtr.inspector.refresh"> | BindingDefinition<"crtr.inspector.quit"> | BindingDefinition<"crtr.browse.quit"> | BindingDefinition<"crtr.browse.down"> | BindingDefinition<"crtr.browse.up"> | BindingDefinition<"crtr.browse.first"> | BindingDefinition<"crtr.browse.last"> | BindingDefinition<"crtr.browse.expand-or-child"> | BindingDefinition<"crtr.browse.collapse-or-parent"> | BindingDefinition<"crtr.browse.toggle-fold"> | BindingDefinition<"crtr.browse.tab-next"> | BindingDefinition<"crtr.browse.tab-previous"> | BindingDefinition<"crtr.browse.tab.2" | "crtr.browse.tab.1" | "crtr.browse.tab.3" | "crtr.browse.tab.4" | "crtr.browse.tab.5"> | BindingDefinition<"crtr.browse.close-subtree"> | BindingDefinition<"crtr.browse.confirm-close"> | BindingDefinition<"crtr.browse.sort"> | BindingDefinition<"crtr.browse.scope"> | BindingDefinition<"crtr.browse.profile"> | BindingDefinition<"crtr.browse.residents"> | BindingDefinition<"crtr.browse.preview"> | BindingDefinition<"crtr.browse.search"> | BindingDefinition<"crtr.browse.revive-now"> | BindingDefinition<"crtr.browse.focus"> | BindingDefinition<"crtr.browse.focus-background"> | BindingDefinition<"crtr.browse.yank-id"> | BindingDefinition<"crtr.browse.search.cancel"> | BindingDefinition<"crtr.browse.search.commit"> | BindingDefinition<"crtr.browse.search.delete"> | BindingDefinition<"crtr.browse.search.quit"> | BindingDefinition<"crtr.profile.menu.up"> | BindingDefinition<"crtr.profile.menu.down"> | BindingDefinition<"crtr.profile.menu.select"> | BindingDefinition<"crtr.profile.menu.accept-default"> | BindingDefinition<"crtr.profile.menu.abort"> | BindingDefinition<"crtr.profile.menu.slot.2" | "crtr.profile.menu.slot.1" | "crtr.profile.menu.slot.3" | "crtr.profile.menu.slot.4" | "crtr.profile.menu.slot.5" | "crtr.profile.menu.slot.6" | "crtr.profile.menu.slot.7" | "crtr.profile.menu.slot.8" | "crtr.profile.menu.slot.9">)[], BindingDefinition<"crtr.profile.menu.create">, BindingDefinition<"crtr.profile.menu.search-existing">, BindingDefinition<"crtr.profile.menu.use-root">, BindingDefinition<"crtr.profile.menu.set-default">, BindingDefinition<"crtr.profile.menu.add-directory">, BindingDefinition<"crtr.profile.menu.unpin">, BindingDefinition<"crtr.profile.search.up">, BindingDefinition<"crtr.profile.search.down">, BindingDefinition<"crtr.profile.search.pick">, BindingDefinition<"crtr.profile.search.back">, BindingDefinition<"crtr.profile.search.delete">, BindingDefinition<"crtr.profile.search.abort">, BindingDefinition<"crtr.setup.cancel">, BindingDefinition<"crtr.setup.tab-next">, BindingDefinition<"crtr.setup.tab-previous">, BindingDefinition<"crtr.setup.open">, BindingDefinition<"crtr.setup.list-down">, BindingDefinition<"crtr.setup.list-up">, BindingDefinition<"crtr.setup.toggle">, BindingDefinition<"crtr.setup.details">, BindingDefinition<"crtr.setup.advance">, BindingDefinition<"crtr.setup.confirm.accept">, BindingDefinition<"crtr.setup.confirm.back">, BindingDefinition<"crtr.setup.keybindings.edit">, BindingDefinition<"crtr.setup.keybindings.commit-edit">, BindingDefinition<"crtr.setup.keybindings.cancel-edit">, BindingDefinition<"crtr.setup.keybindings.unbind">, BindingDefinition<"crtr.setup.keybindings.reset-row">, BindingDefinition<"crtr.setup.keybindings.reset-all">, BindingDefinition<"crtr.setup.keybindings.save">, BindingDefinition<"crtr.setup.keybindings.discard">, BindingDefinition<"crtr.setup.keybindings.page-up">, BindingDefinition<"crtr.setup.keybindings.page-down">, BindingDefinition<"crtr.setup.keybindings.filter">, BindingDefinition<"crtr.setup.keybindings.filter-delete">, BindingDefinition<"crtr.setup.keybindings.filter-clear">, BindingDefinition<"crtr.setup.cell-previous">, BindingDefinition<"crtr.setup.cell-next">, BindingDefinition<"crtr.setup.models.open">, BindingDefinition<"crtr.setup.models.scope-next">, BindingDefinition<"crtr.setup.models.commit">, BindingDefinition<"crtr.setup.models.cancel">];
|
|
13
13
|
export declare const BINDING_IDS: ReadonlySet<string>;
|
|
14
14
|
/** True for an action whose direct gesture is handled inside an attach viewer.
|
|
15
15
|
* Tmux menu selectors and root shortcuts are chrome, not pane input. */
|
|
@@ -77,6 +77,7 @@ export const BINDING_CATALOG = Object.freeze([
|
|
|
77
77
|
...attachEntry('crtr.tmux.menu.attach.command.inspect', 'crtr.attach.command.inspect', 'Inspect loaded command', 'Open the loaded slash command’s expanded prompt.', ['h'], ['alt+shift+h']),
|
|
78
78
|
...attachEntry('crtr.tmux.menu.attach.file-review', 'crtr.attach.file-review', 'Review a file', 'Open a line-anchored review of a file named in the transcript.', [], ['alt+shift+r']),
|
|
79
79
|
...attachEntry('crtr.tmux.menu.attach.profile-files', 'crtr.attach.profile-files', 'Search profile files', 'Search files across the node’s profile projects, profile memory, cwd, and ancestor crouter memory stores.', ['f'], ['ctrl+f']),
|
|
80
|
+
...attachEntry('crtr.tmux.menu.attach.search', 'crtr.attach.search', 'Search transcript', 'Search the transcript as it is currently displayed — folded tool output is not searched until it is unfolded.', ['/'], ['alt+/']),
|
|
80
81
|
...attachEntry('crtr.tmux.menu.attach.whip', 'crtr.attach.whip', 'Whip agent', 'Interrupt the agent and send a playful command.', ['w'], []),
|
|
81
82
|
b('crtr.attach.scroll.up', 'Attach', 'Scroll up', 'Scroll the transcript one line up.', ['shift+up'], 'terminal', ATTACH_CONTEXTS),
|
|
82
83
|
b('crtr.attach.scroll.down', 'Attach', 'Scroll down', 'Scroll the transcript one line down.', ['shift+down'], 'terminal', ATTACH_CONTEXTS),
|
|
@@ -85,6 +86,10 @@ export const BINDING_CATALOG = Object.freeze([
|
|
|
85
86
|
b('crtr.attach.scroll.live', 'Attach', 'Return to live', 'Return the transcript to the live bottom.', ['shift+end'], 'terminal', ATTACH_CONTEXTS),
|
|
86
87
|
b('crtr.attach.mouse.toggle', 'Attach', 'Toggle mouse wheel scrolling', 'Toggle mouse wheel scrolling on or off.', ['alt+shift+o'], 'terminal', ATTACH_CONTEXTS),
|
|
87
88
|
b('crtr.attach.copy.message', 'Attach', 'Copy visible message', 'Copy the newest message visible in the transcript viewport.', ['alt+shift+y'], 'terminal', ATTACH_CONTEXTS),
|
|
89
|
+
b('crtr.attach.search.next', 'Attach', 'Next match', 'Move to the next transcript search match.', ['n'], 'terminal', ['attach.search']),
|
|
90
|
+
b('crtr.attach.search.previous', 'Attach', 'Previous match', 'Move to the previous transcript search match.', ['shift+n'], 'terminal', ['attach.search']),
|
|
91
|
+
b('crtr.attach.search.edit', 'Attach', 'Edit query', 'Return to editing the transcript search query.', ['/'], 'terminal', ['attach.search']),
|
|
92
|
+
b('crtr.attach.search.close', 'Attach', 'Close search', 'Leave transcript search and clear its highlights.', ['escape'], 'terminal', ['attach.search'], 'dismiss-modal'),
|
|
88
93
|
b('crtr.graph.down', 'GRAPH', 'Move down', 'Move to the next graph row.', ['j'], 'terminal', ['graph']),
|
|
89
94
|
b('crtr.graph.up', 'GRAPH', 'Move up', 'Move to the previous graph row.', ['k'], 'terminal', ['graph']),
|
|
90
95
|
b('crtr.graph.first', 'GRAPH', 'First row', 'Move to the first graph row.', ['g'], 'terminal', ['graph']),
|
|
@@ -219,6 +224,7 @@ export const SAFETY_REQUIREMENTS = Object.freeze([
|
|
|
219
224
|
{ context: 'graph.confirm', capability: 'dismiss-modal' },
|
|
220
225
|
{ context: 'inspector', capability: 'leave-screen' },
|
|
221
226
|
{ context: 'inspector', capability: 'dismiss-modal' },
|
|
227
|
+
{ context: 'attach.search', capability: 'dismiss-modal' },
|
|
222
228
|
{ context: 'browse', capability: 'leave-screen' },
|
|
223
229
|
{ context: 'browse.search', capability: 'dismiss-modal' },
|
|
224
230
|
{ context: 'profile.menu', capability: 'leave-screen' },
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** Sidecar tree name under a store's `memory/` dir. Dot-prefixed and holding
|
|
2
|
+
* only `.jsonl`, so every substrate scan (which filters to `*.md`, and in
|
|
3
|
+
* `lint`'s case skips dot-dirs outright) passes it over without an exclusion
|
|
4
|
+
* rule of its own. */
|
|
5
|
+
export declare const HISTORY_DIR = ".history";
|
|
6
|
+
/** File operations only — deliberately not a review lifecycle. */
|
|
7
|
+
export type HistoryOp = 'create' | 'edit' | 'delete';
|
|
8
|
+
export interface HistoryRecord {
|
|
9
|
+
op: HistoryOp;
|
|
10
|
+
at: string;
|
|
11
|
+
/** Authoring node id; absent for a human at a bare CLI. */
|
|
12
|
+
node?: string;
|
|
13
|
+
cwd: string;
|
|
14
|
+
/** Why this revision happened. Required on `edit`, absent otherwise. */
|
|
15
|
+
rationale?: string;
|
|
16
|
+
/** True when the body was declared a verbatim payload from the principal. */
|
|
17
|
+
verbatim?: boolean;
|
|
18
|
+
/** Complete document text before the mutation; empty for `create`. */
|
|
19
|
+
before: string;
|
|
20
|
+
/** Complete document text after the mutation; empty for `delete`. */
|
|
21
|
+
after: string;
|
|
22
|
+
}
|
|
23
|
+
/** The log path for a document, derived from its PHYSICAL path so the history
|
|
24
|
+
* tree mirrors the doc tree exactly (`memory/area/topic.md` →
|
|
25
|
+
* `memory/.history/area/topic.jsonl`). `memoryRoot` is the store's `memory/`
|
|
26
|
+
* dir the doc lives under. */
|
|
27
|
+
export declare function historyLogPathFor(memoryRoot: string, docPath: string): string;
|
|
28
|
+
/** Stamp the runtime-owned fields of a record. Field insertion order IS the
|
|
29
|
+
* serialized key order — the on-disk shape consumers read. */
|
|
30
|
+
export declare function buildHistoryRecord(input: {
|
|
31
|
+
op: HistoryOp;
|
|
32
|
+
rationale?: string;
|
|
33
|
+
verbatim?: boolean;
|
|
34
|
+
before: string;
|
|
35
|
+
after: string;
|
|
36
|
+
}): HistoryRecord;
|
|
37
|
+
/** Append one record. Single write of one line — concurrent appends interleave
|
|
38
|
+
* whole records, and every record stands alone, so no lock is needed. */
|
|
39
|
+
export declare function appendHistoryRecord(logPath: string, record: HistoryRecord): void;
|
|
40
|
+
/** Every record in a log, oldest first (append order). Revision numbers are
|
|
41
|
+
* 1-based positions in this array. */
|
|
42
|
+
export declare function readHistoryRecords(logPath: string): HistoryRecord[];
|
|
43
|
+
export interface ChangeSummary {
|
|
44
|
+
body_changed: boolean;
|
|
45
|
+
/** Frontmatter field names whose values differ between the two texts. */
|
|
46
|
+
frontmatter_changed: string[];
|
|
47
|
+
}
|
|
48
|
+
/** What one record changed, derived from its own two texts. `last-updated` is
|
|
49
|
+
* excluded: the runtime stamps it on every edit, so listing it says nothing. */
|
|
50
|
+
export declare function summarizeChange(before: string, after: string): ChangeSummary;
|
|
51
|
+
/** Unified diff between two document texts, computed at read time. Dependency
|
|
52
|
+
* free (an LCS over lines) — memory docs are lint-capped small, so the
|
|
53
|
+
* quadratic table is never a cost. */
|
|
54
|
+
export declare function unifiedDiff(before: string, after: string, context?: number): string;
|