@mnemom/mnemom 0.17.33 → 0.17.34-next.2
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/base-url-proxy.mjs +18 -4
- package/dist/commands/agent-config.js +5 -1
- package/dist/commands/agent-doctor.d.ts +9 -0
- package/dist/commands/agent-doctor.js +4 -0
- package/dist/commands/agent-session.d.ts +1 -2
- package/dist/commands/agent-session.js +1 -2
- package/dist/commands/agent-setup.d.ts +6 -0
- package/dist/commands/agent-setup.js +12 -1
- package/dist/commands/agent-share-render.d.ts +1 -1
- package/dist/commands/agent-share-render.js +1 -1
- package/dist/commands/agent.d.ts +26 -0
- package/dist/commands/agent.js +228 -53
- package/dist/commands/durable-doctor.d.ts +50 -0
- package/dist/commands/durable-doctor.js +186 -0
- package/dist/commands/durable-lock.d.ts +254 -0
- package/dist/commands/durable-lock.js +595 -0
- package/dist/commands/durable-sessions.d.ts +87 -0
- package/dist/commands/durable-sessions.js +303 -0
- package/dist/commands/durable-verbs.d.ts +116 -0
- package/dist/commands/durable-verbs.js +393 -0
- package/dist/commands/protection.js +1 -1
- package/dist/commands/scan.d.ts +97 -0
- package/dist/commands/scan.js +238 -0
- package/dist/durable/flock.d.ts +26 -0
- package/dist/durable/flock.js +80 -0
- package/dist/durable/launch-spec.d.ts +7 -1
- package/dist/durable/launch-spec.js +10 -3
- package/dist/durable/seat-proxy-entry.d.ts +11 -0
- package/dist/durable/seat-proxy-entry.js +25 -0
- package/dist/durable/seat-proxy.d.ts +107 -0
- package/dist/durable/seat-proxy.js +599 -0
- package/dist/durable/seat-socket.d.ts +46 -0
- package/dist/durable/seat-socket.js +179 -0
- package/dist/durable/seat-spec-validator.d.ts +28 -0
- package/dist/durable/seat-spec-validator.js +59 -0
- package/dist/durable/test-mode.mjs +81 -0
- package/dist/durable/turn-chain.d.ts +55 -0
- package/dist/durable/turn-chain.js +178 -0
- package/dist/host/auto-continue.d.ts +67 -0
- package/dist/host/auto-continue.js +118 -0
- package/dist/host/bootid.d.ts +13 -0
- package/dist/host/bootid.js +41 -0
- package/dist/host/client.d.ts +123 -0
- package/dist/host/client.js +171 -0
- package/dist/host/commands.d.ts +2 -0
- package/dist/host/commands.js +122 -0
- package/dist/host/control.d.ts +33 -0
- package/dist/host/control.js +144 -0
- package/dist/host/daemon.d.ts +217 -0
- package/dist/host/daemon.js +606 -0
- package/dist/host/gate.d.ts +46 -0
- package/dist/host/gate.js +82 -0
- package/dist/host/grant.d.ts +24 -0
- package/dist/host/grant.js +24 -0
- package/dist/host/instance.d.ts +35 -0
- package/dist/host/instance.js +62 -0
- package/dist/host/integrity.d.ts +35 -0
- package/dist/host/integrity.js +115 -0
- package/dist/host/launchd.d.ts +16 -0
- package/dist/host/launchd.js +45 -0
- package/dist/host/lock.d.ts +20 -0
- package/dist/host/lock.js +63 -0
- package/dist/host/owner.d.ts +55 -0
- package/dist/host/owner.js +121 -0
- package/dist/host/plist.d.ts +16 -0
- package/dist/host/plist.js +85 -0
- package/dist/host/procs.d.ts +27 -0
- package/dist/host/procs.js +105 -0
- package/dist/host/run.d.ts +66 -0
- package/dist/host/run.js +314 -0
- package/dist/host/runtime.d.ts +8 -0
- package/dist/host/runtime.js +22 -0
- package/dist/host/s1-names.d.ts +22 -0
- package/dist/host/s1-names.js +21 -0
- package/dist/host/seat-credential.d.ts +27 -0
- package/dist/host/seat-credential.js +41 -0
- package/dist/host/seat-launch-main.d.ts +1 -0
- package/dist/host/seat-launch-main.js +59 -0
- package/dist/host/seat-launch.d.ts +116 -0
- package/dist/host/seat-launch.js +302 -0
- package/dist/host/seat.d.ts +48 -0
- package/dist/host/seat.js +149 -0
- package/dist/host/state.d.ts +33 -0
- package/dist/host/state.js +117 -0
- package/dist/host/status.d.ts +101 -0
- package/dist/host/status.js +307 -0
- package/dist/host/tmux.d.ts +56 -0
- package/dist/host/tmux.js +182 -0
- package/dist/host/transcript.d.ts +30 -0
- package/dist/host/transcript.js +130 -0
- package/dist/host/verbs.d.ts +50 -0
- package/dist/host/verbs.js +271 -0
- package/dist/index.d.ts +8 -6
- package/dist/index.js +232 -35
- package/dist/lib/agent.d.ts +8 -26
- package/dist/lib/agent.js +11 -26
- package/dist/lib/api.d.ts +13 -0
- package/dist/lib/api.js +8 -0
- package/dist/lib/claude-plugin.d.ts +11 -2
- package/dist/lib/claude-plugin.js +10 -1
- package/dist/lib/durable/api.d.ts +136 -0
- package/dist/lib/durable/api.js +220 -0
- package/dist/lib/durable/codes.d.ts +8 -0
- package/dist/lib/durable/codes.js +2 -0
- package/dist/lib/durable/first-turn.d.ts +50 -0
- package/dist/lib/durable/first-turn.js +102 -0
- package/dist/lib/durable/gate.d.ts +48 -0
- package/dist/lib/durable/gate.js +122 -0
- package/dist/lib/durable/handover.d.ts +101 -0
- package/dist/lib/durable/handover.js +175 -0
- package/dist/lib/durable/launch.d.ts +182 -0
- package/dist/lib/durable/launch.js +443 -0
- package/dist/lib/durable/launching.d.ts +15 -0
- package/dist/lib/durable/launching.js +70 -0
- package/dist/lib/durable/live.d.ts +194 -0
- package/dist/lib/durable/live.js +635 -0
- package/dist/lib/durable/refusal.d.ts +57 -0
- package/dist/lib/durable/refusal.js +78 -0
- package/dist/lib/durable/view.d.ts +83 -0
- package/dist/lib/durable/view.js +244 -0
- package/dist/lib/experimental.d.ts +0 -9
- package/dist/lib/experimental.js +18 -17
- package/dist/lib/launcher-config.d.ts +7 -0
- package/dist/lib/launcher-config.js +21 -0
- package/dist/lib/scan/caps.d.ts +35 -0
- package/dist/lib/scan/caps.js +67 -0
- package/dist/lib/scan/credential.d.ts +37 -0
- package/dist/lib/scan/credential.js +104 -0
- package/dist/lib/scan/failure.d.ts +10 -0
- package/dist/lib/scan/failure.js +43 -0
- package/dist/lib/scan/pr-source.d.ts +57 -0
- package/dist/lib/scan/pr-source.js +130 -0
- package/dist/lib/scan/record.d.ts +49 -0
- package/dist/lib/scan/record.js +85 -0
- package/dist/lib/scan/report-email.d.ts +92 -0
- package/dist/lib/scan/report-email.js +144 -0
- package/dist/lib/scan/report.d.ts +13 -0
- package/dist/lib/scan/report.js +30 -0
- package/dist/lib/scan/scan.d.ts +64 -0
- package/dist/lib/scan/scan.js +182 -0
- package/dist/lib/scan/share.d.ts +38 -0
- package/dist/lib/scan/share.js +47 -0
- package/dist/lib/scan/spend/ai-tool-detect.d.ts +6 -0
- package/dist/lib/scan/spend/ai-tool-detect.js +18 -0
- package/dist/lib/scan/spend/anthropic-usage.d.ts +57 -0
- package/dist/lib/scan/spend/anthropic-usage.js +244 -0
- package/dist/lib/scan/spend/cost-corpus.v1.d.ts +56 -0
- package/dist/lib/scan/spend/cost-corpus.v1.js +64 -0
- package/dist/lib/scan/spend/prices.d.ts +33 -0
- package/dist/lib/scan/spend/prices.js +57 -0
- package/dist/lib/scan/spend/spend-estimate.d.ts +30 -0
- package/dist/lib/scan/spend/spend-estimate.js +70 -0
- package/dist/lib/scan/spend/spend-tiers.d.ts +25 -0
- package/dist/lib/scan/spend/spend-tiers.js +42 -0
- package/dist/lib/scan/spend/token-rate.d.ts +30 -0
- package/dist/lib/scan/spend/token-rate.js +54 -0
- package/dist/lib/session-key.d.ts +41 -0
- package/dist/lib/session-key.js +124 -0
- package/dist/lib/subscription-login.d.ts +15 -0
- package/dist/lib/subscription-login.js +28 -1
- package/dist/rc-key-401.mjs +75 -0
- package/dist/rc-proxy.mjs +221 -25
- package/dist/vendor/durable-contract/index.js +240 -18
- package/dist/vendor/shared/durable-contract/admission.d.ts +240 -0
- package/dist/vendor/shared/durable-contract/admission.js +533 -0
- package/dist/vendor/shared/durable-contract/arm.d.ts +51 -0
- package/dist/vendor/shared/durable-contract/arm.js +133 -0
- package/dist/vendor/shared/durable-contract/canonical.d.ts +29 -0
- package/dist/vendor/shared/durable-contract/canonical.js +98 -0
- package/dist/vendor/shared/durable-contract/cell-id.d.ts +22 -0
- package/dist/vendor/shared/durable-contract/cell-id.js +38 -0
- package/dist/vendor/shared/durable-contract/cli-output.d.ts +342 -0
- package/dist/vendor/shared/durable-contract/cli-output.js +201 -0
- package/dist/vendor/shared/durable-contract/cli-surface.d.ts +119 -0
- package/dist/vendor/shared/durable-contract/cli-surface.js +270 -0
- package/dist/vendor/shared/durable-contract/codes.d.ts +537 -0
- package/dist/vendor/shared/durable-contract/codes.js +475 -0
- package/dist/vendor/shared/durable-contract/constants.d.ts +153 -0
- package/dist/vendor/shared/durable-contract/constants.js +179 -0
- package/dist/vendor/shared/durable-contract/doors.d.ts +119 -0
- package/dist/vendor/shared/durable-contract/doors.js +249 -0
- package/dist/vendor/shared/durable-contract/envelope.d.ts +103 -0
- package/dist/vendor/shared/durable-contract/envelope.js +317 -0
- package/dist/vendor/shared/durable-contract/events.d.ts +147 -0
- package/dist/vendor/shared/durable-contract/events.js +630 -0
- package/dist/vendor/shared/durable-contract/executor-state.d.ts +41 -0
- package/dist/vendor/shared/durable-contract/executor-state.js +32 -0
- package/dist/vendor/shared/durable-contract/fixlines.d.ts +318 -0
- package/dist/vendor/shared/durable-contract/fixlines.js +483 -0
- package/dist/vendor/shared/durable-contract/freeze-record.d.ts +787 -0
- package/dist/vendor/shared/durable-contract/freeze-record.js +361 -0
- package/dist/vendor/shared/durable-contract/index.d.ts +36 -0
- package/dist/vendor/shared/durable-contract/index.js +37 -0
- package/dist/vendor/shared/durable-contract/journal-approvals.d.ts +29 -0
- package/dist/vendor/shared/durable-contract/journal-approvals.js +60 -0
- package/dist/vendor/shared/durable-contract/keys.d.ts +24 -0
- package/dist/vendor/shared/durable-contract/keys.js +66 -0
- package/dist/vendor/shared/durable-contract/lock.d.ts +128 -0
- package/dist/vendor/shared/durable-contract/lock.js +151 -0
- package/dist/vendor/shared/durable-contract/names.d.ts +25 -0
- package/dist/vendor/shared/durable-contract/names.js +78 -0
- package/dist/vendor/shared/durable-contract/owner-verbs.d.ts +175 -0
- package/dist/vendor/shared/durable-contract/owner-verbs.js +403 -0
- package/dist/vendor/shared/durable-contract/paths.d.ts +23 -0
- package/dist/vendor/shared/durable-contract/paths.js +35 -0
- package/dist/vendor/shared/durable-contract/pure-fn.d.ts +48 -0
- package/dist/vendor/shared/durable-contract/pure-fn.js +89 -0
- package/dist/vendor/shared/durable-contract/routes.d.ts +205 -0
- package/dist/vendor/shared/durable-contract/routes.js +315 -0
- package/dist/vendor/shared/durable-contract/schema.d.ts +126 -0
- package/dist/vendor/shared/durable-contract/schema.js +441 -0
- package/dist/vendor/shared/durable-contract/seal.d.ts +54 -0
- package/dist/vendor/shared/durable-contract/seal.js +99 -0
- package/dist/vendor/shared/durable-contract/secret-scope.d.ts +28 -0
- package/dist/vendor/shared/durable-contract/secret-scope.js +76 -0
- package/dist/vendor/shared/durable-contract/session-id.d.ts +21 -0
- package/dist/vendor/shared/durable-contract/session-id.js +58 -0
- package/dist/vendor/shared/durable-contract/spec.d.ts +162 -0
- package/dist/vendor/shared/durable-contract/spec.js +286 -0
- package/dist/vendor/shared/durable-contract/states.d.ts +96 -0
- package/dist/vendor/shared/durable-contract/states.js +218 -0
- package/dist/vendor/shared/durable-contract/telemetry.d.ts +94 -0
- package/dist/vendor/shared/durable-contract/telemetry.js +180 -0
- package/dist/vendor/shared/durable-contract/test-mode.d.ts +62 -0
- package/dist/vendor/shared/durable-contract/test-mode.js +140 -0
- package/dist/vendor/shared/durable-contract/turn.d.ts +57 -0
- package/dist/vendor/shared/durable-contract/turn.js +81 -0
- package/dist/vendor/shared/durable-contract/vectors.d.ts +191 -0
- package/dist/vendor/shared/durable-contract/vectors.js +208 -0
- package/package.json +4 -3
- package/plugins/integrity-manifest.json +35 -0
- package/plugins/mnemom/hooks/hooks.json +15 -1
- package/plugins/mnemom/hooks/register.tsx +60 -2
- package/plugins/mnemom/hooks/seat-hello.mjs +116 -0
- package/dist/commands/agent-implicit-notice.d.ts +0 -31
- package/dist/commands/agent-implicit-notice.js +0 -73
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The durable lines of `mnemom agent doctor` (BACKBONE C1 "doctor (MFA, branch protection)";
|
|
3
|
+
* C02-R19): for each of the caller's open sessions, the MFA and branch-protection verdicts lock would
|
|
4
|
+
* compute at that moment, with the same probes and codes. The API computes them (S1 lock.ts steps
|
|
5
|
+
* `mfa` and `protection`); the doctor only renders. Read-only: one GET per session, never a POST.
|
|
6
|
+
*
|
|
7
|
+
* Not frozen by S1 (A2 owns it; gaps/c1-doctor-lock-preflight.md):
|
|
8
|
+
* GET /v1/agent/sessions/{cid}/lock?agent_id=&dry_run=true
|
|
9
|
+
* 200 {mfa, repos: [{repo, verdict | protected | reason, pushable?}]} (read defensively), or the
|
|
10
|
+
* lock refusal envelope of the first failing step (403 durable_lock_mfa_required, 409
|
|
11
|
+
* durable_lock_branch_unprotected {refusals}, 503 durable_precondition_unavailable, ...).
|
|
12
|
+
* A route that is absent (404/405) prints one info line; nothing here is ever a MISS, so the doctor's
|
|
13
|
+
* exit status keeps meaning "launch-ready".
|
|
14
|
+
*/
|
|
15
|
+
import { errorCodeOf, errorDetailsOf, } from "../lib/durable/api.js";
|
|
16
|
+
import { fixLine } from "../lib/durable/refusal.js";
|
|
17
|
+
import { isEnded } from "../lib/durable/view.js";
|
|
18
|
+
import { CODE } from "../lib/durable/codes.js";
|
|
19
|
+
/** At most this many open sessions are checked (one GET each). */
|
|
20
|
+
export const DOCTOR_SESSION_CAP = 5;
|
|
21
|
+
function rec(v) {
|
|
22
|
+
return v !== null && typeof v === "object" && !Array.isArray(v)
|
|
23
|
+
? v
|
|
24
|
+
: {};
|
|
25
|
+
}
|
|
26
|
+
function mfaOf(v) {
|
|
27
|
+
if (typeof v === "boolean")
|
|
28
|
+
return v ? "ok" : "missing";
|
|
29
|
+
if (typeof v === "string")
|
|
30
|
+
return v === "ok" ? "ok" : "missing";
|
|
31
|
+
if (typeof rec(v).ok === "boolean")
|
|
32
|
+
return rec(v).ok ? "ok" : "missing";
|
|
33
|
+
return "unknown";
|
|
34
|
+
}
|
|
35
|
+
const STATE_WORDS = {
|
|
36
|
+
protected: "protected",
|
|
37
|
+
unprotected: "unprotected",
|
|
38
|
+
unsupported_host: "unsupported_host",
|
|
39
|
+
};
|
|
40
|
+
/** One repo entry; null for a read-only repo (lock ignores it) or one without a name. */
|
|
41
|
+
function repoOf(v) {
|
|
42
|
+
const r = rec(v);
|
|
43
|
+
if (typeof r.repo !== "string")
|
|
44
|
+
return null;
|
|
45
|
+
if (r.pushable === false)
|
|
46
|
+
return null;
|
|
47
|
+
const word = r.verdict ?? r.state ?? r.reason;
|
|
48
|
+
if (word === "read_only")
|
|
49
|
+
return null;
|
|
50
|
+
if (typeof r.protected === "boolean")
|
|
51
|
+
return { repo: r.repo, state: r.protected ? "protected" : "unprotected" };
|
|
52
|
+
return { repo: r.repo, state: (typeof word === "string" && STATE_WORDS[word]) || "unknown" };
|
|
53
|
+
}
|
|
54
|
+
const reposOf = (list) => (Array.isArray(list) ? list : []).map(repoOf).filter((r) => r !== null);
|
|
55
|
+
/**
|
|
56
|
+
* A refusals[] list fails closed: membership is the server refusing the repo, so every listed repo
|
|
57
|
+
* shows as refused whatever state word sits next to it. A "protected", "read_only", unknown word or
|
|
58
|
+
* pushable:false cannot turn a refusal into an ok or drop it; the one finer reason kept is
|
|
59
|
+
* unsupported_host, itself a refusal (a non-github.com host cannot be protected).
|
|
60
|
+
*/
|
|
61
|
+
const refusedReposOf = (list) => (Array.isArray(list) ? list : []).flatMap((v) => {
|
|
62
|
+
const r = rec(v);
|
|
63
|
+
if (typeof r.repo !== "string")
|
|
64
|
+
return [];
|
|
65
|
+
const word = r.verdict ?? r.state ?? r.reason;
|
|
66
|
+
const state = word === "unsupported_host" ? "unsupported_host" : "unprotected";
|
|
67
|
+
return [{ repo: r.repo, state }];
|
|
68
|
+
});
|
|
69
|
+
/**
|
|
70
|
+
* The api's own codes for a route it does not serve (mnemom-api http-errors.ts STATUS_CODE_MAP: the
|
|
71
|
+
* router's fallback `errorResponse("Not found", 404)` carries not_found, a wrong method 405
|
|
72
|
+
* method_not_allowed). Only these, or no code at all, mean "no preflight here"; any other code on a
|
|
73
|
+
* 404/405 (agent_not_found, ...) is the api refusing, shown as such.
|
|
74
|
+
*/
|
|
75
|
+
const ROUTE_ABSENT_CODE = { 404: "not_found", 405: "method_not_allowed" };
|
|
76
|
+
/** Read a preflight answer into the verdict the doctor prints. */
|
|
77
|
+
export function preflightVerdict(a) {
|
|
78
|
+
const code = errorCodeOf(a.body);
|
|
79
|
+
if (a.status === 200) {
|
|
80
|
+
const b = rec(a.body);
|
|
81
|
+
const list = b.repos ?? b.protection;
|
|
82
|
+
const repos = Array.isArray(list) ? reposOf(list) : refusedReposOf(b.refusals);
|
|
83
|
+
return { kind: "verdict", mfa: mfaOf(b.mfa), repos };
|
|
84
|
+
}
|
|
85
|
+
const absentCode = ROUTE_ABSENT_CODE[a.status];
|
|
86
|
+
if (absentCode !== undefined && (code === null || code === absentCode))
|
|
87
|
+
return { kind: "absent" };
|
|
88
|
+
if (code === CODE.durable_lock_mfa_required)
|
|
89
|
+
return { kind: "verdict", mfa: "missing", repos: [] };
|
|
90
|
+
if (code === CODE.durable_lock_branch_unprotected)
|
|
91
|
+
return { kind: "verdict", mfa: "ok", repos: refusedReposOf(errorDetailsOf(a.body).refusals) };
|
|
92
|
+
if (code === CODE.durable_precondition_unavailable)
|
|
93
|
+
return { kind: "unavailable", detail: code };
|
|
94
|
+
return { kind: "earlier", code: code ?? `http_${a.status}` };
|
|
95
|
+
}
|
|
96
|
+
function verdictLines(name, v) {
|
|
97
|
+
const p = `durable lock (${name})`;
|
|
98
|
+
switch (v.kind) {
|
|
99
|
+
case "absent":
|
|
100
|
+
return [
|
|
101
|
+
{
|
|
102
|
+
mark: "info",
|
|
103
|
+
text: `${p}: this server has no lock preflight; \`mnemom agent lock ${name}\` reports MFA and branch protection`,
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
case "unavailable":
|
|
107
|
+
return [
|
|
108
|
+
{ mark: "info", text: `${p}: could not check MFA and branch protection (${v.detail})` },
|
|
109
|
+
];
|
|
110
|
+
case "earlier":
|
|
111
|
+
return [
|
|
112
|
+
{
|
|
113
|
+
mark: "info",
|
|
114
|
+
text: `${p}: lock refuses first with ${v.code}, before MFA and branch protection`,
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
case "verdict": {
|
|
118
|
+
const lines = [];
|
|
119
|
+
if (v.mfa === "ok")
|
|
120
|
+
lines.push({ mark: "ok", text: `${p}: MFA ok` });
|
|
121
|
+
else if (v.mfa === "missing")
|
|
122
|
+
lines.push({
|
|
123
|
+
mark: "warn",
|
|
124
|
+
text: `${p}: MFA missing (lock refuses: durable_lock_mfa_required)`,
|
|
125
|
+
hint: fixLine("lock_mfa_required", "off", true, { name }),
|
|
126
|
+
});
|
|
127
|
+
else
|
|
128
|
+
lines.push({ mark: "info", text: `${p}: MFA could not be determined` });
|
|
129
|
+
for (const r of v.repos) {
|
|
130
|
+
const fix = fixLine("lock_branch_unprotected", "off", true, { name, repo: r.repo });
|
|
131
|
+
if (r.state === "protected")
|
|
132
|
+
lines.push({ mark: "ok", text: `${p}: ${r.repo} protected` });
|
|
133
|
+
else if (r.state === "unprotected")
|
|
134
|
+
lines.push({ mark: "warn", text: `${p}: ${r.repo} unprotected`, hint: fix });
|
|
135
|
+
else if (r.state === "unsupported_host")
|
|
136
|
+
lines.push({
|
|
137
|
+
mark: "warn",
|
|
138
|
+
text: `${p}: ${r.repo} not on github.com, so it cannot be protected`,
|
|
139
|
+
hint: fix,
|
|
140
|
+
});
|
|
141
|
+
else
|
|
142
|
+
lines.push({ mark: "info", text: `${p}: ${r.repo} protection could not be determined` });
|
|
143
|
+
}
|
|
144
|
+
return lines;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const detailOf = (e) => (e instanceof Error ? e.message : String(e));
|
|
149
|
+
/** The doctor's durable lines: one block per open session (newest first as listed), capped. */
|
|
150
|
+
export async function durableDoctorLines(api) {
|
|
151
|
+
let rows;
|
|
152
|
+
try {
|
|
153
|
+
rows = await api.listSessions({});
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
return [{ mark: "info", text: `durable lock: could not read your sessions (${detailOf(e)})` }];
|
|
157
|
+
}
|
|
158
|
+
const open = rows.filter((r) => !isEnded(r));
|
|
159
|
+
if (open.length === 0)
|
|
160
|
+
return [
|
|
161
|
+
{
|
|
162
|
+
mark: "info",
|
|
163
|
+
text: "durable lock: no open session to check",
|
|
164
|
+
hint: "`mnemom agent lock <name>` checks MFA and branch protection for that session",
|
|
165
|
+
},
|
|
166
|
+
];
|
|
167
|
+
const lines = [];
|
|
168
|
+
for (const r of open.slice(0, DOCTOR_SESSION_CAP)) {
|
|
169
|
+
const name = r.name ?? r.conversation_id.slice(0, 8);
|
|
170
|
+
let v;
|
|
171
|
+
try {
|
|
172
|
+
v = preflightVerdict(await api.lockPreflight(r.conversation_id, r.agent_id));
|
|
173
|
+
}
|
|
174
|
+
catch (e) {
|
|
175
|
+
v = { kind: "unavailable", detail: detailOf(e) };
|
|
176
|
+
}
|
|
177
|
+
lines.push(...verdictLines(name, v));
|
|
178
|
+
}
|
|
179
|
+
const rest = open.length - DOCTOR_SESSION_CAP;
|
|
180
|
+
if (rest > 0)
|
|
181
|
+
lines.push({
|
|
182
|
+
mark: "info",
|
|
183
|
+
text: `durable lock: ${rest} more open sessions not checked; \`mnemom agent lock <name>\` checks each`,
|
|
184
|
+
});
|
|
185
|
+
return lines;
|
|
186
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `mnemom agent lock <name> [--own-agent] [--json]` (BACKBONE C1, design PR 6; C02-R01, R05, R17,
|
|
3
|
+
* R20, R26, R27; S1 lock.ts, spec.ts, fixlines.ts).
|
|
4
|
+
*
|
|
5
|
+
* The idle-source lock (no live tracked claude): resolve the name through the registry, apply the
|
|
6
|
+
* R27 cwd rule to the session's registered cwd BEFORE any lock request (a failing cwd sends none),
|
|
7
|
+
* read this machine's executor id, build the launch spec, POST ROUTES.own_lock, and print the one
|
|
8
|
+
* quiet lock line plus the S1 laptop risk sentence. The API runs the S1 LOCK_STEPS in order; the CLI
|
|
9
|
+
* only renders its answer.
|
|
10
|
+
*
|
|
11
|
+
* Boundaries (cross-piece, see notes-c1.md):
|
|
12
|
+
* - The spec comes from C4's `buildDurableLaunchSpec` by the builder input table (`lockSpecOf`):
|
|
13
|
+
* a live original is read exactly (C14-R13), else the launcher's resolved continue flags; what
|
|
14
|
+
* it dropped is named on the lock line (C14-R12).
|
|
15
|
+
* - The executor id is read from S1 EXECUTOR_STATE_FILE (C2 writes it); only `executor_id` is kept.
|
|
16
|
+
* - A live tracked claude for the session on this machine (the verb from another terminal, or
|
|
17
|
+
* `/mnemom lock` inside the session) takes the C02-R26 handover order (lib/durable/handover.ts)
|
|
18
|
+
* around the commit when `handover` is injected; the launcher's own lock (idle source) injects
|
|
19
|
+
* none and sends zero signals. The verb takes no passthrough.
|
|
20
|
+
*/
|
|
21
|
+
import { type LauncherKey } from "../vendor/shared/durable-contract/fixlines.js";
|
|
22
|
+
import { type LaunchSpec } from "../vendor/shared/durable-contract/spec.js";
|
|
23
|
+
import type { CwdKind } from "../vendor/shared/durable-contract/codes.js";
|
|
24
|
+
import { type RegistryRow } from "../lib/durable/api.js";
|
|
25
|
+
import { type HandoverDeps } from "../lib/durable/handover.js";
|
|
26
|
+
import { DurableRefusal } from "../lib/durable/refusal.js";
|
|
27
|
+
import { type DurableVerbDeps } from "./durable-verbs.js";
|
|
28
|
+
import { type BuiltLaunchSpec } from "../durable/launch-spec.js";
|
|
29
|
+
export type CwdRuleResult = {
|
|
30
|
+
ok: true;
|
|
31
|
+
root: string;
|
|
32
|
+
} | {
|
|
33
|
+
ok: false;
|
|
34
|
+
kind: CwdKind;
|
|
35
|
+
params: Record<string, string>;
|
|
36
|
+
};
|
|
37
|
+
export interface CwdProbe {
|
|
38
|
+
/** realpath(dir), or null when it does not exist. */
|
|
39
|
+
realpath: (dir: string) => string | null;
|
|
40
|
+
/** `git rev-parse --show-toplevel` in dir (hooks and fsmonitor off, GIT_* cleared), or null. */
|
|
41
|
+
toplevel: (dir: string) => string | null;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* C02-R27: the registered root is realpath(D) when git's toplevel equals it. not_git knows no
|
|
45
|
+
* checkout, so it carries no <checkout> and prints no fix line (C11-R27, as the C4 rule).
|
|
46
|
+
*/
|
|
47
|
+
export declare function cwdRule(dir: string, probe: CwdProbe): CwdRuleResult;
|
|
48
|
+
/** The live probe: node realpath and git with hooks/fsmonitor off and every GIT_* var cleared. */
|
|
49
|
+
export declare const liveCwdProbe: CwdProbe;
|
|
50
|
+
/** Claude Code's project directory name for a cwd: every non-alphanumeric character becomes '-'. */
|
|
51
|
+
export declare const claudeSlug: (cwd: string) => string;
|
|
52
|
+
/**
|
|
53
|
+
* The Claude Code project dirs holding `<ccSessionId>.jsonl`: empty when there are none (no
|
|
54
|
+
* projects dir at all included), null when the projects dir exists but cannot be read, so the
|
|
55
|
+
* check cannot be made.
|
|
56
|
+
*/
|
|
57
|
+
export type TranscriptDirs = (ccSessionId: string) => string[] | null;
|
|
58
|
+
/** The live lookup under `${CLAUDE_CONFIG_DIR:-~/.claude}/projects`. */
|
|
59
|
+
export declare function liveTranscriptDirs(env: NodeJS.ProcessEnv): TranscriptDirs;
|
|
60
|
+
/**
|
|
61
|
+
* BACKBONE C1 lock-time cwd check: the realpath `root` is committed only if the session's transcript
|
|
62
|
+
* (found by cc_session_id) sits under the project dir slugged from it; a transcript found only under
|
|
63
|
+
* another slug (the symlink path's) is symlink_transcript. No transcript found: nothing to check.
|
|
64
|
+
*/
|
|
65
|
+
export declare function transcriptSlugRule(root: string, ccSessionId: string | null, dirs: TranscriptDirs | undefined): CwdRuleResult | {
|
|
66
|
+
ok: false;
|
|
67
|
+
kind: "transcript_unverifiable";
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Claude Code's folder trust as the shipped claude (2.1.285) reads it: projects[<key>].hasTrustDialogAccepted
|
|
71
|
+
* in `${CLAUDE_CONFIG_DIR}/.claude.json` (else `~/.claude.json`). A seat has nobody to accept the trust
|
|
72
|
+
* dialog, so a root without it would park the seat at "Quick safety check". Read-only: never written.
|
|
73
|
+
*/
|
|
74
|
+
export interface TrustProbe {
|
|
75
|
+
/** The `projects` map of Claude Code's global config, or null when the file is missing or unreadable. */
|
|
76
|
+
projects: () => Record<string, unknown> | null;
|
|
77
|
+
/** realpath of the main checkout's toplevel for a dir in a git work tree (a linked worktree's main), or null. */
|
|
78
|
+
mainRoot: (dir: string) => string | null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* claude's order for a root (the cwd rule already made it a realpath'd toplevel): the canonical key
|
|
82
|
+
* (the main checkout for a linked worktree) first, then the bounded walk, which at a toplevel is the
|
|
83
|
+
* root itself. A parent above the root never counts; the keys are realpaths, never symlink paths.
|
|
84
|
+
*/
|
|
85
|
+
export declare function rootTrusted(root: string, probe: TrustProbe): boolean;
|
|
86
|
+
/** A path as one POSIX shell word: bare when it is plainly safe, else single-quoted. */
|
|
87
|
+
export declare const shellWord: (p: string) => string;
|
|
88
|
+
/** The live probe: the config file Claude Code reads, and git with hooks/fsmonitor off and GIT_* cleared. */
|
|
89
|
+
export declare function liveTrustProbe(env: NodeJS.ProcessEnv): TrustProbe;
|
|
90
|
+
/** This machine's executor id from S1 EXECUTOR_STATE_FILE, or null (not enrolled here). */
|
|
91
|
+
export declare function readLocalExecutorId(home: string, read?: (p: string) => string): string | null;
|
|
92
|
+
export interface IdleSpecInput {
|
|
93
|
+
root: string;
|
|
94
|
+
trimMcp?: boolean;
|
|
95
|
+
model?: string;
|
|
96
|
+
effort?: string;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The idle-source spec in the S1 closed schema; fields the schema refuses are dropped, not sent.
|
|
100
|
+
* Null when even the required core does not parse (e.g. a root the schema refuses).
|
|
101
|
+
*/
|
|
102
|
+
export declare function idleLaunchSpec(i: IdleSpecInput): LaunchSpec | null;
|
|
103
|
+
/** The model/effort one launch recorded in the local ledger (agent-ledger SessionRecord). */
|
|
104
|
+
export interface LaunchModelRecord {
|
|
105
|
+
conversation_id: string;
|
|
106
|
+
model: string | null;
|
|
107
|
+
effort: string | null;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The lock spec's model/effort: the locked conversation's own launch (its newest ledger record)
|
|
111
|
+
* wins over the launcher defaults, so a session launched with `--model` keeps that model in its
|
|
112
|
+
* seat instead of resuming on launcher.toml's (or Claude Code's default) model.
|
|
113
|
+
*/
|
|
114
|
+
export declare function lockSpecDefaults(conversationId: string, records: readonly LaunchModelRecord[], launcher: {
|
|
115
|
+
trimMcp?: boolean;
|
|
116
|
+
model?: string;
|
|
117
|
+
effort?: string;
|
|
118
|
+
}): {
|
|
119
|
+
trimMcp?: boolean;
|
|
120
|
+
model?: string;
|
|
121
|
+
effort?: string;
|
|
122
|
+
};
|
|
123
|
+
export interface LockFacts {
|
|
124
|
+
name: string;
|
|
125
|
+
rules: string | null;
|
|
126
|
+
budgetUsd: number | null;
|
|
127
|
+
agent: string;
|
|
128
|
+
/** --own-agent: the lock moved the session onto a new agent, named by `agent` (C02-R10). */
|
|
129
|
+
ownAgent: boolean;
|
|
130
|
+
durableOnly: boolean;
|
|
131
|
+
sharedWith: number;
|
|
132
|
+
droppedFlags: readonly string[];
|
|
133
|
+
}
|
|
134
|
+
export declare function lockLine(f: LockFacts): string;
|
|
135
|
+
/** Lock facts from the lock answer, falling back to the row (A2 owns the body; read defensively). */
|
|
136
|
+
export declare function lockFactsOf(body: unknown, row: RegistryRow, name: string, built?: readonly string[]): LockFacts;
|
|
137
|
+
export interface LockDeps extends DurableVerbDeps {
|
|
138
|
+
api: DurableVerbDeps["api"] & {
|
|
139
|
+
lock(cid: string, agentId: string, body: unknown): Promise<{
|
|
140
|
+
status: number;
|
|
141
|
+
body: unknown;
|
|
142
|
+
}>;
|
|
143
|
+
};
|
|
144
|
+
home: string;
|
|
145
|
+
cwd: () => string;
|
|
146
|
+
probe: CwdProbe;
|
|
147
|
+
readExecutorId: (home: string) => string | null;
|
|
148
|
+
/**
|
|
149
|
+
* The org this machine's executor is enrolled in, or null when unknown. Read only after a
|
|
150
|
+
* durable_no_executor answer, to say when the session's org differs; absent: not read.
|
|
151
|
+
*/
|
|
152
|
+
executorOrg?: (executorId: string) => Promise<string | null>;
|
|
153
|
+
/**
|
|
154
|
+
* The spec defaults for the row being locked: the launch's own model/effort (its ledger record),
|
|
155
|
+
* else the launcher's resolved defaults (launcher.toml model/effort/trim_mcp).
|
|
156
|
+
*/
|
|
157
|
+
specDefaults: (row: RegistryRow) => {
|
|
158
|
+
trimMcp?: boolean;
|
|
159
|
+
model?: string;
|
|
160
|
+
effort?: string;
|
|
161
|
+
};
|
|
162
|
+
/** Where Claude Code holds a session's transcript (the symlink_transcript check); absent: none. */
|
|
163
|
+
transcriptDirs?: TranscriptDirs;
|
|
164
|
+
/** The running-session handover (C02-R26); absent on the launcher's idle-source lock. */
|
|
165
|
+
handover?: HandoverDeps;
|
|
166
|
+
/** Claude Code's folder trust for the root (cwd_untrusted); absent: not checked. */
|
|
167
|
+
trust?: TrustProbe;
|
|
168
|
+
/** The live original's exact argv (KERN_PROCARGS2, C14-R13), or null when unreadable. */
|
|
169
|
+
readOriginal?: (pid: number) => OriginalLaunch | null;
|
|
170
|
+
/**
|
|
171
|
+
* Idle source: would the launcher's continue have been Remote Control (an explicit RC shape, or
|
|
172
|
+
* the auto-upgrade: entitled and capable)? Evaluated only to name `--remote-control` in
|
|
173
|
+
* `flags dropped:` (BACKBONE builder input table); never committed.
|
|
174
|
+
*/
|
|
175
|
+
rcNamed?: () => Promise<boolean>;
|
|
176
|
+
/** A durable launch of this name has started and not yet registered (launching.ts); absent: none. */
|
|
177
|
+
launching?: (name: string) => boolean;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Would the launcher's continue have been Remote Control? An explicit shape counts only when it is
|
|
181
|
+
* RC on an entitled account (a non-entitled one is degraded to terminal by the launcher, nothing
|
|
182
|
+
* is dropped); an unpinned one only by the auto-upgrade (entitled and capable). The capability
|
|
183
|
+
* probe runs only when it decides.
|
|
184
|
+
*/
|
|
185
|
+
export declare function rcWouldBeNamed(i: {
|
|
186
|
+
shape: string;
|
|
187
|
+
shapeExplicit: boolean;
|
|
188
|
+
rcAllowed: boolean;
|
|
189
|
+
rcCapable: () => boolean;
|
|
190
|
+
}): boolean;
|
|
191
|
+
/** What the running source reads of the live original claude (C14-R13). */
|
|
192
|
+
export interface OriginalLaunch {
|
|
193
|
+
/** argv[0..] exactly as exec'd. */
|
|
194
|
+
argv: readonly string[];
|
|
195
|
+
/** The parent launcher's tokens after its `--`, or null (see discover.ts launcherPassthrough). */
|
|
196
|
+
passthroughHint: readonly string[] | null;
|
|
197
|
+
/** RC shape (the conversation id came from the RC proxy variable). */
|
|
198
|
+
remoteControl: boolean;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* The spec by the builder input table (BACKBONE C4, final F01): a live tracked claude for the
|
|
202
|
+
* session on this machine is the running source, read exactly; otherwise the idle source, the
|
|
203
|
+
* launcher's resolved continue flags. `unreadable` when the original is live but its argv cannot be
|
|
204
|
+
* read (re-resolving the launcher config for a running session is C02-M53); null when the spec does
|
|
205
|
+
* not fit the closed schema.
|
|
206
|
+
*/
|
|
207
|
+
export declare function lockSpecOf(row: RegistryRow, root: string, deps: Pick<LockDeps, "handover" | "readOriginal" | "rcNamed" | "specDefaults">): Promise<BuiltLaunchSpec | "unreadable" | null>;
|
|
208
|
+
/** The refusal for a session that has not run its first turn, registered or not yet. */
|
|
209
|
+
export declare function firstTurnPending(name: string): DurableRefusal;
|
|
210
|
+
/**
|
|
211
|
+
* A lock refusal's fix line from the S1 lock catalogue, its params (<repo>, <toplevel>, <checkout>,
|
|
212
|
+
* <realpath>) from the API details. A param the answer does not carry leaves no fix line: an
|
|
213
|
+
* unfilled placeholder is never printed.
|
|
214
|
+
*/
|
|
215
|
+
export declare function lockRefusalFix(code: string, details: Record<string, unknown>, name: string, launcherKey: LauncherKey, nameHeld: boolean): string | null;
|
|
216
|
+
/** The answer of one lock attempt on a resolved row: the API body, or the refusal to print. */
|
|
217
|
+
export type LockOutcome = {
|
|
218
|
+
ok: true;
|
|
219
|
+
body: unknown;
|
|
220
|
+
changed: boolean;
|
|
221
|
+
dropped: string[];
|
|
222
|
+
} | {
|
|
223
|
+
ok: false;
|
|
224
|
+
refusal: DurableRefusal;
|
|
225
|
+
};
|
|
226
|
+
/**
|
|
227
|
+
* Lock one resolved row (the idle source): the R27 cwd rule, this machine's executor id, the spec,
|
|
228
|
+
* POST own_lock. Never prints; the lock verb and the launcher (C02-R23 c, e) render the outcome.
|
|
229
|
+
*/
|
|
230
|
+
export declare function lockResolvedRow(row: RegistryRow, name: string, opts: {
|
|
231
|
+
ownAgent?: boolean;
|
|
232
|
+
}, deps: LockDeps): Promise<LockOutcome>;
|
|
233
|
+
/**
|
|
234
|
+
* durable_no_executor because this machine's executor is enrolled in another org than the
|
|
235
|
+
* session's: say so, with no fix line (installing a host here would not change the org). Null when
|
|
236
|
+
* the orgs match or either is unknown; the refusal then renders as the API answered.
|
|
237
|
+
*/
|
|
238
|
+
export declare function orgMismatchRefusal(row: RegistryRow, name: string, executorId: string, deps: Pick<LockDeps, "executorOrg">, apiBody: unknown): Promise<DurableRefusal | null>;
|
|
239
|
+
/**
|
|
240
|
+
* 422 durable_unsupported {feature: launch_spec_change} on a re-lock (BACKBONE l.57, final F09): the
|
|
241
|
+
* S1 `launch_spec_change` entry with <flags> filled from the spec this lock built, so the printed
|
|
242
|
+
* line is a command that works (C11-R27). The locked row holds the name: the done clause applies.
|
|
243
|
+
*/
|
|
244
|
+
export declare function specChangeRefusal(name: string, built: BuiltLaunchSpec, launcherKey: LauncherKey, apiBody: unknown): DurableRefusal;
|
|
245
|
+
/** Print a successful lock: the one quiet line and the S1 laptop risk sentence (C02-R17). */
|
|
246
|
+
export declare function printLocked(o: {
|
|
247
|
+
body: unknown;
|
|
248
|
+
changed: boolean;
|
|
249
|
+
dropped?: readonly string[];
|
|
250
|
+
}, row: RegistryRow, name: string, out: (l: string) => void): void;
|
|
251
|
+
export declare function lockCommand(name: string, opts: {
|
|
252
|
+
ownAgent?: boolean;
|
|
253
|
+
json?: boolean;
|
|
254
|
+
}, deps: LockDeps): Promise<number>;
|