@mono-agent/agent-app 0.6.2 → 0.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/README.md +210 -19
- package/dist/adapter-send-proxy.d.ts +18 -0
- package/dist/adapter-send-proxy.d.ts.map +1 -0
- package/dist/adapter-send-proxy.js +229 -0
- package/dist/adapter-send-proxy.js.map +1 -0
- package/dist/adapter-send-tools-main.js +62 -4
- package/dist/adapter-send-tools-main.js.map +1 -1
- package/dist/adapter-send-tools.d.ts +33 -6
- package/dist/adapter-send-tools.d.ts.map +1 -1
- package/dist/adapter-send-tools.js +485 -87
- package/dist/adapter-send-tools.js.map +1 -1
- package/dist/app-config.d.ts.map +1 -1
- package/dist/app-config.js +8 -0
- package/dist/app-config.js.map +1 -1
- package/dist/app.d.ts +2 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +528 -135
- package/dist/app.js.map +1 -1
- package/dist/broadcast-recorder.d.ts.map +1 -1
- package/dist/broadcast-recorder.js +24 -2
- package/dist/broadcast-recorder.js.map +1 -1
- package/dist/channels.d.ts.map +1 -1
- package/dist/channels.js +16 -6
- package/dist/channels.js.map +1 -1
- package/dist/cli.d.ts +76 -4
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2102 -102
- package/dist/cli.js.map +1 -1
- package/dist/config-reference.d.ts.map +1 -1
- package/dist/config-reference.js +195 -6
- package/dist/config-reference.js.map +1 -1
- package/dist/configuration-proposal-main.d.ts +3 -0
- package/dist/configuration-proposal-main.d.ts.map +1 -0
- package/dist/configuration-proposal-main.js +13 -0
- package/dist/configuration-proposal-main.js.map +1 -0
- package/dist/configuration-proposal-tool.d.ts +26 -0
- package/dist/configuration-proposal-tool.d.ts.map +1 -0
- package/dist/configuration-proposal-tool.js +139 -0
- package/dist/configuration-proposal-tool.js.map +1 -0
- package/dist/configured-agent.d.ts +12 -0
- package/dist/configured-agent.d.ts.map +1 -1
- package/dist/configured-agent.js +150 -18
- package/dist/configured-agent.js.map +1 -1
- package/dist/consumer-contract.d.ts +3 -0
- package/dist/consumer-contract.d.ts.map +1 -1
- package/dist/consumer-contract.js +44 -1
- package/dist/consumer-contract.js.map +1 -1
- package/dist/doctor.d.ts +27 -5
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +1197 -101
- package/dist/doctor.js.map +1 -1
- package/dist/first-run-managed-memory.d.ts +39 -0
- package/dist/first-run-managed-memory.d.ts.map +1 -0
- package/dist/first-run-managed-memory.js +521 -0
- package/dist/first-run-managed-memory.js.map +1 -0
- package/dist/first-run-readiness.d.ts +111 -0
- package/dist/first-run-readiness.d.ts.map +1 -0
- package/dist/first-run-readiness.js +757 -0
- package/dist/first-run-readiness.js.map +1 -0
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +88 -3
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +1212 -23
- package/dist/init.js.map +1 -1
- package/dist/interaction-bridge.d.ts +25 -1
- package/dist/interaction-bridge.d.ts.map +1 -1
- package/dist/interaction-bridge.js +281 -19
- package/dist/interaction-bridge.js.map +1 -1
- package/dist/local-configuration.d.ts +56 -0
- package/dist/local-configuration.d.ts.map +1 -0
- package/dist/local-configuration.js +914 -0
- package/dist/local-configuration.js.map +1 -0
- package/dist/memory-command.d.ts +4 -0
- package/dist/memory-command.d.ts.map +1 -1
- package/dist/memory-command.js +751 -27
- package/dist/memory-command.js.map +1 -1
- package/dist/memory-recall.d.ts +38 -10
- package/dist/memory-recall.d.ts.map +1 -1
- package/dist/memory-recall.js +82 -25
- package/dist/memory-recall.js.map +1 -1
- package/dist/memory-retrieval.d.ts +66 -0
- package/dist/memory-retrieval.d.ts.map +1 -0
- package/dist/memory-retrieval.js +332 -0
- package/dist/memory-retrieval.js.map +1 -0
- package/dist/modules/base.d.ts +6 -5
- package/dist/modules/base.d.ts.map +1 -1
- package/dist/modules/base.js +11 -7
- package/dist/modules/base.js.map +1 -1
- package/dist/modules/catalog.d.ts.map +1 -1
- package/dist/modules/catalog.js +54 -20
- package/dist/modules/catalog.js.map +1 -1
- package/dist/modules/index.d.ts +2 -2
- package/dist/modules/index.d.ts.map +1 -1
- package/dist/modules/index.js +1 -1
- package/dist/modules/index.js.map +1 -1
- package/dist/modules/known-tools.d.ts +3 -0
- package/dist/modules/known-tools.d.ts.map +1 -1
- package/dist/modules/known-tools.js +11 -1
- package/dist/modules/known-tools.js.map +1 -1
- package/dist/modules/types.d.ts +20 -6
- package/dist/modules/types.d.ts.map +1 -1
- package/dist/modules/types.js.map +1 -1
- package/dist/package-version.d.ts +3 -0
- package/dist/package-version.d.ts.map +1 -0
- package/dist/package-version.js +14 -0
- package/dist/package-version.js.map +1 -0
- package/dist/pi-auth-store-inspection.d.ts +21 -0
- package/dist/pi-auth-store-inspection.d.ts.map +1 -0
- package/dist/pi-auth-store-inspection.js +120 -0
- package/dist/pi-auth-store-inspection.js.map +1 -0
- package/dist/pi-oauth-login-main.d.ts +3 -0
- package/dist/pi-oauth-login-main.d.ts.map +1 -0
- package/dist/pi-oauth-login-main.js +14 -0
- package/dist/pi-oauth-login-main.js.map +1 -0
- package/dist/pi-oauth-login.d.ts +22 -0
- package/dist/pi-oauth-login.d.ts.map +1 -0
- package/dist/pi-oauth-login.js +79 -0
- package/dist/pi-oauth-login.js.map +1 -0
- package/dist/project-skills.d.ts +40 -0
- package/dist/project-skills.d.ts.map +1 -0
- package/dist/project-skills.js +573 -0
- package/dist/project-skills.js.map +1 -0
- package/dist/provider-setup.d.ts +117 -2
- package/dist/provider-setup.d.ts.map +1 -1
- package/dist/provider-setup.js +1505 -49
- package/dist/provider-setup.js.map +1 -1
- package/dist/readiness-probe-worker.d.ts +12 -0
- package/dist/readiness-probe-worker.d.ts.map +1 -0
- package/dist/readiness-probe-worker.js +276 -0
- package/dist/readiness-probe-worker.js.map +1 -0
- package/dist/readiness-probe.d.ts +100 -0
- package/dist/readiness-probe.d.ts.map +1 -0
- package/dist/readiness-probe.js +780 -0
- package/dist/readiness-probe.js.map +1 -0
- package/dist/request-model-override.d.ts +43 -1
- package/dist/request-model-override.d.ts.map +1 -1
- package/dist/request-model-override.js +178 -19
- package/dist/request-model-override.js.map +1 -1
- package/dist/run-history.d.ts +32 -0
- package/dist/run-history.d.ts.map +1 -0
- package/dist/run-history.js +859 -0
- package/dist/run-history.js.map +1 -0
- package/dist/runs-health.d.ts.map +1 -1
- package/dist/runs-health.js +12 -4
- package/dist/runs-health.js.map +1 -1
- package/dist/runtime-option-extensions.d.ts +14 -0
- package/dist/runtime-option-extensions.d.ts.map +1 -0
- package/dist/runtime-option-extensions.js +98 -0
- package/dist/runtime-option-extensions.js.map +1 -0
- package/dist/runtime-routes.d.ts +9 -0
- package/dist/runtime-routes.d.ts.map +1 -0
- package/dist/runtime-routes.js +13 -0
- package/dist/runtime-routes.js.map +1 -0
- package/dist/sandbox-manager.d.ts +69 -0
- package/dist/sandbox-manager.d.ts.map +1 -0
- package/dist/sandbox-manager.js +856 -0
- package/dist/sandbox-manager.js.map +1 -0
- package/dist/supermemory-plugin.d.ts +54 -0
- package/dist/supermemory-plugin.d.ts.map +1 -0
- package/dist/supermemory-plugin.js +136 -0
- package/dist/supermemory-plugin.js.map +1 -0
- package/dist/tui-command.d.ts +15 -0
- package/dist/tui-command.d.ts.map +1 -1
- package/dist/tui-command.js +46 -6
- package/dist/tui-command.js.map +1 -1
- package/dist/web-command.d.ts +8 -0
- package/dist/web-command.d.ts.map +1 -1
- package/dist/web-command.js +174 -17
- package/dist/web-command.js.map +1 -1
- package/dist/wizard/answers.d.ts +31 -4
- package/dist/wizard/answers.d.ts.map +1 -1
- package/dist/wizard/answers.js +145 -16
- package/dist/wizard/answers.js.map +1 -1
- package/dist/wizard/from-flags.d.ts +7 -0
- package/dist/wizard/from-flags.d.ts.map +1 -1
- package/dist/wizard/from-flags.js +36 -2
- package/dist/wizard/from-flags.js.map +1 -1
- package/dist/wizard/index.d.ts +4 -4
- package/dist/wizard/index.d.ts.map +1 -1
- package/dist/wizard/index.js +2 -2
- package/dist/wizard/index.js.map +1 -1
- package/dist/wizard/model-discovery.d.ts +33 -2
- package/dist/wizard/model-discovery.d.ts.map +1 -1
- package/dist/wizard/model-discovery.js +826 -108
- package/dist/wizard/model-discovery.js.map +1 -1
- package/dist/wizard/presets.d.ts +1 -1
- package/dist/wizard/presets.d.ts.map +1 -1
- package/dist/wizard/presets.js +7 -12
- package/dist/wizard/presets.js.map +1 -1
- package/dist/wizard/prompts.d.ts +56 -9
- package/dist/wizard/prompts.d.ts.map +1 -1
- package/dist/wizard/prompts.js +172 -20
- package/dist/wizard/prompts.js.map +1 -1
- package/dist/wizard/run.d.ts +59 -3
- package/dist/wizard/run.d.ts.map +1 -1
- package/dist/wizard/run.js +1133 -191
- package/dist/wizard/run.js.map +1 -1
- package/package.json +21 -16
- package/resources/srt/package-lock.json +66 -0
- package/resources/srt/package.json +9 -0
- package/schema/mono-agent.config.schema.json +381 -17
- package/skills/mono-agent-composer/SKILL.md +4 -4
- package/skills/mono-agent-composer/references/config-blueprint.md +5 -4
- package/skills/mono-agent-composer/references/discovery-questions.md +8 -8
- package/skills/mono-agent-composer/references/feature-coverage.md +8 -6
- package/skills/mono-agent-composer/references/package-map.md +1 -1
- package/skills/mono-agent-composer/references/playbooks.md +8 -7
- package/skills/mono-agent-composer/references/validation.md +1 -1
package/dist/wizard/run.js
CHANGED
|
@@ -1,15 +1,113 @@
|
|
|
1
1
|
import { stat } from "node:fs/promises";
|
|
2
2
|
import { basename, join } from "node:path";
|
|
3
|
+
import { emitKeypressEvents } from "node:readline";
|
|
3
4
|
import * as p from "@clack/prompts";
|
|
5
|
+
import { parseMonoRuntimeModelReference } from "@mono-agent/runtime-adapter";
|
|
4
6
|
import { findModule } from "../modules/catalog.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
7
|
+
import { hasSensitivePersistedEnvironmentValue } from "../first-run-readiness.js";
|
|
8
|
+
import { DEFAULT_MODEL } from "../modules/base.js";
|
|
9
|
+
import { ALLOW_ALL_TOOLS, APP_TOOL_NAMES, BUILTIN_TOOL_NAMES, isAllowAllTools } from "../modules/known-tools.js";
|
|
10
|
+
import { hasDurableProviderEnvironmentCredential, isProviderSetupPiApiKeyAction, planProviderSetup, providerSetupActionCommandLine, } from "../provider-setup.js";
|
|
11
|
+
import { isSupermemoryPluginInstalled } from "../supermemory-plugin.js";
|
|
12
|
+
import { alwaysOnTools, composeWizardPlan, defaultAnswers, effectiveFallbacks, humanizeAgentName, recommendedToolSelection, referencedSetupModelRefs, } from "./answers.js";
|
|
8
13
|
import { findPreset, presetAnswers } from "./presets.js";
|
|
9
|
-
import { channelSelectOptions, effortSelectOptions, fallbackModelSelectOptions,
|
|
10
|
-
import {
|
|
11
|
-
const LOCAL_PROVIDER_MODEL = /^pi:(?:ollama|lmstudio):/u;
|
|
14
|
+
import { assertConcreteWizardModelRef, channelSelectOptions, creationReviewOptions, CUSTOM_PI_MODEL_OPTION, effortSelectOptions, fallbackModelSelectOptions, formatRouteSafetyMatrix, isMixedRouteChain, memorySelectOptions, MODEL_AUTOCOMPLETE_MAX_ITEMS, modelSelectOptions, piModelSelectOptions, presetSelectOptions, ROUTE_SAFETY_OPTIONS, toolMultiselectOptions, validateWizardAgentName, validateWizardAgentPurpose, wizardCancelIntentForKey, WizardBack, WizardCancelled, WizardExitRequested, } from "./prompts.js";
|
|
15
|
+
import { discoverWizardModelCandidates, formatModelDiscoveryStatus, guidedPiProviderProblem, } from "./model-discovery.js";
|
|
12
16
|
const SANDBOXABLE_TOOLS = new Set(["Bash", "Write", "Edit"]);
|
|
17
|
+
/**
|
|
18
|
+
* Clack intentionally coalesces Escape and Ctrl-C into one cancel sentinel. The
|
|
19
|
+
* wizard owns a scoped keypress observer so the state machine can interpret
|
|
20
|
+
* Escape as Back and Ctrl-C as Exit without changing Clack globally.
|
|
21
|
+
*/
|
|
22
|
+
async function runPrompt(invoke) {
|
|
23
|
+
let intent;
|
|
24
|
+
const input = process.stdin;
|
|
25
|
+
const listener = (_text, key) => {
|
|
26
|
+
intent = wizardCancelIntentForKey(key) ?? intent;
|
|
27
|
+
};
|
|
28
|
+
if (input.isTTY) {
|
|
29
|
+
emitKeypressEvents(input);
|
|
30
|
+
input.on("keypress", listener);
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const value = await invoke();
|
|
34
|
+
if (!p.isCancel(value))
|
|
35
|
+
return value;
|
|
36
|
+
if (intent === "back")
|
|
37
|
+
throw new WizardBack();
|
|
38
|
+
throw new WizardExitRequested();
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
if (input.isTTY)
|
|
42
|
+
input.off("keypress", listener);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function discoverModelsInterruptibly(options) {
|
|
46
|
+
const controller = new AbortController();
|
|
47
|
+
let intent;
|
|
48
|
+
const input = process.stdin;
|
|
49
|
+
const wasRaw = input.isRaw === true;
|
|
50
|
+
const wasFlowing = input.readableFlowing;
|
|
51
|
+
const interrupt = (nextIntent) => {
|
|
52
|
+
intent ??= nextIntent;
|
|
53
|
+
controller.abort();
|
|
54
|
+
};
|
|
55
|
+
const onKeypress = (_text, key) => {
|
|
56
|
+
const nextIntent = wizardCancelIntentForKey(key);
|
|
57
|
+
if (nextIntent !== undefined)
|
|
58
|
+
interrupt(nextIntent);
|
|
59
|
+
};
|
|
60
|
+
const onSigint = () => interrupt("exit");
|
|
61
|
+
if (input.isTTY) {
|
|
62
|
+
emitKeypressEvents(input);
|
|
63
|
+
input.setRawMode?.(true);
|
|
64
|
+
input.resume();
|
|
65
|
+
input.on("keypress", onKeypress);
|
|
66
|
+
}
|
|
67
|
+
process.on("SIGINT", onSigint);
|
|
68
|
+
try {
|
|
69
|
+
const result = await discoverWizardModelCandidates({
|
|
70
|
+
...options,
|
|
71
|
+
abortSignal: controller.signal,
|
|
72
|
+
});
|
|
73
|
+
if (!controller.signal.aborted)
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (!controller.signal.aborted)
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
process.off("SIGINT", onSigint);
|
|
82
|
+
if (input.isTTY) {
|
|
83
|
+
input.off("keypress", onKeypress);
|
|
84
|
+
input.setRawMode?.(wasRaw);
|
|
85
|
+
if (wasFlowing !== true)
|
|
86
|
+
input.pause();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (intent === "back")
|
|
90
|
+
throw new WizardBack();
|
|
91
|
+
throw new WizardExitRequested();
|
|
92
|
+
}
|
|
93
|
+
async function select(options) {
|
|
94
|
+
return runPrompt(() => p.select(options));
|
|
95
|
+
}
|
|
96
|
+
async function autocomplete(options) {
|
|
97
|
+
return runPrompt(() => p.autocomplete(options));
|
|
98
|
+
}
|
|
99
|
+
async function confirm(options) {
|
|
100
|
+
return runPrompt(() => p.confirm(options));
|
|
101
|
+
}
|
|
102
|
+
async function multiselect(options) {
|
|
103
|
+
return runPrompt(() => p.multiselect(options));
|
|
104
|
+
}
|
|
105
|
+
async function textPrompt(options) {
|
|
106
|
+
return runPrompt(() => p.text(options));
|
|
107
|
+
}
|
|
108
|
+
async function passwordPrompt(options) {
|
|
109
|
+
return runPrompt(() => p.password(options));
|
|
110
|
+
}
|
|
13
111
|
/**
|
|
14
112
|
* The interactive `init` wizard: a colourful, step-by-step flow that COLLECTS a
|
|
15
113
|
* {@link WizardAnswers} and hands it back — it never writes anything. The caller
|
|
@@ -25,6 +123,10 @@ export async function runInitWizard(ctx) {
|
|
|
25
123
|
answers: result.answers,
|
|
26
124
|
runProviderSetup: result.runProviderSetup,
|
|
27
125
|
providerSetupSecrets: result.providerSetupSecrets,
|
|
126
|
+
providerEnvironmentSecrets: result.providerEnvironmentSecrets,
|
|
127
|
+
piApiKeyPersistenceByProvider: result.piApiKeyPersistenceByProvider,
|
|
128
|
+
credentialStates: result.credentialStates,
|
|
129
|
+
moduleSecrets: result.moduleSecrets,
|
|
28
130
|
};
|
|
29
131
|
}
|
|
30
132
|
catch (error) {
|
|
@@ -35,149 +137,287 @@ export async function runInitWizard(ctx) {
|
|
|
35
137
|
throw error;
|
|
36
138
|
}
|
|
37
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Re-open an already reviewed setup at the Creation review. The caller keeps
|
|
142
|
+
* ownership of the original state: Escape returns `cancelled`, and no mutable
|
|
143
|
+
* credential or answer state is exposed unless the operator confirms a repair.
|
|
144
|
+
*/
|
|
145
|
+
export async function runSetupRepairWizard(ctx) {
|
|
146
|
+
try {
|
|
147
|
+
p.log.step("Repair setup choices");
|
|
148
|
+
const initialStep = ctx.initialStep ?? 8;
|
|
149
|
+
const result = await collectInteractiveFromSeed(ctx, ctx.answers, {
|
|
150
|
+
initialStep,
|
|
151
|
+
...(initialStep === 8 ? {} : { initialReturnToReviewStep: initialStep }),
|
|
152
|
+
returnToCallerOnReviewBack: true,
|
|
153
|
+
repairState: ctx,
|
|
154
|
+
});
|
|
155
|
+
return {
|
|
156
|
+
status: "answers",
|
|
157
|
+
answers: result.answers,
|
|
158
|
+
runProviderSetup: result.runProviderSetup,
|
|
159
|
+
providerSetupSecrets: result.providerSetupSecrets,
|
|
160
|
+
providerEnvironmentSecrets: result.providerEnvironmentSecrets,
|
|
161
|
+
piApiKeyPersistenceByProvider: result.piApiKeyPersistenceByProvider,
|
|
162
|
+
credentialStates: result.credentialStates,
|
|
163
|
+
moduleSecrets: result.moduleSecrets,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
if (error instanceof WizardBack || error instanceof WizardCancelled) {
|
|
168
|
+
p.log.info("Returning to the preflight recovery menu; previous setup choices were kept.");
|
|
169
|
+
return { status: "cancelled" };
|
|
170
|
+
}
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Re-select only primary/fallback models and effort after a failed model check.
|
|
176
|
+
* Capabilities, module inputs, and observability are copied from `ctx.answers`,
|
|
177
|
+
* and no module secret is requested again. Tool/sandbox choices are re-confirmed
|
|
178
|
+
* when the runtime family crosses the direct-Codex boundary or changes whether
|
|
179
|
+
* a provider-owned Claude/direct-OpenCode tool loop is present.
|
|
180
|
+
*/
|
|
181
|
+
export async function runModelRepairWizard(ctx) {
|
|
182
|
+
try {
|
|
183
|
+
p.log.step("Repair model configuration");
|
|
184
|
+
for (;;) {
|
|
185
|
+
const draft = draftFrom(ctx.answers);
|
|
186
|
+
try {
|
|
187
|
+
const previousFamilies = selectedRuntimeModels(draft).map(runtimeFamily).join(",");
|
|
188
|
+
await promptModelSettings(draft, ctx);
|
|
189
|
+
const nextFamilies = selectedRuntimeModels(draft).map(runtimeFamily).join(",");
|
|
190
|
+
if (previousFamilies !== nextFamilies
|
|
191
|
+
|| !hasExactAllowAllTools(draft)
|
|
192
|
+
|| hasInvalidUniformManagedSrtChain(draft)) {
|
|
193
|
+
p.log.info("The runtime route changed, so tool and sandbox safety choices must be confirmed again.");
|
|
194
|
+
await promptTools(draft);
|
|
195
|
+
await promptSafetyPolicy(draft);
|
|
196
|
+
}
|
|
197
|
+
const answers = toWizardAnswers(draft);
|
|
198
|
+
const plan = await composePlanForCwd(answers, ctx.cwd);
|
|
199
|
+
const providerSetup = await promptProviderSetup(plan, ctx, draft.credentialStates);
|
|
200
|
+
if (!await confirm({ message: "Use this model configuration?", initialValue: true })) {
|
|
201
|
+
return { status: "cancelled" };
|
|
202
|
+
}
|
|
203
|
+
return { status: "answers", answers, ...providerSetup, credentialStates: { ...draft.credentialStates } };
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
if (error instanceof WizardBack) {
|
|
207
|
+
p.log.info("Returning to the preflight recovery menu; previous model choices were kept.");
|
|
208
|
+
return { status: "cancelled" };
|
|
209
|
+
}
|
|
210
|
+
if (error instanceof WizardExitRequested) {
|
|
211
|
+
if (await confirmExitSetup())
|
|
212
|
+
throw new WizardCancelled();
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
if (error instanceof WizardCancelled) {
|
|
221
|
+
p.cancel("Model repair cancelled — previous choices were kept.");
|
|
222
|
+
return { status: "cancelled" };
|
|
223
|
+
}
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
38
227
|
/** Walk the flow (preset or custom), returning the fully collected answers. */
|
|
39
228
|
async function collectAnswers(ctx) {
|
|
40
229
|
p.intro("mono-agent init — let's build your agent");
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
230
|
+
for (;;) {
|
|
231
|
+
let choice;
|
|
232
|
+
try {
|
|
233
|
+
choice = await select({
|
|
234
|
+
message: "Start from…",
|
|
235
|
+
options: presetSelectOptions(),
|
|
236
|
+
initialValue: "__custom__",
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
if (error instanceof WizardBack || error instanceof WizardExitRequested) {
|
|
241
|
+
if (await confirmExitSetup())
|
|
242
|
+
throw new WizardCancelled();
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
try {
|
|
248
|
+
return choice === "__custom__"
|
|
249
|
+
? await collectCustom(ctx)
|
|
250
|
+
: await collectFromPreset(ctx, choice);
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
// Escape from the first detail step returns to the preset chooser. Ctrl-C
|
|
254
|
+
// is handled inside the detail state machine and should not normally leak.
|
|
255
|
+
if (error instanceof WizardBack)
|
|
256
|
+
continue;
|
|
257
|
+
if (error instanceof WizardExitRequested) {
|
|
258
|
+
if (await confirmExitSetup())
|
|
259
|
+
throw new WizardCancelled();
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
throw error;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
49
265
|
}
|
|
50
266
|
/**
|
|
51
267
|
* The full custom flow: model → effort → channels → memory → per-module inputs
|
|
52
268
|
* → tools → sandbox (only if code tools were chosen) → observability → summary.
|
|
53
269
|
*/
|
|
54
270
|
async function collectCustom(ctx) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const discovery = await discoverWizardModelCandidates();
|
|
58
|
-
const discoveredByValue = new Map(discovery.candidates.map((candidate) => [candidate.value, candidate]));
|
|
59
|
-
p.note(formatModelDiscoveryStatus(discovery.statuses), "Model discovery");
|
|
60
|
-
const model = guard(await p.select({
|
|
61
|
-
message: "Which model?",
|
|
62
|
-
options: modelSelectOptions(discovery.candidates),
|
|
63
|
-
initialValue: draft.model,
|
|
64
|
-
}));
|
|
65
|
-
draft.model = await resolveModelSelection(model);
|
|
66
|
-
// Optional fallback chain.
|
|
67
|
-
if (guard(await p.confirm({ message: "Add fallback models?", initialValue: false }))) {
|
|
68
|
-
await promptFallbackModels(draft, discovery.candidates);
|
|
69
|
-
}
|
|
70
|
-
const derivedEffort = discoveredByValue.get(draft.model)?.defaultEffort ?? defaultEffortForModelRef(draft.model);
|
|
71
|
-
const effort = guard(await p.select({
|
|
72
|
-
message: derivedEffort === undefined
|
|
73
|
-
? "Reasoning effort?"
|
|
74
|
-
: `Reasoning effort? (derived from selected model: ${derivedEffort})`,
|
|
75
|
-
options: effortSelectOptions(derivedEffort),
|
|
76
|
-
initialValue: derivedEffort ?? "",
|
|
77
|
-
}));
|
|
78
|
-
draft.effort = effort.length === 0 ? undefined : effort;
|
|
79
|
-
// 2. Channels.
|
|
80
|
-
const channels = guard(await p.multiselect({
|
|
81
|
-
message: "How will you talk to this agent?",
|
|
82
|
-
options: channelSelectOptions(),
|
|
83
|
-
initialValues: ["channel:webhook"],
|
|
84
|
-
required: false,
|
|
85
|
-
}));
|
|
86
|
-
draft.channels = [...channels];
|
|
87
|
-
// 3. Memory (before per-module inputs so a memory module's inputs are prompted).
|
|
88
|
-
const memory = guard(await p.select({
|
|
89
|
-
message: "Should the agent remember across conversations?",
|
|
90
|
-
options: memorySelectOptions(),
|
|
91
|
-
initialValue: "",
|
|
271
|
+
return await collectInteractiveFromSeed(ctx, defaultAnswers({
|
|
272
|
+
name: humanizeAgentName(basename(ctx.cwd)),
|
|
92
273
|
}));
|
|
93
|
-
draft.memory = memory === "" ? undefined : memory;
|
|
94
|
-
// 4. Per-module (non-secret) inputs for the chosen channels + memory.
|
|
95
|
-
await promptModuleInputs(draft);
|
|
96
|
-
// 5. Tools.
|
|
97
|
-
await promptTools(draft);
|
|
98
|
-
// 6. Sandbox — only meaningful once shell/file tools are in play. Allow-all
|
|
99
|
-
// includes Bash/Write/Edit, so the sandbox question must still appear under it.
|
|
100
|
-
if (isAllowAllTools(draft.allowedTools) || draft.allowedTools.some((tool) => SANDBOXABLE_TOOLS.has(tool))) {
|
|
101
|
-
draft.sandbox = guard(await p.confirm({
|
|
102
|
-
message: "Sandbox shell/file tools? (native srt, localhost-only network, fails closed if srt is missing)",
|
|
103
|
-
initialValue: true,
|
|
104
|
-
}));
|
|
105
|
-
}
|
|
106
|
-
// 7. Observability.
|
|
107
|
-
draft.observability = guard(await p.confirm({
|
|
108
|
-
message: "Export traces to Phoenix (best-effort OTLP, sensitive data excluded)?",
|
|
109
|
-
initialValue: false,
|
|
110
|
-
}));
|
|
111
|
-
// 8. Summary + final confirm.
|
|
112
|
-
const providerSetup = await confirmSummary(draft, ctx);
|
|
113
|
-
return { answers: toWizardAnswers(draft), ...providerSetup };
|
|
114
274
|
}
|
|
115
|
-
async function resolveModelSelection(model) {
|
|
275
|
+
async function resolveModelSelection(model, opts) {
|
|
116
276
|
if (model === "__pi_other__") {
|
|
117
|
-
|
|
118
|
-
message: "Pi provider id",
|
|
119
|
-
placeholder: "openai-codex",
|
|
120
|
-
validate: (v) => {
|
|
121
|
-
const value = (v ?? "").trim();
|
|
122
|
-
if (value.length === 0) {
|
|
123
|
-
return "Enter a Pi provider id (e.g. openai-codex, opencode-go, ollama, lmstudio)";
|
|
124
|
-
}
|
|
125
|
-
return value.includes(":") ? "Provider id cannot contain ':'." : undefined;
|
|
126
|
-
},
|
|
127
|
-
})).trim();
|
|
128
|
-
const modelId = guard(await p.text({
|
|
129
|
-
message: "Pi model id",
|
|
130
|
-
placeholder: provider === "openai-codex" ? "gpt-5.5" : "llama3.1:8b",
|
|
131
|
-
validate: (v) => (v ?? "").trim().length === 0
|
|
132
|
-
? "Enter the provider-specific model id (e.g. gpt-5.5, kimi-k2.6, llama3.1:8b)"
|
|
133
|
-
: undefined,
|
|
134
|
-
})).trim();
|
|
135
|
-
return `pi:${provider}:${modelId}`;
|
|
277
|
+
return await promptPiModelSelection(opts);
|
|
136
278
|
}
|
|
137
279
|
if (model === "__other__") {
|
|
138
|
-
|
|
139
|
-
message: "Model reference",
|
|
280
|
+
const resolved = (await textPrompt({
|
|
281
|
+
message: opts.context === "primary" ? "Model reference" : "Fallback model reference",
|
|
140
282
|
placeholder: "pi:ollama:llama3.1:8b",
|
|
141
|
-
validate:
|
|
142
|
-
? "Enter a provider:model reference (e.g. pi:ollama:llama3.1:8b)"
|
|
143
|
-
: undefined,
|
|
283
|
+
validate: validateFullModelReference,
|
|
144
284
|
})).trim();
|
|
285
|
+
assertConcreteWizardModelRef(resolved);
|
|
286
|
+
assertGuidedModelRef(resolved);
|
|
287
|
+
return resolved;
|
|
145
288
|
}
|
|
289
|
+
assertConcreteWizardModelRef(model);
|
|
290
|
+
assertGuidedModelRef(model);
|
|
146
291
|
return model;
|
|
147
292
|
}
|
|
293
|
+
async function promptPiModelSelection(opts) {
|
|
294
|
+
const options = piModelSelectOptions(opts.candidates, opts.excludedModels ?? []);
|
|
295
|
+
const choice = await autocomplete({
|
|
296
|
+
message: opts.context === "primary" ? "Other Pi model" : "Other Pi fallback model",
|
|
297
|
+
options,
|
|
298
|
+
initialValue: options[0]?.value ?? CUSTOM_PI_MODEL_OPTION,
|
|
299
|
+
placeholder: "Type to search Pi providers and models…",
|
|
300
|
+
maxItems: MODEL_AUTOCOMPLETE_MAX_ITEMS,
|
|
301
|
+
});
|
|
302
|
+
if (choice !== CUSTOM_PI_MODEL_OPTION) {
|
|
303
|
+
assertConcreteWizardModelRef(choice);
|
|
304
|
+
return choice;
|
|
305
|
+
}
|
|
306
|
+
return await promptManualPiModelRef();
|
|
307
|
+
}
|
|
308
|
+
async function promptManualPiModelRef() {
|
|
309
|
+
const provider = (await textPrompt({
|
|
310
|
+
message: "Pi provider id",
|
|
311
|
+
placeholder: "openai-codex",
|
|
312
|
+
validate: (v) => {
|
|
313
|
+
const value = (v ?? "").trim();
|
|
314
|
+
if (value.length === 0) {
|
|
315
|
+
return "Enter a supported Pi provider id (anthropic, github-copilot, openai-codex, opencode-go, ollama, or lmstudio)";
|
|
316
|
+
}
|
|
317
|
+
if (value.includes(":"))
|
|
318
|
+
return "Provider id cannot contain ':'.";
|
|
319
|
+
return guidedPiProviderProblem(value);
|
|
320
|
+
},
|
|
321
|
+
})).trim();
|
|
322
|
+
const modelId = (await textPrompt({
|
|
323
|
+
message: "Pi model id",
|
|
324
|
+
placeholder: provider === "openai-codex" ? "gpt-5.6-terra" : "llama3.1:8b",
|
|
325
|
+
validate: (v) => (v ?? "").trim().length === 0
|
|
326
|
+
? "Enter the provider-specific model id (e.g. gpt-5.6-terra, gpt-5.6-sol, kimi-k2.6, llama3.1:8b)"
|
|
327
|
+
: undefined,
|
|
328
|
+
})).trim();
|
|
329
|
+
return `pi:${provider}:${modelId}`;
|
|
330
|
+
}
|
|
331
|
+
function validateFullModelReference(value) {
|
|
332
|
+
const trimmed = (value ?? "").trim();
|
|
333
|
+
if (trimmed.length === 0) {
|
|
334
|
+
return "Enter a provider:model reference (e.g. pi:ollama:llama3.1:8b)";
|
|
335
|
+
}
|
|
336
|
+
if (trimmed.includes(",")) {
|
|
337
|
+
return "Enter one model reference. Add more fallbacks one at a time.";
|
|
338
|
+
}
|
|
339
|
+
try {
|
|
340
|
+
assertConcreteWizardModelRef(trimmed);
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
return error instanceof Error ? error.message : "Enter a concrete model reference.";
|
|
344
|
+
}
|
|
345
|
+
return guidedModelRefProblem(trimmed);
|
|
346
|
+
}
|
|
347
|
+
function assertGuidedModelRef(model) {
|
|
348
|
+
const problem = guidedModelRefProblem(model);
|
|
349
|
+
if (problem !== undefined) {
|
|
350
|
+
throw new Error(problem);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
export function guidedModelRefProblem(model) {
|
|
354
|
+
let parsed;
|
|
355
|
+
try {
|
|
356
|
+
parsed = parseMonoRuntimeModelReference(model);
|
|
357
|
+
}
|
|
358
|
+
catch (error) {
|
|
359
|
+
return error instanceof Error ? error.message : "Enter a concrete model reference.";
|
|
360
|
+
}
|
|
361
|
+
if (parsed.sdk === "opencode") {
|
|
362
|
+
return "Direct OpenCode is scaffold/config-only. Choose pi:opencode-go:* for guided readiness.";
|
|
363
|
+
}
|
|
364
|
+
if (parsed.sdk === "pi" && parsed.provider !== undefined) {
|
|
365
|
+
return guidedPiProviderProblem(parsed.provider);
|
|
366
|
+
}
|
|
367
|
+
return parsed.sdk === "claude" || parsed.sdk === "codex"
|
|
368
|
+
? undefined
|
|
369
|
+
: "Guided init supports Claude, Codex, supported Pi providers, and local Pi routes.";
|
|
370
|
+
}
|
|
148
371
|
/**
|
|
149
372
|
* Build an ordered fallback chain one model at a time. Each step reuses the same
|
|
150
373
|
* discovered model labels as the primary picker while hiding the selected primary
|
|
151
374
|
* and any fallback already chosen. `Other…` is the explicit path for custom refs.
|
|
152
375
|
*/
|
|
153
376
|
async function promptFallbackModels(draft, candidates) {
|
|
377
|
+
const byValue = new Map(candidates.map((candidate) => [candidate.value, candidate]));
|
|
154
378
|
for (;;) {
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
379
|
+
const selectedModels = draft.fallbacks.map((fallback) => fallback.model);
|
|
380
|
+
let choice;
|
|
381
|
+
try {
|
|
382
|
+
choice = await autocomplete({
|
|
383
|
+
message: `Fallback model #${draft.fallbacks.length + 1}`,
|
|
384
|
+
options: fallbackModelSelectOptions(candidates, draft.model, selectedModels),
|
|
385
|
+
initialValue: "__done__",
|
|
386
|
+
placeholder: "Type to search all supported models…",
|
|
387
|
+
maxItems: MODEL_AUTOCOMPLETE_MAX_ITEMS,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
catch (error) {
|
|
391
|
+
if (!(error instanceof WizardBack) || draft.fallbacks.length === 0)
|
|
392
|
+
throw error;
|
|
393
|
+
draft.fallbacks.pop();
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
160
396
|
if (choice === "__done__") {
|
|
161
397
|
return;
|
|
162
398
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
399
|
+
const resolved = await resolveModelSelection(choice, {
|
|
400
|
+
candidates,
|
|
401
|
+
excludedModels: [draft.model, ...selectedModels],
|
|
402
|
+
context: "fallback",
|
|
403
|
+
});
|
|
404
|
+
if (resolved !== draft.model && !selectedModels.includes(resolved)) {
|
|
405
|
+
let effort;
|
|
406
|
+
try {
|
|
407
|
+
effort = await promptEffortForModel(resolved, byValue.get(resolved));
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
if (error instanceof WizardBack)
|
|
411
|
+
continue;
|
|
412
|
+
throw error;
|
|
413
|
+
}
|
|
414
|
+
draft.fallbacks.push({ model: resolved, ...(effort === undefined ? {} : { effort }) });
|
|
173
415
|
}
|
|
174
|
-
draft.fallbackModels.push(choice);
|
|
175
416
|
}
|
|
176
417
|
}
|
|
177
418
|
/**
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
* confirm the summary.
|
|
419
|
+
* Presets seed every choice, but do not silently freeze them. A first run must
|
|
420
|
+
* still choose its model, channels, tool policy, and sandbox safety posture.
|
|
181
421
|
*/
|
|
182
422
|
async function collectFromPreset(ctx, presetId) {
|
|
183
423
|
const preset = findPreset(presetId);
|
|
@@ -186,19 +426,428 @@ async function collectFromPreset(ctx, presetId) {
|
|
|
186
426
|
throw new WizardCancelled();
|
|
187
427
|
}
|
|
188
428
|
p.log.step(`Preset: ${preset.title}`);
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
429
|
+
return await collectInteractiveFromSeed(ctx, defaultAnswers({
|
|
430
|
+
...presetAnswers(preset),
|
|
431
|
+
name: humanizeAgentName(basename(ctx.cwd)),
|
|
432
|
+
}));
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Whether a logical wizard step will actually ask the operator anything for
|
|
436
|
+
* the current draft. Some runtime families make Tools and Safety informative
|
|
437
|
+
* only; treating those as back-navigation destinations traps Escape in a
|
|
438
|
+
* silent forward loop.
|
|
439
|
+
*/
|
|
440
|
+
function wizardStepHasInteractivePrompt(step, draft) {
|
|
441
|
+
switch (step) {
|
|
442
|
+
case 0:
|
|
443
|
+
case 1:
|
|
444
|
+
case 2:
|
|
445
|
+
case 3:
|
|
446
|
+
case 7:
|
|
447
|
+
case 8:
|
|
448
|
+
return true;
|
|
449
|
+
case 4:
|
|
450
|
+
return [...draft.channels, ...(draft.memory === undefined ? [] : [draft.memory])]
|
|
451
|
+
.some((id) => findModule(id)?.inputs.some((input) => input.secret !== true) === true);
|
|
452
|
+
case 5:
|
|
453
|
+
return !selectedRuntimeModels(draft).every(hasFixedAllowAllToolPolicyRef);
|
|
454
|
+
case 6:
|
|
455
|
+
return safetyPolicyHasInteractivePrompt(draft);
|
|
456
|
+
default:
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
function safetyPolicyHasInteractivePrompt(draft) {
|
|
461
|
+
const models = selectedRuntimeModels(draft);
|
|
462
|
+
const mixed = isMixedRouteChain(models);
|
|
463
|
+
if (mixed)
|
|
464
|
+
return true;
|
|
465
|
+
const hasSandboxableTools = isAllowAllTools(draft.allowedTools)
|
|
466
|
+
|| draft.allowedTools.some((tool) => SANDBOXABLE_TOOLS.has(tool));
|
|
467
|
+
const hasPiRoute = models.some((model) => model.startsWith("pi:"));
|
|
468
|
+
const providerNative = models.some((model) => !model.startsWith("pi:"));
|
|
469
|
+
if (hasSandboxableTools && hasPiRoute)
|
|
470
|
+
return true;
|
|
471
|
+
if (draft.routeSafety === "per-route-native" && providerNative)
|
|
472
|
+
return true;
|
|
473
|
+
return hasSandboxableTools
|
|
474
|
+
&& providerNative
|
|
475
|
+
&& !models.every(isDirectCodexRef)
|
|
476
|
+
&& isAllowAllTools(draft.allowedTools);
|
|
477
|
+
}
|
|
478
|
+
function previousInteractiveWizardStep(step, draft) {
|
|
479
|
+
for (let candidate = step - 1; candidate >= 0; candidate -= 1) {
|
|
480
|
+
if (wizardStepHasInteractivePrompt(candidate, draft))
|
|
481
|
+
return candidate;
|
|
482
|
+
}
|
|
483
|
+
return undefined;
|
|
484
|
+
}
|
|
485
|
+
/** Shared custom/preset first-run chooser; seed answers only set sensible defaults. */
|
|
486
|
+
async function collectInteractiveFromSeed(ctx, seed, options = {}) {
|
|
487
|
+
const draft = draftFrom(seed);
|
|
488
|
+
if (options.repairState !== undefined) {
|
|
489
|
+
draft.credentialStates = { ...options.repairState.credentialStates };
|
|
490
|
+
}
|
|
491
|
+
const modelDiscoveryCache = {};
|
|
492
|
+
let step = options.initialStep ?? 0;
|
|
493
|
+
let returnToReviewAfterStep = options.initialReturnToReviewStep;
|
|
494
|
+
const finalStep = 8;
|
|
495
|
+
const advanceAfter = (completedStep) => {
|
|
496
|
+
if (returnToReviewAfterStep === completedStep) {
|
|
497
|
+
returnToReviewAfterStep = undefined;
|
|
498
|
+
step = finalStep;
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
step = completedStep + 1;
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
for (;;) {
|
|
505
|
+
const snapshot = cloneDraft(draft);
|
|
506
|
+
let finalizing = false;
|
|
507
|
+
try {
|
|
508
|
+
switch (step) {
|
|
509
|
+
case 0:
|
|
510
|
+
draft.name = (await textPrompt({
|
|
511
|
+
message: "What should this agent be called?",
|
|
512
|
+
// Keep the current answer visible and editable when Escape returns
|
|
513
|
+
// here from model selection. `defaultValue` only substitutes an
|
|
514
|
+
// empty submission, so Clack would otherwise render the folder-name
|
|
515
|
+
// placeholder and make a preserved custom name look lost.
|
|
516
|
+
initialValue: draft.name,
|
|
517
|
+
placeholder: humanizeAgentName(basename(ctx.cwd)),
|
|
518
|
+
validate: validateWizardAgentName,
|
|
519
|
+
})).trim();
|
|
520
|
+
draft.purpose = (await textPrompt({
|
|
521
|
+
message: "What should this agent help with?",
|
|
522
|
+
initialValue: draft.purpose,
|
|
523
|
+
placeholder: "Help with work in this folder",
|
|
524
|
+
validate: validateWizardAgentPurpose,
|
|
525
|
+
})).trim();
|
|
526
|
+
advanceAfter(0);
|
|
527
|
+
break;
|
|
528
|
+
case 1: {
|
|
529
|
+
const previousFamilies = selectedRuntimeModels(draft).map(runtimeFamily).join(",");
|
|
530
|
+
await promptModelSettings(draft, ctx, modelDiscoveryCache);
|
|
531
|
+
const nextFamilies = selectedRuntimeModels(draft).map(runtimeFamily).join(",");
|
|
532
|
+
if (returnToReviewAfterStep === 1
|
|
533
|
+
&& (previousFamilies !== nextFamilies
|
|
534
|
+
|| !hasExactAllowAllTools(draft)
|
|
535
|
+
|| hasInvalidUniformManagedSrtChain(draft))) {
|
|
536
|
+
p.log.info("The runtime route changed, so tool and sandbox safety choices must be confirmed again.");
|
|
537
|
+
await promptTools(draft);
|
|
538
|
+
await promptSafetyPolicy(draft);
|
|
539
|
+
}
|
|
540
|
+
advanceAfter(1);
|
|
541
|
+
break;
|
|
542
|
+
}
|
|
543
|
+
case 2: {
|
|
544
|
+
const previousChannels = new Set(draft.channels);
|
|
545
|
+
draft.channels = [...await multiselect({
|
|
546
|
+
message: "How will you talk to this agent?",
|
|
547
|
+
options: channelSelectOptions({ readyOnly: true }),
|
|
548
|
+
initialValues: draft.channels,
|
|
549
|
+
required: false,
|
|
550
|
+
})];
|
|
551
|
+
if (returnToReviewAfterStep === 2) {
|
|
552
|
+
await promptModuleInputs(draft, draft.channels.filter((channel) => !previousChannels.has(channel)));
|
|
553
|
+
await promptTools(draft);
|
|
554
|
+
await promptSafetyPolicy(draft);
|
|
555
|
+
}
|
|
556
|
+
advanceAfter(2);
|
|
557
|
+
break;
|
|
558
|
+
}
|
|
559
|
+
case 3: {
|
|
560
|
+
const previousMemory = draft.memory;
|
|
561
|
+
const memory = await select({
|
|
562
|
+
message: "Should the agent remember across conversations?",
|
|
563
|
+
options: memorySelectOptions({
|
|
564
|
+
includeOptionalPlugins: draft.memory === "memory:supermemory"
|
|
565
|
+
|| isSupermemoryPluginInstalled({ cwd: ctx.cwd }),
|
|
566
|
+
}),
|
|
567
|
+
initialValue: draft.memory ?? "",
|
|
568
|
+
});
|
|
569
|
+
draft.memory = memory === "" ? undefined : memory;
|
|
570
|
+
if (returnToReviewAfterStep === 3
|
|
571
|
+
&& draft.memory !== undefined
|
|
572
|
+
&& draft.memory !== previousMemory) {
|
|
573
|
+
await promptModuleInputs(draft, [draft.memory]);
|
|
574
|
+
}
|
|
575
|
+
advanceAfter(3);
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
case 4:
|
|
579
|
+
await promptModuleInputs(draft);
|
|
580
|
+
advanceAfter(4);
|
|
581
|
+
break;
|
|
582
|
+
case 5:
|
|
583
|
+
await promptTools(draft);
|
|
584
|
+
if (returnToReviewAfterStep === 5) {
|
|
585
|
+
await promptSafetyPolicy(draft);
|
|
586
|
+
}
|
|
587
|
+
advanceAfter(5);
|
|
588
|
+
break;
|
|
589
|
+
case 6:
|
|
590
|
+
await promptSafetyPolicy(draft);
|
|
591
|
+
advanceAfter(6);
|
|
592
|
+
break;
|
|
593
|
+
case 7:
|
|
594
|
+
draft.observability = await confirm({
|
|
595
|
+
message: "Export traces to Phoenix (best-effort OTLP, sensitive data excluded)?",
|
|
596
|
+
initialValue: draft.observability,
|
|
597
|
+
});
|
|
598
|
+
advanceAfter(7);
|
|
599
|
+
break;
|
|
600
|
+
case 8: {
|
|
601
|
+
const reviewed = await confirmSummary(draft, ctx, options.repairState === undefined
|
|
602
|
+
? undefined
|
|
603
|
+
: {
|
|
604
|
+
existing: options.repairState,
|
|
605
|
+
});
|
|
606
|
+
if (reviewed.status === "edit") {
|
|
607
|
+
step = reviewed.step;
|
|
608
|
+
returnToReviewAfterStep = reviewed.step === finalStep ? undefined : reviewed.step;
|
|
609
|
+
break;
|
|
610
|
+
}
|
|
611
|
+
finalizing = true;
|
|
612
|
+
const answers = toWizardAnswers(draft);
|
|
613
|
+
const moduleSecrets = await promptRequiredModuleSecrets(answers, ctx.persistedEnv, options.repairState?.moduleSecrets);
|
|
614
|
+
return {
|
|
615
|
+
answers,
|
|
616
|
+
...reviewed.providerSetup,
|
|
617
|
+
moduleSecrets,
|
|
618
|
+
credentialStates: { ...draft.credentialStates },
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
default:
|
|
622
|
+
throw new Error(`Unknown wizard step ${step}.`);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
catch (error) {
|
|
626
|
+
restoreDraft(draft, snapshot);
|
|
627
|
+
if (error instanceof WizardBack) {
|
|
628
|
+
if (options.returnToCallerOnReviewBack === true)
|
|
629
|
+
throw error;
|
|
630
|
+
if (returnToReviewAfterStep === step) {
|
|
631
|
+
returnToReviewAfterStep = undefined;
|
|
632
|
+
step = finalStep;
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
const previous = previousInteractiveWizardStep(step, draft);
|
|
636
|
+
if (previous === undefined)
|
|
637
|
+
throw error;
|
|
638
|
+
// Secret/auth collection happens after the review choice. Escape there
|
|
639
|
+
// returns to the review rather than discarding unrelated answers.
|
|
640
|
+
step = step === finalStep && finalizing ? finalStep : previous;
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
if (error instanceof WizardExitRequested) {
|
|
644
|
+
if (await confirmExitSetup())
|
|
645
|
+
throw new WizardCancelled();
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
throw error;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
async function promptSafetyPolicy(draft) {
|
|
653
|
+
const models = selectedRuntimeModels(draft);
|
|
654
|
+
const mixed = isMixedRouteChain(models);
|
|
655
|
+
if (mixed) {
|
|
656
|
+
draft.routeSafety = await select({
|
|
657
|
+
message: "How should safety apply across this mixed fallback chain?",
|
|
658
|
+
options: [...ROUTE_SAFETY_OPTIONS],
|
|
659
|
+
initialValue: draft.routeSafety,
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
else {
|
|
663
|
+
draft.routeSafety = "uniform";
|
|
664
|
+
}
|
|
665
|
+
const hasSandboxableTools = isAllowAllTools(draft.allowedTools)
|
|
666
|
+
|| draft.allowedTools.some((tool) => SANDBOXABLE_TOOLS.has(tool));
|
|
667
|
+
const hasPiRoute = models.some((model) => model.startsWith("pi:"));
|
|
668
|
+
const providerNative = models.some((model) => !model.startsWith("pi:"));
|
|
669
|
+
const mixedPiProviderNativeChain = mixed && hasPiRoute && providerNative;
|
|
670
|
+
if (hasSandboxableTools && hasPiRoute) {
|
|
671
|
+
draft.sandbox = await confirm({
|
|
672
|
+
message: "Install and use managed SRT for Pi shell/file tools? (localhost-only network; setup verifies fail-closed enforcement)",
|
|
673
|
+
initialValue: true,
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
else {
|
|
677
|
+
draft.sandbox = false;
|
|
678
|
+
}
|
|
679
|
+
if (mixedPiProviderNativeChain && draft.routeSafety === "uniform" && draft.sandbox) {
|
|
680
|
+
p.note("Managed SRT applies to Pi routes only. A mixed Pi/provider-native chain therefore cannot promise one uniform managed-SRT contract.", "Safety choice required");
|
|
681
|
+
const resolution = await select({
|
|
682
|
+
message: "How should this mixed chain resolve the managed-SRT mismatch?",
|
|
683
|
+
options: [
|
|
684
|
+
{
|
|
685
|
+
value: "per-route-native",
|
|
686
|
+
label: "Use per-route native safety",
|
|
687
|
+
hint: "keep managed SRT for Pi and show every provider's native contract",
|
|
688
|
+
},
|
|
689
|
+
{
|
|
690
|
+
value: "disable-managed-srt",
|
|
691
|
+
label: "Disable managed SRT",
|
|
692
|
+
hint: "keep the uniform contract without claiming SRT on provider-native routes",
|
|
693
|
+
},
|
|
694
|
+
],
|
|
695
|
+
initialValue: "per-route-native",
|
|
696
|
+
});
|
|
697
|
+
if (resolution === "per-route-native")
|
|
698
|
+
draft.routeSafety = "per-route-native";
|
|
699
|
+
else
|
|
700
|
+
draft.sandbox = false;
|
|
701
|
+
}
|
|
702
|
+
if (draft.routeSafety === "per-route-native" && (mixed || providerNative)) {
|
|
703
|
+
p.note(formatRouteSafetyMatrix({ model: draft.model, ...(draft.effort === undefined ? {} : { effort: draft.effort }) }, draft.fallbacks, draft.sandbox), "Per-route safety contract");
|
|
704
|
+
const accepted = await confirm({
|
|
705
|
+
message: "Use these per-route safety contracts? Provider-native routes cannot enforce every mono-agent capability.",
|
|
706
|
+
initialValue: false,
|
|
707
|
+
});
|
|
708
|
+
if (!accepted) {
|
|
709
|
+
draft.routeSafety = "uniform";
|
|
710
|
+
if (mixedPiProviderNativeChain && draft.sandbox) {
|
|
711
|
+
draft.sandbox = false;
|
|
712
|
+
p.log.info("Managed SRT disabled because this mixed chain kept the uniform compatibility contract.");
|
|
713
|
+
}
|
|
714
|
+
p.log.info("Per-route native safety was not accepted; restored the uniform compatibility contract.");
|
|
715
|
+
}
|
|
716
|
+
else {
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if (hasSandboxableTools && hasPiRoute && isAllowAllTools(draft.allowedTools) && !draft.sandbox) {
|
|
721
|
+
const accepted = await confirmHighRiskUnsandboxedAccess();
|
|
722
|
+
if (!accepted) {
|
|
723
|
+
draft.sandbox = true;
|
|
724
|
+
if (mixedPiProviderNativeChain && draft.routeSafety === "uniform") {
|
|
725
|
+
draft.routeSafety = "per-route-native";
|
|
726
|
+
p.note(formatRouteSafetyMatrix({ model: draft.model, ...(draft.effort === undefined ? {} : { effort: draft.effort }) }, draft.fallbacks, draft.sandbox), "Per-route safety contract");
|
|
727
|
+
if (!await confirm({
|
|
728
|
+
message: "Use these per-route safety contracts? Provider-native routes cannot enforce every mono-agent capability.",
|
|
729
|
+
initialValue: false,
|
|
730
|
+
})) {
|
|
731
|
+
throw new WizardBack();
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
else {
|
|
735
|
+
p.log.info("Managed SRT enabled because unsandboxed Pi allow-all access was not confirmed.");
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
else if (hasSandboxableTools
|
|
740
|
+
&& providerNative
|
|
741
|
+
&& !models.every(isDirectCodexRef)
|
|
742
|
+
&& isAllowAllTools(draft.allowedTools)
|
|
743
|
+
&& !await confirmHighRiskUnsandboxedAccess()) {
|
|
744
|
+
throw new WizardBack();
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
async function confirmHighRiskUnsandboxedAccess() {
|
|
748
|
+
return confirm({
|
|
749
|
+
message: "Proceed with high-risk unsandboxed access? The model may run shell commands, change files, access the web, and send through enabled channels.",
|
|
750
|
+
initialValue: false,
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
/** Select primary, fallbacks, and effort without touching any other answer. */
|
|
754
|
+
async function promptModelSettings(draft, ctx, cache = {}) {
|
|
755
|
+
if (cache.result === undefined)
|
|
756
|
+
p.log.step("Discovering supported model catalogs…");
|
|
757
|
+
const discovery = cache.result ??= await discoverModelsInterruptibly({
|
|
758
|
+
...(ctx.piAuthPath === undefined ? {} : { piAuthPath: ctx.piAuthPath }),
|
|
759
|
+
...(ctx.persistedEnv === undefined ? {} : { persistedEnv: ctx.persistedEnv }),
|
|
760
|
+
});
|
|
761
|
+
const discoveredByValue = new Map(discovery.candidates.map((candidate) => [candidate.value, candidate]));
|
|
762
|
+
p.note(formatModelDiscoveryStatus(discovery.statuses), "Model discovery");
|
|
763
|
+
const previousModel = draft.model;
|
|
764
|
+
const previousEffort = draft.effort;
|
|
765
|
+
const providerDefault = discovery.candidates.find((candidate) => candidate.providerDefault === true);
|
|
766
|
+
const initialModel = draft.model === DEFAULT_MODEL ? providerDefault?.value ?? draft.model : draft.model;
|
|
767
|
+
let substep = 0;
|
|
768
|
+
for (;;) {
|
|
769
|
+
try {
|
|
770
|
+
if (substep === 0) {
|
|
771
|
+
const model = await autocomplete({
|
|
772
|
+
message: "Which model?",
|
|
773
|
+
options: modelSelectOptions(discovery.candidates, draft.model),
|
|
774
|
+
initialValue: draft.model === DEFAULT_MODEL ? initialModel : draft.model,
|
|
775
|
+
placeholder: "Type to search Pi, Codex, Claude, and local models…",
|
|
776
|
+
maxItems: MODEL_AUTOCOMPLETE_MAX_ITEMS,
|
|
777
|
+
});
|
|
778
|
+
draft.model = await resolveModelSelection(model, { candidates: discovery.candidates, context: "primary" });
|
|
779
|
+
substep = 1;
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
if (substep === 1) {
|
|
783
|
+
draft.effort = await promptEffortForModel(draft.model, discoveredByValue.get(draft.model), draft.model === previousModel ? previousEffort : undefined);
|
|
784
|
+
substep = 2;
|
|
785
|
+
continue;
|
|
786
|
+
}
|
|
787
|
+
if (substep === 2) {
|
|
788
|
+
draft.fallbacks = [];
|
|
789
|
+
if (!await confirm({ message: "Add fallback models?", initialValue: false }))
|
|
790
|
+
break;
|
|
791
|
+
substep = 3;
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
await promptFallbackModels(draft, discovery.candidates);
|
|
795
|
+
break;
|
|
796
|
+
}
|
|
797
|
+
catch (error) {
|
|
798
|
+
if (!(error instanceof WizardBack) || substep === 0)
|
|
799
|
+
throw error;
|
|
800
|
+
substep -= 1;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
draft.credentialStates = selectedCredentialStates(draft, discoveredByValue, ctx.persistedEnv);
|
|
804
|
+
}
|
|
805
|
+
function selectedCredentialStates(draft, candidates, persistedEnv = {}) {
|
|
806
|
+
const states = {};
|
|
807
|
+
for (const model of selectedRuntimeModels(draft)) {
|
|
808
|
+
const key = providerCredentialKey(model);
|
|
809
|
+
if (key === undefined)
|
|
810
|
+
continue;
|
|
811
|
+
const authState = candidates.get(model)?.authState;
|
|
812
|
+
states[key] = authState === "verified"
|
|
813
|
+
? "verified"
|
|
814
|
+
: hasDurableProviderEnvironmentCredential(model, persistedEnv)
|
|
815
|
+
? "credential_detected"
|
|
816
|
+
: authState === "credential_detected"
|
|
817
|
+
? "credential_detected"
|
|
818
|
+
: "auth_required";
|
|
819
|
+
}
|
|
820
|
+
return states;
|
|
821
|
+
}
|
|
822
|
+
function providerCredentialKey(model) {
|
|
823
|
+
if (model.startsWith("codex:"))
|
|
824
|
+
return "codex";
|
|
825
|
+
if (model.startsWith("claude:"))
|
|
826
|
+
return "claude";
|
|
827
|
+
if (!model.startsWith("pi:"))
|
|
828
|
+
return undefined;
|
|
829
|
+
const provider = model.split(":")[1];
|
|
830
|
+
return provider === undefined || provider === "ollama" || provider === "lmstudio" ? undefined : `pi:${provider}`;
|
|
831
|
+
}
|
|
832
|
+
async function promptEffortForModel(model, candidate, initial) {
|
|
833
|
+
const supported = candidate?.supportedEfforts ?? [];
|
|
834
|
+
const options = effortSelectOptions(supported, candidate?.defaultEffort);
|
|
835
|
+
const initialValue = initial !== undefined && supported.includes(initial) ? initial : "";
|
|
836
|
+
const effort = await select({
|
|
837
|
+
message: `Reasoning effort for ${model}`,
|
|
838
|
+
options,
|
|
839
|
+
initialValue,
|
|
840
|
+
});
|
|
841
|
+
return effort === "" ? undefined : effort;
|
|
194
842
|
}
|
|
195
843
|
/**
|
|
196
844
|
* Prompt every non-secret input of the selected channel + memory modules and store
|
|
197
|
-
* the answers into `draft.moduleInputs`.
|
|
198
|
-
*
|
|
845
|
+
* the answers into `draft.moduleInputs`. Required secrets are collected only after
|
|
846
|
+
* the write confirmation so they can never appear in the plan summary.
|
|
199
847
|
*/
|
|
200
|
-
async function promptModuleInputs(draft) {
|
|
201
|
-
const moduleIds =
|
|
848
|
+
async function promptModuleInputs(draft, requestedModuleIds) {
|
|
849
|
+
const moduleIds = requestedModuleIds
|
|
850
|
+
?? [...draft.channels, ...(draft.memory === undefined ? [] : [draft.memory])];
|
|
202
851
|
for (const id of moduleIds) {
|
|
203
852
|
const module = findModule(id);
|
|
204
853
|
if (module === undefined) {
|
|
@@ -208,21 +857,50 @@ async function promptModuleInputs(draft) {
|
|
|
208
857
|
if (input.secret === true) {
|
|
209
858
|
continue;
|
|
210
859
|
}
|
|
211
|
-
const
|
|
860
|
+
const currentValue = draft.moduleInputs[module.id]?.[input.id];
|
|
861
|
+
const answer = await textPrompt({
|
|
212
862
|
message: `${module.title}: ${input.label}`,
|
|
213
863
|
placeholder: input.description,
|
|
214
864
|
...(input.default === undefined ? {} : { defaultValue: input.default }),
|
|
215
|
-
|
|
865
|
+
...(currentValue === undefined ? {} : { initialValue: currentValue }),
|
|
866
|
+
...(input.validate === undefined ? {} : { validate: input.validate }),
|
|
867
|
+
});
|
|
216
868
|
const trimmed = answer.trim();
|
|
217
869
|
if (trimmed.length > 0) {
|
|
218
870
|
(draft.moduleInputs[module.id] ??= {})[input.id] = trimmed;
|
|
219
871
|
}
|
|
872
|
+
else {
|
|
873
|
+
delete draft.moduleInputs[module.id]?.[input.id];
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
/** Collect only typed required secrets, masked and retained solely for this run. */
|
|
879
|
+
async function promptRequiredModuleSecrets(answers, persistedEnv = {}, existing = {}) {
|
|
880
|
+
const plan = composeWizardPlan(answers, { dirBasename: "agent", skillsRootExists: false });
|
|
881
|
+
const secrets = {};
|
|
882
|
+
for (const secret of plan.secrets) {
|
|
883
|
+
if (!secret.required || hasNonEmptyValue(persistedEnv[secret.envVar]))
|
|
884
|
+
continue;
|
|
885
|
+
if (hasNonEmptyValue(existing[secret.envVar])) {
|
|
886
|
+
secrets[secret.envVar] = existing[secret.envVar];
|
|
887
|
+
continue;
|
|
220
888
|
}
|
|
889
|
+
secrets[secret.envVar] = await passwordPrompt({
|
|
890
|
+
message: `${secret.label} (${secret.envVar})`,
|
|
891
|
+
validate: (value) => (value ?? "").trim().length === 0 ? "This secret is required for the selected capability." : undefined,
|
|
892
|
+
clearOnError: true,
|
|
893
|
+
});
|
|
221
894
|
}
|
|
895
|
+
return secrets;
|
|
896
|
+
}
|
|
897
|
+
function hasNonEmptyValue(value) {
|
|
898
|
+
return value !== undefined && value.trim().length > 0;
|
|
222
899
|
}
|
|
223
900
|
/** Short reasons annotating each always-on tool in the framing note. */
|
|
224
901
|
const ALWAYS_ON_TOOL_REASONS = {
|
|
225
902
|
MemoryRecall: "memory recall is on",
|
|
903
|
+
ReadSkill: "configuration skills load on demand",
|
|
226
904
|
};
|
|
227
905
|
/** The always-on tool names annotated with their reason, e.g. `MemoryRecall (memory recall is on)`. */
|
|
228
906
|
function alwaysOnDisplay(alwaysOn) {
|
|
@@ -238,7 +916,7 @@ function alwaysOnDisplay(alwaysOn) {
|
|
|
238
916
|
*/
|
|
239
917
|
function channelSendTools(channels) {
|
|
240
918
|
return toolMultiselectOptions(channels)
|
|
241
|
-
.slice(BUILTIN_TOOL_NAMES.length)
|
|
919
|
+
.slice(BUILTIN_TOOL_NAMES.length + APP_TOOL_NAMES.length)
|
|
242
920
|
.map((option) => option.value)
|
|
243
921
|
.filter((value) => value !== "AskUser");
|
|
244
922
|
}
|
|
@@ -247,7 +925,8 @@ function channelSendTools(channels) {
|
|
|
247
925
|
* knows what "Allow all" turns on and what is unaffected by the choice:
|
|
248
926
|
* 1. Always on — auto-provisioned, NOT gated by this choice (e.g. MemoryRecall).
|
|
249
927
|
* 2. Built-ins — file/shell/web tools.
|
|
250
|
-
* 3.
|
|
928
|
+
* 3. App tools — safe host capabilities such as bounded run inspection.
|
|
929
|
+
* 4. Channel tools — the send/ask tools that came with the channels you enabled,
|
|
251
930
|
* plus AskUser (ask the human, any channel).
|
|
252
931
|
*/
|
|
253
932
|
function toolSituationFraming(draft, alwaysOn) {
|
|
@@ -260,8 +939,9 @@ function toolSituationFraming(draft, alwaysOn) {
|
|
|
260
939
|
? `Always on (auto-provisioned, not affected by this choice): ${alwaysOnDisplay(alwaysOn).join(", ")}.`
|
|
261
940
|
: "Always on (auto-provisioned): none for this setup.",
|
|
262
941
|
"Built-ins: files (Read/Write/Edit/Glob/Grep), shell (Bash), web (WebFetch/WebSearch).",
|
|
942
|
+
"App tools: RunHistory (read-only evidence from completed prior runs in this conversation).",
|
|
263
943
|
channelLine,
|
|
264
|
-
'"Allow all"
|
|
944
|
+
'"Allow all" lets the model run shell commands, read/change files, access the web, and send through enabled channels. These actions can modify data or contact people; you can turn specific tools off later via tools.disallowedTools.',
|
|
265
945
|
].join("\n");
|
|
266
946
|
}
|
|
267
947
|
/**
|
|
@@ -273,10 +953,15 @@ function toolSituationFraming(draft, alwaysOn) {
|
|
|
273
953
|
async function promptTools(draft) {
|
|
274
954
|
const alwaysOn = alwaysOnTools(toWizardAnswers(draft));
|
|
275
955
|
p.note(toolSituationFraming(draft, alwaysOn), "Tools");
|
|
276
|
-
|
|
277
|
-
|
|
956
|
+
if (selectedRuntimeModels(draft).every(hasFixedAllowAllToolPolicyRef)) {
|
|
957
|
+
draft.allowedTools = [ALLOW_ALL_TOOLS];
|
|
958
|
+
p.log.info("Direct Codex uses its native app-server tool set and cannot enforce mono-agent per-tool allowlists. Tool policy is fixed to allow-all; use Pi or Claude for a restrictive tool list.");
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
const allowAll = await confirm({
|
|
962
|
+
message: "Allow all tools? (shell commands, file changes, web access, and enabled-channel sends)",
|
|
278
963
|
initialValue: true,
|
|
279
|
-
})
|
|
964
|
+
});
|
|
280
965
|
if (allowAll) {
|
|
281
966
|
draft.allowedTools = [ALLOW_ALL_TOOLS];
|
|
282
967
|
return;
|
|
@@ -294,17 +979,17 @@ async function pickSpecificTools(draft) {
|
|
|
294
979
|
const optionOrder = new Map(options.map((option, index) => [option.value, index]));
|
|
295
980
|
const recommended = recommendedToolSelection(toWizardAnswers(draft));
|
|
296
981
|
for (;;) {
|
|
297
|
-
const tools =
|
|
982
|
+
const tools = await multiselect({
|
|
298
983
|
message: "Which tools may the model call?",
|
|
299
984
|
options,
|
|
300
985
|
initialValues: [...recommended],
|
|
301
986
|
required: false,
|
|
302
|
-
})
|
|
987
|
+
});
|
|
303
988
|
if (tools.length === 0) {
|
|
304
|
-
const proceed =
|
|
989
|
+
const proceed = await confirm({
|
|
305
990
|
message: "⚠ Zero tools selected — the agent will be chat-only (cannot read files, run commands, or send proactively). Continue?",
|
|
306
991
|
initialValue: false,
|
|
307
|
-
})
|
|
992
|
+
});
|
|
308
993
|
if (!proceed) {
|
|
309
994
|
continue;
|
|
310
995
|
}
|
|
@@ -313,81 +998,315 @@ async function pickSpecificTools(draft) {
|
|
|
313
998
|
return;
|
|
314
999
|
}
|
|
315
1000
|
}
|
|
316
|
-
|
|
317
|
-
* Render the compact plan summary and ask for the single write-time confirmation.
|
|
318
|
-
* Provider modules are implementation detail (auto-added for local models), so
|
|
319
|
-
* they are excluded from the user-facing capabilities line. A "no" cancels.
|
|
320
|
-
*/
|
|
321
|
-
async function confirmSummary(draft, ctx) {
|
|
322
|
-
if ([draft.model, ...draft.fallbackModels].some((model) => LOCAL_PROVIDER_MODEL.test(model))) {
|
|
323
|
-
p.note("A matching local provider block is added automatically.\nThe first turn may be slow while the model loads.", "Local model");
|
|
324
|
-
}
|
|
1001
|
+
async function confirmSummary(draft, ctx, options) {
|
|
325
1002
|
const answers = toWizardAnswers(draft);
|
|
326
|
-
const plan =
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
1003
|
+
const plan = await composePlanForCwd(answers, ctx.cwd);
|
|
1004
|
+
const setupModelRefs = referencedSetupModelRefs(plan);
|
|
1005
|
+
const existingSetupModelRefs = options === undefined
|
|
1006
|
+
? undefined
|
|
1007
|
+
: referencedSetupModelRefs(await composePlanForCwd(options.existing.answers, ctx.cwd));
|
|
1008
|
+
const preserveProviderSetup = options !== undefined
|
|
1009
|
+
&& existingSetupModelRefs !== undefined
|
|
1010
|
+
&& sameOrderedValues(setupModelRefs, existingSetupModelRefs);
|
|
1011
|
+
const preliminarySetupPlan = providerSetupPlan(plan, ctx, draft.credentialStates);
|
|
1012
|
+
// Resolve destinations before the final review; collect masked values only
|
|
1013
|
+
// after the operator chooses Create.
|
|
1014
|
+
const piApiKeyPersistenceByProvider = preserveProviderSetup
|
|
1015
|
+
? { ...options.existing.piApiKeyPersistenceByProvider }
|
|
1016
|
+
: await selectPiApiKeyPersistence(preliminarySetupPlan, options?.existing.piApiKeyPersistenceByProvider);
|
|
1017
|
+
const setupPlan = providerSetupPlan(plan, ctx, draft.credentialStates, piApiKeyPersistenceByProvider);
|
|
1018
|
+
if (setupModelRefs.some((model) => /^pi:(?:ollama|lmstudio):/u.test(model))) {
|
|
1019
|
+
p.note("Local runtime or memory services are checked before readiness. The first load may take longer while models start.", "Local dependencies");
|
|
1020
|
+
}
|
|
330
1021
|
const capabilities = plan.selectedModules
|
|
331
1022
|
.filter((module) => module.kind !== "provider")
|
|
332
1023
|
.map((module) => module.title);
|
|
333
|
-
const
|
|
1024
|
+
const creates = ["mono-agent.config.json", "IDENTITY.md", ".mono-agent/artifacts/", ".mono-agent/workspace/"];
|
|
334
1025
|
if (plan.envExample !== undefined) {
|
|
335
|
-
|
|
1026
|
+
creates.push(".env.example (placeholders only)");
|
|
336
1027
|
}
|
|
337
1028
|
for (const file of plan.files) {
|
|
338
|
-
|
|
1029
|
+
creates.push(file.path);
|
|
1030
|
+
}
|
|
1031
|
+
const providerEnvironmentNames = setupPlan.actions
|
|
1032
|
+
.filter(isProviderSetupPiApiKeyAction)
|
|
1033
|
+
.filter((action) => action.persistence === "environment")
|
|
1034
|
+
.map((action) => action.envVar);
|
|
1035
|
+
const moduleSecretsToCollect = plan.secrets
|
|
1036
|
+
.filter((secret) => secret.required && !hasNonEmptyValue(ctx.persistedEnv?.[secret.envVar]))
|
|
1037
|
+
.map((secret) => secret.envVar);
|
|
1038
|
+
const secrets = [...new Set([
|
|
1039
|
+
...moduleSecretsToCollect,
|
|
1040
|
+
...providerEnvironmentNames,
|
|
1041
|
+
])];
|
|
1042
|
+
const hardenExistingDotenv = hasSensitivePersistedEnvironmentValue(ctx.persistedEnv ?? {});
|
|
1043
|
+
const mutableSecretFiles = [];
|
|
1044
|
+
if (secrets.length > 0 || hardenExistingDotenv) {
|
|
1045
|
+
mutableSecretFiles.push(secrets.length > 0 ? ".env (owner-only secret merge)" : ".env (owner-only permission hardening)", ".gitignore (ensure /.env is ignored)");
|
|
339
1046
|
}
|
|
340
|
-
const secrets = plan.secrets.map((secret) => secret.envVar);
|
|
341
1047
|
const toolsLine = isAllowAllTools(draft.allowedTools)
|
|
342
1048
|
? "all tools"
|
|
343
1049
|
: draft.allowedTools.length > 0
|
|
344
1050
|
? draft.allowedTools.join(", ")
|
|
345
1051
|
: "none (chat-only)";
|
|
346
1052
|
const alwaysOn = alwaysOnTools(answers);
|
|
1053
|
+
const runtimeRoutes = [
|
|
1054
|
+
{ model: draft.model, effort: draft.effort },
|
|
1055
|
+
...draft.fallbacks,
|
|
1056
|
+
];
|
|
1057
|
+
const potentiallyBilledCalls = runtimeRoutes.filter((route) => modelRefMayBill(route.model)).length;
|
|
1058
|
+
const providerActions = [
|
|
1059
|
+
...setupPlan.detectedModelRefs.map((modelRef) => `${modelRef}: credential/sign-in detected; skip initial auth and verify with the live readiness call`),
|
|
1060
|
+
...setupPlan.actions.map((action) => {
|
|
1061
|
+
if (isProviderSetupPiApiKeyAction(action)) {
|
|
1062
|
+
return action.persistence === "environment"
|
|
1063
|
+
? `${action.label}: read ${action.envVar} from owner-only .env; do not write Pi auth.json`
|
|
1064
|
+
: `${action.label}: save credential to owner-only ${action.piAuthPath}; do not copy it to .env`;
|
|
1065
|
+
}
|
|
1066
|
+
return `${action.label}: check the current credential/service state first; if needed run ${providerSetupActionCommandLine(action)} (cwd: ${action.cwd})`;
|
|
1067
|
+
}),
|
|
1068
|
+
...(draft.sandbox
|
|
1069
|
+
? ["Managed SRT: install the pinned tool in the private user cache and run the functional fail-closed preflight"]
|
|
1070
|
+
: []),
|
|
1071
|
+
];
|
|
347
1072
|
const lines = [
|
|
348
|
-
`
|
|
349
|
-
`
|
|
1073
|
+
`Agent: ${draft.name}`,
|
|
1074
|
+
`Purpose: ${draft.purpose}`,
|
|
1075
|
+
"Routes:",
|
|
1076
|
+
...runtimeRoutes.map((route, index) => ` ${index === 0 ? "Primary" : `Fallback ${index}`}: ${route.model} [${route.effort ?? "provider default"}]`),
|
|
350
1077
|
`Capabilities: ${capabilities.length > 0 ? capabilities.join(", ") : "none"}`,
|
|
351
1078
|
`Tools: ${toolsLine}`,
|
|
1079
|
+
`Route safety: ${draft.routeSafety}`,
|
|
1080
|
+
...(draft.routeSafety === "per-route-native" || !isMixedRouteChain(runtimeRoutes.map((route) => route.model))
|
|
1081
|
+
? [formatRouteSafetyMatrix({ model: draft.model, ...(draft.effort === undefined ? {} : { effort: draft.effort }) }, draft.fallbacks, draft.sandbox)]
|
|
1082
|
+
: [
|
|
1083
|
+
"Uniform contract: every route must satisfy the same mono-agent tool/sandbox capabilities; an incompatible route is rejected or skipped, never silently weakened.",
|
|
1084
|
+
]),
|
|
352
1085
|
...(alwaysOn.length > 0 ? [`Always on: ${alwaysOnDisplay(alwaysOn).join(", ")}`] : []),
|
|
353
|
-
`
|
|
354
|
-
`
|
|
1086
|
+
`Readiness: ${runtimeRoutes.length} real model call(s), one per selected route; ${potentiallyBilledCalls} potentially billed`,
|
|
1087
|
+
`Verify refs: ${setupModelRefs.join(", ")}`,
|
|
1088
|
+
`Provider actions: ${providerActions.length > 0 ? providerActions.join("\n ") : "none"}`,
|
|
1089
|
+
`Creates if missing (preserves existing scaffold paths): ${creates.join(", ")}`,
|
|
1090
|
+
...(mutableSecretFiles.length > 0
|
|
1091
|
+
? [`May create or update: ${mutableSecretFiles.join(", ")}`]
|
|
1092
|
+
: []),
|
|
1093
|
+
`Secret persistence: ${secrets.length > 0
|
|
1094
|
+
? `${secrets.join(", ")} -> owner-only .env merge (values hidden)`
|
|
1095
|
+
: hardenExistingDotenv
|
|
1096
|
+
? "existing sensitive .env -> owner-only permission/ignore hardening (values unchanged)"
|
|
1097
|
+
: "none"}`,
|
|
1098
|
+
...(Object.values(piApiKeyPersistenceByProvider).some((value) => value === "secure-store")
|
|
1099
|
+
? ["Pi credential persistence: selected keys -> owner-only Pi auth.json (values hidden; not copied to .env)"]
|
|
1100
|
+
: []),
|
|
1101
|
+
...(secrets.length > 0 || hardenExistingDotenv
|
|
1102
|
+
? [`Secret files: .env (${secrets.length > 0 ? "owner-only merge" : "owner-only hardening"}), .gitignore (ensure /.env is ignored)`]
|
|
1103
|
+
: []),
|
|
355
1104
|
];
|
|
356
|
-
p.note(lines.join("\n"), "
|
|
357
|
-
const
|
|
358
|
-
|
|
1105
|
+
p.note(lines.join("\n"), "Creation review");
|
|
1106
|
+
const choice = await select({
|
|
1107
|
+
message: `Create “${draft.name}”?`,
|
|
1108
|
+
options: creationReviewOptions({
|
|
1109
|
+
setupRequired: (preserveProviderSetup
|
|
1110
|
+
? options.existing.runProviderSetup
|
|
1111
|
+
: setupPlan.actions.length > 0) || draft.sandbox,
|
|
1112
|
+
}),
|
|
1113
|
+
initialValue: "create",
|
|
1114
|
+
});
|
|
1115
|
+
if (choice === "cancel") {
|
|
1116
|
+
throw new WizardCancelled();
|
|
1117
|
+
}
|
|
1118
|
+
if (choice === "edit") {
|
|
1119
|
+
try {
|
|
1120
|
+
const step = await select({
|
|
1121
|
+
message: "What would you like to edit?",
|
|
1122
|
+
options: [
|
|
1123
|
+
{ value: "0", label: "Agent name and purpose" },
|
|
1124
|
+
{ value: "1", label: "Models and efforts" },
|
|
1125
|
+
{ value: "2", label: "Channels" },
|
|
1126
|
+
{ value: "3", label: "Memory" },
|
|
1127
|
+
{ value: "4", label: "Capability details" },
|
|
1128
|
+
{ value: "5", label: "Tools" },
|
|
1129
|
+
{ value: "6", label: "Route safety and sandbox" },
|
|
1130
|
+
{ value: "7", label: "Observability" },
|
|
1131
|
+
{ value: "8", label: "Return to review" },
|
|
1132
|
+
],
|
|
1133
|
+
initialValue: "1",
|
|
1134
|
+
});
|
|
1135
|
+
return { status: "edit", step: Number(step) };
|
|
1136
|
+
}
|
|
1137
|
+
catch (error) {
|
|
1138
|
+
if (error instanceof WizardBack) {
|
|
1139
|
+
if (options !== undefined)
|
|
1140
|
+
throw error;
|
|
1141
|
+
return { status: "edit", step: 8 };
|
|
1142
|
+
}
|
|
1143
|
+
throw error;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
if (preserveProviderSetup) {
|
|
1147
|
+
return {
|
|
1148
|
+
status: "create",
|
|
1149
|
+
providerSetup: {
|
|
1150
|
+
runProviderSetup: options.existing.runProviderSetup,
|
|
1151
|
+
providerSetupSecrets: { ...options.existing.providerSetupSecrets },
|
|
1152
|
+
providerEnvironmentSecrets: { ...options.existing.providerEnvironmentSecrets },
|
|
1153
|
+
piApiKeyPersistenceByProvider: { ...options.existing.piApiKeyPersistenceByProvider },
|
|
1154
|
+
},
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1157
|
+
let providerSetup;
|
|
1158
|
+
try {
|
|
1159
|
+
providerSetup = await collectProviderSetup(setupPlan, setupPlan.actions.length > 0, piApiKeyPersistenceByProvider, options?.existing.providerSetupSecrets, options?.existing.providerEnvironmentSecrets);
|
|
1160
|
+
}
|
|
1161
|
+
catch (error) {
|
|
1162
|
+
if (error instanceof WizardBack) {
|
|
1163
|
+
if (options !== undefined)
|
|
1164
|
+
throw error;
|
|
1165
|
+
return { status: "edit", step: 8 };
|
|
1166
|
+
}
|
|
1167
|
+
throw error;
|
|
1168
|
+
}
|
|
1169
|
+
return {
|
|
1170
|
+
status: "create",
|
|
1171
|
+
providerSetup,
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
function selectedRuntimeModels(draft) {
|
|
1175
|
+
return [draft.model, ...draft.fallbacks.map((fallback) => fallback.model)];
|
|
1176
|
+
}
|
|
1177
|
+
function sameOrderedValues(left, right) {
|
|
1178
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
1179
|
+
}
|
|
1180
|
+
function hasFixedAllowAllToolPolicyRef(model) {
|
|
1181
|
+
return isDirectCodexRef(model) || isDirectOpenCodeRef(model);
|
|
1182
|
+
}
|
|
1183
|
+
function hasExactAllowAllTools(draft) {
|
|
1184
|
+
return draft.allowedTools.length === 1 && draft.allowedTools[0] === ALLOW_ALL_TOOLS;
|
|
1185
|
+
}
|
|
1186
|
+
function hasInvalidUniformManagedSrtChain(draft) {
|
|
1187
|
+
if (draft.routeSafety !== "uniform" || !draft.sandbox)
|
|
1188
|
+
return false;
|
|
1189
|
+
const models = selectedRuntimeModels(draft);
|
|
1190
|
+
return isMixedRouteChain(models)
|
|
1191
|
+
&& models.some((model) => model.startsWith("pi:"))
|
|
1192
|
+
&& models.some((model) => !model.startsWith("pi:"));
|
|
1193
|
+
}
|
|
1194
|
+
function isDirectCodexRef(model) {
|
|
1195
|
+
return model.startsWith("codex:");
|
|
1196
|
+
}
|
|
1197
|
+
function isDirectOpenCodeRef(model) {
|
|
1198
|
+
return model.startsWith("opencode:");
|
|
1199
|
+
}
|
|
1200
|
+
function runtimeFamily(model) {
|
|
1201
|
+
return model.split(":", 1)[0] ?? model;
|
|
1202
|
+
}
|
|
1203
|
+
function modelRefMayBill(model) {
|
|
1204
|
+
return !/^pi:(?:ollama|lmstudio):/u.test(model);
|
|
1205
|
+
}
|
|
1206
|
+
/** Build a plan using the destination-derived context for honest review/setup. */
|
|
1207
|
+
async function composePlanForCwd(answers, cwd) {
|
|
1208
|
+
return composeWizardPlan(answers, {
|
|
1209
|
+
dirBasename: basename(cwd),
|
|
1210
|
+
skillsRootExists: await pathExists(join(cwd, "skills")),
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
/** Offer auth/preflight for every runtime and hidden memory model dependency. */
|
|
1214
|
+
async function promptProviderSetup(plan, ctx, credentialStates = {}) {
|
|
1215
|
+
const modelRefs = referencedSetupModelRefs(plan);
|
|
1216
|
+
p.note(modelRefs.join("\n"), "Models and services to verify");
|
|
1217
|
+
const preliminarySetupPlan = providerSetupPlan(plan, ctx, credentialStates);
|
|
1218
|
+
if (preliminarySetupPlan.actions.length === 0) {
|
|
1219
|
+
return { runProviderSetup: false, providerSetupSecrets: {}, providerEnvironmentSecrets: {}, piApiKeyPersistenceByProvider: {} };
|
|
1220
|
+
}
|
|
1221
|
+
const piApiKeyPersistenceByProvider = await selectPiApiKeyPersistence(preliminarySetupPlan);
|
|
1222
|
+
const setupPlan = providerSetupPlan(plan, ctx, credentialStates, piApiKeyPersistenceByProvider);
|
|
1223
|
+
p.note(setupPlan.actions
|
|
1224
|
+
.map(providerSetupActionReviewLine)
|
|
1225
|
+
.join("\n"), "Provider setup");
|
|
1226
|
+
const runProviderSetup = await confirm({
|
|
1227
|
+
message: setupPlan.actions.some((action) => action.id.startsWith("pi-login:"))
|
|
1228
|
+
? "Run provider auth/preflight now? (detected credentials are reused and verified by live readiness; Pi OAuth may update the auth store)"
|
|
1229
|
+
: "Run provider auth/preflight now? (detected credentials are reused and verified by live readiness)",
|
|
1230
|
+
initialValue: false,
|
|
1231
|
+
});
|
|
1232
|
+
return collectProviderSetup(setupPlan, runProviderSetup, piApiKeyPersistenceByProvider);
|
|
1233
|
+
}
|
|
1234
|
+
function providerSetupPlan(plan, ctx, credentialStates = {}, piApiKeyPersistenceByProvider = {}) {
|
|
1235
|
+
const modelRefs = referencedSetupModelRefs(plan);
|
|
1236
|
+
const configuredPiAuthPath = typeof plan.configJson.providers?.piAuthPath === "string"
|
|
1237
|
+
? plan.configJson.providers.piAuthPath
|
|
1238
|
+
: undefined;
|
|
1239
|
+
const piAuthPath = ctx.piAuthPath ?? configuredPiAuthPath;
|
|
1240
|
+
return planProviderSetup({
|
|
1241
|
+
modelRefs,
|
|
359
1242
|
cwd: ctx.cwd,
|
|
360
|
-
|
|
1243
|
+
credentialStates,
|
|
1244
|
+
piApiKeyPersistenceByProvider,
|
|
1245
|
+
...(piAuthPath === undefined ? {} : { piAuthPath }),
|
|
361
1246
|
});
|
|
362
|
-
|
|
1247
|
+
}
|
|
1248
|
+
async function collectProviderSetup(setupPlan, runProviderSetup, selectedPersistence, existingSetupSecrets = {}, existingEnvironmentSecrets = {}) {
|
|
363
1249
|
const providerSetupSecrets = {};
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
validate: (value) => (value ?? "").trim().length === 0 ? "API key is required." : undefined,
|
|
382
|
-
clearOnError: true,
|
|
383
|
-
}));
|
|
1250
|
+
const providerEnvironmentSecrets = {};
|
|
1251
|
+
if (!runProviderSetup) {
|
|
1252
|
+
return {
|
|
1253
|
+
runProviderSetup: false,
|
|
1254
|
+
providerSetupSecrets,
|
|
1255
|
+
providerEnvironmentSecrets,
|
|
1256
|
+
piApiKeyPersistenceByProvider: { ...(selectedPersistence ?? {}) },
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
const piApiKeyPersistenceByProvider = { ...(selectedPersistence ?? {}) };
|
|
1260
|
+
for (const action of setupPlan.actions) {
|
|
1261
|
+
if (!isProviderSetupPiApiKeyAction(action))
|
|
1262
|
+
continue;
|
|
1263
|
+
if (action.persistence === "environment") {
|
|
1264
|
+
if (hasNonEmptyValue(existingEnvironmentSecrets[action.envVar])) {
|
|
1265
|
+
providerEnvironmentSecrets[action.envVar] = existingEnvironmentSecrets[action.envVar];
|
|
1266
|
+
continue;
|
|
384
1267
|
}
|
|
1268
|
+
providerEnvironmentSecrets[action.envVar] = await passwordPrompt({
|
|
1269
|
+
message: `${action.label} (${action.envVar}, saved to owner-only .env)`,
|
|
1270
|
+
validate: (value) => (value ?? "").trim().length === 0 ? "API key is required." : undefined,
|
|
1271
|
+
clearOnError: true,
|
|
1272
|
+
});
|
|
1273
|
+
continue;
|
|
1274
|
+
}
|
|
1275
|
+
if (hasNonEmptyValue(existingSetupSecrets[action.id])) {
|
|
1276
|
+
providerSetupSecrets[action.id] = existingSetupSecrets[action.id];
|
|
1277
|
+
continue;
|
|
385
1278
|
}
|
|
1279
|
+
providerSetupSecrets[action.id] = await passwordPrompt({
|
|
1280
|
+
message: `${action.label} (${action.envVar})`,
|
|
1281
|
+
validate: (value) => (value ?? "").trim().length === 0 ? "API key is required." : undefined,
|
|
1282
|
+
clearOnError: true,
|
|
1283
|
+
});
|
|
386
1284
|
}
|
|
387
|
-
|
|
388
|
-
|
|
1285
|
+
return { runProviderSetup, providerSetupSecrets, providerEnvironmentSecrets, piApiKeyPersistenceByProvider };
|
|
1286
|
+
}
|
|
1287
|
+
async function selectPiApiKeyPersistence(setupPlan, existing = {}) {
|
|
1288
|
+
const selected = {};
|
|
1289
|
+
for (const action of setupPlan.actions) {
|
|
1290
|
+
if (!isProviderSetupPiApiKeyAction(action))
|
|
1291
|
+
continue;
|
|
1292
|
+
const label = action.label.replace(/ \((?:secure store|environment)\)$/u, "");
|
|
1293
|
+
const existingSelection = existing[action.provider];
|
|
1294
|
+
selected[action.provider] = existingSelection ?? await select({
|
|
1295
|
+
message: `Where should ${label} store ${action.envVar}?`,
|
|
1296
|
+
options: [
|
|
1297
|
+
{ value: "secure-store", label: "Store securely in Pi auth.json", hint: "owner-only Pi credential store" },
|
|
1298
|
+
{ value: "environment", label: "Use environment variable", hint: `owner-only .env (${action.envVar})` },
|
|
1299
|
+
],
|
|
1300
|
+
initialValue: "secure-store",
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
return selected;
|
|
1304
|
+
}
|
|
1305
|
+
function providerSetupActionReviewLine(action) {
|
|
1306
|
+
if (isProviderSetupPiApiKeyAction(action) && action.persistence === "environment") {
|
|
1307
|
+
return `${action.label}: read ${action.envVar} from the durable agent environment; Pi auth.json remains unchanged (cwd: ${action.cwd})`;
|
|
389
1308
|
}
|
|
390
|
-
return {
|
|
1309
|
+
return `${action.label}: ${providerSetupActionCommandLine(action)} (cwd: ${action.cwd})`;
|
|
391
1310
|
}
|
|
392
1311
|
/** Seed a mutable draft from immutable answers (defaults or a preset). */
|
|
393
1312
|
function draftFrom(answers) {
|
|
@@ -402,9 +1321,13 @@ function draftFrom(answers) {
|
|
|
402
1321
|
moduleInputs[moduleId] = bag;
|
|
403
1322
|
}
|
|
404
1323
|
return {
|
|
1324
|
+
name: answers.name?.trim() || "Mono Agent",
|
|
1325
|
+
purpose: answers.purpose?.trim() || "Help the operator work effectively in this folder.",
|
|
405
1326
|
model: answers.model,
|
|
406
|
-
|
|
1327
|
+
fallbacks: effectiveFallbacks(answers).map((fallback) => ({ ...fallback })),
|
|
407
1328
|
effort: answers.effort,
|
|
1329
|
+
routeSafety: answers.routeSafety,
|
|
1330
|
+
credentialStates: {},
|
|
408
1331
|
channels: [...answers.channels],
|
|
409
1332
|
memory: answers.memory,
|
|
410
1333
|
sandbox: answers.sandbox,
|
|
@@ -419,10 +1342,16 @@ function draftFrom(answers) {
|
|
|
419
1342
|
* `undefined`).
|
|
420
1343
|
*/
|
|
421
1344
|
function toWizardAnswers(draft) {
|
|
1345
|
+
for (const modelRef of selectedRuntimeModels(draft)) {
|
|
1346
|
+
assertConcreteWizardModelRef(modelRef);
|
|
1347
|
+
}
|
|
422
1348
|
return {
|
|
1349
|
+
name: draft.name,
|
|
1350
|
+
purpose: draft.purpose,
|
|
423
1351
|
model: draft.model,
|
|
424
|
-
|
|
1352
|
+
fallbacks: draft.fallbacks.map((fallback) => ({ ...fallback })),
|
|
425
1353
|
...(draft.effort === undefined ? {} : { effort: draft.effort }),
|
|
1354
|
+
routeSafety: draft.routeSafety,
|
|
426
1355
|
channels: [...draft.channels],
|
|
427
1356
|
...(draft.memory === undefined ? {} : { memory: draft.memory }),
|
|
428
1357
|
sandbox: draft.sandbox,
|
|
@@ -431,9 +1360,22 @@ function toWizardAnswers(draft) {
|
|
|
431
1360
|
moduleInputs: draft.moduleInputs,
|
|
432
1361
|
};
|
|
433
1362
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
1363
|
+
function cloneDraft(draft) {
|
|
1364
|
+
return {
|
|
1365
|
+
...draft,
|
|
1366
|
+
fallbacks: draft.fallbacks.map((fallback) => ({ ...fallback })),
|
|
1367
|
+
channels: [...draft.channels],
|
|
1368
|
+
allowedTools: [...draft.allowedTools],
|
|
1369
|
+
moduleInputs: Object.fromEntries(Object.entries(draft.moduleInputs).map(([id, values]) => [id, { ...values }])),
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
function restoreDraft(target, source) {
|
|
1373
|
+
Object.assign(target, cloneDraft(source));
|
|
1374
|
+
}
|
|
1375
|
+
/** A second Esc/Ctrl-C while this default-No prompt is open exits cleanly. */
|
|
1376
|
+
async function confirmExitSetup() {
|
|
1377
|
+
const answer = await p.confirm({ message: "Exit setup?", initialValue: false });
|
|
1378
|
+
return p.isCancel(answer) ? true : answer;
|
|
437
1379
|
}
|
|
438
1380
|
/** True when `path` exists (a local mirror of the CLI's private helper). */
|
|
439
1381
|
async function pathExists(path) {
|