muonroi-cli 1.8.5 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/packages/agent-harness-core/src/event-filter.js +11 -0
- package/dist/packages/agent-harness-core/src/event-redact.js +7 -0
- package/dist/packages/agent-harness-core/src/event-tee.d.ts +20 -4
- package/dist/packages/agent-harness-core/src/event-tee.js +32 -5
- package/dist/packages/agent-harness-core/src/mcp-server.d.ts +14 -0
- package/dist/packages/agent-harness-core/src/mcp-server.js +86 -10
- package/dist/packages/agent-harness-core/src/predicate.d.ts +1 -1
- package/dist/packages/agent-harness-core/src/protocol.d.ts +24 -2
- package/dist/src/agent-harness/mock-model.d.ts +10 -0
- package/dist/src/agent-harness/mock-model.js +6 -2
- package/dist/src/chat/chat-keychain.d.ts +7 -12
- package/dist/src/chat/chat-keychain.js +19 -86
- package/dist/src/cli/keys.d.ts +8 -45
- package/dist/src/cli/keys.js +18 -324
- package/dist/src/council/clarifier.d.ts +4 -2
- package/dist/src/council/clarifier.js +158 -36
- package/dist/src/council/debate-planner.js +3 -2
- package/dist/src/council/debate.js +59 -5
- package/dist/src/council/index.d.ts +23 -0
- package/dist/src/council/index.js +37 -2
- package/dist/src/council/llm.d.ts +62 -0
- package/dist/src/council/llm.js +123 -23
- package/dist/src/council/panel-select.js +13 -3
- package/dist/src/council/planner.js +17 -0
- package/dist/src/council/preflight.d.ts +10 -0
- package/dist/src/council/preflight.js +36 -0
- package/dist/src/council/prompts.d.ts +9 -1
- package/dist/src/council/prompts.js +23 -6
- package/dist/src/council/types.d.ts +10 -0
- package/dist/src/ee/auth.d.ts +19 -0
- package/dist/src/ee/auth.js +39 -0
- package/dist/src/ee/client.js +28 -3
- package/dist/src/ee/ee-onboarding.js +6 -26
- package/dist/src/flow/compaction/compress.d.ts +2 -2
- package/dist/src/flow/compaction/compress.js +21 -8
- package/dist/src/flow/compaction/extract.d.ts +3 -3
- package/dist/src/flow/compaction/extract.js +6 -6
- package/dist/src/flow/compaction/index.d.ts +2 -1
- package/dist/src/flow/compaction/index.js +29 -3
- package/dist/src/flow/compaction/progress.d.ts +35 -0
- package/dist/src/flow/compaction/progress.js +35 -0
- package/dist/src/generated/version.d.ts +1 -1
- package/dist/src/generated/version.js +1 -1
- package/dist/src/gsd/flags.d.ts +11 -0
- package/dist/src/gsd/flags.js +19 -0
- package/dist/src/gsd/plan-council.js +104 -72
- package/dist/src/gsd/verdict-schema.d.ts +1 -1
- package/dist/src/headless/council-answers.js +4 -0
- package/dist/src/index.js +129 -260
- package/dist/src/lsp/builtins.js +3 -1
- package/dist/src/lsp/manager.d.ts +5 -1
- package/dist/src/lsp/manager.js +249 -3
- package/dist/src/lsp/npm-cache.d.ts +11 -1
- package/dist/src/lsp/npm-cache.js +17 -1
- package/dist/src/lsp/runtime.d.ts +6 -1
- package/dist/src/lsp/runtime.js +17 -1
- package/dist/src/lsp/types.d.ts +83 -1
- package/dist/src/lsp/types.js +10 -0
- package/dist/src/mcp/client-pool.js +43 -15
- package/dist/src/mcp/lsp-tools.d.ts +5 -1
- package/dist/src/mcp/lsp-tools.js +93 -2
- package/dist/src/mcp/mcp-keychain.d.ts +3 -5
- package/dist/src/mcp/mcp-keychain.js +9 -49
- package/dist/src/mcp/setup-guide-text.d.ts +1 -1
- package/dist/src/mcp/setup-guide-text.js +22 -2
- package/dist/src/mcp/tools-server.d.ts +10 -0
- package/dist/src/mcp/tools-server.js +10 -2
- package/dist/src/models/catalog.json +19 -19
- package/dist/src/orchestrator/ask-user.d.ts +61 -0
- package/dist/src/orchestrator/ask-user.js +65 -0
- package/dist/src/orchestrator/compaction.d.ts +2 -3
- package/dist/src/orchestrator/compaction.js +8 -8
- package/dist/src/orchestrator/council-manager.js +9 -8
- package/dist/src/orchestrator/council-request.d.ts +49 -0
- package/dist/src/orchestrator/council-request.js +62 -0
- package/dist/src/orchestrator/interactive-pause.d.ts +26 -0
- package/dist/src/orchestrator/interactive-pause.js +36 -0
- package/dist/src/orchestrator/message-processor.d.ts +4 -0
- package/dist/src/orchestrator/message-processor.js +26 -8
- package/dist/src/orchestrator/orchestrator.d.ts +25 -0
- package/dist/src/orchestrator/orchestrator.js +204 -50
- package/dist/src/orchestrator/preprocessor.js +2 -2
- package/dist/src/orchestrator/safety-askcard.d.ts +1 -1
- package/dist/src/orchestrator/safety-askcard.js +5 -2
- package/dist/src/orchestrator/safety-intercept.d.ts +5 -0
- package/dist/src/orchestrator/safety-intercept.js +7 -0
- package/dist/src/orchestrator/stall-watchdog.d.ts +8 -1
- package/dist/src/orchestrator/stall-watchdog.js +24 -3
- package/dist/src/orchestrator/stream-runner.d.ts +13 -3
- package/dist/src/orchestrator/stream-runner.js +54 -21
- package/dist/src/orchestrator/tool-engine.d.ts +19 -0
- package/dist/src/orchestrator/tool-engine.js +241 -25
- package/dist/src/orchestrator/turn-watchdog.d.ts +7 -0
- package/dist/src/orchestrator/turn-watchdog.js +38 -9
- package/dist/src/pil/agent-operating-contract.d.ts +1 -1
- package/dist/src/pil/agent-operating-contract.js +6 -4
- package/dist/src/pil/discovery.d.ts +1 -1
- package/dist/src/pil/discovery.js +2 -2
- package/dist/src/pil/layer1_5-complexity-size.d.ts +7 -0
- package/dist/src/pil/layer1_5-complexity-size.js +31 -5
- package/dist/src/pil/llm-classify.d.ts +78 -3
- package/dist/src/pil/llm-classify.js +351 -111
- package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
- package/dist/src/pil/native-capabilities-workbook.js +7 -0
- package/dist/src/pil/pipeline.js +2 -0
- package/dist/src/pil/repo-grounding-probe.d.ts +15 -0
- package/dist/src/pil/repo-grounding-probe.js +136 -0
- package/dist/src/pil/repo-structure-hints.d.ts +7 -0
- package/dist/src/pil/repo-structure-hints.js +45 -0
- package/dist/src/product-loop/artifact-io.js +4 -0
- package/dist/src/product-loop/criteria-seed.d.ts +51 -0
- package/dist/src/product-loop/criteria-seed.js +200 -0
- package/dist/src/product-loop/discovery-interview.d.ts +9 -0
- package/dist/src/product-loop/discovery-interview.js +37 -18
- package/dist/src/product-loop/discovery-recommender.js +2 -1
- package/dist/src/product-loop/discovery-schema.js +14 -1
- package/dist/src/product-loop/discovery-triage.d.ts +23 -0
- package/dist/src/product-loop/discovery-triage.js +109 -0
- package/dist/src/product-loop/gather.js +150 -2
- package/dist/src/product-loop/index.js +7 -0
- package/dist/src/product-loop/loop-driver.js +21 -8
- package/dist/src/product-loop/phase-plan.d.ts +16 -0
- package/dist/src/product-loop/phase-plan.js +42 -4
- package/dist/src/product-loop/phase-rituals.d.ts +3 -0
- package/dist/src/product-loop/phase-rituals.js +8 -3
- package/dist/src/product-loop/phase-runner.js +30 -11
- package/dist/src/product-loop/plan-adherence-review.d.ts +26 -0
- package/dist/src/product-loop/plan-adherence-review.js +144 -0
- package/dist/src/product-loop/sprint-runner.d.ts +62 -0
- package/dist/src/product-loop/sprint-runner.js +309 -8
- package/dist/src/product-loop/types.d.ts +25 -0
- package/dist/src/providers/anthropic.d.ts +9 -8
- package/dist/src/providers/anthropic.js +13 -47
- package/dist/src/providers/auth/grok-oauth.d.ts +1 -0
- package/dist/src/providers/auth/grok-oauth.js +30 -5
- package/dist/src/providers/auth/openai-oauth.d.ts +1 -0
- package/dist/src/providers/auth/openai-oauth.js +14 -0
- package/dist/src/providers/auth/token-store.d.ts +9 -9
- package/dist/src/providers/auth/token-store.js +8 -70
- package/dist/src/providers/auth/types.d.ts +8 -0
- package/dist/src/providers/env-store.d.ts +17 -0
- package/dist/src/providers/env-store.js +228 -0
- package/dist/src/providers/keychain.d.ts +21 -17
- package/dist/src/providers/keychain.js +124 -135
- package/dist/src/providers/runtime.d.ts +24 -9
- package/dist/src/providers/runtime.js +48 -37
- package/dist/src/providers/strategies/thinking-mode.js +9 -1
- package/dist/src/providers/strategies/xai.strategy.js +27 -0
- package/dist/src/providers/warm.d.ts +65 -0
- package/dist/src/providers/warm.js +145 -0
- package/dist/src/self-qa/agentic-loop.js +3 -2
- package/dist/src/storage/transcript.js +56 -2
- package/dist/src/tools/git-safety.d.ts +19 -0
- package/dist/src/tools/git-safety.js +168 -0
- package/dist/src/tools/native-tools.d.ts +1 -1
- package/dist/src/tools/native-tools.js +76 -1
- package/dist/src/tools/registry.d.ts +17 -0
- package/dist/src/tools/registry.js +116 -1
- package/dist/src/types/index.d.ts +29 -1
- package/dist/src/ui/app.js +67 -4
- package/dist/src/ui/components/agent-rail-activities.d.ts +26 -0
- package/dist/src/ui/components/agent-rail-activities.js +47 -0
- package/dist/src/ui/components/compact-progress-card.d.ts +24 -0
- package/dist/src/ui/components/compact-progress-card.js +42 -0
- package/dist/src/ui/components/council-phase-timeline.js +17 -2
- package/dist/src/ui/components/council-question-card.js +1 -0
- package/dist/src/ui/components/message-view.d.ts +15 -0
- package/dist/src/ui/components/message-view.js +50 -1
- package/dist/src/ui/components/tool-group.d.ts +15 -3
- package/dist/src/ui/components/tool-group.js +69 -11
- package/dist/src/ui/containers/modals-layer.d.ts +2 -1
- package/dist/src/ui/containers/modals-layer.js +2 -2
- package/dist/src/ui/council-harness-event.d.ts +57 -0
- package/dist/src/ui/council-harness-event.js +46 -0
- package/dist/src/ui/heartbeat-debug.d.ts +29 -0
- package/dist/src/ui/heartbeat-debug.js +45 -0
- package/dist/src/ui/modals/api-key-modal.js +1 -1
- package/dist/src/ui/modals/model-picker-modal.d.ts +8 -18
- package/dist/src/ui/modals/model-picker-modal.js +8 -10
- package/dist/src/ui/slash/ee.js +81 -0
- package/dist/src/ui/slash/menu-items.js +11 -2
- package/dist/src/ui/use-app-logic.js +354 -224
- package/dist/src/ui/utils/agent-activities.d.ts +39 -0
- package/dist/src/ui/utils/agent-activities.js +96 -0
- package/dist/src/ui/utils/group-tool-entries.d.ts +26 -0
- package/dist/src/ui/utils/group-tool-entries.js +111 -0
- package/dist/src/ui/utils/tool-summary.d.ts +21 -0
- package/dist/src/ui/utils/tool-summary.js +91 -0
- package/dist/src/utils/event-loop-monitor.d.ts +85 -0
- package/dist/src/utils/event-loop-monitor.js +107 -0
- package/dist/src/utils/llm-deadline.d.ts +14 -0
- package/dist/src/utils/llm-deadline.js +19 -0
- package/dist/src/utils/loop-profiler.d.ts +102 -0
- package/dist/src/utils/loop-profiler.js +202 -0
- package/dist/src/utils/settings.d.ts +27 -0
- package/dist/src/utils/settings.js +38 -2
- package/dist/src/utils/side-question.d.ts +1 -2
- package/dist/src/utils/side-question.js +2 -2
- package/dist/src/verify/entrypoint.js +51 -16
- package/dist/src/verify/orchestrator.d.ts +1 -1
- package/dist/src/verify/orchestrator.js +20 -3
- package/package.json +1 -2
- package/dist/src/cli/bw-vault.d.ts +0 -55
- package/dist/src/cli/bw-vault.js +0 -133
- package/dist/src/mcp/ee-tools.d.ts +0 -46
- package/dist/src/mcp/ee-tools.js +0 -194
|
@@ -28,9 +28,20 @@ export const LIFECYCLE_PRESET = new Set([
|
|
|
28
28
|
"askcard-cancel",
|
|
29
29
|
"sprint-stage",
|
|
30
30
|
"sprint-halt",
|
|
31
|
+
// Emitted at product-loop/index.ts on plan commit; a wake-at-milestone monitor
|
|
32
|
+
// must see it. Was previously dropped by this preset despite being emitted.
|
|
33
|
+
"sprint-plan-committed",
|
|
31
34
|
"route-decision",
|
|
32
35
|
"steer-inject",
|
|
33
36
|
"usage",
|
|
37
|
+
// Ephemeral kinds carry a visualText snapshot for wake-at-milestone monitors
|
|
38
|
+
// (event-tee.ts EPHEMERAL_KINDS). The default preset must not drop them, or a
|
|
39
|
+
// monitor on MUONROI_HARNESS_EVENT_LOG misses errors/timeouts/disconnects.
|
|
40
|
+
"ee-timeout",
|
|
41
|
+
"ee-error",
|
|
42
|
+
"grounding-flag",
|
|
43
|
+
"stream-retry",
|
|
44
|
+
"disconnect",
|
|
34
45
|
]);
|
|
35
46
|
/**
|
|
36
47
|
* Returns a predicate that returns true when an event kind is allowed to emit.
|
|
@@ -53,6 +53,13 @@ const ALLOWED_FIELDS = {
|
|
|
53
53
|
status: "pass",
|
|
54
54
|
round: "pass",
|
|
55
55
|
correlationId: "pass",
|
|
56
|
+
// Liveness counters. Plain magnitudes (like elapsedMs / charCount above) —
|
|
57
|
+
// no prompt or response content — and without them a harness consumer
|
|
58
|
+
// cannot tell a slow reasoning call from a hung one, because `elapsedMs`
|
|
59
|
+
// alone freezes whenever the tick generator stops being pulled.
|
|
60
|
+
streamedChars: "pass",
|
|
61
|
+
lastDeltaAgeMs: "pass",
|
|
62
|
+
elapsedMs: "pass",
|
|
56
63
|
},
|
|
57
64
|
"council-turn-length": {
|
|
58
65
|
role: "pass",
|
|
@@ -19,8 +19,11 @@
|
|
|
19
19
|
* card) stay put, so the agent renders them fresh on demand; embedding a stale
|
|
20
20
|
* snapshot for those would be worse, not better.
|
|
21
21
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
22
|
+
* ON BY DEFAULT (see {@link resolveEventLogPath}): an opt-in sink is one nobody
|
|
23
|
+
* remembers to opt into, and the cost of the log missing is an agent that
|
|
24
|
+
* cannot tell "waiting for a human" from "hung" — the exact confusion this file
|
|
25
|
+
* exists to prevent. Set MUONROI_HARNESS_EVENT_LOG=0 (or "off"/"false") to
|
|
26
|
+
* disable, or to a path to choose your own.
|
|
24
27
|
*/
|
|
25
28
|
import type { LiveEvent } from "./protocol.js";
|
|
26
29
|
/**
|
|
@@ -37,8 +40,21 @@ export interface TeedEventLine {
|
|
|
37
40
|
visualText?: string;
|
|
38
41
|
}
|
|
39
42
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
43
|
+
* Resolve where events are teed, from the MUONROI_HARNESS_EVENT_LOG value.
|
|
44
|
+
*
|
|
45
|
+
* - unset/blank → a per-pid file in the OS temp dir (the default-on path)
|
|
46
|
+
* - "0" / "off" / "false" / "no" → null, sink disabled
|
|
47
|
+
* - anything else → that literal path
|
|
48
|
+
*
|
|
49
|
+
* Per-pid keeps concurrent harness servers from interleaving into one file, and
|
|
50
|
+
* puts the log where the OS already reclaims it — no rotation to maintain.
|
|
51
|
+
*
|
|
52
|
+
* @param pid - process id (injectable for tests).
|
|
53
|
+
*/
|
|
54
|
+
export declare function resolveEventLogPath(envValue?: string, pid?: number): string | null;
|
|
55
|
+
/**
|
|
56
|
+
* Build an event-tee sink. Returns null only when the sink is explicitly
|
|
57
|
+
* disabled — see {@link resolveEventLogPath}.
|
|
42
58
|
*
|
|
43
59
|
* @param getVisualText - lazily renders the current visual frame to plain text
|
|
44
60
|
* (driver.render_visual). Called ONLY for ephemeral kinds, so persistent
|
|
@@ -19,10 +19,15 @@
|
|
|
19
19
|
* card) stay put, so the agent renders them fresh on demand; embedding a stale
|
|
20
20
|
* snapshot for those would be worse, not better.
|
|
21
21
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
22
|
+
* ON BY DEFAULT (see {@link resolveEventLogPath}): an opt-in sink is one nobody
|
|
23
|
+
* remembers to opt into, and the cost of the log missing is an agent that
|
|
24
|
+
* cannot tell "waiting for a human" from "hung" — the exact confusion this file
|
|
25
|
+
* exists to prevent. Set MUONROI_HARNESS_EVENT_LOG=0 (or "off"/"false") to
|
|
26
|
+
* disable, or to a path to choose your own.
|
|
24
27
|
*/
|
|
25
28
|
import { appendFileSync } from "node:fs";
|
|
29
|
+
import { tmpdir } from "node:os";
|
|
30
|
+
import { join } from "node:path";
|
|
26
31
|
/**
|
|
27
32
|
* Event kinds that flash and disappear before an agent can wake + render.
|
|
28
33
|
* Only these carry an at-emit visual snapshot in the teed line.
|
|
@@ -35,9 +40,31 @@ export const EPHEMERAL_KINDS = new Set([
|
|
|
35
40
|
"ee-error",
|
|
36
41
|
"grounding-flag",
|
|
37
42
|
]);
|
|
43
|
+
/** Values that turn the sink off. Anything else is treated as a path. */
|
|
44
|
+
const DISABLE_VALUES = new Set(["0", "off", "false", "no"]);
|
|
38
45
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
46
|
+
* Resolve where events are teed, from the MUONROI_HARNESS_EVENT_LOG value.
|
|
47
|
+
*
|
|
48
|
+
* - unset/blank → a per-pid file in the OS temp dir (the default-on path)
|
|
49
|
+
* - "0" / "off" / "false" / "no" → null, sink disabled
|
|
50
|
+
* - anything else → that literal path
|
|
51
|
+
*
|
|
52
|
+
* Per-pid keeps concurrent harness servers from interleaving into one file, and
|
|
53
|
+
* puts the log where the OS already reclaims it — no rotation to maintain.
|
|
54
|
+
*
|
|
55
|
+
* @param pid - process id (injectable for tests).
|
|
56
|
+
*/
|
|
57
|
+
export function resolveEventLogPath(envValue, pid = process.pid) {
|
|
58
|
+
const raw = (envValue ?? "").trim();
|
|
59
|
+
if (DISABLE_VALUES.has(raw.toLowerCase()))
|
|
60
|
+
return null;
|
|
61
|
+
if (raw)
|
|
62
|
+
return raw;
|
|
63
|
+
return join(tmpdir(), `muonroi-harness-events-${pid}.jsonl`);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Build an event-tee sink. Returns null only when the sink is explicitly
|
|
67
|
+
* disabled — see {@link resolveEventLogPath}.
|
|
41
68
|
*
|
|
42
69
|
* @param getVisualText - lazily renders the current visual frame to plain text
|
|
43
70
|
* (driver.render_visual). Called ONLY for ephemeral kinds, so persistent
|
|
@@ -45,7 +72,7 @@ export const EPHEMERAL_KINDS = new Set([
|
|
|
45
72
|
* @param envValue - value of MUONROI_HARNESS_EVENT_LOG (injectable for tests).
|
|
46
73
|
*/
|
|
47
74
|
export function createEventTee(getVisualText, envValue) {
|
|
48
|
-
const path = (envValue
|
|
75
|
+
const path = resolveEventLogPath(envValue);
|
|
49
76
|
if (!path)
|
|
50
77
|
return null;
|
|
51
78
|
return (event) => {
|
|
@@ -47,6 +47,18 @@ export declare function validateStartArgs(args: string[]): {
|
|
|
47
47
|
bad: string;
|
|
48
48
|
};
|
|
49
49
|
export declare function sanitizeEnv(env: Record<string, string>): Record<string, string>;
|
|
50
|
+
/**
|
|
51
|
+
* Build the child TUI's environment for tui.start.
|
|
52
|
+
*
|
|
53
|
+
* Starts from the driver's own env so PATH/HOME survive, overlays the
|
|
54
|
+
* caller-supplied keys, THEN strips the dangerous ones. The merge-before-strip
|
|
55
|
+
* order keeps the security posture (NODE_OPTIONS/LD_PRELOAD/… still removed)
|
|
56
|
+
* while guaranteeing PATH is present: bun drops the fd 3/4 stdio channels when a
|
|
57
|
+
* child is spawned with a partial env that lacks PATH, which yields a null
|
|
58
|
+
* inWrite and crashes the whole driver on spawn. `base` defaults to process.env
|
|
59
|
+
* and is injectable for tests.
|
|
60
|
+
*/
|
|
61
|
+
export declare function buildChildEnv(callerEnv?: Record<string, string>, base?: Record<string, string | undefined>): Record<string, string>;
|
|
50
62
|
/**
|
|
51
63
|
* Opt-in extra cwd roots for tui.start, layered ON TOP of the default
|
|
52
64
|
* {home, repo-root} allowlist. The posture stays deny-by-default: only roots an
|
|
@@ -73,6 +85,8 @@ export declare function validateMockLlmPath(value: string): boolean;
|
|
|
73
85
|
export declare function buildCapabilitiesPayload(): {
|
|
74
86
|
protocol: string;
|
|
75
87
|
features: readonly string[];
|
|
88
|
+
/** Where LiveEvents are teed as JSONL, or null when the sink is disabled. */
|
|
89
|
+
eventLogPath: string | null;
|
|
76
90
|
};
|
|
77
91
|
export declare function registerReadTools(server: McpServer, getDriver: () => Driver | null): void;
|
|
78
92
|
export declare function registerActionTools(server: McpServer, getDriver: () => Driver | null): void;
|
|
@@ -18,7 +18,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
18
18
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
19
19
|
import { z } from "zod";
|
|
20
20
|
import { createDriver } from "./driver.js";
|
|
21
|
-
import { createEventTee } from "./event-tee.js";
|
|
21
|
+
import { createEventTee, resolveEventLogPath } from "./event-tee.js";
|
|
22
22
|
import { PROTOCOL_VERSION } from "./protocol.js";
|
|
23
23
|
const ARG_ALLOW = /^(--agent-[a-z-]+(=.*)?|--mock-llm(=.+)?|--profile=[a-zA-Z0-9_-]+)$/;
|
|
24
24
|
const ENV_KEY_RE = /^[A-Z_][A-Z0-9_]{0,63}$/;
|
|
@@ -50,6 +50,25 @@ export function sanitizeEnv(env) {
|
|
|
50
50
|
}
|
|
51
51
|
return out;
|
|
52
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Build the child TUI's environment for tui.start.
|
|
55
|
+
*
|
|
56
|
+
* Starts from the driver's own env so PATH/HOME survive, overlays the
|
|
57
|
+
* caller-supplied keys, THEN strips the dangerous ones. The merge-before-strip
|
|
58
|
+
* order keeps the security posture (NODE_OPTIONS/LD_PRELOAD/… still removed)
|
|
59
|
+
* while guaranteeing PATH is present: bun drops the fd 3/4 stdio channels when a
|
|
60
|
+
* child is spawned with a partial env that lacks PATH, which yields a null
|
|
61
|
+
* inWrite and crashes the whole driver on spawn. `base` defaults to process.env
|
|
62
|
+
* and is injectable for tests.
|
|
63
|
+
*/
|
|
64
|
+
export function buildChildEnv(callerEnv = {}, base = process.env) {
|
|
65
|
+
const merged = {};
|
|
66
|
+
for (const [k, v] of Object.entries(base)) {
|
|
67
|
+
if (v !== undefined)
|
|
68
|
+
merged[k] = v;
|
|
69
|
+
}
|
|
70
|
+
return sanitizeEnv({ ...merged, ...callerEnv });
|
|
71
|
+
}
|
|
53
72
|
const REPO_ROOT = process.cwd();
|
|
54
73
|
/**
|
|
55
74
|
* Opt-in extra cwd roots for tui.start, layered ON TOP of the default
|
|
@@ -147,11 +166,17 @@ const FEATURES = [
|
|
|
147
166
|
"snapshot_visual",
|
|
148
167
|
"cell",
|
|
149
168
|
"visual_quality",
|
|
169
|
+
"wait_for_event",
|
|
170
|
+
"event_log",
|
|
150
171
|
];
|
|
151
172
|
export function buildCapabilitiesPayload() {
|
|
152
173
|
return {
|
|
153
174
|
protocol: PROTOCOL_VERSION,
|
|
154
175
|
features: FEATURES,
|
|
176
|
+
// A default-on sink nobody can locate is still opt-in. Reporting the
|
|
177
|
+
// resolved path here is what makes it discoverable without the caller
|
|
178
|
+
// reproducing the env/tmpdir/pid rule.
|
|
179
|
+
eventLogPath: resolveEventLogPath(process.env["MUONROI_HARNESS_EVENT_LOG"]),
|
|
155
180
|
};
|
|
156
181
|
}
|
|
157
182
|
export function registerReadTools(server, getDriver) {
|
|
@@ -211,7 +236,13 @@ export function registerReadTools(server, getDriver) {
|
|
|
211
236
|
return noDriver();
|
|
212
237
|
return { content: [{ type: "text", text: String(d.count(selector)) }] };
|
|
213
238
|
});
|
|
214
|
-
server.registerTool("tui.render_text", {
|
|
239
|
+
server.registerTool("tui.render_text", {
|
|
240
|
+
// "debug render" undersold it: this is the primary way to see what is on
|
|
241
|
+
// screen right now — every node, modal and message in the semantic tree.
|
|
242
|
+
description: "See the current screen as a semantic tree (every node, modal, message, and focus state). " +
|
|
243
|
+
"Use tui.render_visual instead for the literal characters a human reads.",
|
|
244
|
+
inputSchema: {},
|
|
245
|
+
}, async () => {
|
|
215
246
|
const d = getDriver();
|
|
216
247
|
if (!d)
|
|
217
248
|
return noDriver();
|
|
@@ -318,21 +349,53 @@ export function registerAsyncTools(server, getDriver, deps) {
|
|
|
318
349
|
const waitConditionShape = {
|
|
319
350
|
selector: z.string().max(500).optional(),
|
|
320
351
|
idle: z.boolean().optional(),
|
|
352
|
+
event: z.string().max(64).optional(),
|
|
321
353
|
};
|
|
354
|
+
/**
|
|
355
|
+
* Rebuild the driver's WaitArgs from validated MCP input.
|
|
356
|
+
*
|
|
357
|
+
* Built key-by-key rather than passed through, because the driver dispatches
|
|
358
|
+
* on `"selector" in args` BEFORE `"event" in args` — an explicitly-`undefined`
|
|
359
|
+
* `selector` key would still win that check and silently wait on nothing.
|
|
360
|
+
*/
|
|
361
|
+
function toWaitArgs(c) {
|
|
362
|
+
if (typeof c.selector === "string")
|
|
363
|
+
return { selector: c.selector };
|
|
364
|
+
if (typeof c.event === "string")
|
|
365
|
+
return { event: c.event };
|
|
366
|
+
if (c.idle)
|
|
367
|
+
return { idle: true };
|
|
368
|
+
return {};
|
|
369
|
+
}
|
|
322
370
|
server.registerTool("tui.wait_for", {
|
|
323
|
-
description: "
|
|
371
|
+
description: "Block until a selector matches, a LiveEvent of the given kind arrives, or the TUI is idle " +
|
|
372
|
+
"(all= requires every condition). Prefer this over polling: the wait resolves on the driver's " +
|
|
373
|
+
"own event stream, so a modal pause (event='askcard-open') or a sprint halt wakes it immediately.",
|
|
324
374
|
inputSchema: {
|
|
325
375
|
selector: z.string().max(500).optional(),
|
|
326
376
|
idle: z.boolean().optional(),
|
|
377
|
+
// The driver has always supported an `event` condition (buildCheck in
|
|
378
|
+
// driver.ts); only this MCP schema withheld it, so external agents fell
|
|
379
|
+
// back to polling a DB that never records askcard-open at all.
|
|
380
|
+
event: z.string().max(64).optional(),
|
|
327
381
|
all: z.array(z.object(waitConditionShape)).max(10).optional(),
|
|
328
|
-
|
|
382
|
+
// 10 min, matching the longest legitimate single wait (a council debate
|
|
383
|
+
// phase measured 119.5s, and an unattended /ideal sprint runs longer).
|
|
384
|
+
// At 60s a caller had to re-issue the wait repeatedly — polling wearing
|
|
385
|
+
// a wait_for costume.
|
|
386
|
+
timeoutMs: z.number().int().min(0).max(600_000).optional(),
|
|
329
387
|
},
|
|
330
388
|
}, async (input) => {
|
|
331
389
|
const d = getDriver();
|
|
332
390
|
if (!d)
|
|
333
391
|
return noDriver();
|
|
392
|
+
const args = input.all
|
|
393
|
+
? { all: input.all.map(toWaitArgs) }
|
|
394
|
+
: toWaitArgs(input);
|
|
395
|
+
if (typeof input.timeoutMs === "number")
|
|
396
|
+
args.timeoutMs = input.timeoutMs;
|
|
334
397
|
try {
|
|
335
|
-
await d.wait_for(
|
|
398
|
+
await d.wait_for(args);
|
|
336
399
|
return { content: [{ type: "text", text: "ok" }] };
|
|
337
400
|
}
|
|
338
401
|
catch (e) {
|
|
@@ -355,7 +418,17 @@ export function registerAsyncTools(server, getDriver, deps) {
|
|
|
355
418
|
return { content: [{ type: "text", text: String(ok) }] };
|
|
356
419
|
});
|
|
357
420
|
server.registerTool("tui.last_event", {
|
|
358
|
-
|
|
421
|
+
// Keyword-loaded on purpose: deferred-tool search matches this text, and
|
|
422
|
+
// the terse original ("Return the most recent event of the given kind")
|
|
423
|
+
// did not rank for "is the TUI waiting for input or hung" — the exact
|
|
424
|
+
// question it answers. An unfindable tool is an absent one.
|
|
425
|
+
description: "Check what a live run is doing: is it waiting on a human (kind='askcard-open' — the " +
|
|
426
|
+
"modal/prompt/approval question), what stage it reached (kind='sprint-stage'), did it " +
|
|
427
|
+
"fail or hang (kind='sprint-halt' / 'toast'), how the council progressed " +
|
|
428
|
+
"(kind='council-step'). Returns the most recent event of that kind with its FULL payload " +
|
|
429
|
+
"(null if none) — including the complete askcard question text, which tui.query truncates. " +
|
|
430
|
+
"Use this instead of polling a database or log file: modal pauses write no DB row, so a " +
|
|
431
|
+
"poller cannot tell 'waiting for a human' from 'hung'. Pair with tui.wait_for to block.",
|
|
359
432
|
// Full protocol event set (minus the idle sentinel) so an external agent can
|
|
360
433
|
// observe lifecycle events — council/sprint/route/askcard, not just toasts.
|
|
361
434
|
// The Driver accepts any kind; this enum is the MCP-boundary validation.
|
|
@@ -473,7 +546,9 @@ export function createMcpHarnessServer({ spawn }) {
|
|
|
473
546
|
isError: true,
|
|
474
547
|
};
|
|
475
548
|
}
|
|
476
|
-
|
|
549
|
+
// See buildChildEnv: merge process.env (keeps PATH so bun allocates the
|
|
550
|
+
// fd 3/4 transport) with caller keys, then strip dangerous vars.
|
|
551
|
+
const sanitizedEnv = buildChildEnv(input.env ?? {});
|
|
477
552
|
// Build final arg list.
|
|
478
553
|
const finalArgs = [...input.args];
|
|
479
554
|
if (input.mockLlmDir && !finalArgs.some((a) => a.startsWith("--mock-llm"))) {
|
|
@@ -509,9 +584,10 @@ export function createMcpHarnessServer({ spawn }) {
|
|
|
509
584
|
sendKey: (k) => sendLine(JSON.stringify({ op: "press", key: k })),
|
|
510
585
|
sendType: (t) => sendLine(JSON.stringify({ op: "type", text: t })),
|
|
511
586
|
});
|
|
512
|
-
//
|
|
513
|
-
// MUONROI_HARNESS_EVENT_LOG
|
|
514
|
-
// visual snapshot so flash events aren't lost
|
|
587
|
+
// JSONL event sink for external milestone watchers — on by default, null
|
|
588
|
+
// only when MUONROI_HARNESS_EVENT_LOG explicitly disables it. Ephemeral
|
|
589
|
+
// kinds carry an at-emit visual snapshot so flash events aren't lost
|
|
590
|
+
// before an agent wakes. Path is reported by tui.capabilities.
|
|
515
591
|
const eventTee = createEventTee(() => driver.render_visual(), process.env["MUONROI_HARNESS_EVENT_LOG"]);
|
|
516
592
|
// onLine already delivers complete newline-stripped lines — no extra
|
|
517
593
|
// splitting required.
|
|
@@ -131,12 +131,34 @@ export type LiveEvent = {
|
|
|
131
131
|
kind: "council-speaker";
|
|
132
132
|
/** The council role label (e.g. "architect", "security", "qa"). */
|
|
133
133
|
role: string;
|
|
134
|
-
/**
|
|
135
|
-
|
|
134
|
+
/**
|
|
135
|
+
* "start" — speaker began; "tick" — 1s progress heartbeat (long phases
|
|
136
|
+
* like research emit these with an advancing `elapsedMs`); "done" —
|
|
137
|
+
* speaker finished. A harness poller distinguishes ALIVE (tick with
|
|
138
|
+
* advancing elapsedMs) from HUNG (elapsedMs frozen) via `tui_last_event`.
|
|
139
|
+
*/
|
|
140
|
+
status: "start" | "tick" | "done";
|
|
136
141
|
/** Round number if available from the status chunk. */
|
|
137
142
|
round?: number;
|
|
138
143
|
/** Correlation ID linking this speaker event to the enclosing council run. */
|
|
139
144
|
correlationId: string;
|
|
145
|
+
/** Milliseconds elapsed in this speaker's turn/phase; advances on ticks. */
|
|
146
|
+
elapsedMs?: number;
|
|
147
|
+
/**
|
|
148
|
+
* Chars (text + reasoning deltas) streamed since this phase began.
|
|
149
|
+
* `elapsedMs` can freeze on a HEALTHY call — its tick generator only
|
|
150
|
+
* advances when the consumer pulls, and a round awaiting its pairs via
|
|
151
|
+
* Promise.all does not pull. This counter is pushed from the token stream
|
|
152
|
+
* itself, so growth here proves liveness regardless of pumping.
|
|
153
|
+
*/
|
|
154
|
+
streamedChars?: number;
|
|
155
|
+
/**
|
|
156
|
+
* Age in ms of the most recent stream delta. Small + growing
|
|
157
|
+
* `streamedChars` = SLOW BUT ALIVE (e.g. a reasoning model emitting
|
|
158
|
+
* reasoning tokens for minutes before any text); growing age + static
|
|
159
|
+
* chars = genuinely STUCK.
|
|
160
|
+
*/
|
|
161
|
+
lastDeltaAgeMs?: number;
|
|
140
162
|
} | {
|
|
141
163
|
t: "event";
|
|
142
164
|
kind: "council-turn-length";
|
|
@@ -62,6 +62,16 @@ export interface MockModelFixture {
|
|
|
62
62
|
* <taskType>,<style>,<intent>,<deliverable>,<depth>,<scope>,<lang>,<clarity>
|
|
63
63
|
*/
|
|
64
64
|
classify?: string;
|
|
65
|
+
/**
|
|
66
|
+
* Milliseconds to wait before each streamed chunk. Default 0 (instant).
|
|
67
|
+
*
|
|
68
|
+
* A real provider takes seconds; the mock is instant, which makes anything
|
|
69
|
+
* that only exists WHILE a call is in flight — a progress card, a spinner —
|
|
70
|
+
* impossible to observe from a spec. Set this to hold the in-flight state open
|
|
71
|
+
* long enough to assert on it. Keep it small: it is wall-clock in every spec
|
|
72
|
+
* that sets it.
|
|
73
|
+
*/
|
|
74
|
+
chunkDelayMs?: number;
|
|
65
75
|
/**
|
|
66
76
|
* Opt in to the PIL-classifier intercept described on `classify`. When true,
|
|
67
77
|
* a `doStream` call carrying the classifier's system prompt is answered with
|
|
@@ -85,6 +85,7 @@ export function createMockModel(fx) {
|
|
|
85
85
|
// delivered to the classifier — intercepting would override their reply with
|
|
86
86
|
// DEFAULT_CLASSIFY_LINE and make every classification collapse to "generate".
|
|
87
87
|
const interceptClassify = fx.autoClassify === true || fx.classify !== undefined;
|
|
88
|
+
const chunkDelayMs = Math.max(0, fx.chunkDelayMs ?? 0);
|
|
88
89
|
const model = new MockLanguageModelV3({
|
|
89
90
|
provider,
|
|
90
91
|
modelId,
|
|
@@ -116,8 +117,11 @@ export function createMockModel(fx) {
|
|
|
116
117
|
return {
|
|
117
118
|
stream: simulateReadableStream({
|
|
118
119
|
chunks,
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
// Only the turn stream honours the delay — the classify intercept above
|
|
121
|
+
// fires on EVERY turn, so pacing it would tax specs that just want a
|
|
122
|
+
// slow answer.
|
|
123
|
+
initialDelayInMs: chunkDelayMs || null,
|
|
124
|
+
chunkDelayInMs: chunkDelayMs || null,
|
|
121
125
|
}),
|
|
122
126
|
};
|
|
123
127
|
},
|
|
@@ -1,26 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* src/chat/chat-keychain.ts
|
|
3
3
|
*
|
|
4
|
-
* Chat-service
|
|
5
|
-
*
|
|
6
|
-
* (
|
|
7
|
-
*
|
|
8
|
-
* Priority: OS keychain (keytar) > environment variable > null.
|
|
9
|
-
* On CLI startup, hydrateChatEnvFromKeychain() populates process.env from
|
|
10
|
-
* keychain for any chat secret not already set in env.
|
|
4
|
+
* Chat-service secret store, keyed by ChatSecretId (Discord, Slack). Backed by
|
|
5
|
+
* the env-store (`.env` file + process.env + Windows registry mirror) — the OS
|
|
6
|
+
* keychain (keytar) has been removed. Reads come straight from process.env.
|
|
11
7
|
*/
|
|
12
8
|
export type ChatSecretId = "discord-token" | "discord-guild-id" | "slack-token" | "slack-team-id";
|
|
13
9
|
export declare function setChatSecret(id: ChatSecretId, value: string): Promise<boolean>;
|
|
14
10
|
export declare function getChatSecret(id: ChatSecretId): Promise<string | null>;
|
|
15
11
|
export declare function deleteChatSecret(id: ChatSecretId): Promise<boolean>;
|
|
16
12
|
/**
|
|
17
|
-
* List all stored chat secret IDs.
|
|
18
|
-
* Note: This only returns secrets stored in OS keychain, not those in env.
|
|
13
|
+
* List all stored chat secret IDs (those present in the environment).
|
|
19
14
|
*/
|
|
20
15
|
export declare function listChatSecrets(): Promise<ChatSecretId[]>;
|
|
21
16
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
17
|
+
* Retained for boot compatibility (index.ts). Chat secrets now live in the
|
|
18
|
+
* environment (loaded from the env-store at startup), so there is nothing to
|
|
19
|
+
* hydrate — this is a no-op.
|
|
25
20
|
*/
|
|
26
21
|
export declare function hydrateChatEnvFromKeychain(): Promise<void>;
|
|
@@ -1,22 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* src/chat/chat-keychain.ts
|
|
3
3
|
*
|
|
4
|
-
* Chat-service
|
|
5
|
-
*
|
|
6
|
-
* (
|
|
7
|
-
*
|
|
8
|
-
* Priority: OS keychain (keytar) > environment variable > null.
|
|
9
|
-
* On CLI startup, hydrateChatEnvFromKeychain() populates process.env from
|
|
10
|
-
* keychain for any chat secret not already set in env.
|
|
4
|
+
* Chat-service secret store, keyed by ChatSecretId (Discord, Slack). Backed by
|
|
5
|
+
* the env-store (`.env` file + process.env + Windows registry mirror) — the OS
|
|
6
|
+
* keychain (keytar) has been removed. Reads come straight from process.env.
|
|
11
7
|
*/
|
|
8
|
+
import { clearEnvVar, persistEnvVar } from "../providers/env-store.js";
|
|
12
9
|
import { redactor } from "../utils/redactor.js";
|
|
13
|
-
const KEYCHAIN_SERVICE = "muonroi-cli";
|
|
14
|
-
const ACCOUNT_BY_CHAT = {
|
|
15
|
-
"discord-token": "chat-discord-token",
|
|
16
|
-
"discord-guild-id": "chat-discord-guild-id",
|
|
17
|
-
"slack-token": "chat-slack-token",
|
|
18
|
-
"slack-team-id": "chat-slack-team-id",
|
|
19
|
-
};
|
|
20
10
|
const ENV_BY_CHAT = {
|
|
21
11
|
"discord-token": "MUONROI_DISCORD_TOKEN",
|
|
22
12
|
"discord-guild-id": "MUONROI_DISCORD_GUILD_ID",
|
|
@@ -24,107 +14,50 @@ const ENV_BY_CHAT = {
|
|
|
24
14
|
"slack-team-id": "MUONROI_SLACK_TEAM_ID",
|
|
25
15
|
};
|
|
26
16
|
const MIN_LEN = 8; // guild IDs are short (18-19 digits); tokens ~70 chars. Use 8 as floor.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return (await import("keytar"));
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
17
|
+
function isTokenSecret(id) {
|
|
18
|
+
return id === "discord-token" || id === "slack-token";
|
|
34
19
|
}
|
|
35
20
|
export async function setChatSecret(id, value) {
|
|
36
21
|
if (!value || value.length < MIN_LEN) {
|
|
37
22
|
throw new Error(`Value for chat secret '${id}' is too short (< ${MIN_LEN} chars).`);
|
|
38
23
|
}
|
|
39
|
-
|
|
40
|
-
if (!kt?.setPassword)
|
|
41
|
-
return false;
|
|
42
|
-
// Enroll token values in redactor, but not IDs (they are not secrets)
|
|
43
|
-
if (id === "discord-token" || id === "slack-token") {
|
|
24
|
+
if (isTokenSecret(id))
|
|
44
25
|
redactor.enrollSecret(value);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
await kt.setPassword(KEYCHAIN_SERVICE, ACCOUNT_BY_CHAT[id], value);
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
50
|
-
catch (err) {
|
|
51
|
-
// Runtime backend failure (e.g. Linux without libsecret or no active secret service).
|
|
52
|
-
if (process.env.DEBUG || process.env.MUONROI_DEBUG_KEYCHAIN) {
|
|
53
|
-
console.error(`[chat-keychain] setPassword backend error for ${id}:`, err?.message || err);
|
|
54
|
-
}
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
26
|
+
persistEnvVar(ENV_BY_CHAT[id], value);
|
|
27
|
+
return true;
|
|
57
28
|
}
|
|
58
29
|
export async function getChatSecret(id) {
|
|
59
|
-
const kt = await loadKeytar();
|
|
60
|
-
if (kt) {
|
|
61
|
-
try {
|
|
62
|
-
const v = await kt.getPassword(KEYCHAIN_SERVICE, ACCOUNT_BY_CHAT[id]);
|
|
63
|
-
if (v && v.length >= MIN_LEN) {
|
|
64
|
-
// Enroll in redactor only for token values
|
|
65
|
-
if (id === "discord-token" || id === "slack-token") {
|
|
66
|
-
redactor.enrollSecret(v);
|
|
67
|
-
}
|
|
68
|
-
return v;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
catch {
|
|
72
|
-
/* keytar backend failure → fall through to env */
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
30
|
const envKey = process.env[ENV_BY_CHAT[id]];
|
|
76
31
|
if (envKey && envKey.length >= MIN_LEN) {
|
|
77
|
-
|
|
78
|
-
if (id === "discord-token" || id === "slack-token") {
|
|
32
|
+
if (isTokenSecret(id))
|
|
79
33
|
redactor.enrollSecret(envKey);
|
|
80
|
-
}
|
|
81
34
|
return envKey;
|
|
82
35
|
}
|
|
83
36
|
return null;
|
|
84
37
|
}
|
|
85
38
|
export async function deleteChatSecret(id) {
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
return await kt.deletePassword(KEYCHAIN_SERVICE, ACCOUNT_BY_CHAT[id]);
|
|
91
|
-
}
|
|
92
|
-
catch (err) {
|
|
93
|
-
if (process.env.DEBUG || process.env.MUONROI_DEBUG_KEYCHAIN) {
|
|
94
|
-
console.error(`[chat-keychain] deletePassword backend error for ${id}:`, err?.message || err);
|
|
95
|
-
}
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
39
|
+
const had = !!process.env[ENV_BY_CHAT[id]];
|
|
40
|
+
clearEnvVar(ENV_BY_CHAT[id]);
|
|
41
|
+
return had;
|
|
98
42
|
}
|
|
99
43
|
/**
|
|
100
|
-
* List all stored chat secret IDs.
|
|
101
|
-
* Note: This only returns secrets stored in OS keychain, not those in env.
|
|
44
|
+
* List all stored chat secret IDs (those present in the environment).
|
|
102
45
|
*/
|
|
103
46
|
export async function listChatSecrets() {
|
|
104
47
|
const ids = ["discord-token", "discord-guild-id", "slack-token", "slack-team-id"];
|
|
105
48
|
const stored = [];
|
|
106
49
|
for (const id of ids) {
|
|
107
|
-
|
|
108
|
-
if (v) {
|
|
50
|
+
if (await getChatSecret(id))
|
|
109
51
|
stored.push(id);
|
|
110
|
-
}
|
|
111
52
|
}
|
|
112
53
|
return stored;
|
|
113
54
|
}
|
|
114
55
|
/**
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
56
|
+
* Retained for boot compatibility (index.ts). Chat secrets now live in the
|
|
57
|
+
* environment (loaded from the env-store at startup), so there is nothing to
|
|
58
|
+
* hydrate — this is a no-op.
|
|
118
59
|
*/
|
|
119
60
|
export async function hydrateChatEnvFromKeychain() {
|
|
120
|
-
|
|
121
|
-
for (const id of ids) {
|
|
122
|
-
const envName = ENV_BY_CHAT[id];
|
|
123
|
-
if (process.env[envName])
|
|
124
|
-
continue; // env wins
|
|
125
|
-
const v = await getChatSecret(id);
|
|
126
|
-
if (v)
|
|
127
|
-
process.env[envName] = v;
|
|
128
|
-
}
|
|
61
|
+
// Intentional no-op: env-store already populated process.env at startup.
|
|
129
62
|
}
|
|
130
63
|
//# sourceMappingURL=chat-keychain.js.map
|