@bli-cockpit/cli 0.2.237 → 0.2.239
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/adapters/attribution-core-fallbacks.js +15 -11
- package/dist/adapters/attribution-core-score.js +111 -87
- package/dist/adapters/car-state.js +38 -35
- package/dist/adapters/claude-attribution-discovery.js +81 -56
- package/dist/adapters/claude-attribution-score.js +108 -94
- package/dist/adapters/claude-attribution-signals.js +52 -42
- package/dist/adapters/codex-attribution.js +42 -33
- package/dist/adapters/codex-session-signals.js +11 -9
- package/dist/adapters/git-state.js +56 -50
- package/dist/adapters/local-sources.js +52 -43
- package/dist/adapters/raw-evidence-git-diff.js +4 -2
- package/dist/adapters/raw-evidence-sanitize.js +15 -12
- package/dist/adapters/raw-evidence-transcript-file.js +32 -31
- package/dist/autostart-self-heal.js +18 -2
- package/dist/commands/agent-session-report.js +51 -59
- package/dist/commands/agent-setup-observed.js +30 -23
- package/dist/commands/agent-setup.js +96 -45
- package/dist/commands/autostart-heal.js +8 -2
- package/dist/commands/autostart-reading.js +3 -0
- package/dist/commands/backfill-evidence-outcomes.js +9 -6
- package/dist/commands/backfill-issues.js +3 -0
- package/dist/commands/backfill-scan.js +27 -22
- package/dist/commands/backfill-upload.js +69 -60
- package/dist/commands/backfill.js +26 -21
- package/dist/commands/brief-done.js +4 -1
- package/dist/commands/brief-edit.js +71 -46
- package/dist/commands/cal-detail.js +35 -0
- package/dist/commands/cal-direct.js +119 -0
- package/dist/commands/cal-overlay.js +73 -30
- package/dist/commands/cal.js +62 -25
- package/dist/commands/clean.js +4 -1
- package/dist/commands/docs-read.js +5 -2
- package/dist/commands/docs.js +38 -6
- package/dist/commands/doctor-backfill.js +88 -71
- package/dist/commands/doctor-pipeline.js +59 -46
- package/dist/commands/heartbeat-post.js +116 -0
- package/dist/commands/heartbeat-token-lock.js +46 -33
- package/dist/commands/heartbeat.js +43 -125
- package/dist/commands/install-update.js +17 -15
- package/dist/commands/issue-show.js +27 -28
- package/dist/commands/issue-versions.js +93 -0
- package/dist/commands/issue-write.js +70 -23
- package/dist/commands/issue.js +73 -37
- package/dist/commands/jarvis-live-answer.js +99 -0
- package/dist/commands/jarvis-render.js +1 -94
- package/dist/commands/jarvis-trace.js +30 -22
- package/dist/commands/jarvis-turn-report.js +124 -0
- package/dist/commands/jarvis-turn.js +108 -182
- package/dist/commands/jarvis.js +26 -17
- package/dist/commands/local-args-collector-setup.js +82 -84
- package/dist/commands/local-args-collector-work.js +37 -38
- package/dist/commands/local-args-tower-admin.js +155 -142
- package/dist/commands/local-args-tower-cal.js +69 -54
- package/dist/commands/local-args-tower-careers.js +73 -54
- package/dist/commands/local-args-tower-docs-msg.js +79 -51
- package/dist/commands/local-args-tower-github.js +26 -20
- package/dist/commands/local-args-tower-hits.js +32 -20
- package/dist/commands/local-args-tower-manager.js +85 -4
- package/dist/commands/local-args-tower-memory.js +139 -117
- package/dist/commands/local-args-tower-msg.js +24 -6
- package/dist/commands/local-args-tower-notes.js +346 -0
- package/dist/commands/local-args-tower-pages.js +100 -378
- package/dist/commands/local-args-tower-settings.js +28 -29
- package/dist/commands/local-args-tower-setup.js +11 -4
- package/dist/commands/local-args-tower-work.js +74 -37
- package/dist/commands/local-args.js +80 -129
- package/dist/commands/local-help-commands-tower-ops.js +15 -4
- package/dist/commands/local-help-commands-tower.js +20 -7
- package/dist/commands/local-help-commands.js +342 -341
- package/dist/commands/local-help.js +87 -83
- package/dist/commands/local-run-guards.js +100 -0
- package/dist/commands/local.js +137 -206
- package/dist/commands/login.js +13 -9
- package/dist/commands/logs.js +55 -38
- package/dist/commands/mail-attachments.js +44 -26
- package/dist/commands/manager-render-home.js +208 -0
- package/dist/commands/manager-render.js +7 -3
- package/dist/commands/manager-requests.js +33 -0
- package/dist/commands/manager.js +39 -5
- package/dist/commands/memory-consolidation.js +18 -9
- package/dist/commands/memory-daemon-probe.js +37 -28
- package/dist/commands/memory-install-config.js +64 -60
- package/dist/commands/memory-install.js +21 -41
- package/dist/commands/memory-requeue.js +26 -19
- package/dist/commands/memory-rules.js +26 -18
- package/dist/commands/memory-search-print.js +26 -0
- package/dist/commands/memory-store.js +14 -30
- package/dist/commands/memory-verb-list.js +49 -48
- package/dist/commands/msg-read-line.js +32 -0
- package/dist/commands/msg.js +45 -3
- package/dist/commands/notes-accounts.js +49 -32
- package/dist/commands/notes-sync-loop.js +115 -89
- package/dist/commands/notes-wisprflow-login.js +44 -23
- package/dist/commands/notes-writes.js +115 -100
- package/dist/commands/notes.js +33 -55
- package/dist/commands/onboard-flows.js +69 -51
- package/dist/commands/onboard.js +11 -9
- package/dist/commands/ops-render-memory.js +10 -7
- package/dist/commands/ops-render.js +33 -30
- package/dist/commands/ops.js +3 -0
- package/dist/commands/public-root.js +1 -1
- package/dist/commands/session-sync-outside-roots.js +40 -36
- package/dist/commands/session-sync-plan.js +14 -10
- package/dist/commands/session-sync-upload.js +24 -22
- package/dist/commands/session-sync.js +19 -8
- package/dist/commands/setup.js +13 -0
- package/dist/commands/slack.js +25 -16
- package/dist/commands/sources-check.js +13 -7
- package/dist/commands/sources-discovery.js +25 -17
- package/dist/commands/sources-verdict.js +19 -18
- package/dist/commands/speed.js +23 -19
- package/dist/commands/start.js +20 -16
- package/dist/commands/sync-doctor.js +52 -48
- package/dist/commands/sync.js +22 -16
- package/dist/commands/usage-telemetry-toml.js +71 -45
- package/dist/commands/usage.js +94 -68
- package/dist/commands/workbook-render.js +10 -7
- package/dist/evidence-reconcile-client.js +75 -64
- package/dist/evidence-redelivery-plan.js +37 -34
- package/dist/evidence-redelivery.js +32 -29
- package/dist/evidence-transcripts-client.js +20 -20
- package/dist/evidence-upload-object.js +86 -89
- package/dist/evidence-upload-plan.js +94 -83
- package/dist/evidence-upload-terminal.js +45 -50
- package/dist/image-description-session.js +156 -159
- package/dist/local-state-pairing-code.js +39 -31
- package/dist/local-state-status.js +9 -2
- package/dist/onboarding-roots.js +5 -8
- package/dist/process-runner.js +8 -8
- package/dist/raw-evidence-gc.js +33 -38
- package/dist/raw-evidence-stuck-summary.js +7 -14
- package/dist/repo-identity.js +26 -23
- package/dist/scheduled-self-update.js +11 -15
- package/dist/second-install.js +3 -0
- package/dist/slim/codex-rules.js +23 -36
- package/dist/spool/local-spool-parse.js +13 -12
- package/dist/sync-lock.js +5 -15
- package/dist/tower-client.js +36 -53
- package/dist/tower-stream.js +7 -24
- package/dist/upload-agent-artifacts.js +58 -64
- package/dist/upload-envelope-build.js +60 -59
- package/dist/upload-evidence-delivery-reconcile.js +58 -56
- package/dist/upload-evidence-delivery-summary.js +32 -34
- package/dist/upload-ingest-receipt.js +20 -23
- package/dist/upload-session-reports-queue.js +4 -1
- package/dist/upload-session-reports-wire.js +64 -68
- package/dist/upload-sync.js +171 -157
- package/package.json +4 -4
|
@@ -37,82 +37,86 @@ export async function resolveMemoryConfig(command, io, platform, deps) {
|
|
|
37
37
|
cliEntryPoint: deps.cliEntryPoint,
|
|
38
38
|
realpath: deps.realpath,
|
|
39
39
|
});
|
|
40
|
-
if (!found)
|
|
41
|
-
return
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
40
|
+
if (!found)
|
|
41
|
+
return binMissing();
|
|
42
|
+
if (isUnsafeBinPath(found.path))
|
|
43
|
+
return binPathUnsafe(found.source);
|
|
44
|
+
return await configFromPresentBin(io, found, platform, dashboardUrl);
|
|
45
|
+
}
|
|
46
|
+
function binMissing() {
|
|
47
|
+
return {
|
|
48
|
+
config: null,
|
|
49
|
+
source: "none",
|
|
50
|
+
binTarget: {
|
|
51
|
+
target: "bin",
|
|
52
|
+
status: "skipped",
|
|
53
|
+
reason: "bin_missing",
|
|
54
|
+
detail: `${MEMORY_MCP_BIN} is not installed beside this CLI or on PATH; nothing was written, and the next daily run will try again`,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function binPathUnsafe(binSource) {
|
|
59
|
+
// A hook command is a shell string by the platform's design. A path that
|
|
60
|
+
// cannot be expressed safely in one is not escaped cleverly, and it is not
|
|
61
|
+
// swapped for a bare name that may resolve to something else either — the
|
|
62
|
+
// install refuses and says why. See `shellSafeBinPath` for what CAN be
|
|
63
|
+
// expressed, including the Windows separator normalisation of BLI-4136.
|
|
64
|
+
return {
|
|
65
|
+
config: null,
|
|
66
|
+
source: "none",
|
|
67
|
+
binTarget: {
|
|
68
|
+
target: "bin",
|
|
69
|
+
status: "failed",
|
|
70
|
+
reason: "bin_path_unsafe",
|
|
71
|
+
detail: "the resolved bin path cannot be expressed safely in a hook command; nothing was written",
|
|
72
|
+
},
|
|
73
|
+
bin_source: binSource,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/** The bin exists and its path is safe: take its own printed shape, or the built-in one. */
|
|
77
|
+
async function configFromPresentBin(io, found, platform, dashboardUrl) {
|
|
70
78
|
const printed = await printedMemoryConfig(io, found.path);
|
|
71
|
-
if (printed) {
|
|
72
|
-
// The bin prints a BARE command name — it cannot know where it was
|
|
73
|
-
// installed, and it is nested inside the CLI's node_modules rather than on
|
|
74
|
-
// PATH. Path-qualifying it here is what makes the registration runnable at
|
|
75
|
-
// all; see `withResolvedBinPath`.
|
|
76
|
-
const qualified = withResolvedBinPath(printed, {
|
|
77
|
-
binPath: found.path,
|
|
78
|
-
platform,
|
|
79
|
-
dashboardUrl,
|
|
80
|
-
});
|
|
81
|
-
if (qualified) {
|
|
82
|
-
return {
|
|
83
|
-
config: qualified,
|
|
84
|
-
source: "bin",
|
|
85
|
-
binTarget: { target: "bin", status: "already", reason: "bin_printed_config" },
|
|
86
|
-
bin_source: found.source,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
// A printed hook command this installer cannot re-point. The template is
|
|
90
|
-
// always path-qualified, so it is the safe answer — and the reason says
|
|
91
|
-
// which of the two fallbacks happened.
|
|
79
|
+
if (!printed) {
|
|
92
80
|
return {
|
|
93
81
|
config: builtinMemoryInstallConfig({ binPath: found.path, platform, dashboardUrl }),
|
|
94
82
|
source: "template",
|
|
95
83
|
binTarget: {
|
|
96
84
|
target: "bin",
|
|
97
85
|
status: "already",
|
|
98
|
-
reason: "
|
|
99
|
-
detail: "the bin
|
|
86
|
+
reason: "bin_present_template_used",
|
|
87
|
+
detail: "the bin did not print a usable --print-config; the built-in shape was used",
|
|
100
88
|
},
|
|
101
89
|
bin_source: found.source,
|
|
102
90
|
};
|
|
103
91
|
}
|
|
92
|
+
// The bin prints a BARE command name — it cannot know where it was
|
|
93
|
+
// installed, and it is nested inside the CLI's node_modules rather than on
|
|
94
|
+
// PATH. Path-qualifying it here is what makes the registration runnable at
|
|
95
|
+
// all; see `withResolvedBinPath`.
|
|
96
|
+
const qualified = withResolvedBinPath(printed, {
|
|
97
|
+
binPath: found.path,
|
|
98
|
+
platform,
|
|
99
|
+
dashboardUrl,
|
|
100
|
+
});
|
|
101
|
+
if (qualified) {
|
|
102
|
+
return {
|
|
103
|
+
config: qualified,
|
|
104
|
+
source: "bin",
|
|
105
|
+
binTarget: { target: "bin", status: "already", reason: "bin_printed_config" },
|
|
106
|
+
bin_source: found.source,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// A printed hook command this installer cannot re-point. The template is
|
|
110
|
+
// always path-qualified, so it is the safe answer — and the reason says
|
|
111
|
+
// which of the two fallbacks happened.
|
|
104
112
|
return {
|
|
105
|
-
config: builtinMemoryInstallConfig({
|
|
106
|
-
binPath: found.path,
|
|
107
|
-
platform,
|
|
108
|
-
dashboardUrl,
|
|
109
|
-
}),
|
|
113
|
+
config: builtinMemoryInstallConfig({ binPath: found.path, platform, dashboardUrl }),
|
|
110
114
|
source: "template",
|
|
111
115
|
binTarget: {
|
|
112
116
|
target: "bin",
|
|
113
117
|
status: "already",
|
|
114
|
-
reason: "
|
|
115
|
-
detail: "the bin
|
|
118
|
+
reason: "bin_printed_config_unqualifiable",
|
|
119
|
+
detail: "the bin printed a hook command this installer could not re-point at the resolved path; the built-in shape was used",
|
|
116
120
|
},
|
|
117
121
|
bin_source: found.source,
|
|
118
122
|
};
|
|
@@ -97,18 +97,9 @@ export async function installMemoryIntegration(command, io, deps = {}) {
|
|
|
97
97
|
// `no_bin_no_write`: with no resolved server there is nothing safe to
|
|
98
98
|
// register, so not one file is opened. The bin target carries the reason.
|
|
99
99
|
if (resolved.config) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
dryRun: command.dryRun,
|
|
104
|
-
io: fileIo,
|
|
105
|
-
})));
|
|
106
|
-
targets.push(...(await installCodexMemoryIntegration({
|
|
107
|
-
homeDir,
|
|
108
|
-
config: resolved.config,
|
|
109
|
-
dryRun: command.dryRun,
|
|
110
|
-
io: fileIo,
|
|
111
|
-
})));
|
|
100
|
+
const host = { homeDir, config: resolved.config, dryRun: command.dryRun, io: fileIo };
|
|
101
|
+
targets.push(...(await installClaudeMemoryIntegration(host)));
|
|
102
|
+
targets.push(...(await installCodexMemoryIntegration(host)));
|
|
112
103
|
}
|
|
113
104
|
// BLI-3706: `bli-tower` (docs_*/msg_* over the collector device token)
|
|
114
105
|
// registers beside `bli-memory` on the SAME command — see
|
|
@@ -127,6 +118,20 @@ export async function installMemoryIntegration(command, io, deps = {}) {
|
|
|
127
118
|
}, command.dryRun)));
|
|
128
119
|
const outcome = {
|
|
129
120
|
action: "install",
|
|
121
|
+
...(await readBackOutcome(targets, resolved, homeDir, fileIo)),
|
|
122
|
+
};
|
|
123
|
+
logMemoryOutcome(outcome, platform);
|
|
124
|
+
// The heartbeat reads the cache, not the machine — see the receipt module's
|
|
125
|
+
// "Where the receipt lives between runs". A dry run caches nothing: it proves
|
|
126
|
+
// what WOULD happen and nothing about what is on disk.
|
|
127
|
+
if (!command.dryRun) {
|
|
128
|
+
await cacheReceipt(homeDir ?? command.homeDir, outcome.receipt);
|
|
129
|
+
}
|
|
130
|
+
return outcome;
|
|
131
|
+
}
|
|
132
|
+
/** What both `install` and `status` report once the targets are known: their verdict and the receipt. */
|
|
133
|
+
async function readBackOutcome(targets, resolved, homeDir, fileIo) {
|
|
134
|
+
return {
|
|
130
135
|
...aggregate(targets),
|
|
131
136
|
config_source: resolved.source,
|
|
132
137
|
bin_found: resolved.config !== null,
|
|
@@ -139,14 +144,6 @@ export async function installMemoryIntegration(command, io, deps = {}) {
|
|
|
139
144
|
expectedTrustRows: resolved.config ? expectedCodexTrustRows(resolved.config) : 0,
|
|
140
145
|
}),
|
|
141
146
|
};
|
|
142
|
-
logMemoryOutcome(outcome, platform);
|
|
143
|
-
// The heartbeat reads the cache, not the machine — see the receipt module's
|
|
144
|
-
// "Where the receipt lives between runs". A dry run caches nothing: it proves
|
|
145
|
-
// what WOULD happen and nothing about what is on disk.
|
|
146
|
-
if (!command.dryRun) {
|
|
147
|
-
await cacheReceipt(homeDir ?? command.homeDir, outcome.receipt);
|
|
148
|
-
}
|
|
149
|
-
return outcome;
|
|
150
147
|
}
|
|
151
148
|
/**
|
|
152
149
|
* Reading the trust state never fails an install. A `config.toml` we cannot
|
|
@@ -185,16 +182,9 @@ export async function inspectMemoryIntegration(command, io, deps = {}) {
|
|
|
185
182
|
// against, so `status` says the one true thing — the server is not here —
|
|
186
183
|
// rather than reporting four targets against a shape we invented.
|
|
187
184
|
if (resolved.config) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
io: fileIo,
|
|
192
|
-
})));
|
|
193
|
-
targets.push(...(await inspectCodexMemoryIntegration({
|
|
194
|
-
homeDir,
|
|
195
|
-
config: resolved.config,
|
|
196
|
-
io: fileIo,
|
|
197
|
-
})));
|
|
185
|
+
const host = { homeDir, config: resolved.config, io: fileIo };
|
|
186
|
+
targets.push(...(await inspectClaudeMemoryIntegration(host)));
|
|
187
|
+
targets.push(...(await inspectCodexMemoryIntegration(host)));
|
|
198
188
|
}
|
|
199
189
|
// BLI-3706: see the matching note in `installMemoryIntegration`.
|
|
200
190
|
targets.push(...(await inspectTowerIntegration({
|
|
@@ -213,17 +203,7 @@ export async function inspectMemoryIntegration(command, io, deps = {}) {
|
|
|
213
203
|
// bounded, it opens nothing but a local socket, and a machine without one
|
|
214
204
|
// is reported as exactly that rather than as a problem.
|
|
215
205
|
daemon: await probeMemoryDaemon({ ...(homeDir ? { homeDir } : {}) }),
|
|
216
|
-
...
|
|
217
|
-
config_source: resolved.source,
|
|
218
|
-
bin_found: resolved.config !== null,
|
|
219
|
-
...(resolved.bin_source ? { bin_source: resolved.bin_source } : {}),
|
|
220
|
-
targets,
|
|
221
|
-
receipt: buildMemoryInstallReceipt({
|
|
222
|
-
targets,
|
|
223
|
-
binFound: resolved.config !== null,
|
|
224
|
-
codexTrust: await codexTrustFor(homeDir, fileIo),
|
|
225
|
-
expectedTrustRows: resolved.config ? expectedCodexTrustRows(resolved.config) : 0,
|
|
226
|
-
}),
|
|
206
|
+
...(await readBackOutcome(targets, resolved, homeDir, fileIo)),
|
|
227
207
|
};
|
|
228
208
|
// `status` is a pure read and its reading is exactly as good as an install's
|
|
229
209
|
// — both re-parse the stored files — so it refreshes the cache too. A person
|
|
@@ -54,7 +54,19 @@ export async function runMemoryRequeue(command, io) {
|
|
|
54
54
|
if (typeof body.matched !== "number") {
|
|
55
55
|
return failAgentDoor(door, TAG, "requeue_answer_unreadable", "Tower answered 200 with no requeue receipt in it. The usual cause is a dashboard that has not shipped POST /api/memory/requeue yet; nothing was requeued.");
|
|
56
56
|
}
|
|
57
|
-
|
|
57
|
+
logRequeueReceipt(door, command, body);
|
|
58
|
+
if (door.json)
|
|
59
|
+
return emitAgentDoor(door, answer.body);
|
|
60
|
+
const matched = body.matched ?? 0;
|
|
61
|
+
if (matched === 0) {
|
|
62
|
+
writeNothingMatched(door, command);
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
writeRequeueSummary(door, body, matched);
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
/** Counts and labels only: a receipt's content never enters a log line. */
|
|
69
|
+
function logRequeueReceipt(door, command, body) {
|
|
58
70
|
writeLine(door.io.stderr, `${TAG} ${body.dryRun ? "dry run" : "applied"} ${JSON.stringify({
|
|
59
71
|
since: command.since ?? "",
|
|
60
72
|
until: command.until ?? null,
|
|
@@ -65,15 +77,15 @@ export async function runMemoryRequeue(command, io) {
|
|
|
65
77
|
refused: body.refused ?? 0,
|
|
66
78
|
coverage: body.coverage ?? "unknown",
|
|
67
79
|
})}`);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Silence is not an outage, and it is not a wrong filter either — say
|
|
83
|
+
* which one this is by naming the window that was asked about.
|
|
84
|
+
*/
|
|
85
|
+
function writeNothingMatched(door, command) {
|
|
86
|
+
writeLine(door.io.stdout, `No finished extraction receipt is in that window (${command.since} → ${command.until ?? "now"}${command.source ? `, ${command.source} only` : ""}). Nothing to requeue; the receipts table answered.`);
|
|
87
|
+
}
|
|
88
|
+
function writeRequeueSummary(door, body, matched) {
|
|
77
89
|
writeLine(door.io.stdout, body.dryRun
|
|
78
90
|
? `${matched} receipt(s) would be requeued (${describeSplit(body.bySource)}).`
|
|
79
91
|
: `${body.requeued ?? 0} of ${matched} receipt(s) requeued (${describeSplit(body.bySource)}).`);
|
|
@@ -86,15 +98,10 @@ export async function runMemoryRequeue(command, io) {
|
|
|
86
98
|
if (body.coverage === "capped") {
|
|
87
99
|
writeLine(door.io.stdout, " The page ceiling stopped this run before the window ended. Run it again for the rest; nothing is lost.");
|
|
88
100
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
else {
|
|
94
|
-
writeLine(door.io.stdout, "");
|
|
95
|
-
writeLine(door.io.stdout, "They are queued again: the hourly sweep reruns the hook receipts and the harvest pass reserves the rest. The previous result is kept on each row under result.requeued_from.");
|
|
96
|
-
}
|
|
97
|
-
return 0;
|
|
101
|
+
writeLine(door.io.stdout, "");
|
|
102
|
+
writeLine(door.io.stdout, body.dryRun
|
|
103
|
+
? "Nothing was written. Add --apply to do it for real."
|
|
104
|
+
: "They are queued again: the hourly sweep reruns the hook receipts and the harvest pass reserves the rest. The previous result is kept on each row under result.requeued_from.");
|
|
98
105
|
}
|
|
99
106
|
/** `hook:104, harvest:18` — counts and labels, in the order the door sorted them. */
|
|
100
107
|
function describeSplit(split) {
|
|
@@ -46,19 +46,31 @@ function printProjects(groups, machine, write) {
|
|
|
46
46
|
for (const group of groups) {
|
|
47
47
|
write("");
|
|
48
48
|
write(`${group.project}, ${group.count.toLocaleString("en-US")} memories`);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
49
|
+
printProjectIdeas(group, machine, write);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function printProjectIdeas(group, machine, write) {
|
|
53
|
+
const wording = machine ? agentWording : personWording;
|
|
54
|
+
const topics = group.topics ?? [];
|
|
55
|
+
if (topics.length === 0) {
|
|
56
|
+
for (const idea of group.memories ?? [])
|
|
57
|
+
write(` ${wording(idea)}`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// Topic headings help only when topics rank the ideas; a lone "Other" heading is noise.
|
|
61
|
+
const headed = Boolean(group.ranked) && topics.length > 1;
|
|
62
|
+
for (const topic of topics) {
|
|
63
|
+
if (headed)
|
|
64
|
+
write(` ${topic.title}`);
|
|
65
|
+
for (const idea of topic.memories)
|
|
66
|
+
write(`${headed ? " " : " "}${wording(idea)}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function printOwnerGroups(groups, machine, write) {
|
|
70
|
+
for (const group of groups) {
|
|
71
|
+
write(group.owner);
|
|
72
|
+
for (const rule of group.rules)
|
|
73
|
+
write(` [${rule.created_at.slice(0, 10)}] ${rule.project ? `[${rule.project}] ` : ""}${machine ? rule.text : personWording(rule)}`);
|
|
62
74
|
}
|
|
63
75
|
}
|
|
64
76
|
export async function runMemoryRules(command, door) {
|
|
@@ -87,11 +99,7 @@ export async function runMemoryRules(command, door) {
|
|
|
87
99
|
if (projects)
|
|
88
100
|
printProjects(body.projectGroups ?? [], Boolean(command.machine), write);
|
|
89
101
|
else
|
|
90
|
-
|
|
91
|
-
write(group.owner);
|
|
92
|
-
for (const rule of group.rules)
|
|
93
|
-
write(` [${rule.created_at.slice(0, 10)}] ${rule.project ? `[${rule.project}] ` : ""}${command.machine ? rule.text : personWording(rule)}`);
|
|
94
|
-
}
|
|
102
|
+
printOwnerGroups(body.groups ?? [], Boolean(command.machine), write);
|
|
95
103
|
if (!body.returned)
|
|
96
104
|
write(`No ${command.action === "knowledge" ? "project knowledge" : "ground rules"} matched.`);
|
|
97
105
|
if (body.capped)
|
|
@@ -92,4 +92,30 @@ function printOriginals(out, row, alreadyPrinted) {
|
|
|
92
92
|
const hidden = (row["originalsHidden"] ?? {});
|
|
93
93
|
if (Number(hidden["private"] ?? 0) > 0)
|
|
94
94
|
writeLine(out, ` (${String(hidden["private"])} private original(s) not shown)`);
|
|
95
|
+
}
|
|
96
|
+
export function writeSearchResults(out, results, degraded) {
|
|
97
|
+
if (results.length > 0) {
|
|
98
|
+
printSearchRows(out, results);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (degraded) {
|
|
102
|
+
// BLI-10448: an empty answer from a search that lost a channel is NOT the
|
|
103
|
+
// record being silent. `semantic_timed_out` is the store answering on
|
|
104
|
+
// keywords alone after its vector search ran out of time, and saying
|
|
105
|
+
// "the record is silent" over it sends a person away from memories that
|
|
106
|
+
// exist.
|
|
107
|
+
writeLine(out, `No memories matched on the channels that answered, and one did not (${degraded}). Do not read this as the record being silent; ask again.`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// The distinction the whole store is built on: silence is not an outage.
|
|
111
|
+
// A failure took the `!answer.ok` branch above and named itself there.
|
|
112
|
+
writeLine(out, "No memories matched. The record is silent on this — it does not mean the store is down.");
|
|
113
|
+
}
|
|
114
|
+
export function writeRecentContext(out, recent) {
|
|
115
|
+
writeLine(out, "");
|
|
116
|
+
writeLine(out, "Recent context:");
|
|
117
|
+
for (const row of recent) {
|
|
118
|
+
const author = typeof row["savedBy"] === "string" && row["savedBy"] ? `(from ${row["savedBy"]}) ` : "";
|
|
119
|
+
writeLine(out, ` - ${memoryProjectPrefix(row)}${row["project"] ? "" : author}${oneLine(row["preview"] ?? row["memory"])}`);
|
|
120
|
+
}
|
|
95
121
|
}
|
|
@@ -45,8 +45,7 @@
|
|
|
45
45
|
import { runMemoryMap } from "./memory-map.js";
|
|
46
46
|
import { runMemoryRepeats } from "./memory-repeats.js";
|
|
47
47
|
import { runMemoryRules } from "./memory-rules.js";
|
|
48
|
-
import {
|
|
49
|
-
import { oneLine, printSearchRows } from "./memory-search-print.js";
|
|
48
|
+
import { oneLine, writeRecentContext, writeSearchResults } from "./memory-search-print.js";
|
|
50
49
|
import { memorySaveOutsideRoots } from "@bli-cockpit/memory-mcp/dist/recorded-outside-roots.js";
|
|
51
50
|
import { resolveContainerTag } from "@bli-cockpit/memory-mcp/dist/container-tag.js";
|
|
52
51
|
import { askAgentDoor, emitAgentDoor, failAgentDoor, openAgentDoor, } from "./agent-door.js";
|
|
@@ -169,9 +168,8 @@ function containerTagsFor(command) {
|
|
|
169
168
|
return command.containerTags;
|
|
170
169
|
return [resolveContainerTag({ cwd: command.workspace ?? process.cwd() }).containerTag];
|
|
171
170
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const requestBody = {
|
|
171
|
+
function searchRequestBody(command, containerTags) {
|
|
172
|
+
return {
|
|
175
173
|
query: command.query,
|
|
176
174
|
containerTag: containerTags,
|
|
177
175
|
...(command.limit === undefined ? {} : { limit: command.limit }),
|
|
@@ -181,6 +179,10 @@ async function searchMemory(command, door) {
|
|
|
181
179
|
// BLI-19255: absent lets the door decide by the question.
|
|
182
180
|
...(command.includeSource === undefined ? {} : { includeSource: command.includeSource }),
|
|
183
181
|
};
|
|
182
|
+
}
|
|
183
|
+
async function searchMemory(command, door) {
|
|
184
|
+
const containerTags = containerTagsFor(command);
|
|
185
|
+
const requestBody = searchRequestBody(command, containerTags);
|
|
184
186
|
const answer = await readThroughCache({
|
|
185
187
|
io: door.io,
|
|
186
188
|
homeDir: door.homeDir,
|
|
@@ -219,30 +221,9 @@ async function searchMemory(command, door) {
|
|
|
219
221
|
if (answer.fromCache) {
|
|
220
222
|
writeLine(door.io.stderr, `(${cacheAgeLabel(answer.ageMs ?? 0)}, refreshing in the background)`);
|
|
221
223
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
// keywords alone after its vector search ran out of time, and saying
|
|
226
|
-
// "the record is silent" over it sends a person away from memories that
|
|
227
|
-
// exist.
|
|
228
|
-
writeLine(door.io.stdout, `No memories matched on the channels that answered, and one did not (${body.degraded}). Do not read this as the record being silent; ask again.`);
|
|
229
|
-
}
|
|
230
|
-
else if (results.length === 0) {
|
|
231
|
-
// The distinction the whole store is built on: silence is not an outage.
|
|
232
|
-
// A failure took the `!answer.ok` branch above and named itself there.
|
|
233
|
-
writeLine(door.io.stdout, "No memories matched. The record is silent on this — it does not mean the store is down.");
|
|
234
|
-
}
|
|
235
|
-
else {
|
|
236
|
-
printSearchRows(door.io.stdout, results);
|
|
237
|
-
}
|
|
238
|
-
if (recent.length > 0) {
|
|
239
|
-
writeLine(door.io.stdout, "");
|
|
240
|
-
writeLine(door.io.stdout, "Recent context:");
|
|
241
|
-
for (const row of recent) {
|
|
242
|
-
const author = typeof row["savedBy"] === "string" && row["savedBy"] ? `(from ${row["savedBy"]}) ` : "";
|
|
243
|
-
writeLine(door.io.stdout, ` - ${memoryProjectPrefix(row)}${row["project"] ? "" : author}${oneLine(row["preview"] ?? row["memory"])}`);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
224
|
+
writeSearchResults(door.io.stdout, results, body.degraded);
|
|
225
|
+
if (recent.length > 0)
|
|
226
|
+
writeRecentContext(door.io.stdout, recent);
|
|
246
227
|
if (body.degraded) {
|
|
247
228
|
writeLine(door.io.stdout, "");
|
|
248
229
|
writeLine(door.io.stdout, `This answer is thinner than usual (${body.degraded}) — one search channel did not run.`);
|
|
@@ -289,6 +270,10 @@ async function saveMemory(command, door) {
|
|
|
289
270
|
})}`);
|
|
290
271
|
if (door.json)
|
|
291
272
|
return emitAgentDoor(door, answer.body);
|
|
273
|
+
writeSavedMemory(door, saved, containerTag);
|
|
274
|
+
return 0;
|
|
275
|
+
}
|
|
276
|
+
function writeSavedMemory(door, saved, containerTag) {
|
|
292
277
|
// The container the SERVER used, which is the canonical one when this
|
|
293
278
|
// repository has more than one tag on the shelf; saying the tag we sent
|
|
294
279
|
// would name a space the row is not in.
|
|
@@ -300,7 +285,6 @@ async function saveMemory(command, door) {
|
|
|
300
285
|
if (saved["extract"] === "fell_back_verbatim") {
|
|
301
286
|
writeLine(door.io.stdout, `Saved as written; extraction failed (${String(saved["extractFailureReason"] ?? "unknown")}).`);
|
|
302
287
|
}
|
|
303
|
-
return 0;
|
|
304
288
|
}
|
|
305
289
|
async function updateMemory(command, door) {
|
|
306
290
|
const body = await readBody(command, door, BODY_MAX_CHARS, "A memory");
|
|
@@ -11,6 +11,54 @@
|
|
|
11
11
|
* Every string moved verbatim.
|
|
12
12
|
*/
|
|
13
13
|
import { writeLine } from "./cli-io.js";
|
|
14
|
+
/** The human page: what the noun does, one screen. */
|
|
15
|
+
const VERB_LIST_LINES = [
|
|
16
|
+
"cockpit memory — BLI Memory: the store, and this machine's registration.",
|
|
17
|
+
"",
|
|
18
|
+
"The store (Tower, over /api/memory/*):",
|
|
19
|
+
" cockpit memory search \"<question>\" [--container <tag>] [--limit 1..20] [--no-recent] [--include-originals|--no-originals] [--include-source|--no-source] [--json]",
|
|
20
|
+
" cockpit memory save [--container <tag>] [--custom-id <id>] [--extract] [--file <path>] [--json]",
|
|
21
|
+
" the memory comes from stdin, or --file; never from the command line",
|
|
22
|
+
" cockpit memory update <id> [--file <path>] [--json] replacement text on stdin",
|
|
23
|
+
" cockpit memory forget <id> [--reason <label>] [--json]",
|
|
24
|
+
" cockpit memory forget --content-stdin --container <tag> [--reason <label>] [--json]",
|
|
25
|
+
" you can forget or update only your own memories (a super admin, any)",
|
|
26
|
+
" cockpit memory private <id> [--json] only you and super admins can read this memory",
|
|
27
|
+
" cockpit memory public <id> [--json] every member can read it again (how every memory starts)",
|
|
28
|
+
" cockpit memory map [--json]",
|
|
29
|
+
" cockpit memory repeats [--project <name-or-slug>] [--min 2..50] [--limit 1..200] [--json]",
|
|
30
|
+
" subjects a person corrected an agent about, or ordered with emphasis, more than once",
|
|
31
|
+
" cockpit memory rules [--person <name>] [--project <name-or-slug>] [--limit <n>] [--per-project <n>] [--machine] [--json]",
|
|
32
|
+
" cockpit memory knowledge [--person <name>] [--project <name-or-slug>] [--limit <n>] [--per-project <n>] [--machine] [--json]",
|
|
33
|
+
" plain sentences per project; --per-project 1..100 (default 15) is each project's own",
|
|
34
|
+
" quota, --limit 1..1000 caps the ideas in all; --machine prints the agent wording with",
|
|
35
|
+
" scores and spaces; --json is the whole answer, both wordings (--by-project is accepted)",
|
|
36
|
+
" cockpit memory profile [--container <tag>] [--json]",
|
|
37
|
+
" the compiled one-page briefing every session opens with; a read, never a compile",
|
|
38
|
+
" cockpit memory consolidation [--container <tag>] [--json]",
|
|
39
|
+
" what the nightly tidy-up pass did: clusters, edits by verb, refusals, cost",
|
|
40
|
+
" cockpit memory chain <id> [--json]",
|
|
41
|
+
" cockpit memory exam [--night <YYYY-MM-DD>] [--misses] [--json]",
|
|
42
|
+
" the nightly memory exam: purity, coverage, trend and worst project; yours alone",
|
|
43
|
+
" cockpit memory source <id> [--query \"<words>\"] [--chars 200..6000] [--json]",
|
|
44
|
+
" the memories the nightly pass folded into this one: who, when, what they said",
|
|
45
|
+
" cockpit memory requeue --since <iso> [--until <iso>] [--source hook|harvest] [--reason <label>] [--apply] [--json]",
|
|
46
|
+
" put a window of finished extractions back in the queue after the store refused",
|
|
47
|
+
" their writes; super admins only, and a DRY RUN unless you pass --apply",
|
|
48
|
+
"",
|
|
49
|
+
"This machine:",
|
|
50
|
+
" cockpit memory install [--dry-run] [--json] register the MCP server and the three hooks",
|
|
51
|
+
" cockpit memory status [--json] read the stored config back",
|
|
52
|
+
"",
|
|
53
|
+
"The experience log:",
|
|
54
|
+
" cockpit memory log <win|loss|noise> --store <bli|supermemory|both> \"<reason>\"",
|
|
55
|
+
"",
|
|
56
|
+
"Search: --limit caps the MEMORIES (default 5, at most 20). Up to 3 Tower pages and",
|
|
57
|
+
"tickets ride beside them whatever --limit says; for more, use `cockpit search`.",
|
|
58
|
+
"Save accepts repeated --alias names (up to 16 names of 64 characters).",
|
|
59
|
+
"Omit --container and this repository's own memory space is used — the same",
|
|
60
|
+
"space the bli-memory MCP server and its hooks read and write.",
|
|
61
|
+
];
|
|
14
62
|
/**
|
|
15
63
|
* The verb list a bare `cockpit memory` prints (BLI-4047).
|
|
16
64
|
*
|
|
@@ -31,54 +79,7 @@ export function runMemoryVerbList(io, json) {
|
|
|
31
79
|
}));
|
|
32
80
|
return 0;
|
|
33
81
|
}
|
|
34
|
-
for (const line of
|
|
35
|
-
"cockpit memory — BLI Memory: the store, and this machine's registration.",
|
|
36
|
-
"",
|
|
37
|
-
"The store (Tower, over /api/memory/*):",
|
|
38
|
-
" cockpit memory search \"<question>\" [--container <tag>] [--limit 1..20] [--no-recent] [--include-originals|--no-originals] [--include-source|--no-source] [--json]",
|
|
39
|
-
" cockpit memory save [--container <tag>] [--custom-id <id>] [--extract] [--file <path>] [--json]",
|
|
40
|
-
" the memory comes from stdin, or --file; never from the command line",
|
|
41
|
-
" cockpit memory update <id> [--file <path>] [--json] replacement text on stdin",
|
|
42
|
-
" cockpit memory forget <id> [--reason <label>] [--json]",
|
|
43
|
-
" cockpit memory forget --content-stdin --container <tag> [--reason <label>] [--json]",
|
|
44
|
-
" you can forget or update only your own memories (a super admin, any)",
|
|
45
|
-
" cockpit memory private <id> [--json] only you and super admins can read this memory",
|
|
46
|
-
" cockpit memory public <id> [--json] every member can read it again (how every memory starts)",
|
|
47
|
-
" cockpit memory map [--json]",
|
|
48
|
-
" cockpit memory repeats [--project <name-or-slug>] [--min 2..50] [--limit 1..200] [--json]",
|
|
49
|
-
" subjects a person corrected an agent about, or ordered with emphasis, more than once",
|
|
50
|
-
" cockpit memory rules [--person <name>] [--project <name-or-slug>] [--limit <n>] [--per-project <n>] [--machine] [--json]",
|
|
51
|
-
" cockpit memory knowledge [--person <name>] [--project <name-or-slug>] [--limit <n>] [--per-project <n>] [--machine] [--json]",
|
|
52
|
-
" plain sentences per project; --per-project 1..100 (default 15) is each project's own",
|
|
53
|
-
" quota, --limit 1..1000 caps the ideas in all; --machine prints the agent wording with",
|
|
54
|
-
" scores and spaces; --json is the whole answer, both wordings (--by-project is accepted)",
|
|
55
|
-
" cockpit memory profile [--container <tag>] [--json]",
|
|
56
|
-
" the compiled one-page briefing every session opens with; a read, never a compile",
|
|
57
|
-
" cockpit memory consolidation [--container <tag>] [--json]",
|
|
58
|
-
" what the nightly tidy-up pass did: clusters, edits by verb, refusals, cost",
|
|
59
|
-
" cockpit memory chain <id> [--json]",
|
|
60
|
-
" cockpit memory exam [--night <YYYY-MM-DD>] [--misses] [--json]",
|
|
61
|
-
" the nightly memory exam: purity, coverage, trend and worst project; yours alone",
|
|
62
|
-
" cockpit memory source <id> [--query \"<words>\"] [--chars 200..6000] [--json]",
|
|
63
|
-
" the memories the nightly pass folded into this one: who, when, what they said",
|
|
64
|
-
" cockpit memory requeue --since <iso> [--until <iso>] [--source hook|harvest] [--reason <label>] [--apply] [--json]",
|
|
65
|
-
" put a window of finished extractions back in the queue after the store refused",
|
|
66
|
-
" their writes; super admins only, and a DRY RUN unless you pass --apply",
|
|
67
|
-
"",
|
|
68
|
-
"This machine:",
|
|
69
|
-
" cockpit memory install [--dry-run] [--json] register the MCP server and the three hooks",
|
|
70
|
-
" cockpit memory status [--json] read the stored config back",
|
|
71
|
-
"",
|
|
72
|
-
"The experience log:",
|
|
73
|
-
" cockpit memory log <win|loss|noise> --store <bli|supermemory|both> \"<reason>\"",
|
|
74
|
-
"",
|
|
75
|
-
"Search: --limit caps the MEMORIES (default 5, at most 20). Up to 3 Tower pages and",
|
|
76
|
-
"tickets ride beside them whatever --limit says; for more, use `cockpit search`.",
|
|
77
|
-
"Save accepts repeated --alias names (up to 16 names of 64 characters).",
|
|
78
|
-
"Omit --container and this repository's own memory space is used — the same",
|
|
79
|
-
"space the bli-memory MCP server and its hooks read and write.",
|
|
80
|
-
]) {
|
|
82
|
+
for (const line of VERB_LIST_LINES)
|
|
81
83
|
writeLine(io.stdout, line);
|
|
82
|
-
}
|
|
83
84
|
return 0;
|
|
84
85
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The read line, from a terminal (BLI-21299), split out of `msg.ts` to keep it
|
|
3
|
+
* under the file-size ratchet: the two fields every channel read now carries
|
|
4
|
+
* (`provenance`, `readBoundary`) and the `read --mark-read` step. The web page,
|
|
5
|
+
* MCP `msg_read` `mark_read` and this all call `POST /api/msg/channels/[id]/read`.
|
|
6
|
+
*/
|
|
7
|
+
import { askAgentDoor } from "./agent-door.js";
|
|
8
|
+
import { writeLine } from "./cli-io.js";
|
|
9
|
+
const TAG = "[msg cli]";
|
|
10
|
+
const WRITE_DEADLINE_MS = 30_000;
|
|
11
|
+
/** POST the read line to the newest top-level message of a page just read (newest first). */
|
|
12
|
+
export async function markReadThrough(door, channelId, messages) {
|
|
13
|
+
const newest = messages.find((message) => !message.thread_parent_id);
|
|
14
|
+
if (!newest) {
|
|
15
|
+
writeLine(door.io.stderr, `${TAG} mark-read skipped ${JSON.stringify({ channel_id: channelId, reason: "nothing_to_mark" })}`);
|
|
16
|
+
return { ok: false, reason: "nothing_to_mark", detail: "There was no top-level message on this page to mark read." };
|
|
17
|
+
}
|
|
18
|
+
const answer = await askAgentDoor(door, {
|
|
19
|
+
path: `/api/msg/channels/${encodeURIComponent(channelId)}/read`,
|
|
20
|
+
method: "POST",
|
|
21
|
+
label: "msg mark-read",
|
|
22
|
+
timeoutMs: WRITE_DEADLINE_MS,
|
|
23
|
+
body: { message_id: newest.id },
|
|
24
|
+
});
|
|
25
|
+
if (!answer.ok) {
|
|
26
|
+
writeLine(door.io.stderr, `${TAG} mark-read failed ${JSON.stringify({ channel_id: channelId, reason: answer.reason })}`);
|
|
27
|
+
return { ok: false, reason: answer.reason, detail: answer.detail };
|
|
28
|
+
}
|
|
29
|
+
const changed = answer.body.move?.changed === true;
|
|
30
|
+
writeLine(door.io.stderr, `${TAG} mark-read ${JSON.stringify({ channel_id: channelId, message_id: newest.id, changed })}`);
|
|
31
|
+
return { ok: true, messageId: newest.id, changed };
|
|
32
|
+
}
|