muonroi-cli 1.7.2 → 1.8.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/LICENSE +21 -21
- package/README.md +122 -122
- package/dist/packages/agent-harness-core/src/event-filter.js +1 -0
- package/dist/packages/agent-harness-core/src/event-redact.js +8 -0
- package/dist/packages/agent-harness-core/src/mcp-server.d.ts +16 -0
- package/dist/packages/agent-harness-core/src/mcp-server.js +88 -10
- package/dist/packages/agent-harness-core/src/protocol.d.ts +15 -0
- package/dist/packages/agent-harness-core/src/selector.js +2 -2
- package/dist/packages/agent-harness-core/src/transports/sidechannel.js +1 -1
- package/dist/packages/agent-harness-opentui/src/agent-mode.js +1 -1
- package/dist/packages/agent-harness-opentui/src/install.js +1 -1
- package/dist/src/__test-stubs__/vitest-setup.js +4 -0
- package/dist/src/agent-harness/__tests__/mock-model.spec.js +54 -0
- package/dist/src/agent-harness/mock-model.d.ts +1 -1
- package/dist/src/agent-harness/mock-model.js +32 -3
- package/dist/src/agent-harness/test-spawn.js +3 -1
- package/dist/src/chat/chat-keychain.js +9 -1
- package/dist/src/cli/cost-forensics.js +12 -12
- package/dist/src/cli/keys.d.ts +2 -2
- package/dist/src/cli/keys.js +107 -28
- package/dist/src/cli/usage-report.js +2 -2
- package/dist/src/council/__tests__/abort-threading.test.d.ts +1 -0
- package/dist/src/council/__tests__/abort-threading.test.js +193 -0
- package/dist/src/council/__tests__/clarification-prompt.test.js +21 -1
- package/dist/src/council/__tests__/council-turn-length-emit.test.d.ts +1 -0
- package/dist/src/council/__tests__/council-turn-length-emit.test.js +130 -0
- package/dist/src/council/__tests__/debate-round-budget.test.d.ts +1 -0
- package/dist/src/council/__tests__/debate-round-budget.test.js +45 -0
- package/dist/src/council/__tests__/evaluator-metrics.test.js +3 -0
- package/dist/src/council/__tests__/post-debate-recommendation.test.d.ts +1 -0
- package/dist/src/council/__tests__/post-debate-recommendation.test.js +58 -0
- package/dist/src/council/__tests__/research-tools.test.js +4 -0
- package/dist/src/council/__tests__/round-tools.test.js +6 -0
- package/dist/src/council/clarifier-question-cap.test.d.ts +12 -0
- package/dist/src/council/clarifier-question-cap.test.js +73 -0
- package/dist/src/council/clarifier.d.ts +29 -1
- package/dist/src/council/clarifier.js +64 -1
- package/dist/src/council/debate-planner.d.ts +2 -1
- package/dist/src/council/debate-planner.js +5 -3
- package/dist/src/council/debate.d.ts +9 -0
- package/dist/src/council/debate.js +88 -8
- package/dist/src/council/index.d.ts +46 -1
- package/dist/src/council/index.js +125 -19
- package/dist/src/council/llm.js +41 -13
- package/dist/src/council/prompts.js +56 -21
- package/dist/src/council/types.d.ts +14 -1
- package/dist/src/ee/__tests__/export-transcripts.test.js +1 -1
- package/dist/src/ee/bridge.d.ts +3 -1
- package/dist/src/ee/bridge.js +9 -3
- package/dist/src/ee/bridge.test.js +1 -2
- package/dist/src/ee/client.js +15 -2
- package/dist/src/ee/recall-ledger.d.ts +20 -0
- package/dist/src/ee/recall-ledger.js +40 -1
- package/dist/src/ee/search.d.ts +25 -0
- package/dist/src/ee/search.js +73 -0
- package/dist/src/ee/types.d.ts +15 -1
- package/dist/src/ee/who-am-i.d.ts +41 -0
- package/dist/src/ee/who-am-i.js +143 -0
- package/dist/src/ee/who-am-i.test.d.ts +1 -0
- package/dist/src/ee/who-am-i.test.js +101 -0
- package/dist/src/generated/version.d.ts +1 -1
- package/dist/src/generated/version.js +1 -1
- package/dist/src/headless/council-answers.js +1 -0
- package/dist/src/headless/output.d.ts +30 -1
- package/dist/src/headless/output.js +180 -0
- package/dist/src/headless/output.test.js +61 -1
- package/dist/src/hooks/index.js +48 -2
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +55 -60
- package/dist/src/lsp/client-normalize.test.d.ts +12 -0
- package/dist/src/lsp/client-normalize.test.js +34 -0
- package/dist/src/lsp/client.d.ts +1 -0
- package/dist/src/lsp/client.js +10 -2
- package/dist/src/lsp/manager.d.ts +1 -1
- package/dist/src/lsp/manager.js +2 -2
- package/dist/src/lsp/manager.test.js +2 -2
- package/dist/src/lsp/runtime.d.ts +1 -1
- package/dist/src/lsp/runtime.js +2 -2
- package/dist/src/maintain/__tests__/gh-create-pr.test.js +7 -3
- package/dist/src/maintain/__tests__/pr-builder.test.js +7 -3
- package/dist/src/mcp/__tests__/client-pool.spec.js +3 -3
- package/dist/src/mcp/__tests__/ee-tools.test.js +42 -14
- package/dist/src/mcp/__tests__/forensics-tools.test.js +3 -3
- package/dist/src/mcp/__tests__/lsp-tools.test.js +4 -4
- package/dist/src/mcp/client-pool.d.ts +1 -1
- package/dist/src/mcp/client-pool.js +25 -9
- package/dist/src/mcp/ee-tools.d.ts +10 -0
- package/dist/src/mcp/ee-tools.js +34 -0
- package/dist/src/mcp/oauth-callback.js +2 -2
- package/dist/src/mcp/parse-headers.test.js +14 -14
- package/dist/src/mcp/self-verify-jobs.js +1 -3
- package/dist/src/mcp/setup-guide-text.js +74 -74
- package/dist/src/mcp/smoke.test.js +43 -43
- package/dist/src/models/catalog-gemini.test.js +12 -10
- package/dist/src/models/catalog.json +106 -38
- package/dist/src/ops/doctor.js +7 -7
- package/dist/src/orchestrator/__tests__/auto-commit.test.d.ts +1 -0
- package/dist/src/orchestrator/__tests__/auto-commit.test.js +142 -0
- package/dist/src/orchestrator/__tests__/batch-turn-runner.test.js +2 -0
- package/dist/src/orchestrator/__tests__/delegations.test.d.ts +1 -0
- package/dist/src/orchestrator/__tests__/delegations.test.js +96 -0
- package/dist/src/orchestrator/__tests__/message-processor.test.js +1 -0
- package/dist/src/orchestrator/__tests__/stream-runner.test.js +29 -0
- package/dist/src/orchestrator/agent.test.js +5 -2
- package/dist/src/orchestrator/auto-commit.d.ts +110 -0
- package/dist/src/orchestrator/auto-commit.js +364 -0
- package/dist/src/orchestrator/batch-turn-runner.d.ts +3 -2
- package/dist/src/orchestrator/batch-turn-runner.js +2 -1
- package/dist/src/orchestrator/compaction-proposer-prompt.d.ts +8 -0
- package/dist/src/orchestrator/compaction-proposer-prompt.js +43 -0
- package/dist/src/orchestrator/compaction.d.ts +22 -0
- package/dist/src/orchestrator/compaction.js +42 -0
- package/dist/src/orchestrator/delegations.d.ts +1 -0
- package/dist/src/orchestrator/delegations.js +41 -0
- package/dist/src/orchestrator/error-utils.js +27 -2
- package/dist/src/orchestrator/message-processor.d.ts +21 -5
- package/dist/src/orchestrator/message-processor.js +219 -2573
- package/dist/src/orchestrator/orchestrator.d.ts +8 -2
- package/dist/src/orchestrator/orchestrator.js +187 -34
- package/dist/src/orchestrator/preprocessor.d.ts +13 -0
- package/dist/src/orchestrator/preprocessor.js +145 -0
- package/dist/src/orchestrator/prompts.d.ts +8 -0
- package/dist/src/orchestrator/prompts.js +76 -60
- package/dist/src/orchestrator/safety-askcard.d.ts +66 -0
- package/dist/src/orchestrator/safety-askcard.js +74 -0
- package/dist/src/orchestrator/sandbox.test.js +8 -5
- package/dist/src/orchestrator/scope-ceiling.js +1 -1
- package/dist/src/orchestrator/stall-watchdog.d.ts +47 -0
- package/dist/src/orchestrator/stall-watchdog.js +23 -0
- package/dist/src/orchestrator/stall-watchdog.test.js +36 -1
- package/dist/src/orchestrator/stream-runner.js +52 -30
- package/dist/src/orchestrator/sub-agent-model-tier.js +1 -1
- package/dist/src/orchestrator/subagent-compactor.d.ts +14 -0
- package/dist/src/orchestrator/subagent-compactor.js +83 -10
- package/dist/src/orchestrator/subagent-compactor.spec.js +132 -0
- package/dist/src/orchestrator/text-tool-call-detector.d.ts +9 -5
- package/dist/src/orchestrator/text-tool-call-detector.js +16 -12
- package/dist/src/orchestrator/text-tool-call-detector.test.js +66 -8
- package/dist/src/orchestrator/tool-args-repair.js +1 -1
- package/dist/src/orchestrator/tool-engine.d.ts +151 -0
- package/dist/src/orchestrator/tool-engine.js +3079 -0
- package/dist/src/orchestrator/turn-runner-deps.d.ts +1 -0
- package/dist/src/pil/__tests__/discovery-types.test.js +15 -4
- package/dist/src/pil/__tests__/discovery.test.js +89 -145
- package/dist/src/pil/__tests__/dual-run.test.js +2 -0
- package/dist/src/pil/__tests__/layer1-intent-trace.test.js +3 -0
- package/dist/src/pil/__tests__/layer1-intent.test.js +3 -0
- package/dist/src/pil/__tests__/layer16-clarity.test.js +35 -32
- package/dist/src/pil/__tests__/layer18-acceptance.test.js +1 -78
- package/dist/src/pil/__tests__/layer2_5-ponytail.test.d.ts +1 -0
- package/dist/src/pil/__tests__/layer2_5-ponytail.test.js +53 -0
- package/dist/src/pil/__tests__/layer3-ee-injection.test.js +67 -4
- package/dist/src/pil/__tests__/layer3-injected-chunk.test.js +14 -2
- package/dist/src/pil/__tests__/layer4-gsd.test.js +30 -0
- package/dist/src/pil/__tests__/layer5-context.test.js +39 -1
- package/dist/src/pil/__tests__/layer6-output.test.js +30 -2
- package/dist/src/pil/__tests__/orchestrator-integration.test.js +3 -1
- package/dist/src/pil/__tests__/pipeline.test.js +11 -8
- package/dist/src/pil/__tests__/response-tools.test.js +52 -0
- package/dist/src/pil/agent-operating-contract.d.ts +1 -1
- package/dist/src/pil/agent-operating-contract.js +5 -3
- package/dist/src/pil/agent-operating-contract.test.js +2 -2
- package/dist/src/pil/cheap-model-playbook.d.ts +1 -1
- package/dist/src/pil/cheap-model-playbook.js +1 -1
- package/dist/src/pil/cheap-model-workbooks.d.ts +1 -1
- package/dist/src/pil/cheap-model-workbooks.js +1 -1
- package/dist/src/pil/config.d.ts +20 -0
- package/dist/src/pil/config.js +35 -0
- package/dist/src/pil/discovery-types.d.ts +32 -9
- package/dist/src/pil/discovery.d.ts +3 -3
- package/dist/src/pil/discovery.js +138 -244
- package/dist/src/pil/index.d.ts +1 -1
- package/dist/src/pil/index.js +1 -1
- package/dist/src/pil/layer1-intent.d.ts +9 -1
- package/dist/src/pil/layer1-intent.js +99 -44
- package/dist/src/pil/layer1-intent.test.js +112 -2
- package/dist/src/pil/layer15-context-scan.js +1 -1
- package/dist/src/pil/layer16-clarity.d.ts +21 -25
- package/dist/src/pil/layer16-clarity.js +52 -88
- package/dist/src/pil/layer18-acceptance.d.ts +13 -3
- package/dist/src/pil/layer18-acceptance.js +1 -75
- package/dist/src/pil/layer2-personality.js +7 -8
- package/dist/src/pil/layer2_5-ponytail.d.ts +2 -0
- package/dist/src/pil/layer2_5-ponytail.js +41 -0
- package/dist/src/pil/layer3-ee-injection.js +180 -14
- package/dist/src/pil/layer4-gsd.js +22 -8
- package/dist/src/pil/layer5-context.d.ts +1 -0
- package/dist/src/pil/layer5-context.js +26 -11
- package/dist/src/pil/layer6-output.js +23 -5
- package/dist/src/pil/llm-classify.js +3 -3
- package/dist/src/pil/native-capabilities-workbook.d.ts +1 -1
- package/dist/src/pil/native-capabilities-workbook.js +4 -3
- package/dist/src/pil/pipeline.js +15 -1
- package/dist/src/pil/response-tools.d.ts +16 -0
- package/dist/src/pil/response-tools.js +41 -0
- package/dist/src/pil/schema.d.ts +8 -0
- package/dist/src/pil/schema.js +24 -3
- package/dist/src/pil/task-tier-map.js +7 -7
- package/dist/src/pil/types.d.ts +9 -3
- package/dist/src/product-loop/__tests__/discovery-interview.test.js +103 -1
- package/dist/src/product-loop/__tests__/gather-selectable-alts.test.js +34 -0
- package/dist/src/product-loop/__tests__/sprint-self-verify.test.js +12 -12
- package/dist/src/product-loop/discovery-interview.d.ts +20 -0
- package/dist/src/product-loop/discovery-interview.js +121 -7
- package/dist/src/product-loop/done-gate.js +3 -3
- package/dist/src/product-loop/gather.js +19 -5
- package/dist/src/product-loop/loop-driver.js +20 -20
- package/dist/src/product-loop/progress-snapshot.js +4 -4
- package/dist/src/product-loop/sprint-self-verify.js +2 -2
- package/dist/src/providers/__tests__/reasoning-roundtrip.test.js +12 -0
- package/dist/src/providers/auth/__tests__/gemini-oauth.test.d.ts +1 -5
- package/dist/src/providers/auth/__tests__/gemini-oauth.test.js +1 -5
- package/dist/src/providers/auth/gcloud.d.ts +28 -0
- package/dist/src/providers/auth/gcloud.js +102 -0
- package/dist/src/providers/auth/gemini-oauth.d.ts +46 -19
- package/dist/src/providers/auth/gemini-oauth.js +292 -105
- package/dist/src/providers/auth/grok-oauth.d.ts +1 -1
- package/dist/src/providers/auth/grok-oauth.js +1 -1
- package/dist/src/providers/auth/registry.js +28 -6
- package/dist/src/providers/gemini.d.ts +1 -1
- package/dist/src/providers/gemini.js +10 -3
- package/dist/src/providers/keychain.d.ts +2 -2
- package/dist/src/providers/keychain.js +20 -2
- package/dist/src/providers/mcp-vision-bridge.js +48 -48
- package/dist/src/providers/openai.js +5 -1
- package/dist/src/providers/runtime.d.ts +1 -1
- package/dist/src/providers/runtime.js +11 -2
- package/dist/src/providers/siliconflow-sse-repair.js +1 -1
- package/dist/src/providers/strategies/google.strategy.d.ts +9 -2
- package/dist/src/providers/strategies/google.strategy.js +159 -9
- package/dist/src/providers/strategies/thinking-mode.js +19 -6
- package/dist/src/providers/types.d.ts +1 -1
- package/dist/src/reporter/index.js +1 -1
- package/dist/src/router/decide.test.js +40 -8
- package/dist/src/router/step-router.d.ts +20 -0
- package/dist/src/router/step-router.js +73 -0
- package/dist/src/scaffold/bb-ecosystem-apply.js +47 -47
- package/dist/src/scaffold/bb-quality-gate.js +5 -5
- package/dist/src/scaffold/continuation-prompt.js +60 -60
- package/dist/src/scaffold/init-new.js +453 -453
- package/dist/src/self-qa/__tests__/scenario-planner.test.js +3 -3
- package/dist/src/self-qa/agentic-loop.js +19 -19
- package/dist/src/self-qa/orchestrator.js +5 -5
- package/dist/src/self-qa/scenario-planner.js +1 -1
- package/dist/src/self-qa/spec-emitter.js +4 -4
- package/dist/src/storage/__tests__/migrations.test.js +2 -2
- package/dist/src/storage/index.d.ts +1 -1
- package/dist/src/storage/index.js +1 -1
- package/dist/src/storage/interaction-log.js +5 -5
- package/dist/src/storage/migrations.js +122 -122
- package/dist/src/storage/session-experience-store.js +4 -4
- package/dist/src/storage/sessions.js +43 -43
- package/dist/src/storage/transcript-view.js +7 -1
- package/dist/src/storage/transcript.d.ts +1 -0
- package/dist/src/storage/transcript.js +55 -0
- package/dist/src/storage/ui-interaction-log.d.ts +17 -0
- package/dist/src/storage/usage.js +14 -14
- package/dist/src/storage/workspaces.js +12 -12
- package/dist/src/tools/__tests__/native-tools.test.js +6 -0
- package/dist/src/tools/bash.d.ts +36 -23
- package/dist/src/tools/bash.js +59 -301
- package/dist/src/tools/bash.test.js +2 -257
- package/dist/src/tools/file-tracker.d.ts +5 -0
- package/dist/src/tools/file-tracker.js +14 -0
- package/dist/src/tools/file.js +2 -0
- package/dist/src/tools/git-safety.d.ts +38 -1
- package/dist/src/tools/git-safety.js +94 -4
- package/dist/src/tools/git-safety.test.js +45 -1
- package/dist/src/tools/native-tools.d.ts +1 -1
- package/dist/src/tools/native-tools.js +56 -0
- package/dist/src/tools/registry-bash-empty-command.test.js +4 -3
- package/dist/src/tools/registry-git-safety.test.js +17 -6
- package/dist/src/tools/registry.d.ts +5 -0
- package/dist/src/tools/registry.js +185 -11
- package/dist/src/types/index.d.ts +7 -1
- package/dist/src/ui/app.d.ts +1 -0
- package/dist/src/ui/app.js +0 -0
- package/dist/src/ui/components/council-question-card.js +1 -0
- package/dist/src/ui/components/message-view.js +37 -3
- package/dist/src/ui/components/structured-response-view.js +7 -0
- package/dist/src/ui/components/task-list-panel.js +3 -12
- package/dist/src/ui/containers/chat-feed.d.ts +40 -0
- package/dist/src/ui/containers/chat-feed.js +66 -0
- package/dist/src/ui/containers/modals-layer.d.ts +87 -0
- package/dist/src/ui/containers/modals-layer.js +18 -0
- package/dist/src/ui/slash/__tests__/menu-parity.test.d.ts +1 -0
- package/dist/src/ui/slash/__tests__/menu-parity.test.js +1 -0
- package/dist/src/ui/slash/__tests__/ponytail.test.d.ts +1 -0
- package/dist/src/ui/slash/__tests__/ponytail.test.js +37 -0
- package/dist/src/ui/slash/council-inspect.js +4 -4
- package/dist/src/ui/slash/export.js +16 -1
- package/dist/src/ui/slash/menu-items.js +1 -0
- package/dist/src/ui/slash/ponytail.d.ts +2 -0
- package/dist/src/ui/slash/ponytail.js +19 -0
- package/dist/src/ui/status-bar/store.js +0 -1
- package/dist/src/ui/status-bar/store.test.js +2 -2
- package/dist/src/ui/use-app-logic.d.ts +26 -0
- package/dist/src/ui/use-app-logic.js +0 -0
- package/dist/src/ui/utils/relaunch.js +1 -1
- package/dist/src/ui/utils/text.d.ts +11 -0
- package/dist/src/ui/utils/text.js +21 -1
- package/dist/src/ui/utils/text.test.js +25 -1
- package/dist/src/ui/utils/tools.js +3 -1
- package/dist/src/usage/cost-log.js +2 -2
- package/dist/src/usage/estimator.d.ts +14 -0
- package/dist/src/usage/estimator.js +22 -0
- package/dist/src/utils/__tests__/compaction-caps.test.d.ts +21 -0
- package/dist/src/utils/__tests__/compaction-caps.test.js +187 -0
- package/dist/src/utils/__tests__/llm-deadline-abort.test.d.ts +1 -0
- package/dist/src/utils/__tests__/llm-deadline-abort.test.js +33 -0
- package/dist/src/utils/clipboard-image.js +23 -23
- package/dist/src/utils/llm-deadline.d.ts +9 -1
- package/dist/src/utils/llm-deadline.js +31 -2
- package/dist/src/utils/permission-mode.d.ts +33 -0
- package/dist/src/utils/permission-mode.js +164 -3
- package/dist/src/utils/permission-mode.test.js +92 -1
- package/dist/src/utils/settings.d.ts +27 -31
- package/dist/src/utils/settings.js +100 -109
- package/dist/src/utils/side-question.js +2 -2
- package/dist/src/utils/skills.js +3 -3
- package/dist/src/verify/__tests__/coverage-parsers.test.js +30 -30
- package/package.json +1 -1
|
@@ -21,6 +21,21 @@ export async function iterateInterview(opts) {
|
|
|
21
21
|
// not web) are deferred unless the user explicitly re-runs with more context.
|
|
22
22
|
const specificity = computePromptSpecificity(opts.idea);
|
|
23
23
|
const skipOptionalForMinimal = specificity === "minimal";
|
|
24
|
+
// G2-b: for a minimal OR well-specified ("detailed") prompt the recommender's
|
|
25
|
+
// primary is high-confidence — minimal picks smallest-scope defaults, detailed
|
|
26
|
+
// respects the stated context. In both cases surfacing a card for EVERY
|
|
27
|
+
// required question (productType/targetPlatform/audience/…) is over-asking the
|
|
28
|
+
// user already complained about: they accept by reflex. Instead auto-accept
|
|
29
|
+
// the recommender primary for required questions and surface ONE summary card
|
|
30
|
+
// (the user gate) listing the assumptions so the user can proceed or adjust.
|
|
31
|
+
// "moderate" prompts keep the per-question cards (genuinely ambiguous). Escape
|
|
32
|
+
// hatch: MUONROI_DISCOVERY_AUTOFILL=0 restores per-question cards everywhere.
|
|
33
|
+
const autoFillRequired = (specificity === "minimal" || specificity === "detailed") && process.env.MUONROI_DISCOVERY_AUTOFILL !== "0";
|
|
34
|
+
const assumed = [];
|
|
35
|
+
// G1 follow-up: keep the recommendation behind each auto-filled assumption so
|
|
36
|
+
// the user-gate "edit: <field>" path can re-render the SAME per-question card
|
|
37
|
+
// (Recommended + alternatives) without a second recommender LLM call.
|
|
38
|
+
const assumedRec = new Map();
|
|
24
39
|
// Build the repo brief ONCE per interview run for existing projects. The
|
|
25
40
|
// brief replaces the Muonroi vendor preamble inside leader prompts so
|
|
26
41
|
// rationales must cite real files, deps, and scripts. Skipped for greenfield —
|
|
@@ -74,12 +89,33 @@ export async function iterateInterview(opts) {
|
|
|
74
89
|
else {
|
|
75
90
|
recommendation = await opts.recommender.leaderRecommend(recInput);
|
|
76
91
|
}
|
|
92
|
+
// G2-b: auto-accept the recommender primary for required questions on a
|
|
93
|
+
// minimal/well-specified prompt — no per-question card. Validated (+ FE
|
|
94
|
+
// policy) so a malformed recommendation falls back to the normal card flow.
|
|
95
|
+
// The assumed answers are surfaced together on the single user-gate card.
|
|
96
|
+
let autoAccepted = false;
|
|
97
|
+
if (autoFillRequired && effectivelyRequired && recommendation.primary?.value != null) {
|
|
98
|
+
const v = recommendation.primary.value;
|
|
99
|
+
const validation = validateAnswer(question.id, v);
|
|
100
|
+
const feLib = question.id === "frontendApproach" ? v?.library : undefined;
|
|
101
|
+
const fePolicyOk = !feLib || isFePolicyAccepted(feLib);
|
|
102
|
+
if (validation.ok && fePolicyOk) {
|
|
103
|
+
await recordRecommendation(flowDir, runId, question.id, toEntry(recommendation), recommendation.costUsd);
|
|
104
|
+
await saveDiscoveryAnswer(flowDir, runId, question.id, v);
|
|
105
|
+
assumed.push({ id: question.id, value: v });
|
|
106
|
+
assumedRec.set(question.id, recommendation);
|
|
107
|
+
autoAccepted = true;
|
|
108
|
+
// Fall through to the post-answer user-gate check below (skip the card).
|
|
109
|
+
}
|
|
110
|
+
}
|
|
77
111
|
// Per-question skip-attempt counter for required questions.
|
|
78
112
|
// Resets when the user provides a real answer (accept/override).
|
|
79
113
|
let skipAttempts = 0;
|
|
80
114
|
const MAX_SKIP_ATTEMPTS = 3;
|
|
81
115
|
const _debugInterview = process.env.MUONROI_DEBUG_LEADER === "1";
|
|
82
|
-
|
|
116
|
+
// When auto-accepted above, the card loop body is skipped entirely (the
|
|
117
|
+
// for-condition is false on entry) and we go straight to the gate check.
|
|
118
|
+
for (; !autoAccepted;) {
|
|
83
119
|
const _iterStart = Date.now();
|
|
84
120
|
if (_debugInterview) {
|
|
85
121
|
process.stderr.write(`[interview-timing] iter-start: ${JSON.stringify({ questionId: question.id, skipAttempts })}\n`);
|
|
@@ -176,14 +212,32 @@ export async function iterateInterview(opts) {
|
|
|
176
212
|
if (refreshed &&
|
|
177
213
|
allRequiredAnswered(refreshed.questionsAnswered, refreshedPlatforms) &&
|
|
178
214
|
!refreshed.userGatePassed) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
215
|
+
// Gate loop: "proceed" exits the whole interview, "abort" throws,
|
|
216
|
+
// "edit-field" re-asks one assumed answer then RE-shows the gate (so the
|
|
217
|
+
// user can revise several before proceeding), "ask-more" falls through to
|
|
218
|
+
// keep iterating optional questions.
|
|
219
|
+
let proceeded = false;
|
|
220
|
+
for (;;) {
|
|
221
|
+
const gate = await opts.userPrompt({
|
|
222
|
+
questionId: "__user_gate__",
|
|
223
|
+
assumptions: assumed.length > 0 ? assumed : undefined,
|
|
224
|
+
});
|
|
225
|
+
if (gate.action === "proceed") {
|
|
226
|
+
await markUserGatePassed(flowDir, runId);
|
|
227
|
+
proceeded = true;
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
if (gate.action === "abort")
|
|
231
|
+
throw new Error("discovery aborted at user gate");
|
|
232
|
+
if (gate.action === "edit-field") {
|
|
233
|
+
await reAskAssumedField(opts, flowDir, runId, gate.fieldId, assumed, assumedRec.get(gate.fieldId));
|
|
234
|
+
continue; // re-render the gate with the updated assumption
|
|
235
|
+
}
|
|
236
|
+
// ask-more: stop gating, continue iterating optional questions
|
|
182
237
|
break;
|
|
183
238
|
}
|
|
184
|
-
if (
|
|
185
|
-
|
|
186
|
-
// ask-more: continue iterating optional questions
|
|
239
|
+
if (proceeded)
|
|
240
|
+
break;
|
|
187
241
|
}
|
|
188
242
|
}
|
|
189
243
|
const finalState = await readDiscoveryState(flowDir, runId);
|
|
@@ -197,6 +251,66 @@ export async function iterateInterview(opts) {
|
|
|
197
251
|
await markDone(flowDir, runId);
|
|
198
252
|
return ctx;
|
|
199
253
|
}
|
|
254
|
+
/**
|
|
255
|
+
* G1 follow-up: re-ask ONE auto-filled assumption from the user gate. Renders
|
|
256
|
+
* the same per-question card (Recommended + alternatives) as the normal flow by
|
|
257
|
+
* reusing the stored recommendation, validates (+ FE policy), persists the new
|
|
258
|
+
* answer, and updates the in-memory `assumed` list so the re-rendered gate shows
|
|
259
|
+
* the revised value. "skip" cancels the edit (keeps the current assumption);
|
|
260
|
+
* "abort" aborts the whole interview, consistent with the per-question card.
|
|
261
|
+
*/
|
|
262
|
+
async function reAskAssumedField(opts, flowDir, runId, fieldId, assumed, recommendation) {
|
|
263
|
+
const question = DISCOVERY_QUESTIONS.find((q) => q.id === fieldId);
|
|
264
|
+
if (!question)
|
|
265
|
+
return; // unknown field id — ignore defensively, keep current value
|
|
266
|
+
for (;;) {
|
|
267
|
+
const ans = await opts.userPrompt({ questionId: fieldId, recommendation });
|
|
268
|
+
if (ans.action === "abort")
|
|
269
|
+
throw new Error("discovery aborted by user");
|
|
270
|
+
let chosenValue;
|
|
271
|
+
if (ans.action === "accept") {
|
|
272
|
+
if (recommendation?.primary?.value == null)
|
|
273
|
+
continue; // nothing to accept — re-prompt
|
|
274
|
+
chosenValue = recommendation.primary.value;
|
|
275
|
+
}
|
|
276
|
+
else if (ans.action === "override") {
|
|
277
|
+
chosenValue = ans.value;
|
|
278
|
+
}
|
|
279
|
+
else if (ans.action === "skip") {
|
|
280
|
+
return; // user backed out of editing this field — keep the existing assumption
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
continue; // more-options / edit-field / proceed / ask-more are no-ops here
|
|
284
|
+
}
|
|
285
|
+
const validation = validateAnswer(fieldId, chosenValue);
|
|
286
|
+
if (!validation.ok) {
|
|
287
|
+
if (fieldId !== "frontendApproach") {
|
|
288
|
+
await opts.userPrompt({ questionId: fieldId, message: validation.reason ?? "invalid answer" });
|
|
289
|
+
}
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (fieldId === "frontendApproach") {
|
|
293
|
+
const lib = chosenValue?.library;
|
|
294
|
+
if (lib && !isFePolicyAccepted(lib)) {
|
|
295
|
+
await opts.userPrompt({
|
|
296
|
+
questionId: fieldId,
|
|
297
|
+
message: "FE policy: library must be shadcn, radix, headlessui, or none",
|
|
298
|
+
});
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (ans.action === "override" && recommendation) {
|
|
303
|
+
await appendUserOverride(flowDir, runId, fieldId, recommendation.primary.value, chosenValue, ans.reason);
|
|
304
|
+
}
|
|
305
|
+
await saveDiscoveryAnswer(flowDir, runId, fieldId, chosenValue);
|
|
306
|
+
const entry = assumed.find((a) => a.id === fieldId);
|
|
307
|
+
if (entry)
|
|
308
|
+
entry.value = chosenValue;
|
|
309
|
+
else
|
|
310
|
+
assumed.push({ id: fieldId, value: chosenValue });
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
200
314
|
function allRequiredAnswered(answered, platforms) {
|
|
201
315
|
const baseRequired = REQUIRED_QUESTION_IDS.every((id) => answered.includes(id));
|
|
202
316
|
if (!baseRequired)
|
|
@@ -142,9 +142,9 @@ async function runCustomerDebate(ctx) {
|
|
|
142
142
|
const criteriaText = ctx.criteria
|
|
143
143
|
.map((c) => `- ${c.id}: ${c.status}${c.evidence ? ` (Evidence: ${c.evidence})` : ""}`)
|
|
144
144
|
.join("\n");
|
|
145
|
-
let conversation = `System: You are in a "Definition of Done" debate.
|
|
146
|
-
PO's goal: Prove the product is ready to ship.
|
|
147
|
-
Customer's goal: Ensure all requirements are met and it's high quality.
|
|
145
|
+
let conversation = `System: You are in a "Definition of Done" debate.
|
|
146
|
+
PO's goal: Prove the product is ready to ship.
|
|
147
|
+
Customer's goal: Ensure all requirements are met and it's high quality.
|
|
148
148
|
Criteria:\n${criteriaText}\n`;
|
|
149
149
|
for (let r = 1; r <= rounds; r++) {
|
|
150
150
|
const poPrompt = `${conversation}\nRound ${r}: PO, explain why this is ready to ship.`;
|
|
@@ -93,15 +93,29 @@ function buildLiveTuiAsk(emit, respondToQuestion) {
|
|
|
93
93
|
export function buildGatherUserPrompt(tuiAsk) {
|
|
94
94
|
return async (args) => {
|
|
95
95
|
if (args.questionId === "__user_gate__") {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
96
|
+
// G2-b: when required answers were auto-filled from the recommender
|
|
97
|
+
// (minimal/well-specified prompt), this single card IS the summary — list
|
|
98
|
+
// the assumptions so the user reviews them in one place instead of N
|
|
99
|
+
// sequential per-question cards. "ask-more" lets them revisit/expand.
|
|
100
|
+
const hasAssumptions = !!args.assumptions && args.assumptions.length > 0;
|
|
101
|
+
const label = hasAssumptions
|
|
102
|
+
? `Assumed from your prompt:\n${args
|
|
103
|
+
.assumptions.map((a) => ` • ${a.id} = ${JSON.stringify(a.value)}`)
|
|
104
|
+
.join("\n")}\n\nProceed, edit one of the assumptions, or ask more to adjust/expand?`
|
|
105
|
+
: "All required questions answered. Proceed to research or ask more?";
|
|
106
|
+
// G1 follow-up: one "edit: <field>" option per auto-filled assumption so a
|
|
107
|
+
// single wrong default can be revised in place — no abort+reprompt or
|
|
108
|
+
// MUONROI_DISCOVERY_AUTOFILL=0. The prefix must match the parse below.
|
|
109
|
+
const EDIT_PREFIX = "edit: ";
|
|
110
|
+
const editOpts = hasAssumptions ? args.assumptions.map((a) => `${EDIT_PREFIX}${a.id}`) : [];
|
|
111
|
+
const choice = await tuiAsk(label, ["proceed", ...editOpts, "ask-more", "abort"]);
|
|
101
112
|
if (choice === "proceed")
|
|
102
113
|
return { action: "proceed" };
|
|
103
114
|
if (choice === "abort")
|
|
104
115
|
return { action: "abort" };
|
|
116
|
+
if (choice.startsWith(EDIT_PREFIX)) {
|
|
117
|
+
return { action: "edit-field", fieldId: choice.slice(EDIT_PREFIX.length) };
|
|
118
|
+
}
|
|
105
119
|
return { action: "ask-more" };
|
|
106
120
|
}
|
|
107
121
|
if (args.message) {
|
|
@@ -39,10 +39,10 @@ function wrapLLMForUsageTracking(llm, ctx) {
|
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
41
|
return {
|
|
42
|
-
generate: (modelId, system, prompt, maxTokens, onUsage) => llm.generate(modelId, system, prompt, maxTokens, (u) => {
|
|
42
|
+
generate: (modelId, system, prompt, maxTokens, onUsage, signal) => llm.generate(modelId, system, prompt, maxTokens, (u) => {
|
|
43
43
|
recorder(modelId)(u);
|
|
44
44
|
onUsage?.(u);
|
|
45
|
-
}),
|
|
45
|
+
}, signal),
|
|
46
46
|
debate: (modelId, system, prompt, signal, persistTrace, options, onUsage) => llm.debate(modelId, system, prompt, signal, persistTrace, options, (u) => {
|
|
47
47
|
recorder(modelId)(u);
|
|
48
48
|
onUsage?.(u);
|
|
@@ -684,24 +684,24 @@ export async function* runLoopDriver(ctx) {
|
|
|
684
684
|
},
|
|
685
685
|
};
|
|
686
686
|
// Synthesize ProductSpec
|
|
687
|
-
const synthesisPrompt = `Synthesize a ProductSpec JSON based on the following:
|
|
688
|
-
Idea: ${ctx.idea}
|
|
689
|
-
Clarified Spec: ${JSON.stringify(clarifiedSpec)}
|
|
690
|
-
Debate Summary: ${debateState.runningSummary}
|
|
691
|
-
Research Findings: ${debateState.researchFindings ?? "N/A"}
|
|
692
|
-
|
|
693
|
-
Output ONLY a JSON object matching this interface:
|
|
694
|
-
interface ProductSpec {
|
|
695
|
-
idea: string;
|
|
696
|
-
persona: string;
|
|
697
|
-
mvp: string[];
|
|
698
|
-
phase2: string[];
|
|
699
|
-
architecture: string;
|
|
700
|
-
ioContract: string;
|
|
701
|
-
folderStructure: string;
|
|
702
|
-
sprintEstimate: number;
|
|
703
|
-
costEstimate: number;
|
|
704
|
-
}
|
|
687
|
+
const synthesisPrompt = `Synthesize a ProductSpec JSON based on the following:
|
|
688
|
+
Idea: ${ctx.idea}
|
|
689
|
+
Clarified Spec: ${JSON.stringify(clarifiedSpec)}
|
|
690
|
+
Debate Summary: ${debateState.runningSummary}
|
|
691
|
+
Research Findings: ${debateState.researchFindings ?? "N/A"}
|
|
692
|
+
|
|
693
|
+
Output ONLY a JSON object matching this interface:
|
|
694
|
+
interface ProductSpec {
|
|
695
|
+
idea: string;
|
|
696
|
+
persona: string;
|
|
697
|
+
mvp: string[];
|
|
698
|
+
phase2: string[];
|
|
699
|
+
architecture: string;
|
|
700
|
+
ioContract: string;
|
|
701
|
+
folderStructure: string;
|
|
702
|
+
sprintEstimate: number;
|
|
703
|
+
costEstimate: number;
|
|
704
|
+
}
|
|
705
705
|
`;
|
|
706
706
|
// The scoping phase's only LLM call. Wrapped so a provider hang/
|
|
707
707
|
// timeout leaves a council_error audit row instead of swallowing the
|
|
@@ -20,10 +20,10 @@ function readLatestSprintStage(runId) {
|
|
|
20
20
|
try {
|
|
21
21
|
const db = getDatabase();
|
|
22
22
|
const row = db
|
|
23
|
-
.prepare(`SELECT metadata_json, created_at
|
|
24
|
-
FROM interaction_logs
|
|
25
|
-
WHERE session_id = ? AND event_type = 'ui_interaction' AND event_subtype = 'sprint_stage'
|
|
26
|
-
ORDER BY id DESC
|
|
23
|
+
.prepare(`SELECT metadata_json, created_at
|
|
24
|
+
FROM interaction_logs
|
|
25
|
+
WHERE session_id = ? AND event_type = 'ui_interaction' AND event_subtype = 'sprint_stage'
|
|
26
|
+
ORDER BY id DESC
|
|
27
27
|
LIMIT 1`)
|
|
28
28
|
.get(runId);
|
|
29
29
|
if (!row)
|
|
@@ -31,10 +31,10 @@ const WATCH_DIRS = ["src/ui/", "src/self-qa/", "src/agent-harness/", "packages/a
|
|
|
31
31
|
function isEnabled(forceEnable) {
|
|
32
32
|
if (forceEnable === true)
|
|
33
33
|
return true;
|
|
34
|
-
const v = process.env
|
|
34
|
+
const v = process.env.MUONROI_SPRINT_SELF_VERIFY;
|
|
35
35
|
if (v === "0" || (v && v.toLowerCase() === "false"))
|
|
36
36
|
return false;
|
|
37
|
-
if (process.env
|
|
37
|
+
if (process.env.CI === "true" || process.env.NODE_ENV === "ci")
|
|
38
38
|
return false;
|
|
39
39
|
// Default: ON. v === "1"/"true" still works; absence of env var also enables.
|
|
40
40
|
return true;
|
|
@@ -179,6 +179,18 @@ describe("transformThinkingModeBody — backfill / disable (code 20015 fix)", ()
|
|
|
179
179
|
expect(asst[0].reasoning_content).toBe("real thought"); // untouched
|
|
180
180
|
expect(asst[1].reasoning_content).toBe(""); // backfilled
|
|
181
181
|
});
|
|
182
|
+
it("A (default): backfills content: '' on a reasoning-only turn that lacks content and tool_calls", () => {
|
|
183
|
+
const body = {
|
|
184
|
+
messages: [
|
|
185
|
+
{ role: "user", content: "go" },
|
|
186
|
+
{ role: "assistant", content: null, reasoning_content: "only thought" },
|
|
187
|
+
],
|
|
188
|
+
};
|
|
189
|
+
const out = transformThinkingModeBody(body);
|
|
190
|
+
const asst = out.messages.find((m) => m.role === "assistant");
|
|
191
|
+
expect(asst.reasoning_content).toBe("only thought");
|
|
192
|
+
expect(asst.content).toBe("");
|
|
193
|
+
});
|
|
182
194
|
it("A (default): does not touch non-assistant messages", () => {
|
|
183
195
|
const body = {
|
|
184
196
|
messages: [
|
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for
|
|
3
|
-
*
|
|
4
|
-
* Login flow (callback server mocked), refresh (expiry pre-emption + mutex),
|
|
5
|
-
* revoke, authHeaders, loadGeminiTokensWithRefresh.
|
|
6
|
-
* No live network calls — all HTTP via mockFetch, no real browser opens.
|
|
2
|
+
* Tests for Agy Google OAuth (replaces legacy gemini public client)
|
|
7
3
|
*/
|
|
8
4
|
export {};
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for
|
|
3
|
-
*
|
|
4
|
-
* Login flow (callback server mocked), refresh (expiry pre-emption + mutex),
|
|
5
|
-
* revoke, authHeaders, loadGeminiTokensWithRefresh.
|
|
6
|
-
* No live network calls — all HTTP via mockFetch, no real browser opens.
|
|
2
|
+
* Tests for Agy Google OAuth (replaces legacy gemini public client)
|
|
7
3
|
*/
|
|
8
4
|
import { describe, expect, it, vi } from "vitest";
|
|
9
5
|
import { GeminiOAuthProvider, loadGeminiTokensWithRefresh } from "../gemini-oauth.js";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/providers/auth/gcloud.ts
|
|
3
|
+
*
|
|
4
|
+
* Reads and refreshes tokens from Google Cloud SDK credentials
|
|
5
|
+
* (~/.config/gcloud/application_default_credentials.json).
|
|
6
|
+
*
|
|
7
|
+
* The gcloud ADC token uses GCP's official OAuth client with cloud-platform
|
|
8
|
+
* scopes, which IS accepted by generativelanguage.googleapis.com (unlike the
|
|
9
|
+
* Agy OAuth token whose scope is restricted to cloudcode-pa).
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* 1. User runs `gcloud auth application-default login` once.
|
|
13
|
+
* 2. This file reads the resulting credentials file, extracts the token,
|
|
14
|
+
* and returns it as an OAuthTokens-compatible object.
|
|
15
|
+
* 3. The token auto-refreshes when expired (using the refresh_token inside
|
|
16
|
+
* the credentials file).
|
|
17
|
+
*/
|
|
18
|
+
import type { OAuthTokens } from "./types.js";
|
|
19
|
+
/**
|
|
20
|
+
* Try to load a token from gcloud ADC.
|
|
21
|
+
* Returns null if gcloud is not installed or no credentials exist.
|
|
22
|
+
*/
|
|
23
|
+
export declare function loadGcloudToken(): Promise<OAuthTokens | null>;
|
|
24
|
+
/**
|
|
25
|
+
* Obtain auth headers from a gcloud-derived OAuthTokens.
|
|
26
|
+
* Compatible with ProviderOAuth.authHeaders() signature.
|
|
27
|
+
*/
|
|
28
|
+
export declare function gcloudAuthHeaders(tokens: OAuthTokens): Record<string, string>;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/providers/auth/gcloud.ts
|
|
3
|
+
*
|
|
4
|
+
* Reads and refreshes tokens from Google Cloud SDK credentials
|
|
5
|
+
* (~/.config/gcloud/application_default_credentials.json).
|
|
6
|
+
*
|
|
7
|
+
* The gcloud ADC token uses GCP's official OAuth client with cloud-platform
|
|
8
|
+
* scopes, which IS accepted by generativelanguage.googleapis.com (unlike the
|
|
9
|
+
* Agy OAuth token whose scope is restricted to cloudcode-pa).
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* 1. User runs `gcloud auth application-default login` once.
|
|
13
|
+
* 2. This file reads the resulting credentials file, extracts the token,
|
|
14
|
+
* and returns it as an OAuthTokens-compatible object.
|
|
15
|
+
* 3. The token auto-refreshes when expired (using the refresh_token inside
|
|
16
|
+
* the credentials file).
|
|
17
|
+
*/
|
|
18
|
+
import { readFile } from "node:fs/promises";
|
|
19
|
+
import { homedir } from "node:os";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
import { OAuthRefreshError } from "./types.js";
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Paths
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
const GCLOUD_ADC_PATH = join(homedir(), ".config", "gcloud", "application_default_credentials.json");
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Loader
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
/**
|
|
30
|
+
* Load gcloud Application Default Credentials file.
|
|
31
|
+
*/
|
|
32
|
+
async function loadADC() {
|
|
33
|
+
try {
|
|
34
|
+
const raw = await readFile(GCLOUD_ADC_PATH, "utf8");
|
|
35
|
+
const creds = JSON.parse(raw);
|
|
36
|
+
if (creds.type !== "authorized_user") {
|
|
37
|
+
// service-account keys have a different shape; skip
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return creds;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Token exchange
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
/**
|
|
50
|
+
* Exchange a gcloud refresh_token for a fresh access_token.
|
|
51
|
+
* Google's OAuth token endpoint accepts gcloud ADC credentials.
|
|
52
|
+
*/
|
|
53
|
+
async function exchangeRefreshToken(clientId, clientSecret, refreshToken) {
|
|
54
|
+
const body = new URLSearchParams({
|
|
55
|
+
client_id: clientId,
|
|
56
|
+
client_secret: clientSecret,
|
|
57
|
+
refresh_token: refreshToken,
|
|
58
|
+
grant_type: "refresh_token",
|
|
59
|
+
});
|
|
60
|
+
let res;
|
|
61
|
+
try {
|
|
62
|
+
res = await fetch("https://oauth2.googleapis.com/token", {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
65
|
+
body,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
throw new OAuthRefreshError("google", `gcloud ADC refresh network error: ${err.message}`);
|
|
70
|
+
}
|
|
71
|
+
if (!res.ok) {
|
|
72
|
+
const text = await res.text().catch(() => "");
|
|
73
|
+
throw new OAuthRefreshError("google", `gcloud ADC refresh failed (${res.status}): ${text}`);
|
|
74
|
+
}
|
|
75
|
+
const data = (await res.json());
|
|
76
|
+
return {
|
|
77
|
+
accessToken: data.access_token,
|
|
78
|
+
refreshToken,
|
|
79
|
+
expiresAt: Date.now() + data.expires_in * 1000,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Public API
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
/**
|
|
86
|
+
* Try to load a token from gcloud ADC.
|
|
87
|
+
* Returns null if gcloud is not installed or no credentials exist.
|
|
88
|
+
*/
|
|
89
|
+
export async function loadGcloudToken() {
|
|
90
|
+
const creds = await loadADC();
|
|
91
|
+
if (!creds)
|
|
92
|
+
return null;
|
|
93
|
+
return exchangeRefreshToken(creds.client_id, creds.client_secret, creds.refresh_token);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Obtain auth headers from a gcloud-derived OAuthTokens.
|
|
97
|
+
* Compatible with ProviderOAuth.authHeaders() signature.
|
|
98
|
+
*/
|
|
99
|
+
export function gcloudAuthHeaders(tokens) {
|
|
100
|
+
return { Authorization: `Bearer ${tokens.accessToken}` };
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=gcloud.js.map
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* src/providers/auth/gemini-oauth.ts
|
|
3
3
|
*
|
|
4
|
-
* Google
|
|
5
|
-
*
|
|
4
|
+
* Agy (Antigravity) Google OAuth implementation.
|
|
5
|
+
* Replaces the old abandoned public gemini-cli OAuth client.
|
|
6
6
|
*
|
|
7
|
-
* Uses
|
|
8
|
-
*
|
|
9
|
-
* packages/core/src/code_assist/oauth2.ts
|
|
10
|
-
* Rationale: MIT-licensed OSS, publicly committed, standard pattern for native
|
|
11
|
-
* CLI apps registered with Google OAuth. Per RFC 8252, client "secrets" in
|
|
12
|
-
* native/CLI apps are NOT confidential — they are embedded in public bundles
|
|
13
|
-
* by design and cannot be kept secret. Override via MUONROI_GOOGLE_CLIENT_SECRET.
|
|
7
|
+
* Uses client credentials extracted from the local agy.exe binary
|
|
8
|
+
* (registered for agy's "oauth-personal" / Code Assist flow).
|
|
14
9
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
10
|
+
* Agy stores tokens in ~/.gemini/oauth_creds.json (legacy path).
|
|
11
|
+
* We auto-import so existing agy logins continue to work.
|
|
12
|
+
*
|
|
13
|
+
* Override with MUONROI_GOOGLE_* env vars if needed.
|
|
14
|
+
*
|
|
15
|
+
* Note: Even though the provider ID is "google" (for model routing),
|
|
16
|
+
* the OAuth registration and naming is now Agy.
|
|
17
17
|
*/
|
|
18
18
|
import type { OAuthCallbackServer } from "../../mcp/oauth-callback.js";
|
|
19
19
|
import type { FetchFn } from "./device-flow.js";
|
|
@@ -21,12 +21,13 @@ import type { OAuthTokens, ProviderOAuth } from "./types.js";
|
|
|
21
21
|
export type CallbackServerFn = (opts: {
|
|
22
22
|
onCode: (code: string, state: string) => void;
|
|
23
23
|
timeoutMs?: number;
|
|
24
|
+
host?: string;
|
|
24
25
|
}) => Promise<OAuthCallbackServer>;
|
|
25
26
|
export type OpenBrowserFn = (url: string) => void;
|
|
26
|
-
export declare class
|
|
27
|
+
export declare class AgyOAuthProvider implements ProviderOAuth {
|
|
27
28
|
readonly providerId: "google";
|
|
28
|
-
private
|
|
29
|
-
private
|
|
29
|
+
private clientId;
|
|
30
|
+
private clientSecret;
|
|
30
31
|
private readonly fetchFn;
|
|
31
32
|
private readonly callbackServerFn;
|
|
32
33
|
private readonly openBrowserFn;
|
|
@@ -46,10 +47,36 @@ export declare class GeminiOAuthProvider implements ProviderOAuth {
|
|
|
46
47
|
authHeaders(tokens: OAuthTokens): Record<string, string>;
|
|
47
48
|
private _fetchUserEmail;
|
|
48
49
|
}
|
|
49
|
-
/**
|
|
50
|
-
export
|
|
50
|
+
/** Back-compat for tests that still reference the old class name. */
|
|
51
|
+
export { AgyOAuthProvider as GeminiOAuthProvider };
|
|
52
|
+
/** Default Agy OAuth provider singleton (uses agy's Google client registration). */
|
|
53
|
+
export declare const agyOAuth: AgyOAuthProvider;
|
|
54
|
+
/** Back-compat alias (some code still references the old name). */
|
|
55
|
+
export declare const geminiOAuth: AgyOAuthProvider;
|
|
56
|
+
/**
|
|
57
|
+
* Load stored OAuth tokens for the Google provider using Agy's registration.
|
|
58
|
+
* - Auto-imports from ~/.gemini/oauth_creds.json (agy storage).
|
|
59
|
+
* - Refreshes using the agy client/secret when near expiry.
|
|
60
|
+
*/
|
|
61
|
+
export declare function loadAgyTokensWithRefresh(oauthProvider?: AgyOAuthProvider): Promise<OAuthTokens | null>;
|
|
62
|
+
/** Back-compat alias for older call sites. */
|
|
63
|
+
export declare const loadGeminiTokensWithRefresh: typeof loadAgyTokensWithRefresh;
|
|
64
|
+
/**
|
|
65
|
+
* Load a custom OAuth token using user-provided GCP OAuth client credentials.
|
|
66
|
+
* Users register their own OAuth client in GCP Console → use these credentials
|
|
67
|
+
* to obtain tokens with the `generativelanguage` scope (not restricted to
|
|
68
|
+
* cloudcode-pa like the Agy client).
|
|
69
|
+
*
|
|
70
|
+
* Settings sources (in priority order):
|
|
71
|
+
* 1. settings.json → providers → google → oauthClientId / oauthClientSecret
|
|
72
|
+
* 2. MUONROI_GOOGLE_CLIENT_ID / MUONROI_GOOGLE_CLIENT_SECRET env vars
|
|
73
|
+
*
|
|
74
|
+
* Returns null when no custom client is configured.
|
|
75
|
+
*/
|
|
76
|
+
export declare function loadCustomOAuthTokens(): Promise<OAuthTokens | null>;
|
|
51
77
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
78
|
+
* Perform a full OAuth login with a custom client (Approach C).
|
|
79
|
+
* Users run `keys login google` with their own GCP OAuth client configured
|
|
80
|
+
* in settings → this opens the browser with the right scopes.
|
|
54
81
|
*/
|
|
55
|
-
export declare function
|
|
82
|
+
export declare function loginWithCustomClient(): Promise<OAuthTokens | null>;
|