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