@meetless/mla 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +81 -0
- package/dist/build-info.json +9 -0
- package/dist/bundles/ask-core.js +396 -0
- package/dist/bundles/mcp.js +16592 -0
- package/dist/bundles/trace-core.js +263 -0
- package/dist/cli.js +828 -0
- package/dist/commands/activate.js +781 -0
- package/dist/commands/adoption.js +130 -0
- package/dist/commands/ask.js +290 -0
- package/dist/commands/context.js +114 -0
- package/dist/commands/debug.js +313 -0
- package/dist/commands/doctor.js +1021 -0
- package/dist/commands/enrich.js +427 -0
- package/dist/commands/evidence.js +229 -0
- package/dist/commands/flush.js +184 -0
- package/dist/commands/graph.js +104 -0
- package/dist/commands/init.js +272 -0
- package/dist/commands/internal-active-review.js +322 -0
- package/dist/commands/internal-auto-index.js +188 -0
- package/dist/commands/internal-capture-decisions.js +320 -0
- package/dist/commands/internal-evidence-correlate.js +239 -0
- package/dist/commands/internal-evidence-hooks.js +240 -0
- package/dist/commands/internal-evidence-inject.js +231 -0
- package/dist/commands/internal-finalize.js +221 -0
- package/dist/commands/internal-pretool-observe.js +225 -0
- package/dist/commands/internal-refresh.js +136 -0
- package/dist/commands/internal-session-nudge.js +120 -0
- package/dist/commands/internal-steer-sync.js +117 -0
- package/dist/commands/internal-turn-recap.js +140 -0
- package/dist/commands/kb.js +375 -0
- package/dist/commands/kb_add.js +681 -0
- package/dist/commands/kb_forget.js +283 -0
- package/dist/commands/kb_move.js +45 -0
- package/dist/commands/kb_pending.js +410 -0
- package/dist/commands/kb_personal.js +149 -0
- package/dist/commands/kb_promote.js +188 -0
- package/dist/commands/kb_purge.js +168 -0
- package/dist/commands/kb_reingest.js +335 -0
- package/dist/commands/kb_retime.js +170 -0
- package/dist/commands/kb_review.js +391 -0
- package/dist/commands/kb_revision.js +179 -0
- package/dist/commands/kb_show.js +385 -0
- package/dist/commands/label.js +226 -0
- package/dist/commands/login.js +295 -0
- package/dist/commands/logout.js +108 -0
- package/dist/commands/mcp-supervisor.js +93 -0
- package/dist/commands/mcp.js +227 -0
- package/dist/commands/queue-prune.js +98 -0
- package/dist/commands/review.js +358 -0
- package/dist/commands/rewire.js +124 -0
- package/dist/commands/rules.js +728 -0
- package/dist/commands/scan-context.js +67 -0
- package/dist/commands/session.js +347 -0
- package/dist/commands/stats.js +479 -0
- package/dist/commands/status.js +61 -0
- package/dist/commands/summary.js +250 -0
- package/dist/commands/turn.js +114 -0
- package/dist/commands/uninstall.js +222 -0
- package/dist/commands/whoami.js +102 -0
- package/dist/commands/workspace.js +130 -0
- package/dist/hooks-template/ce0-post-tool-use.sh +34 -0
- package/dist/hooks-template/ce0-session-start.sh +49 -0
- package/dist/hooks-template/ce0-stop.sh +29 -0
- package/dist/hooks-template/ce0-user-prompt-submit.sh +38 -0
- package/dist/hooks-template/common.sh +934 -0
- package/dist/hooks-template/event-batch-filter.jq +67 -0
- package/dist/hooks-template/flush.sh +503 -0
- package/dist/hooks-template/post-tool-use.sh +423 -0
- package/dist/hooks-template/pre-tool-use.sh +69 -0
- package/dist/hooks-template/session-start.sh +140 -0
- package/dist/hooks-template/stop.sh +308 -0
- package/dist/hooks-template/user-prompt-submit.sh +1162 -0
- package/dist/lib/activation.js +79 -0
- package/dist/lib/active-conflict-cache.js +141 -0
- package/dist/lib/active-memory.js +59 -0
- package/dist/lib/active-review-runner.js +26 -0
- package/dist/lib/agent-decision/index.js +25 -0
- package/dist/lib/agent-decision/keys.js +49 -0
- package/dist/lib/agent-decision/normalize-claude.js +183 -0
- package/dist/lib/agent-decision/types.js +21 -0
- package/dist/lib/agent-decision/validate.js +216 -0
- package/dist/lib/analytics/capture.js +96 -0
- package/dist/lib/analytics/command-event.js +267 -0
- package/dist/lib/analytics/consent.js +58 -0
- package/dist/lib/analytics/coverage-gap.js +96 -0
- package/dist/lib/analytics/envelope.js +236 -0
- package/dist/lib/analytics/event-id.js +86 -0
- package/dist/lib/analytics/evidence.js +150 -0
- package/dist/lib/analytics/followthrough.js +194 -0
- package/dist/lib/analytics/forwarder.js +109 -0
- package/dist/lib/analytics/logs.js +78 -0
- package/dist/lib/analytics/metrics.js +78 -0
- package/dist/lib/analytics/recorder.js +92 -0
- package/dist/lib/analytics/review-analytics.js +75 -0
- package/dist/lib/analytics/sequence.js +77 -0
- package/dist/lib/analytics/store.js +131 -0
- package/dist/lib/analytics/turn-recap.js +279 -0
- package/dist/lib/artifact_id.js +108 -0
- package/dist/lib/auth-breaker.js +161 -0
- package/dist/lib/auto-index.js +112 -0
- package/dist/lib/classifier.js +88 -0
- package/dist/lib/config.js +298 -0
- package/dist/lib/conflict-advisory.js +64 -0
- package/dist/lib/debug-bundle.js +520 -0
- package/dist/lib/enrichment/ingest.js +301 -0
- package/dist/lib/enrichment/plan.js +253 -0
- package/dist/lib/enrichment/protocol.js +359 -0
- package/dist/lib/enrichment/scout-brief.js +176 -0
- package/dist/lib/failure-telemetry.js +444 -0
- package/dist/lib/git.js +200 -0
- package/dist/lib/governance-cache.js +77 -0
- package/dist/lib/governed-path-cache.js +76 -0
- package/dist/lib/http.js +677 -0
- package/dist/lib/identity-envelope.js +23 -0
- package/dist/lib/kb-candidate.js +65 -0
- package/dist/lib/kb_acl.js +98 -0
- package/dist/lib/login.js +353 -0
- package/dist/lib/mcp-fetchers.js +130 -0
- package/dist/lib/mcp-restart.js +47 -0
- package/dist/lib/observability.js +805 -0
- package/dist/lib/open-url.js +33 -0
- package/dist/lib/orphan-guard.js +70 -0
- package/dist/lib/packaged.js +21 -0
- package/dist/lib/reconcile-sessions.js +171 -0
- package/dist/lib/redactor.js +89 -0
- package/dist/lib/relationship-candidate-query.js +27 -0
- package/dist/lib/render.js +611 -0
- package/dist/lib/rules/applicability.js +64 -0
- package/dist/lib/rules/attest-code-rule-version.js +47 -0
- package/dist/lib/rules/attest-notes-location.js +217 -0
- package/dist/lib/rules/attest-rule-version.js +69 -0
- package/dist/lib/rules/canonical-json.js +97 -0
- package/dist/lib/rules/ce0-emit.js +64 -0
- package/dist/lib/rules/ce0-evidence.js +281 -0
- package/dist/lib/rules/ce0-recall-sample.js +82 -0
- package/dist/lib/rules/ce0-rule.js +55 -0
- package/dist/lib/rules/ce0-sampling-bucket.js +15 -0
- package/dist/lib/rules/ce0-store.js +683 -0
- package/dist/lib/rules/ce0-telemetry-project.js +93 -0
- package/dist/lib/rules/ce0-telemetry.js +158 -0
- package/dist/lib/rules/code-rule-registry.js +17 -0
- package/dist/lib/rules/command-match.js +185 -0
- package/dist/lib/rules/consult-evidence-binding.js +27 -0
- package/dist/lib/rules/consultation-capture-adapter.js +193 -0
- package/dist/lib/rules/content-match.js +56 -0
- package/dist/lib/rules/deny-admission.js +99 -0
- package/dist/lib/rules/durable-observation.js +190 -0
- package/dist/lib/rules/enforce-notes-version.js +421 -0
- package/dist/lib/rules/evaluation-input-hash.js +126 -0
- package/dist/lib/rules/evaluator.js +108 -0
- package/dist/lib/rules/inert-rule-families.js +51 -0
- package/dist/lib/rules/input-authority-resolver.js +241 -0
- package/dist/lib/rules/interception-schema.js +170 -0
- package/dist/lib/rules/interception-store.js +267 -0
- package/dist/lib/rules/live-input-authority.js +66 -0
- package/dist/lib/rules/local-matcher.js +108 -0
- package/dist/lib/rules/local-observe.js +79 -0
- package/dist/lib/rules/local-rule-version-repo.js +214 -0
- package/dist/lib/rules/memory-requirement.js +109 -0
- package/dist/lib/rules/notes-observe.js +39 -0
- package/dist/lib/rules/notes-path.js +261 -0
- package/dist/lib/rules/notes-rule.js +75 -0
- package/dist/lib/rules/observe-adapter.js +114 -0
- package/dist/lib/rules/observed-rule-hash.js +119 -0
- package/dist/lib/rules/prompt-submit-adapter.js +132 -0
- package/dist/lib/rules/requirement-subject.js +240 -0
- package/dist/lib/rules/rule-activity.js +67 -0
- package/dist/lib/rules/rule-version-hash.js +151 -0
- package/dist/lib/rules/runtime-scope.js +55 -0
- package/dist/lib/rules/stop-adapter.js +116 -0
- package/dist/lib/rules/stop-response-snapshot.js +174 -0
- package/dist/lib/rules/types.js +10 -0
- package/dist/lib/rules/ulid.js +46 -0
- package/dist/lib/rules/version-evaluation.js +156 -0
- package/dist/lib/scanner/agent-memory.js +99 -0
- package/dist/lib/scanner/bootstrap-summary.js +87 -0
- package/dist/lib/scanner/cache.js +59 -0
- package/dist/lib/scanner/frontmatter.js +42 -0
- package/dist/lib/scanner/parse-directives.js +69 -0
- package/dist/lib/scanner/parse-structured.js +72 -0
- package/dist/lib/scanner/render.js +73 -0
- package/dist/lib/scanner/scan.js +132 -0
- package/dist/lib/scanner/score.js +38 -0
- package/dist/lib/scanner/scout-mission.js +126 -0
- package/dist/lib/scanner/types.js +7 -0
- package/dist/lib/session-scope.js +195 -0
- package/dist/lib/spool.js +355 -0
- package/dist/lib/staleness.js +100 -0
- package/dist/lib/steer-cache.js +87 -0
- package/dist/lib/tagged-reference.js +20 -0
- package/dist/lib/temporal.js +109 -0
- package/dist/lib/turn-recap-emit.js +67 -0
- package/dist/lib/unwire.js +253 -0
- package/dist/lib/update-check.js +469 -0
- package/dist/lib/update-notifier.js +217 -0
- package/dist/lib/upgrade-apply.js +643 -0
- package/dist/lib/wire.js +1087 -0
- package/dist/lib/workspace.js +96 -0
- package/dist/lib/zip.js +154 -0
- package/dist/pretool-entry.js +37 -0
- package/package.json +75 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AUTH_BREAKER_PATH = void 0;
|
|
37
|
+
exports.fingerprintToken = fingerprintToken;
|
|
38
|
+
exports.tripAuthBreaker = tripAuthBreaker;
|
|
39
|
+
exports.consultAuthBreaker = consultAuthBreaker;
|
|
40
|
+
exports.clearAuthBreaker = clearAuthBreaker;
|
|
41
|
+
const crypto = __importStar(require("crypto"));
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const config_1 = require("./config");
|
|
44
|
+
// Dead-auth circuit breaker (incident: a dead `mla login` self-DoSing control).
|
|
45
|
+
//
|
|
46
|
+
// THE PROBLEM. When a user-token session's refresh token is genuinely dead
|
|
47
|
+
// (expired ~30d idle, revoked, or rotated out from under a stale process), every
|
|
48
|
+
// hook-driven `mla` call (heartbeat, steer-sync, flush, _internal refresh) does
|
|
49
|
+
// the same dance: authenticated control call -> 401 -> refresh -> 401 -> surface
|
|
50
|
+
// "login expired". With the editor firing those hooks on every tool use across
|
|
51
|
+
// several long-lived `mla mcp` workers, that became a tight validate+refresh
|
|
52
|
+
// storm against control (measured ~6-8 req/sec on constant hashed keys), i.e. a
|
|
53
|
+
// self-inflicted DoS that no server-side rate limit can cure (the server can only
|
|
54
|
+
// cheapen each rejection, not stop the client from asking).
|
|
55
|
+
//
|
|
56
|
+
// THE CURE. The FIRST process to have its refresh token REJECTED writes a small
|
|
57
|
+
// sentinel here, keyed to a one-way fingerprint of that exact refresh token.
|
|
58
|
+
// Every later user-token control call consults it first (consultAuthBreaker) and
|
|
59
|
+
// fails fast WITHOUT touching control. The flood collapses from "forever" to a
|
|
60
|
+
// bounded burst (one validate+refresh per process until the shared sentinel lands,
|
|
61
|
+
// then silence).
|
|
62
|
+
//
|
|
63
|
+
// WHY FINGERPRINT-KEYED (self-healing). The sentinel records sha256(refreshToken)
|
|
64
|
+
// .slice(0,16), NOT a bare "auth is dead" flag. consult re-reads the ON-DISK
|
|
65
|
+
// config (not the caller's possibly-stale in-memory cfg) and only stays open while
|
|
66
|
+
// the on-disk refresh token still matches the fingerprint. The instant the token
|
|
67
|
+
// changes (an `mla login` writes a fresh pair) the fingerprint no longer matches,
|
|
68
|
+
// consult clears the sentinel and lets the call through. That is what lets a
|
|
69
|
+
// re-login heal the long-lived `mla mcp` workers LIVE: the worker bound its cfg
|
|
70
|
+
// object once at boot, but consult reads disk, sees the new token, and reopens the
|
|
71
|
+
// gate without a restart. Comparing the in-memory token instead would wedge the
|
|
72
|
+
// worker forever (it would never observe the re-login).
|
|
73
|
+
//
|
|
74
|
+
// WHY THIS LEAKS NOTHING. The fingerprint is a one-way sha256 slice; it cannot be
|
|
75
|
+
// reversed to a token, and the file lives right next to cli-config.json (which
|
|
76
|
+
// holds the actual tokens, mode 0600) anyway.
|
|
77
|
+
// Sibling of CFG_PATH (config.ts), so it shares the config's home and is wiped by
|
|
78
|
+
// the same `rm -rf ~/.meetless` an operator already uses to reset.
|
|
79
|
+
exports.AUTH_BREAKER_PATH = `${config_1.HOME}/auth-dead.json`;
|
|
80
|
+
// One-way, non-reversible. Same construction as control's hashToken prefix and
|
|
81
|
+
// http.ts's rate-limit keys, so the value is recognizable in logs without
|
|
82
|
+
// exposing token material.
|
|
83
|
+
function fingerprintToken(token) {
|
|
84
|
+
return crypto.createHash("sha256").update(token).digest("hex").slice(0, 16);
|
|
85
|
+
}
|
|
86
|
+
// Trip the breaker for a specific refresh token that control just REJECTED. Only
|
|
87
|
+
// http.ts's refreshUserToken calls this, and only on a true "unauthorized"
|
|
88
|
+
// (401/410) outcome, never on a transient/throttled one, so the gate can never
|
|
89
|
+
// close on a mere rate-limit burst. Best-effort: a write failure just means the
|
|
90
|
+
// next process re-attempts the dance and trips it then.
|
|
91
|
+
function tripAuthBreaker(refreshToken, reason) {
|
|
92
|
+
const sentinel = {
|
|
93
|
+
refreshFingerprint: fingerprintToken(refreshToken),
|
|
94
|
+
deadSince: new Date().toISOString(),
|
|
95
|
+
reason,
|
|
96
|
+
};
|
|
97
|
+
try {
|
|
98
|
+
// Write-then-rename so a concurrent reader never sees a torn file (5 workers
|
|
99
|
+
// + N hooks share this path).
|
|
100
|
+
const tmp = `${exports.AUTH_BREAKER_PATH}.tmp.${process.pid}`;
|
|
101
|
+
fs.writeFileSync(tmp, JSON.stringify(sentinel) + "\n", { mode: 0o600 });
|
|
102
|
+
fs.renameSync(tmp, exports.AUTH_BREAKER_PATH);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// Best-effort sentinel; never let a breaker write break the caller.
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Returns true iff the breaker is OPEN: a prior refresh was rejected AND the
|
|
109
|
+
// on-disk refresh token still matches that rejection's fingerprint. Fails OPEN
|
|
110
|
+
// (returns true) for nothing: every uncertain path fails CLOSED (returns false,
|
|
111
|
+
// let the call proceed) so a bug here can never wedge a healthy session. Clears a
|
|
112
|
+
// stale sentinel as a side effect when the on-disk credential has moved on.
|
|
113
|
+
function consultAuthBreaker() {
|
|
114
|
+
let raw;
|
|
115
|
+
try {
|
|
116
|
+
raw = fs.readFileSync(exports.AUTH_BREAKER_PATH, "utf8");
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return false; // No sentinel (the common, healthy case) -> proceed.
|
|
120
|
+
}
|
|
121
|
+
let sentinel;
|
|
122
|
+
try {
|
|
123
|
+
sentinel = JSON.parse(raw);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return false; // Torn/garbage read (concurrent writer) -> proceed, don't block.
|
|
127
|
+
}
|
|
128
|
+
if (!sentinel || typeof sentinel.refreshFingerprint !== "string") {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
// Compare against the ON-DISK token, not the caller's in-memory cfg, so a
|
|
132
|
+
// re-login that rotated the token on disk reopens the gate for live workers.
|
|
133
|
+
let cfg;
|
|
134
|
+
try {
|
|
135
|
+
cfg = (0, config_1.readConfig)();
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return false; // Config unreadable -> fail open, never block on our own read.
|
|
139
|
+
}
|
|
140
|
+
if (cfg.auth.mode !== "user-token") {
|
|
141
|
+
// Logged out / downgraded to shared-key: the dead user-token is irrelevant.
|
|
142
|
+
clearAuthBreaker();
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
if (fingerprintToken(cfg.auth.refreshToken) === sentinel.refreshFingerprint) {
|
|
146
|
+
return true; // Same dead token still on disk -> gate stays shut.
|
|
147
|
+
}
|
|
148
|
+
// The on-disk token changed (a re-login happened): the sentinel is stale.
|
|
149
|
+
clearAuthBreaker();
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
// Clear the breaker. Called on a successful login/refresh and on the stale-token
|
|
153
|
+
// path above. Idempotent and best-effort.
|
|
154
|
+
function clearAuthBreaker() {
|
|
155
|
+
try {
|
|
156
|
+
fs.unlinkSync(exports.AUTH_BREAKER_PATH);
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// Already gone (never tripped, or another process cleared it first).
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.selectIndexTargets = selectIndexTargets;
|
|
37
|
+
exports.buildKbAddArgv = buildKbAddArgv;
|
|
38
|
+
// tools/meetless-agent/src/lib/auto-index.ts
|
|
39
|
+
// Pure selection + argv construction for the Zone 2 auto-index loop. Given the
|
|
40
|
+
// reduced Active Review records for a session, pick the produced docs to index
|
|
41
|
+
// into the owner's Personal KB and build the `mla kb add` argv for each. No I/O;
|
|
42
|
+
// the command layer (internal-auto-index.ts) owns the store read, the on-disk
|
|
43
|
+
// existence check, and the (fail-soft) add invocation.
|
|
44
|
+
// See notes/20260605-mla-auto-index-loop-implementation-plan.md.
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const observability_1 = require("./observability");
|
|
47
|
+
// One target per (repoRootHash, canonicalPath). Only produced_doc records that
|
|
48
|
+
// carry a repoRoot are eligible: tagged_reference docs are user-named, not
|
|
49
|
+
// agent-produced, and a record without a repoRoot predates Phase A and cannot be
|
|
50
|
+
// resolved on disk. reduceActiveMemory yields records most-recent-last, so a later
|
|
51
|
+
// record overwrites an earlier one for the same key -> latest content wins.
|
|
52
|
+
function selectIndexTargets(records) {
|
|
53
|
+
const latest = new Map();
|
|
54
|
+
for (const r of records) {
|
|
55
|
+
if (r.kind !== "produced_doc")
|
|
56
|
+
continue;
|
|
57
|
+
if (!r.repoRoot || r.repoRoot.length === 0)
|
|
58
|
+
continue;
|
|
59
|
+
latest.set(`${r.repoRootHash}|${r.canonicalPath}`, r);
|
|
60
|
+
}
|
|
61
|
+
return Array.from(latest.values()).map((r) => ({
|
|
62
|
+
absPath: path.join(r.repoRoot, r.canonicalPath),
|
|
63
|
+
workspaceId: r.workspaceId,
|
|
64
|
+
canonicalPath: r.canonicalPath,
|
|
65
|
+
contentHash: r.contentHash,
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
// The Zone 2 personal-KB add contract: agent_distilled provenance (ADVISORY echo
|
|
69
|
+
// under the two-axis model; the server derives recorded trust from the capture
|
|
70
|
+
// path), workspace pinned from the record (NOT marker-resolved, since the detached
|
|
71
|
+
// run has cwd=$HOME), and --queue so the add returns after the revision commits
|
|
72
|
+
// without blocking on the async GRAPH_EXTRACT job.
|
|
73
|
+
//
|
|
74
|
+
// NO --posture: commit e7f20756 removed the --posture contract from `mla kb add`
|
|
75
|
+
// (every notes ingest is born reviewOutcome=PENDING; LIVE/SHADOW posture is dead).
|
|
76
|
+
// `mla kb add` now REJECTS --posture as an unknown flag, so emitting it here made
|
|
77
|
+
// every auto-index ingest fail ("Unknown flag: --posture") -- session files were
|
|
78
|
+
// recorded but never ingested or mined for relationships. Keep this argv in lockstep
|
|
79
|
+
// with kb_add.ts's VALUE_FLAGS/BOOLEAN_FLAGS.
|
|
80
|
+
//
|
|
81
|
+
// --reingest-if-active makes this an add-or-UPDATE. Without it, a doc the agent
|
|
82
|
+
// produced once is ACTIVE in the KB, and every later edit re-runs `kb add` over an
|
|
83
|
+
// ACTIVE identity, which the kb add route hard-refuses ("use mla kb reingest"). The
|
|
84
|
+
// loop swallows that exit-2 as a failure, so a re-edited doc silently never accrues
|
|
85
|
+
// a second revision. With the flag, a changed body reingests in place (new revision)
|
|
86
|
+
// and a frontmatter-only change patches; an unchanged doc still no-ops.
|
|
87
|
+
function buildKbAddArgv(t, sessionId) {
|
|
88
|
+
const argv = [
|
|
89
|
+
t.absPath,
|
|
90
|
+
"--mode",
|
|
91
|
+
"file",
|
|
92
|
+
"--provenance",
|
|
93
|
+
"agent_distilled",
|
|
94
|
+
"--workspace",
|
|
95
|
+
t.workspaceId,
|
|
96
|
+
"--queue",
|
|
97
|
+
"--reingest-if-active",
|
|
98
|
+
];
|
|
99
|
+
// Channel B (sync ingest): carry THIS session's raw Claude UUID through to the
|
|
100
|
+
// intel ingest route as `--agent-session <uuid>` (it rides the kb add HTTP body)
|
|
101
|
+
// so the workspace-authoritative sink composes the Langfuse session exactly once
|
|
102
|
+
// (INV-COMPOSE-ONCE). The value is canonicalized here (trim, uuid-shape, lowercase)
|
|
103
|
+
// but NEVER composed; an absent or malformed session simply omits the flag and the
|
|
104
|
+
// ingest still runs (intel falls back to its own grouping). No env var is read: the
|
|
105
|
+
// detached auto-index process has a bare environment, so the session arrives
|
|
106
|
+
// explicitly on its `--session` wire.
|
|
107
|
+
const agentSession = (0, observability_1.canonicalizeSessionId)(sessionId ?? null);
|
|
108
|
+
if (agentSession) {
|
|
109
|
+
argv.push("--agent-session", agentSession);
|
|
110
|
+
}
|
|
111
|
+
return argv;
|
|
112
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* File-path risk classifier.
|
|
4
|
+
*
|
|
5
|
+
* Maps a repository file path to a coarse risk category so an automated code
|
|
6
|
+
* reviewer can state deterministic pre-prompt facts ("this change touches
|
|
7
|
+
* auth", "this is a schema migration") before the LLM reasons about the diff.
|
|
8
|
+
*
|
|
9
|
+
* This is a generic, config-driven utility: it ships a DEFAULT_RULES ruleset
|
|
10
|
+
* built from COMMON repository conventions (a Prisma schema, a `migrations/`
|
|
11
|
+
* dir, an `auth`/`authz` dir, `webhooks`/`integrations`, a `prompts/` dir of
|
|
12
|
+
* YAML, an `outbox`/`handlers` dir, NestJS-style `*.controller.ts` / `*.dto.ts`
|
|
13
|
+
* or an `api/` dir, shell scripts and a `tools/` dir, docs). None of the rules
|
|
14
|
+
* encode any one repo's internal service map; point `classify` at your own
|
|
15
|
+
* ruleset (or extend DEFAULT_RULES) to fit a differently-structured codebase.
|
|
16
|
+
*
|
|
17
|
+
* Ordering is load-bearing. First match wins. Narrow rules come before broad
|
|
18
|
+
* rules (e.g. `*.controller.ts` before a bare `api/` dir).
|
|
19
|
+
*
|
|
20
|
+
* The ruleset is pinned by a VERSIONED fixture (`fixtures/risk-classifier.fixture.json`,
|
|
21
|
+
* `version: 1`) and a drift test (`test/lib/classifier.spec.ts`): every fixture
|
|
22
|
+
* case must classify to its declared category, the fixture's declared taxonomy
|
|
23
|
+
* must equal ALL_CATEGORIES, and every non-`unknown` category must be exercised.
|
|
24
|
+
* Adding or changing a rule therefore forces a matching fixture update (and, on
|
|
25
|
+
* a taxonomy change, a version bump).
|
|
26
|
+
*/
|
|
27
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
|
+
exports.DEFAULT_RULES = exports.ALL_CATEGORIES = void 0;
|
|
29
|
+
exports.classify = classify;
|
|
30
|
+
exports.classifyMany = classifyMany;
|
|
31
|
+
// Runtime list of every category. The drift test asserts the fixture's declared
|
|
32
|
+
// taxonomy equals this set, so the type and the fixture cannot drift apart.
|
|
33
|
+
exports.ALL_CATEGORIES = [
|
|
34
|
+
"schema_or_migration",
|
|
35
|
+
"auth_or_permission",
|
|
36
|
+
"external_integration",
|
|
37
|
+
"llm_prompt",
|
|
38
|
+
"outbox_or_handler",
|
|
39
|
+
"api_contract",
|
|
40
|
+
"cli_or_tooling",
|
|
41
|
+
"docs",
|
|
42
|
+
"unknown",
|
|
43
|
+
];
|
|
44
|
+
const _categoryCompletenessCheck = true;
|
|
45
|
+
void _categoryCompletenessCheck;
|
|
46
|
+
// Generic default ruleset. Patterns are prefix-agnostic ((^|\/) anchors a path
|
|
47
|
+
// segment whether it is at the repo root or nested), so the same rule matches
|
|
48
|
+
// `auth/x.ts`, `src/auth/x.ts`, and `packages/api/src/auth/x.ts`.
|
|
49
|
+
exports.DEFAULT_RULES = [
|
|
50
|
+
{ pattern: /(^|\/)prisma\/schema\.prisma$/, category: "schema_or_migration" },
|
|
51
|
+
{ pattern: /(^|\/)migrations?\//, category: "schema_or_migration" },
|
|
52
|
+
{ pattern: /(^|\/)(auth|authz)\//, category: "auth_or_permission" },
|
|
53
|
+
{ pattern: /(^|\/)(webhooks?|integrations?)\//, category: "external_integration" },
|
|
54
|
+
{ pattern: /(^|\/)prompts\/.*\.ya?ml$/, category: "llm_prompt" },
|
|
55
|
+
{ pattern: /(^|\/)(outbox|handlers?)\//, category: "outbox_or_handler" },
|
|
56
|
+
{ pattern: /\.(controller|dto)\.ts$/, category: "api_contract" },
|
|
57
|
+
{ pattern: /(^|\/)api\//, category: "api_contract" },
|
|
58
|
+
{ pattern: /(^|\/)tools\//, category: "cli_or_tooling" },
|
|
59
|
+
{ pattern: /\.sh$/, category: "cli_or_tooling" },
|
|
60
|
+
{ pattern: /(^|\/)docs\//, category: "docs" },
|
|
61
|
+
{ pattern: /\.md$/, category: "docs" },
|
|
62
|
+
];
|
|
63
|
+
function normalize(path) {
|
|
64
|
+
let p = path.replace(/\\/g, "/");
|
|
65
|
+
while (p.startsWith("./") || p.startsWith(".\\")) {
|
|
66
|
+
p = p.slice(2);
|
|
67
|
+
}
|
|
68
|
+
while (p.startsWith("/")) {
|
|
69
|
+
p = p.slice(1);
|
|
70
|
+
}
|
|
71
|
+
return p;
|
|
72
|
+
}
|
|
73
|
+
function classify(path, rules = exports.DEFAULT_RULES) {
|
|
74
|
+
if (!path)
|
|
75
|
+
return "unknown";
|
|
76
|
+
const normalized = normalize(path);
|
|
77
|
+
for (const rule of rules) {
|
|
78
|
+
if (rule.pattern.test(normalized))
|
|
79
|
+
return rule.category;
|
|
80
|
+
}
|
|
81
|
+
return "unknown";
|
|
82
|
+
}
|
|
83
|
+
function classifyMany(paths, rules = exports.DEFAULT_RULES) {
|
|
84
|
+
const out = {};
|
|
85
|
+
for (const p of paths)
|
|
86
|
+
out[p] = classify(p, rules);
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.SESSION_GATE_DIR = exports.HOOKS_DIR = exports.QUEUE_DIR = exports.CFG_PATH = exports.HOME = exports.DEFAULT_CONSOLE_URL = exports.DEFAULT_INTEL_URL = exports.DEFAULT_CONTROL_URL = void 0;
|
|
37
|
+
exports.getConsoleUrl = getConsoleUrl;
|
|
38
|
+
exports.telemetryDisabled = telemetryDisabled;
|
|
39
|
+
exports.readConfig = readConfig;
|
|
40
|
+
exports.loadWorkspaceConfig = loadWorkspaceConfig;
|
|
41
|
+
exports.readKbConfig = readKbConfig;
|
|
42
|
+
exports.writeConfig = writeConfig;
|
|
43
|
+
exports.configExists = configExists;
|
|
44
|
+
exports.readUpdateConfig = readUpdateConfig;
|
|
45
|
+
const fs = __importStar(require("fs"));
|
|
46
|
+
const os = __importStar(require("os"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const workspace_1 = require("./workspace");
|
|
49
|
+
// Hosted prod defaults. A freshly-installed `mla` reaches the Meetless
|
|
50
|
+
// production backend out of the box with zero flags. Override to staging or
|
|
51
|
+
// local with the MEETLESS_BACKEND_URL / MEETLESS_INTEL_URL / MEETLESS_CONSOLE_URL
|
|
52
|
+
// env vars (or the matching cli-config.json fields), which always win over these.
|
|
53
|
+
exports.DEFAULT_CONTROL_URL = "https://control.meetless.ai";
|
|
54
|
+
exports.DEFAULT_INTEL_URL = "https://intel.meetless.ai";
|
|
55
|
+
exports.DEFAULT_CONSOLE_URL = "https://app.meetless.ai";
|
|
56
|
+
// Strip trailing slash so callers can always concatenate `${base}/relationships/<id>`
|
|
57
|
+
// without producing a `//` in the URL.
|
|
58
|
+
function getConsoleUrl(cfg) {
|
|
59
|
+
const raw = process.env.MEETLESS_CONSOLE_URL || cfg.consoleUrl || exports.DEFAULT_CONSOLE_URL;
|
|
60
|
+
return raw.replace(/\/+$/, "");
|
|
61
|
+
}
|
|
62
|
+
exports.HOME = process.env.MEETLESS_HOME || path.join(os.homedir(), ".meetless");
|
|
63
|
+
exports.CFG_PATH = path.join(exports.HOME, "cli-config.json");
|
|
64
|
+
exports.QUEUE_DIR = path.join(exports.HOME, "queue");
|
|
65
|
+
exports.HOOKS_DIR = path.join(exports.HOME, "hooks");
|
|
66
|
+
// Per-session OFF sentinels (`<sid>.off`) written by `mla mute` (removed by
|
|
67
|
+
// `mla unmute`) and read by meetless_session_disabled in common.sh. This is the
|
|
68
|
+
// per-session capture lifecycle, distinct from the `.meetless.json` workspace
|
|
69
|
+
// binding that `mla activate` / `mla deactivate` manage. Must match
|
|
70
|
+
// SESSION_GATE_DIR in common.sh.
|
|
71
|
+
exports.SESSION_GATE_DIR = path.join(exports.HOME, "session-gate");
|
|
72
|
+
// The master telemetry kill switch. The single source of truth for "no telemetry
|
|
73
|
+
// of any kind leaves (or, for the local deadletter, is even recorded on) this
|
|
74
|
+
// machine." It lives here in low-level config (not in observability.ts) so the
|
|
75
|
+
// trace plane, the analytics-consent gate, the debug command, AND the
|
|
76
|
+
// failure-telemetry deadletter can all share it without an import cycle.
|
|
77
|
+
// observability.ts re-exports it for back-compat. MEETLESS_TELEMETRY in
|
|
78
|
+
// {off,0,false,no} hard-disables; a truthy MEETLESS_NO_TELEMETRY does the same.
|
|
79
|
+
// An unset MEETLESS_TELEMETRY is NOT disabled here: per-plane opt-IN (analytics
|
|
80
|
+
// forwarding) is enforced at the forwarding sites, not by this hard switch.
|
|
81
|
+
function telemetryDisabled(env = process.env) {
|
|
82
|
+
const t = (env.MEETLESS_TELEMETRY || "").trim().toLowerCase();
|
|
83
|
+
if (t === "off" || t === "0" || t === "false" || t === "no")
|
|
84
|
+
return true;
|
|
85
|
+
const no = (env.MEETLESS_NO_TELEMETRY || "").trim().toLowerCase();
|
|
86
|
+
if (no && no !== "0" && no !== "false" && no !== "no")
|
|
87
|
+
return true;
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
function asString(value) {
|
|
91
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
92
|
+
}
|
|
93
|
+
// Normalize the optional on-disk `update` object into a UpdateConfig, or
|
|
94
|
+
// undefined when absent/empty so OnDiskConfig omits it (no defaults written to
|
|
95
|
+
// disk). autoApply is strictly boolean-true to opt in; anything else stays
|
|
96
|
+
// nag-only. channel falls back to "stable". Returns undefined when neither field
|
|
97
|
+
// is meaningfully set, so a bare `{}` does not get persisted back.
|
|
98
|
+
function normalizeUpdate(value) {
|
|
99
|
+
if (!value || typeof value !== "object")
|
|
100
|
+
return undefined;
|
|
101
|
+
const o = value;
|
|
102
|
+
const channel = asString(o.channel);
|
|
103
|
+
const autoApply = o.autoApply === true;
|
|
104
|
+
if (!autoApply && !channel)
|
|
105
|
+
return undefined;
|
|
106
|
+
return { autoApply, channel: channel ?? "stable" };
|
|
107
|
+
}
|
|
108
|
+
// Normalize a parsed user-token-shaped object (either `auth: {mode:'user-token'}`
|
|
109
|
+
// or a legacy expanded top-level) into the CliAuth user-token variant. Throws if
|
|
110
|
+
// the access token is absent: a user-token config with no bearer is corrupt and
|
|
111
|
+
// must fail loud, not silently degrade.
|
|
112
|
+
function normalizeUserToken(src) {
|
|
113
|
+
const accessToken = asString(src.accessToken);
|
|
114
|
+
if (!accessToken) {
|
|
115
|
+
throw new Error(`cli-config.json at ${exports.CFG_PATH} has auth.mode 'user-token' but no access token. ` +
|
|
116
|
+
"The login is corrupt. Run `mla logout` then `mla login`, or " +
|
|
117
|
+
"`mla init --control-token <T>` to fall back to shared-key.");
|
|
118
|
+
}
|
|
119
|
+
const user = src.user ?? {};
|
|
120
|
+
return {
|
|
121
|
+
mode: "user-token",
|
|
122
|
+
accessToken,
|
|
123
|
+
refreshToken: asString(src.refreshToken) ?? "",
|
|
124
|
+
accessExpiresAt: asString(src.accessExpiresAt) ?? "",
|
|
125
|
+
refreshExpiresAt: asString(src.refreshExpiresAt) ?? "",
|
|
126
|
+
sessionId: asString(src.sessionId) ?? "",
|
|
127
|
+
user: {
|
|
128
|
+
id: asString(user.id) ?? "",
|
|
129
|
+
displayName: asString(user.displayName) ?? "",
|
|
130
|
+
email: typeof user.email === "string" ? user.email : null,
|
|
131
|
+
role: asString(user.role) ?? "",
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
// The §6.4 compat shim: collapse the three accepted on-disk shapes into one
|
|
136
|
+
// CliAuth. (1) new nested `auth`; (2) legacy shared-key (top-level controlToken,
|
|
137
|
+
// no refresh); (3) legacy expanded user-token (top-level controlToken + refresh
|
|
138
|
+
// + authMode==='user-token'); absent everything => 'none'.
|
|
139
|
+
function normalizeAuthFromDisk(cfg) {
|
|
140
|
+
// (1) New nested shape wins when present and well-formed.
|
|
141
|
+
if (cfg.auth !== null && typeof cfg.auth === "object") {
|
|
142
|
+
const a = cfg.auth;
|
|
143
|
+
if (a.mode === "none")
|
|
144
|
+
return { mode: "none" };
|
|
145
|
+
if (a.mode === "shared-key") {
|
|
146
|
+
const accessToken = asString(a.accessToken);
|
|
147
|
+
if (accessToken)
|
|
148
|
+
return { mode: "shared-key", accessToken };
|
|
149
|
+
// shared-key with no token is meaningless; treat as logged out.
|
|
150
|
+
return { mode: "none" };
|
|
151
|
+
}
|
|
152
|
+
if (a.mode === "user-token") {
|
|
153
|
+
return normalizeUserToken(a);
|
|
154
|
+
}
|
|
155
|
+
// Unknown mode: fail loud rather than guess a credential path.
|
|
156
|
+
throw new Error(`cli-config.json at ${exports.CFG_PATH} has an unrecognized auth.mode. ` +
|
|
157
|
+
"Run `mla logout` then `mla login`, or `mla init --control-token <T>`.");
|
|
158
|
+
}
|
|
159
|
+
// (2)/(3) Legacy top-level controlToken.
|
|
160
|
+
const legacyToken = asString(cfg.controlToken);
|
|
161
|
+
if (legacyToken) {
|
|
162
|
+
if (asString(cfg.refreshToken) && cfg.authMode === "user-token") {
|
|
163
|
+
return normalizeUserToken({ ...cfg, accessToken: legacyToken });
|
|
164
|
+
}
|
|
165
|
+
return { mode: "shared-key", accessToken: legacyToken };
|
|
166
|
+
}
|
|
167
|
+
// Nothing on disk => terminal logged-out state.
|
|
168
|
+
return { mode: "none" };
|
|
169
|
+
}
|
|
170
|
+
function readConfig() {
|
|
171
|
+
if (!fs.existsSync(exports.CFG_PATH)) {
|
|
172
|
+
throw new Error(`cli-config.json not found at ${exports.CFG_PATH}. Run 'mla init' first.`);
|
|
173
|
+
}
|
|
174
|
+
const raw = fs.readFileSync(exports.CFG_PATH, "utf8");
|
|
175
|
+
const cfg = JSON.parse(raw);
|
|
176
|
+
// Non-credential env aliases select WHICH control plane, not WHO you are, so
|
|
177
|
+
// they are honored in every auth mode (CI / containers). MEETLESS_INTEL_ROOT
|
|
178
|
+
// is handled separately in the intel-root resolver.
|
|
179
|
+
const controlUrl = process.env.MEETLESS_BACKEND_URL || cfg.controlUrl || exports.DEFAULT_CONTROL_URL;
|
|
180
|
+
const intelUrl = process.env.MEETLESS_INTEL_URL || cfg.intelUrl || exports.DEFAULT_INTEL_URL;
|
|
181
|
+
const diskAuth = normalizeAuthFromDisk(cfg);
|
|
182
|
+
// §0.01 clause 4 / Finding H: under an on-disk user-token, MEETLESS_CONTROL_TOKEN
|
|
183
|
+
// is REJECTED loudly, never silently honored. Falling back to shared-key here
|
|
184
|
+
// would mis-attribute the operator's audited actions to INTERNAL_API_KEY.
|
|
185
|
+
const envSharedKey = process.env.MEETLESS_CONTROL_TOKEN;
|
|
186
|
+
let auth;
|
|
187
|
+
if (diskAuth.mode === "user-token" && envSharedKey) {
|
|
188
|
+
throw new Error(`MEETLESS_CONTROL_TOKEN is set but you are logged in as ${diskAuth.user.displayName}.\n` +
|
|
189
|
+
"Unset it (`unset MEETLESS_CONTROL_TOKEN`) or run `mla logout` first.");
|
|
190
|
+
}
|
|
191
|
+
else if (envSharedKey) {
|
|
192
|
+
// Env-driven shared key overrides a shared-key / none on-disk state (the
|
|
193
|
+
// documented CI / container path). It never silently overrides user-token
|
|
194
|
+
// (handled above).
|
|
195
|
+
auth = { mode: "shared-key", accessToken: envSharedKey };
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
auth = diskAuth;
|
|
199
|
+
}
|
|
200
|
+
// workspaceId is intentionally NOT resolved here (folder = workspace, T1.1);
|
|
201
|
+
// loadWorkspaceConfig / readKbConfig add it from the `.meetless.json` marker.
|
|
202
|
+
// controlUrl/intelUrl always resolve (hosted prod default is the final
|
|
203
|
+
// fallback above), so there is no missing-config branch to guard here.
|
|
204
|
+
// actorUserId: pinned to auth.user.id under user-token (P3, so the actor
|
|
205
|
+
// header and session identity can never disagree); otherwise the preserved
|
|
206
|
+
// top-level value (migrated across the rewrite, Finding G).
|
|
207
|
+
const actorUserId = auth.mode === "user-token" ? auth.user.id : asString(cfg.actorUserId);
|
|
208
|
+
// Derived controlToken: the bearer for shared-key / user-token; empty for
|
|
209
|
+
// 'none' (a none-mode request fails fast at the http layer, §6.5).
|
|
210
|
+
const controlToken = auth.mode === "none" ? "" : auth.accessToken;
|
|
211
|
+
return {
|
|
212
|
+
controlUrl,
|
|
213
|
+
controlToken,
|
|
214
|
+
intelUrl,
|
|
215
|
+
mlaPath: cfg.mlaPath || "",
|
|
216
|
+
intelRoot: cfg.intelRoot,
|
|
217
|
+
consoleUrl: cfg.consoleUrl,
|
|
218
|
+
actorUserId,
|
|
219
|
+
update: normalizeUpdate(cfg.update),
|
|
220
|
+
auth,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
// Load the machine config AND resolve the active workspace from the nearest
|
|
224
|
+
// `.meetless.json` marker (folder = workspace, T1.1). This is the single entry
|
|
225
|
+
// point every workspace-scoped command uses instead of readConfig(): it threads
|
|
226
|
+
// the marker-resolved id onto cfg.workspaceId so existing `cfg.workspaceId`
|
|
227
|
+
// call sites keep working, now sourced from the marker rather than cli-config.
|
|
228
|
+
//
|
|
229
|
+
// `override` is the admin `--workspace <id>` escape hatch (KB ops against
|
|
230
|
+
// another workspace): when provided and non-empty it short-circuits marker
|
|
231
|
+
// resolution, so the command never throws NotActivatedError just because the
|
|
232
|
+
// operator is acting cross-workspace from an unbound directory. When absent,
|
|
233
|
+
// resolveWorkspaceId() walks up from cwd and throws a clean "not activated"
|
|
234
|
+
// error if no marker is found.
|
|
235
|
+
function loadWorkspaceConfig(override) {
|
|
236
|
+
const cfg = readConfig();
|
|
237
|
+
const workspaceId = (override || "").trim() || (0, workspace_1.resolveWorkspaceId)();
|
|
238
|
+
return { ...cfg, workspaceId };
|
|
239
|
+
}
|
|
240
|
+
// KB curation loader: resolves the workspace from the marker (via
|
|
241
|
+
// loadWorkspaceConfig, honoring the optional `--workspace` admin override) AND
|
|
242
|
+
// enforces an actorUserId. `workspaceId` is therefore marker-sourced just like
|
|
243
|
+
// every other workspace-scoped command; cli-config is never consulted for it.
|
|
244
|
+
function readKbConfig(override) {
|
|
245
|
+
const cfg = loadWorkspaceConfig(override);
|
|
246
|
+
const actor = (cfg.actorUserId || "").trim();
|
|
247
|
+
if (!actor) {
|
|
248
|
+
throw new Error(`cli-config.json is missing required field 'actorUserId'. ` +
|
|
249
|
+
`KB curation commands stamp this onto every outbox event so the ` +
|
|
250
|
+
`audit trail records who acted. Re-run 'mla init --actor <id>' ` +
|
|
251
|
+
`or edit ${exports.CFG_PATH} directly to add it.`);
|
|
252
|
+
}
|
|
253
|
+
return { ...cfg, actorUserId: actor };
|
|
254
|
+
}
|
|
255
|
+
function writeConfig(cfg) {
|
|
256
|
+
fs.mkdirSync(exports.HOME, { recursive: true });
|
|
257
|
+
fs.mkdirSync(exports.QUEUE_DIR, { recursive: true });
|
|
258
|
+
fs.mkdirSync(exports.HOOKS_DIR, { recursive: true });
|
|
259
|
+
// P3: under a user session the actor is the authenticated user, full stop, so
|
|
260
|
+
// it can never drift from auth.user.id even if a caller passed a stale value.
|
|
261
|
+
const actorUserId = cfg.auth.mode === "user-token" ? cfg.auth.user.id : cfg.actorUserId;
|
|
262
|
+
// Serialize ONLY the canonical fields. `controlToken` is intentionally dropped:
|
|
263
|
+
// it is a read-time projection of auth.accessToken, persisting it would create
|
|
264
|
+
// two sources of truth that can silently diverge on the next login/refresh.
|
|
265
|
+
const onDisk = {
|
|
266
|
+
controlUrl: cfg.controlUrl,
|
|
267
|
+
...(cfg.intelUrl ? { intelUrl: cfg.intelUrl } : {}),
|
|
268
|
+
...(cfg.workspaceId ? { workspaceId: cfg.workspaceId } : {}),
|
|
269
|
+
mlaPath: cfg.mlaPath,
|
|
270
|
+
...(cfg.intelRoot ? { intelRoot: cfg.intelRoot } : {}),
|
|
271
|
+
...(cfg.consoleUrl ? { consoleUrl: cfg.consoleUrl } : {}),
|
|
272
|
+
...(actorUserId ? { actorUserId } : {}),
|
|
273
|
+
...(cfg.update ? { update: cfg.update } : {}),
|
|
274
|
+
auth: cfg.auth,
|
|
275
|
+
};
|
|
276
|
+
fs.writeFileSync(exports.CFG_PATH, JSON.stringify(onDisk, null, 2) + "\n", { mode: 0o600 });
|
|
277
|
+
}
|
|
278
|
+
function configExists() {
|
|
279
|
+
return fs.existsSync(exports.CFG_PATH);
|
|
280
|
+
}
|
|
281
|
+
// Throw-free reader for the self-upgrade hot path. The upgrade machinery (the
|
|
282
|
+
// apply-on-launch promote step and `mla upgrade`) can run BEFORE `mla init`
|
|
283
|
+
// exists, so it must never throw the "run mla init first" error readConfig
|
|
284
|
+
// raises on a missing file. Returns nag-only defaults (autoApply false, channel
|
|
285
|
+
// "stable") on a missing/corrupt/empty config and only enables autoApply when
|
|
286
|
+
// the on-disk `update.autoApply` is exactly true.
|
|
287
|
+
function readUpdateConfig() {
|
|
288
|
+
const fallback = { autoApply: false, channel: "stable" };
|
|
289
|
+
try {
|
|
290
|
+
if (!fs.existsSync(exports.CFG_PATH))
|
|
291
|
+
return fallback;
|
|
292
|
+
const raw = JSON.parse(fs.readFileSync(exports.CFG_PATH, "utf8"));
|
|
293
|
+
return normalizeUpdate(raw.update) ?? fallback;
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
return fallback;
|
|
297
|
+
}
|
|
298
|
+
}
|