openmeld 0.3.210 → 0.3.212
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{add-me-membership-BgDujS4W.js → add-me-membership-BwPpVKtA.js} +2 -2
- package/dist/{add-me-membership-BgDujS4W.js.map → add-me-membership-BwPpVKtA.js.map} +1 -1
- package/dist/agents-Y-UJ74By.js +4153 -0
- package/dist/agents-Y-UJ74By.js.map +1 -0
- package/dist/{api-client-foundation--H_ib5Vh.js → api-client-foundation-CrQN8_-N.js} +1 -1
- package/dist/{api-client-foundation-CQsXMqnr.js → api-client-foundation-DjbtuE52.js} +3 -3
- package/dist/{api-client-foundation-CQsXMqnr.js.map → api-client-foundation-DjbtuE52.js.map} +1 -1
- package/dist/{auth-session-CVxflepT.js → auth-session-Dqz9X4F0.js} +42 -21
- package/dist/auth-session-Dqz9X4F0.js.map +1 -0
- package/dist/auth-session-bRzDGlXy.js +4 -0
- package/dist/{command-DNIZzJuu.js → command-jgHbP21v.js} +10 -9
- package/dist/{command-DNIZzJuu.js.map → command-jgHbP21v.js.map} +1 -1
- package/dist/{computer-connection-TgsnU0K9.js → computer-connection-DvqsgYls.js} +30 -3
- package/dist/computer-connection-DvqsgYls.js.map +1 -0
- package/dist/{computer-connection-review-DqJTijzX.js → computer-connection-review-CzSpaHE0.js} +6 -6
- package/dist/{computer-connection-review-DqJTijzX.js.map → computer-connection-review-CzSpaHE0.js.map} +1 -1
- package/dist/computer-disconnect-DNM7jvda.js +47 -0
- package/dist/computer-disconnect-DNM7jvda.js.map +1 -0
- package/dist/{computer-setup-continuation-DFUxl9kf.js → computer-setup-continuation-BqdhIAjT.js} +4 -4
- package/dist/{computer-setup-continuation-DFUxl9kf.js.map → computer-setup-continuation-BqdhIAjT.js.map} +1 -1
- package/dist/{control-plane-D9jDcbMZ.js → control-plane-PxntAyDq.js} +3 -547
- package/dist/control-plane-PxntAyDq.js.map +1 -0
- package/dist/{daemon-service-runtime-D7cb3-Ov.js → daemon-service-runtime-CWTlAhZY.js} +291 -45
- package/dist/daemon-service-runtime-CWTlAhZY.js.map +1 -0
- package/dist/{openmeld-tools-update-coordinator-i1zATb9g.js → daemon-service-update-coordinator-DcjOzGu1.js} +23099 -22988
- package/dist/daemon-service-update-coordinator-DcjOzGu1.js.map +1 -0
- package/dist/dist--pb6JxqG.js +4 -0
- package/dist/{dist-Ccbpt6rT.js → dist-jsWwS864.js} +1435 -1206
- package/dist/dist-jsWwS864.js.map +1 -0
- package/dist/{elapsed-time-B2UGqUlR.js → elapsed-time-iFOSSUJo.js} +45 -78
- package/dist/elapsed-time-iFOSSUJo.js.map +1 -0
- package/dist/{installation-inventory-DJiJh9nP.js → installation-inventory-B0cbVLun.js} +3 -3
- package/dist/{installation-inventory-DJiJh9nP.js.map → installation-inventory-B0cbVLun.js.map} +1 -1
- package/dist/local-components-H3SkJMC1.js +567 -0
- package/dist/local-components-H3SkJMC1.js.map +1 -0
- package/dist/{openmeld-BqTI30JS.js → openmeld-B4gQCCex.js} +244 -154
- package/dist/openmeld-B4gQCCex.js.map +1 -0
- package/dist/{openmeld-maintenance-worker-DeOu5U2Q.js → openmeld-maintenance-worker-DtY_H7Bv.js} +5 -5
- package/dist/{openmeld-maintenance-worker-DeOu5U2Q.js.map → openmeld-maintenance-worker-DtY_H7Bv.js.map} +1 -1
- package/dist/openmeld-tools-update-join-DS_EIL5q.js +54 -0
- package/dist/openmeld-tools-update-join-DS_EIL5q.js.map +1 -0
- package/dist/openmeld-tools-update-process-frame-CbZPpc7D.js +93 -0
- package/dist/openmeld-tools-update-process-frame-CbZPpc7D.js.map +1 -0
- package/dist/openmeld-tools-update-process-frame-CcPLrSsw.js +4 -0
- package/dist/openmeld.js +7 -3
- package/dist/openmeld.js.map +1 -1
- package/dist/{prepare-session-executor-BSTToACx.js → prepare-session-executor-CWKqfuse.js} +11 -9
- package/dist/{prepare-session-executor-BSTToACx.js.map → prepare-session-executor-CWKqfuse.js.map} +1 -1
- package/dist/{runtime-transport-BrzIQru0.js → runtime-transport-BQAmcHwF.js} +16 -2
- package/dist/runtime-transport-BQAmcHwF.js.map +1 -0
- package/dist/{agents-DdYL--Rj.js → service-CTG3_X0o.js} +476 -4582
- package/dist/service-CTG3_X0o.js.map +1 -0
- package/dist/{service-account-authorization-BM7a5V4I.js → service-account-authorization-Dq4LcAVa.js} +3 -3
- package/dist/{service-account-authorization-BM7a5V4I.js.map → service-account-authorization-Dq4LcAVa.js.map} +1 -1
- package/dist/service-account-control-BTR_X7QM.js +145 -0
- package/dist/service-account-control-BTR_X7QM.js.map +1 -0
- package/dist/{service-control-capabilities-BL8hTCE6.js → service-control-capabilities-C5SxnBg2.js} +2 -2
- package/dist/{service-control-capabilities-BL8hTCE6.js.map → service-control-capabilities-C5SxnBg2.js.map} +1 -1
- package/dist/{service-control-capabilities--ktpSDhp.js → service-control-capabilities-ChRe8hkw.js} +1 -1
- package/dist/{upgrade-JMsVlRjL.js → upgrade-DAVEeo8B.js} +36 -17
- package/dist/upgrade-DAVEeo8B.js.map +1 -0
- package/dist/upgrade-DKXu-1wO.js +4 -0
- package/package.json +1 -1
- package/dist/agents-DdYL--Rj.js.map +0 -1
- package/dist/auth-session-CVxflepT.js.map +0 -1
- package/dist/auth-session-LTSqq_Tb.js +0 -4
- package/dist/computer-connection-TgsnU0K9.js.map +0 -1
- package/dist/control-plane-D9jDcbMZ.js.map +0 -1
- package/dist/daemon-service-runtime-D7cb3-Ov.js.map +0 -1
- package/dist/dist-Ccbpt6rT.js.map +0 -1
- package/dist/dist-Cs3dH2Of.js +0 -4
- package/dist/elapsed-time-B2UGqUlR.js.map +0 -1
- package/dist/openmeld-BqTI30JS.js.map +0 -1
- package/dist/openmeld-tools-update-coordinator-i1zATb9g.js.map +0 -1
- package/dist/runtime-transport-BrzIQru0.js.map +0 -1
- package/dist/upgrade-C5r5IT_8.js +0 -4
- package/dist/upgrade-JMsVlRjL.js.map +0 -1
|
@@ -0,0 +1,4153 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { t as isLoopbackHostname } from "./loopback-hostname-DHQPKq8N.js";
|
|
4
|
+
import { Cn as listAgentTargetStates, Fn as parseAgentEnabledOption, In as sortAgentRegistrations, Ln as repairBuiltinControllerInstall, Mn as updateAgentRegistrations, Nn as normalizeAgentId, On as removeAgentCustomTarget, Pn as normalizeAgentIds, Ra as resolveRuntimeContext, Sn as listAgentRegistrations, Tn as readAgentRuntimeConfigHealth, Xt as DAEMON_SERVICE_WAKEABILITY_ADVISORY_ANOMALIES, _o as compareAgentControllerRefsForDisplay, bn as createRegistrationFromCatalogTarget, jn as updateAgentCustomTarget, kn as resolveAgentTargetState, pa as toErrorMessage, si as buildAgentControllerRef, ui as createAgentControllerInterfaceDisplayRows, xn as listAgentCustomTargets, yn as addAgentCustomTarget } from "./daemon-service-update-coordinator-DcjOzGu1.js";
|
|
5
|
+
import { $n as runInteractivePrompt, A as buildDaemonStartNeedsAttentionCard, B as resolveDaemonServiceParticipationStatus, F as emitCliJsonEnvelope, Jn as renderInfoCard, Qn as isReturnKeypress, Zn as promptTextEntry, _ as buildDaemonBackgroundStartFailure, a as runDaemonBackgroundStartForDecision, dt as collectLocalAgentControllerInventory, ht as resolveAgentControllerInterfaceSnapshot, j as runDaemonStartDecisionPrompt, k as buildDaemonStartDeferredCard, l as runDaemonReinstall, qn as buildTextInfoCardLines, s as runDaemonInstall, v as buildSystemServiceStartupFailureReportFromVerdict } from "./service-CTG3_X0o.js";
|
|
6
|
+
import { Vt as readDaemonServiceContract } from "./daemon-service-runtime-CWTlAhZY.js";
|
|
7
|
+
import { Ft as buildBuiltinAgentSelectionExamples, Gt as createPresenter, It as getBuiltinAgentSelectionOptionalArguments, Kt as sanitizeTerminalDisplayText, Lt as getBuiltinAgentSelectionRequiredArguments, Pt as buildBuiltinAgentSelectionCommand, Wt as formatAgentOverviewPayload, g as canUseInteractivePrompts, i as formatDaemonInstallFailedMessage, p as runDaemonInstallDecisionPrompt, zt as validateBuiltinAgentSelectionInput } from "./elapsed-time-iFOSSUJo.js";
|
|
8
|
+
import { i as normalizeGatewayBaseUrlOrNull, l as DEFAULT_RUNTIME_AGENT_TRANSPORT_MODE } from "./base-url-C6sHRNGu.js";
|
|
9
|
+
import { c as formatOpenMeldCliTextBlock, i as formatInlineOpenMeldCliCommands, o as formatOpenMeldCliCommands } from "./openmeld-environment-target-NtJYJe1D.js";
|
|
10
|
+
import { r as waitForOpenMeldToolsUpdateCompletion } from "./openmeld-tools-update-join-DS_EIL5q.js";
|
|
11
|
+
import { isAbsolute } from "node:path";
|
|
12
|
+
import pc from "picocolors";
|
|
13
|
+
import { cancel, isCancel, select } from "@clack/prompts";
|
|
14
|
+
//#region src/system/local-service-participation-gate.ts
|
|
15
|
+
function shouldRequireDaemonServiceParticipation(localDemand) {
|
|
16
|
+
return localDemand?.decision === "keep_running";
|
|
17
|
+
}
|
|
18
|
+
async function runDaemonServiceParticipationGate(input) {
|
|
19
|
+
const forceRefresh = input.forceRefresh === true;
|
|
20
|
+
if (!shouldRunDaemonServiceParticipationGate({
|
|
21
|
+
...input,
|
|
22
|
+
forceRefresh
|
|
23
|
+
})) return { status: "aligned" };
|
|
24
|
+
const initialStatus = await resolveDaemonServiceParticipationStatus();
|
|
25
|
+
if (isReadyAdvisoryServiceDrift(initialStatus)) {
|
|
26
|
+
const codePrefix = resolveDaemonServiceParticipationCodePrefix(input.intent);
|
|
27
|
+
if (input.autoRepair === true) return await runDaemonServiceParticipationRefresh({
|
|
28
|
+
input,
|
|
29
|
+
codePrefix,
|
|
30
|
+
expectedVersion: initialStatus.expectedVersion,
|
|
31
|
+
scheduleBackgroundUpdate: hasServiceVersionAdvisory(initialStatus)
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
if (isRunningAdvisoryOnlyServiceDrift(initialStatus)) {
|
|
35
|
+
const accountIssue = await resolveDaemonServiceAccountAlignmentIssue(input);
|
|
36
|
+
if (!accountIssue) return { status: "aligned" };
|
|
37
|
+
const codePrefix = resolveDaemonServiceParticipationCodePrefix(input.intent);
|
|
38
|
+
if (input.autoRepair === true) return await runDaemonServiceParticipationRefresh({
|
|
39
|
+
input,
|
|
40
|
+
codePrefix,
|
|
41
|
+
expectedVersion: initialStatus.expectedVersion,
|
|
42
|
+
accountIssue
|
|
43
|
+
});
|
|
44
|
+
renderDaemonStartNeedsAttention({
|
|
45
|
+
presenter: input.presenter,
|
|
46
|
+
codePrefix,
|
|
47
|
+
summary: accountIssue.summary
|
|
48
|
+
});
|
|
49
|
+
input.presenter.line({
|
|
50
|
+
code: `${codePrefix}.account_alignment_failed`,
|
|
51
|
+
text: accountIssue.summary,
|
|
52
|
+
data: { reasonCode: accountIssue.reasonCode }
|
|
53
|
+
});
|
|
54
|
+
return { status: "needs_attention" };
|
|
55
|
+
}
|
|
56
|
+
if (forceRefresh && initialStatus.kind === "ready") return await runDaemonServiceParticipationRefresh({
|
|
57
|
+
input,
|
|
58
|
+
codePrefix: resolveDaemonServiceParticipationCodePrefix(input.intent),
|
|
59
|
+
expectedVersion: initialStatus.expectedVersion
|
|
60
|
+
});
|
|
61
|
+
if (initialStatus.kind === "ready") {
|
|
62
|
+
const accountIssue = await resolveDaemonServiceAccountAlignmentIssue(input);
|
|
63
|
+
if (!accountIssue) return { status: "aligned" };
|
|
64
|
+
const codePrefix = resolveDaemonServiceParticipationCodePrefix(input.intent);
|
|
65
|
+
if (input.autoRepair === true) return await runDaemonServiceParticipationRefresh({
|
|
66
|
+
input,
|
|
67
|
+
codePrefix,
|
|
68
|
+
expectedVersion: initialStatus.expectedVersion,
|
|
69
|
+
accountIssue
|
|
70
|
+
});
|
|
71
|
+
renderDaemonStartNeedsAttention({
|
|
72
|
+
presenter: input.presenter,
|
|
73
|
+
codePrefix,
|
|
74
|
+
summary: accountIssue.summary
|
|
75
|
+
});
|
|
76
|
+
input.presenter.line({
|
|
77
|
+
code: `${codePrefix}.account_alignment_failed`,
|
|
78
|
+
text: accountIssue.summary,
|
|
79
|
+
data: { reasonCode: accountIssue.reasonCode }
|
|
80
|
+
});
|
|
81
|
+
return { status: "needs_attention" };
|
|
82
|
+
}
|
|
83
|
+
const codePrefix = resolveDaemonServiceParticipationCodePrefix(input.intent);
|
|
84
|
+
const installStageResult = await runDaemonServiceParticipationInstallStage({
|
|
85
|
+
input,
|
|
86
|
+
codePrefix,
|
|
87
|
+
status: initialStatus
|
|
88
|
+
});
|
|
89
|
+
if (installStageResult.result) return installStageResult.result;
|
|
90
|
+
return await runDaemonServiceParticipationActionStage({
|
|
91
|
+
input,
|
|
92
|
+
codePrefix,
|
|
93
|
+
status: installStageResult.status
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function isRunningAdvisoryOnlyServiceDrift(status) {
|
|
97
|
+
return status.kind === "needs_repair" && status.runtimeStatus === "running" && status.anomalyCodes.length > 0 && status.anomalyCodes.every((code) => DAEMON_SERVICE_WAKEABILITY_ADVISORY_ANOMALIES.has(code));
|
|
98
|
+
}
|
|
99
|
+
function isReadyAdvisoryServiceDrift(status) {
|
|
100
|
+
return status.kind === "ready" && Array.isArray(status.advisoryAnomalyCodes) && status.advisoryAnomalyCodes.length > 0;
|
|
101
|
+
}
|
|
102
|
+
function hasServiceVersionAdvisory(status) {
|
|
103
|
+
return Boolean(status.advisoryAnomalyCodes?.some((code) => code === "version_outdated" || code === "bundle_version_drift"));
|
|
104
|
+
}
|
|
105
|
+
function shouldRunDaemonServiceParticipationGate(input) {
|
|
106
|
+
const canActWithoutPrompt = input.autoRepair === true && (input.resolvedView === "human" || input.resolvedView === "agent" && (input.forcePrompt === true || input.forceRefresh === true));
|
|
107
|
+
return (input.resolvedView === "human" && (input.allowPrompt || input.autoRepair === true) || canActWithoutPrompt) && (input.forcePrompt === true || input.forceRefresh === true || shouldRequireDaemonServiceParticipation(input.localDemand));
|
|
108
|
+
}
|
|
109
|
+
async function runDaemonServiceParticipationInstallStage(input) {
|
|
110
|
+
if (input.status.kind !== "missing" && input.status.kind !== "outdated") return { status: input.status };
|
|
111
|
+
const installableStatus = input.status;
|
|
112
|
+
if (input.input.autoRepair === true) {
|
|
113
|
+
try {
|
|
114
|
+
await runDaemonServiceParticipationInstallAction({
|
|
115
|
+
input: input.input,
|
|
116
|
+
status: installableStatus
|
|
117
|
+
});
|
|
118
|
+
} catch {
|
|
119
|
+
renderDaemonStartNeedsAttention({
|
|
120
|
+
presenter: input.input.presenter,
|
|
121
|
+
codePrefix: input.codePrefix,
|
|
122
|
+
summary: formatDaemonInstallFailedMessage(installableStatus.kind)
|
|
123
|
+
});
|
|
124
|
+
return {
|
|
125
|
+
status: installableStatus,
|
|
126
|
+
result: { status: "needs_attention" }
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return await resolveDaemonServiceParticipationStatusAfterInstall(input);
|
|
130
|
+
}
|
|
131
|
+
const installResult = await runDaemonInstallDecisionPrompt({
|
|
132
|
+
kind: installableStatus.kind,
|
|
133
|
+
...installableStatus.kind === "outdated" ? { installedVersion: installableStatus.installedVersion } : {},
|
|
134
|
+
expectedVersion: installableStatus.expectedVersion,
|
|
135
|
+
emitLine: (text) => {
|
|
136
|
+
input.input.presenter.line({
|
|
137
|
+
code: `${input.codePrefix}.install`,
|
|
138
|
+
text
|
|
139
|
+
});
|
|
140
|
+
},
|
|
141
|
+
installService: async () => {
|
|
142
|
+
await runDaemonServiceParticipationInstallAction({
|
|
143
|
+
input: input.input,
|
|
144
|
+
status: installableStatus
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
if (installResult === "cancelled") return {
|
|
149
|
+
status: installableStatus,
|
|
150
|
+
result: { status: "cancelled" }
|
|
151
|
+
};
|
|
152
|
+
if (installResult === "failed") return {
|
|
153
|
+
status: installableStatus,
|
|
154
|
+
result: { status: "needs_attention" }
|
|
155
|
+
};
|
|
156
|
+
if (installResult === "not_now" || !shouldStartAfterDaemonInstall(input.input.intent)) return {
|
|
157
|
+
status: installableStatus,
|
|
158
|
+
result: { status: "declined" }
|
|
159
|
+
};
|
|
160
|
+
return await resolveDaemonServiceParticipationStatusAfterInstall(input);
|
|
161
|
+
}
|
|
162
|
+
async function runDaemonServiceParticipationInstallAction(input) {
|
|
163
|
+
if (input.status.kind === "outdated") {
|
|
164
|
+
input.input.serviceProgress?.detail({
|
|
165
|
+
code: "daemon.participation.refresh.updating",
|
|
166
|
+
text: "Updating OpenMeld Service..."
|
|
167
|
+
});
|
|
168
|
+
await runDaemonServiceSafeRefresh({
|
|
169
|
+
openMeldProfileId: input.input.openMeldProfileId,
|
|
170
|
+
gatewayUrl: input.input.gatewayUrl,
|
|
171
|
+
suppressHumanResultCard: input.input.suppressRefreshHumanResultCard,
|
|
172
|
+
view: input.input.view
|
|
173
|
+
});
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
await runDaemonInstall({
|
|
177
|
+
...input.input.gatewayUrl ? { gatewayUrl: input.input.gatewayUrl } : {},
|
|
178
|
+
...input.input.openMeldProfileId ? { profile: input.input.openMeldProfileId } : {},
|
|
179
|
+
view: input.input.view
|
|
180
|
+
}, {
|
|
181
|
+
suppressHumanOutput: shouldSuppressHumanInstallOutput(input.input.intent),
|
|
182
|
+
...input.input.serviceProgress ? { serviceProgress: input.input.serviceProgress } : {}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
async function resolveDaemonServiceParticipationStatusAfterInstall(input) {
|
|
186
|
+
const refreshedStatus = await resolveDaemonServiceParticipationStatus();
|
|
187
|
+
if (refreshedStatus.kind === "ready") return {
|
|
188
|
+
status: refreshedStatus,
|
|
189
|
+
result: { status: "aligned" }
|
|
190
|
+
};
|
|
191
|
+
if (refreshedStatus.kind === "missing" || refreshedStatus.kind === "outdated") {
|
|
192
|
+
renderDaemonStartNeedsAttention({
|
|
193
|
+
presenter: input.input.presenter,
|
|
194
|
+
codePrefix: input.codePrefix,
|
|
195
|
+
summary: refreshedStatus.kind === "missing" ? formatDaemonInstallFailedMessage("missing") : formatDaemonInstallFailedMessage("outdated")
|
|
196
|
+
});
|
|
197
|
+
return {
|
|
198
|
+
status: refreshedStatus,
|
|
199
|
+
result: { status: "needs_attention" }
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
return { status: refreshedStatus };
|
|
203
|
+
}
|
|
204
|
+
async function runDaemonServiceSafeRefresh(input) {
|
|
205
|
+
return await runDaemonReinstall({
|
|
206
|
+
agentOverviewHandled: true,
|
|
207
|
+
...input.scheduleBackgroundUpdate ? { displayAction: "update" } : {},
|
|
208
|
+
...input.gatewayUrl ? { gatewayUrl: input.gatewayUrl } : {},
|
|
209
|
+
...input.openMeldProfileId ? { profile: input.openMeldProfileId } : {},
|
|
210
|
+
view: input.view
|
|
211
|
+
}, {
|
|
212
|
+
skipConfirm: true,
|
|
213
|
+
startAfterInstallMode: "always",
|
|
214
|
+
suppressAgentOverview: true,
|
|
215
|
+
...input.suppressHumanResultCard !== void 0 ? { suppressHumanResultCard: input.suppressHumanResultCard } : {},
|
|
216
|
+
suppressInstallOutput: true,
|
|
217
|
+
...input.forceReinstall === true ? { forceReinstall: true } : {}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
async function runDaemonBackgroundStartForParticipation(input) {
|
|
221
|
+
return await runDaemonBackgroundStartForDecision({
|
|
222
|
+
gatewayUrl: input.gatewayUrl,
|
|
223
|
+
...input.openMeldProfileId ? { profile: input.openMeldProfileId } : {},
|
|
224
|
+
...input.serviceProgress ? { serviceProgress: input.serviceProgress } : {},
|
|
225
|
+
...input.suppressStartSpinner === true ? { suppressStartSpinner: true } : {},
|
|
226
|
+
view: input.view
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
async function runDaemonServiceParticipationActionStage(input) {
|
|
230
|
+
if (input.status.kind === "needs_repair") return await runDaemonServiceParticipationRefresh({
|
|
231
|
+
input: input.input,
|
|
232
|
+
codePrefix: input.codePrefix,
|
|
233
|
+
expectedVersion: input.status.expectedVersion
|
|
234
|
+
});
|
|
235
|
+
if (input.status.kind !== "not_running") return { status: "aligned" };
|
|
236
|
+
if (input.input.autoRepair === true) return await mapDaemonBackgroundStartResult(await runDaemonBackgroundStartForParticipation({
|
|
237
|
+
openMeldProfileId: input.input.openMeldProfileId,
|
|
238
|
+
gatewayUrl: input.input.gatewayUrl,
|
|
239
|
+
serviceProgress: input.input.serviceProgress,
|
|
240
|
+
suppressStartSpinner: input.input.suppressStartSpinner,
|
|
241
|
+
view: input.input.view
|
|
242
|
+
}), {
|
|
243
|
+
expectedVersion: input.status.expectedVersion,
|
|
244
|
+
presenter: input.input.presenter,
|
|
245
|
+
codePrefix: input.codePrefix
|
|
246
|
+
});
|
|
247
|
+
return await mapDaemonBackgroundStartResult(await runDaemonStartDecisionPrompt({ startService: async () => await runDaemonBackgroundStartForParticipation({
|
|
248
|
+
openMeldProfileId: input.input.openMeldProfileId,
|
|
249
|
+
gatewayUrl: input.input.gatewayUrl,
|
|
250
|
+
serviceProgress: input.input.serviceProgress,
|
|
251
|
+
suppressStartSpinner: input.input.suppressStartSpinner,
|
|
252
|
+
view: input.input.view
|
|
253
|
+
}) }), {
|
|
254
|
+
expectedVersion: input.status.expectedVersion,
|
|
255
|
+
presenter: input.input.presenter,
|
|
256
|
+
codePrefix: input.codePrefix
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
async function runDaemonServiceParticipationRefresh(input) {
|
|
260
|
+
emitServiceAccountRefreshNotice(input);
|
|
261
|
+
try {
|
|
262
|
+
input.input.serviceProgress?.detail({
|
|
263
|
+
code: "daemon.participation.refresh.updating",
|
|
264
|
+
text: "Updating OpenMeld Service..."
|
|
265
|
+
});
|
|
266
|
+
const refreshOutcome = await runDaemonServiceSafeRefresh({
|
|
267
|
+
openMeldProfileId: input.input.openMeldProfileId,
|
|
268
|
+
gatewayUrl: input.input.gatewayUrl,
|
|
269
|
+
suppressHumanResultCard: input.input.suppressRefreshHumanResultCard,
|
|
270
|
+
view: input.input.view,
|
|
271
|
+
forceReinstall: shouldForceDaemonServiceReinstall({
|
|
272
|
+
accountIssue: input.accountIssue,
|
|
273
|
+
forceRefresh: input.input.forceRefresh
|
|
274
|
+
}),
|
|
275
|
+
...input.scheduleBackgroundUpdate === void 0 ? {} : { scheduleBackgroundUpdate: input.scheduleBackgroundUpdate }
|
|
276
|
+
});
|
|
277
|
+
if (isCancelledDaemonServiceRefresh(refreshOutcome)) return { status: "cancelled" };
|
|
278
|
+
if (isStoppedDaemonServiceRefresh(refreshOutcome)) {
|
|
279
|
+
const startMapped = await mapDaemonBackgroundStartResult(await runDaemonBackgroundStartForParticipation({
|
|
280
|
+
openMeldProfileId: input.input.openMeldProfileId,
|
|
281
|
+
gatewayUrl: input.input.gatewayUrl,
|
|
282
|
+
serviceProgress: input.input.serviceProgress,
|
|
283
|
+
suppressStartSpinner: input.input.suppressStartSpinner,
|
|
284
|
+
view: input.input.view
|
|
285
|
+
}), {
|
|
286
|
+
expectedVersion: input.expectedVersion,
|
|
287
|
+
presenter: input.input.presenter,
|
|
288
|
+
codePrefix: input.codePrefix
|
|
289
|
+
});
|
|
290
|
+
if (startMapped.status !== "aligned") return startMapped;
|
|
291
|
+
const accountIssueAfterStart = await resolveDaemonServiceAccountAlignmentIssue(input.input);
|
|
292
|
+
if (accountIssueAfterStart) {
|
|
293
|
+
renderDaemonStartNeedsAttention({
|
|
294
|
+
presenter: input.input.presenter,
|
|
295
|
+
codePrefix: input.codePrefix,
|
|
296
|
+
summary: accountIssueAfterStart.summary
|
|
297
|
+
});
|
|
298
|
+
input.input.presenter.line({
|
|
299
|
+
code: `${input.codePrefix}.account_alignment_failed`,
|
|
300
|
+
text: accountIssueAfterStart.summary,
|
|
301
|
+
data: { reasonCode: accountIssueAfterStart.reasonCode }
|
|
302
|
+
});
|
|
303
|
+
return { status: "needs_attention" };
|
|
304
|
+
}
|
|
305
|
+
return startMapped;
|
|
306
|
+
}
|
|
307
|
+
const scheduled = await completeScheduledServiceRefresh({
|
|
308
|
+
outcome: refreshOutcome,
|
|
309
|
+
presenter: input.input.presenter,
|
|
310
|
+
codePrefix: input.codePrefix
|
|
311
|
+
});
|
|
312
|
+
if (!refreshOutcome.ok && (refreshOutcome.result === "start_failed" || refreshOutcome.result === "start_blocked") && (refreshOutcome.startFailureVerdict?.jobDiagnostics || refreshOutcome.startFailureVerdict?.localMachineIssue)) return renderRefreshStartFailure({
|
|
313
|
+
codePrefix: input.codePrefix,
|
|
314
|
+
presenter: input.input.presenter,
|
|
315
|
+
verdict: refreshOutcome.startFailureVerdict
|
|
316
|
+
});
|
|
317
|
+
if (!(scheduled || refreshOutcome.ok && (refreshOutcome.result === "already_healthy" || refreshOutcome.result === "completed") && refreshOutcome.serviceState === "running")) throw new Error(`OpenMeld Service refresh did not complete: ${refreshOutcome.result}`);
|
|
318
|
+
} catch (error) {
|
|
319
|
+
renderRefreshFailureIfVisible({
|
|
320
|
+
codePrefix: input.codePrefix,
|
|
321
|
+
presenter: input.input.presenter,
|
|
322
|
+
summary: "OpenMeld Service was repaired, but it is not running yet. Local Wake work will keep waiting until OpenMeld Service starts. Run `openmeld service start`.",
|
|
323
|
+
suppressed: input.input.suppressRefreshHumanResultCard === true
|
|
324
|
+
});
|
|
325
|
+
return {
|
|
326
|
+
errorMessage: toErrorMessage(error),
|
|
327
|
+
status: "needs_attention"
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
const refreshedStatus = await resolveDaemonServiceParticipationStatus({ expectedVersion: input.expectedVersion });
|
|
331
|
+
const accountIssue = await resolveDaemonServiceAccountAlignmentIssue(input.input);
|
|
332
|
+
if (refreshedStatus.kind === "ready" && !accountIssue) return { status: "aligned" };
|
|
333
|
+
renderDaemonStartNeedsAttention({
|
|
334
|
+
presenter: input.input.presenter,
|
|
335
|
+
codePrefix: input.codePrefix,
|
|
336
|
+
summary: accountIssue?.summary ?? summarizeDaemonServiceParticipationStatus(refreshedStatus)
|
|
337
|
+
});
|
|
338
|
+
if (accountIssue) input.input.presenter.line({
|
|
339
|
+
code: `${input.codePrefix}.account_alignment_failed`,
|
|
340
|
+
text: accountIssue.summary,
|
|
341
|
+
data: { reasonCode: accountIssue.reasonCode }
|
|
342
|
+
});
|
|
343
|
+
return { status: "needs_attention" };
|
|
344
|
+
}
|
|
345
|
+
function emitServiceAccountRefreshNotice(input) {
|
|
346
|
+
if (!input.accountIssue) return;
|
|
347
|
+
input.input.presenter.line({
|
|
348
|
+
code: `${input.codePrefix}.account_alignment_refresh`,
|
|
349
|
+
text: input.accountIssue.summary,
|
|
350
|
+
data: { reasonCode: input.accountIssue.reasonCode }
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
async function completeScheduledServiceRefresh(input) {
|
|
354
|
+
if (!(input.outcome.ok && input.outcome.result === "scheduled")) return false;
|
|
355
|
+
await waitForOpenMeldToolsUpdateCompletion({
|
|
356
|
+
operationId: input.outcome.jobId,
|
|
357
|
+
onWaiting: (operation) => {
|
|
358
|
+
input.presenter.line({
|
|
359
|
+
code: `${input.codePrefix}.update.waiting`,
|
|
360
|
+
text: "OpenMeld is finishing its background update. This command will continue automatically when it finishes.",
|
|
361
|
+
data: {
|
|
362
|
+
operationId: operation.operationId,
|
|
363
|
+
phase: operation.phase,
|
|
364
|
+
targetVersion: operation.targetVersion
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
function renderRefreshFailureIfVisible(input) {
|
|
372
|
+
if (input.suppressed) return;
|
|
373
|
+
renderDaemonStartNeedsAttention({
|
|
374
|
+
autoRepairAttempted: true,
|
|
375
|
+
codePrefix: input.codePrefix,
|
|
376
|
+
presenter: input.presenter,
|
|
377
|
+
summary: input.summary
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
function shouldForceDaemonServiceReinstall(input) {
|
|
381
|
+
return input.accountIssue !== void 0 || input.forceRefresh === true;
|
|
382
|
+
}
|
|
383
|
+
function renderRefreshStartFailure(input) {
|
|
384
|
+
const report = buildSystemServiceStartupFailureReportFromVerdict(input.verdict, { autoRepairAttempted: true });
|
|
385
|
+
renderInfoCard({
|
|
386
|
+
presenter: input.presenter,
|
|
387
|
+
title: report.title,
|
|
388
|
+
codePrefix: `${input.codePrefix}.failed`,
|
|
389
|
+
rows: report.rows
|
|
390
|
+
});
|
|
391
|
+
const { outcome: _outcome, ...failure } = buildDaemonBackgroundStartFailure(input.verdict);
|
|
392
|
+
return {
|
|
393
|
+
...failure,
|
|
394
|
+
status: "needs_attention"
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
async function resolveDaemonServiceAccountAlignmentIssue(input) {
|
|
398
|
+
const expectedProfileId = normalizeOptionalText$1(input.openMeldProfileId);
|
|
399
|
+
const expectedOwnerUserId = normalizeOptionalText$1(input.expectedOwnerUserId);
|
|
400
|
+
const expectedGatewayUrl = normalizeGatewayBaseUrlOrNull(input.gatewayUrl);
|
|
401
|
+
if (!(expectedProfileId || expectedOwnerUserId || expectedGatewayUrl)) return null;
|
|
402
|
+
const contract = await readDaemonServiceContract().catch(() => null);
|
|
403
|
+
if (!contract) return {
|
|
404
|
+
reasonCode: "service_contract.missing",
|
|
405
|
+
summary: "OpenMeld Service is running, but this setup could not verify the service account contract for this computer."
|
|
406
|
+
};
|
|
407
|
+
const contractOwnerUserId = normalizeOptionalText$1(contract.ownerUserId);
|
|
408
|
+
if (expectedOwnerUserId && contractOwnerUserId && contractOwnerUserId !== expectedOwnerUserId) return {
|
|
409
|
+
reasonCode: "service_contract.owner_mismatch",
|
|
410
|
+
summary: "OpenMeld Service is running for a different OpenMeld account. This setup will reconnect OpenMeld Service to the current Web account."
|
|
411
|
+
};
|
|
412
|
+
if (expectedProfileId && contract.profileId && contract.profileId !== expectedProfileId) return {
|
|
413
|
+
reasonCode: "service_contract.profile_mismatch",
|
|
414
|
+
summary: "OpenMeld Service is running for a different OpenMeld account. This setup will reconnect OpenMeld Service to the current Web account."
|
|
415
|
+
};
|
|
416
|
+
if (expectedOwnerUserId && expectedProfileId && !(contractOwnerUserId || contract.profileId)) return {
|
|
417
|
+
reasonCode: "service_contract.owner_unverified",
|
|
418
|
+
summary: "OpenMeld Service is running, but this setup could not verify that OpenMeld Service belongs to the current Web account. This setup will reconnect OpenMeld Service to the current Web account."
|
|
419
|
+
};
|
|
420
|
+
const contractGatewayUrl = normalizeGatewayBaseUrlOrNull(contract.gatewayUrl);
|
|
421
|
+
if (expectedGatewayUrl && contractGatewayUrl && contractGatewayUrl !== expectedGatewayUrl) return {
|
|
422
|
+
reasonCode: "service_contract.gateway_mismatch",
|
|
423
|
+
summary: "OpenMeld Service is running for a different OpenMeld gateway. This setup will reconnect OpenMeld Service to the current Web account."
|
|
424
|
+
};
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
function normalizeOptionalText$1(value) {
|
|
428
|
+
const normalized = String(value ?? "").trim();
|
|
429
|
+
return normalized.length > 0 ? normalized : null;
|
|
430
|
+
}
|
|
431
|
+
function resolveDaemonServiceParticipationCodePrefix(intent) {
|
|
432
|
+
switch (intent) {
|
|
433
|
+
case "start": return "start.service";
|
|
434
|
+
case "space_create_join": return "space.create_join.service";
|
|
435
|
+
case "space_join": return "space.join.service";
|
|
436
|
+
case "space_add_members": return "space.add_members.service";
|
|
437
|
+
default: throw new Error(`Unknown local service participation intent: ${intent}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
function isCancelledDaemonServiceRefresh(outcome) {
|
|
441
|
+
return !outcome.ok && outcome.result === "cancelled";
|
|
442
|
+
}
|
|
443
|
+
function isStoppedDaemonServiceRefresh(outcome) {
|
|
444
|
+
return outcome.ok && (outcome.result === "already_healthy" || outcome.result === "completed" || outcome.result === "stopped_safe") && outcome.serviceState === "stopped";
|
|
445
|
+
}
|
|
446
|
+
function shouldStartAfterDaemonInstall(_intent) {
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
function shouldSuppressHumanInstallOutput(intent) {
|
|
450
|
+
return intent !== "start";
|
|
451
|
+
}
|
|
452
|
+
async function mapDaemonBackgroundStartResult(result, input) {
|
|
453
|
+
if (typeof result === "object") return {
|
|
454
|
+
status: "needs_attention",
|
|
455
|
+
errorMessage: result.errorMessage,
|
|
456
|
+
...result.localMachineIssue ? { localMachineIssue: result.localMachineIssue } : {}
|
|
457
|
+
};
|
|
458
|
+
if (result === "started") {
|
|
459
|
+
const refreshedStatus = await resolveDaemonServiceParticipationStatus({ expectedVersion: input.expectedVersion });
|
|
460
|
+
if (refreshedStatus.kind === "ready") return { status: "aligned" };
|
|
461
|
+
renderDaemonStartNeedsAttention({
|
|
462
|
+
presenter: input.presenter,
|
|
463
|
+
codePrefix: input.codePrefix,
|
|
464
|
+
summary: summarizeDaemonServiceParticipationStatus(refreshedStatus)
|
|
465
|
+
});
|
|
466
|
+
return { status: "needs_attention" };
|
|
467
|
+
}
|
|
468
|
+
if (result === "deferred_with_card") return { status: "declined" };
|
|
469
|
+
if (result === "cancelled") return { status: "cancelled" };
|
|
470
|
+
if (result === "not_now") {
|
|
471
|
+
renderDaemonStartDeferred({
|
|
472
|
+
presenter: input.presenter,
|
|
473
|
+
codePrefix: input.codePrefix
|
|
474
|
+
});
|
|
475
|
+
return { status: "declined" };
|
|
476
|
+
}
|
|
477
|
+
if (result === "failed") {
|
|
478
|
+
renderDaemonStartNeedsAttention({
|
|
479
|
+
presenter: input.presenter,
|
|
480
|
+
codePrefix: input.codePrefix,
|
|
481
|
+
summary: "OpenMeld could not finish starting OpenMeld Service."
|
|
482
|
+
});
|
|
483
|
+
return { status: "needs_attention" };
|
|
484
|
+
}
|
|
485
|
+
return { status: "needs_attention" };
|
|
486
|
+
}
|
|
487
|
+
function summarizeDaemonServiceParticipationStatus(status) {
|
|
488
|
+
switch (status.kind) {
|
|
489
|
+
case "missing": return formatDaemonInstallFailedMessage("missing");
|
|
490
|
+
case "outdated": return formatDaemonInstallFailedMessage("outdated");
|
|
491
|
+
case "needs_repair": return status.detail;
|
|
492
|
+
case "not_running": return "OpenMeld could not finish starting OpenMeld Service.";
|
|
493
|
+
case "ready": return "OpenMeld Service is running.";
|
|
494
|
+
default: return "OpenMeld Service still needs attention on this device.";
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
function renderDaemonStartDeferred(input) {
|
|
498
|
+
const card = buildDaemonStartDeferredCard();
|
|
499
|
+
renderInfoCard({
|
|
500
|
+
presenter: input.presenter,
|
|
501
|
+
title: card.title,
|
|
502
|
+
codePrefix: `${input.codePrefix}.deferred`,
|
|
503
|
+
rows: card.rows
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
function renderDaemonStartNeedsAttention(input) {
|
|
507
|
+
const card = buildDaemonStartNeedsAttentionCard({
|
|
508
|
+
summary: input.summary,
|
|
509
|
+
...input.nextStep ? { nextStep: input.nextStep } : {},
|
|
510
|
+
...input.detail ? { detail: input.detail } : {},
|
|
511
|
+
...input.autoRepairAttempted ? { autoRepairAttempted: input.autoRepairAttempted } : {}
|
|
512
|
+
});
|
|
513
|
+
renderInfoCard({
|
|
514
|
+
presenter: input.presenter,
|
|
515
|
+
title: card.title,
|
|
516
|
+
codePrefix: `${input.codePrefix}.failed`,
|
|
517
|
+
rows: card.rows
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
//#endregion
|
|
521
|
+
//#region src/space/web-url.ts
|
|
522
|
+
function buildSpaceWebUrl(baseUrl, organizationSlug, spaceId, options) {
|
|
523
|
+
const url = new URL(resolveGatewayWebOrigin(baseUrl));
|
|
524
|
+
const orgSlug = requireOrganizationSlug(organizationSlug);
|
|
525
|
+
url.pathname = `/o/${encodeURIComponent(orgSlug)}/spaces/${encodeURIComponent(spaceId)}`;
|
|
526
|
+
url.search = "";
|
|
527
|
+
const profileId = normalizeOptionalText(options?.profileId);
|
|
528
|
+
if (profileId) url.searchParams.set("profileId", profileId);
|
|
529
|
+
url.hash = "";
|
|
530
|
+
return url.toString();
|
|
531
|
+
}
|
|
532
|
+
function resolveGatewayWebOrigin(baseUrl) {
|
|
533
|
+
const url = new URL(baseUrl);
|
|
534
|
+
if (isLoopbackHostname(url.hostname) && url.port === LOCAL_GATEWAY_PORT) url.port = LOCAL_WEB_PORT;
|
|
535
|
+
const pair = OPENMELD_ORIGIN_HOST_PAIRS.find(({ gateway }) => gateway === url.hostname);
|
|
536
|
+
if (pair) {
|
|
537
|
+
url.hostname = pair.web;
|
|
538
|
+
url.port = "";
|
|
539
|
+
url.protocol = "https:";
|
|
540
|
+
}
|
|
541
|
+
url.pathname = "";
|
|
542
|
+
url.search = "";
|
|
543
|
+
url.hash = "";
|
|
544
|
+
return url.origin;
|
|
545
|
+
}
|
|
546
|
+
function resolveSpaceWebGatewayOrigin(spaceUrl) {
|
|
547
|
+
const url = new URL(spaceUrl.toString());
|
|
548
|
+
const pair = OPENMELD_ORIGIN_HOST_PAIRS.find(({ web }) => web === url.hostname);
|
|
549
|
+
if (pair) {
|
|
550
|
+
if (url.protocol !== "https:" || url.port.length > 0) throw new Error(TRUSTED_SPACE_WEB_ORIGIN_ERROR);
|
|
551
|
+
url.hostname = pair.gateway;
|
|
552
|
+
} else if (isLoopbackHostname(url.hostname) && (url.protocol === "http:" || url.protocol === "https:") && url.port === LOCAL_WEB_PORT) url.port = LOCAL_GATEWAY_PORT;
|
|
553
|
+
else throw new Error(TRUSTED_SPACE_WEB_ORIGIN_ERROR);
|
|
554
|
+
url.pathname = "";
|
|
555
|
+
url.search = "";
|
|
556
|
+
url.hash = "";
|
|
557
|
+
return url.origin;
|
|
558
|
+
}
|
|
559
|
+
function normalizeOptionalText(value) {
|
|
560
|
+
const normalized = String(value ?? "").trim();
|
|
561
|
+
return normalized.length > 0 ? normalized : null;
|
|
562
|
+
}
|
|
563
|
+
function requireOrganizationSlug(value) {
|
|
564
|
+
const normalized = normalizeOptionalText(value);
|
|
565
|
+
if (!normalized) throw new Error("Active organization slug is required to build a Space URL.");
|
|
566
|
+
return normalized;
|
|
567
|
+
}
|
|
568
|
+
const OPENMELD_ORIGIN_HOST_PAIRS = [
|
|
569
|
+
{
|
|
570
|
+
gateway: "gateway.openmeld.ai",
|
|
571
|
+
web: "openmeld.ai"
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
gateway: "gateway.staging.openmeld.ai",
|
|
575
|
+
web: "staging.openmeld.ai"
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
gateway: "gateway.openmeld.invalid",
|
|
579
|
+
web: "openmeld.invalid"
|
|
580
|
+
}
|
|
581
|
+
];
|
|
582
|
+
const LOCAL_GATEWAY_PORT = "8080";
|
|
583
|
+
const LOCAL_WEB_PORT = "3000";
|
|
584
|
+
const TRUSTED_SPACE_WEB_ORIGIN_ERROR = "Space URL must use a trusted OpenMeld Web origin. For a custom local environment, pass --gateway-url explicitly.";
|
|
585
|
+
//#endregion
|
|
586
|
+
//#region src/runtime/retry-delay.ts
|
|
587
|
+
const RETRY_DELAY_SCHEDULE_MS = [
|
|
588
|
+
250,
|
|
589
|
+
500,
|
|
590
|
+
1e3,
|
|
591
|
+
2e3
|
|
592
|
+
];
|
|
593
|
+
const RETRY_DELAY_CAP_MS = 2e3;
|
|
594
|
+
function computeRetryDelayMs(attempt) {
|
|
595
|
+
const index = Math.max(0, Math.min(attempt - 1, RETRY_DELAY_SCHEDULE_MS.length - 1));
|
|
596
|
+
return RETRY_DELAY_SCHEDULE_MS[index] ?? RETRY_DELAY_CAP_MS;
|
|
597
|
+
}
|
|
598
|
+
//#endregion
|
|
599
|
+
//#region src/agents/agent-overview.ts
|
|
600
|
+
function emitAgentsAgentOverview(input) {
|
|
601
|
+
input.presenter.data({
|
|
602
|
+
code: `agents.${input.command}.agent_overview`,
|
|
603
|
+
payload: formatAgentOverviewPayload(buildAgentsAgentOverviewPayload(input.command))
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
function buildBuiltinAgentSelectionOverviewPayload(command) {
|
|
607
|
+
return {
|
|
608
|
+
commands: buildBuiltinAgentSelectionExamples({
|
|
609
|
+
command,
|
|
610
|
+
includeViewFlag: true
|
|
611
|
+
}),
|
|
612
|
+
optionalArguments: getBuiltinAgentSelectionOptionalArguments(command),
|
|
613
|
+
requiredArguments: getBuiltinAgentSelectionRequiredArguments(command)
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
function buildAgentsAgentOverviewPayload(command) {
|
|
617
|
+
switch (command) {
|
|
618
|
+
case "menu": return {
|
|
619
|
+
summary: "Manage local OpenMeld-connected agents, agent visibility in Space, and custom agent targets.",
|
|
620
|
+
requiredArguments: [],
|
|
621
|
+
optionalArguments: [],
|
|
622
|
+
commands: [
|
|
623
|
+
"openmeld agents --view agent",
|
|
624
|
+
"openmeld agents detect --view agent",
|
|
625
|
+
"openmeld agents list --view agent",
|
|
626
|
+
...buildBuiltinAgentSelectionExamples({
|
|
627
|
+
command: "enable",
|
|
628
|
+
includeViewFlag: true
|
|
629
|
+
}),
|
|
630
|
+
"openmeld agents manage --view agent"
|
|
631
|
+
],
|
|
632
|
+
stages: ["Inspect available agents and current registrations.", "Choose an explicit subcommand for detect, list, enable, disable, show, config, or custom targets."],
|
|
633
|
+
notes: ["Agent view does not open the human interactive agents menu.", "Use explicit subcommands to keep automation deterministic."],
|
|
634
|
+
humanActionRequired: false
|
|
635
|
+
};
|
|
636
|
+
case "manage": return {
|
|
637
|
+
summary: "Review connected agent IDs and decide whether to inspect, enable, or disable them with explicit commands.",
|
|
638
|
+
requiredArguments: [],
|
|
639
|
+
optionalArguments: [],
|
|
640
|
+
commands: [
|
|
641
|
+
"openmeld agents list --view agent",
|
|
642
|
+
"openmeld agents show <agent-id> --view agent",
|
|
643
|
+
...buildBuiltinAgentSelectionExamples({
|
|
644
|
+
command: "repair",
|
|
645
|
+
includeViewFlag: true
|
|
646
|
+
}),
|
|
647
|
+
"openmeld agents config <agent-id> --enabled true|false --view agent",
|
|
648
|
+
...buildBuiltinAgentSelectionExamples({
|
|
649
|
+
command: "disable",
|
|
650
|
+
includeViewFlag: true
|
|
651
|
+
})
|
|
652
|
+
],
|
|
653
|
+
stages: ["List connected agent IDs.", "Inspect one agent or change enabled state with explicit subcommands."],
|
|
654
|
+
notes: ["The manage menu is interactive in human view only."],
|
|
655
|
+
humanActionRequired: true
|
|
656
|
+
};
|
|
657
|
+
case "detect": return {
|
|
658
|
+
summary: "Detect supported local AI tools on this computer and report which ones are available to OpenMeld now.",
|
|
659
|
+
requiredArguments: [],
|
|
660
|
+
optionalArguments: [],
|
|
661
|
+
commands: ["openmeld agents detect --view agent"],
|
|
662
|
+
stages: ["Scan supported builtin agent targets.", "Return detected agents and whether each one is immediately selectable."],
|
|
663
|
+
notes: [],
|
|
664
|
+
humanActionRequired: false
|
|
665
|
+
};
|
|
666
|
+
case "list": return {
|
|
667
|
+
summary: "List currently registered OpenMeld agent IDs that can wake and reply in Space.",
|
|
668
|
+
requiredArguments: [],
|
|
669
|
+
optionalArguments: [],
|
|
670
|
+
commands: ["openmeld agents list --view agent"],
|
|
671
|
+
stages: ["Load agent registrations from your local OpenMeld config.", "Return registered agents and enabled state."],
|
|
672
|
+
notes: [],
|
|
673
|
+
humanActionRequired: false
|
|
674
|
+
};
|
|
675
|
+
case "repair": return {
|
|
676
|
+
summary: "Repair one or more builtin local agents so OpenMeld can start them again on this computer.",
|
|
677
|
+
...buildBuiltinAgentSelectionOverviewPayload("repair"),
|
|
678
|
+
stages: ["Resolve a fresh launch contract for each requested builtin agent.", "Persist the repaired machine launch contract into OpenMeld runtime state."],
|
|
679
|
+
notes: ["Repair does not enable or disable agents. It refreshes how OpenMeld starts them on this computer."],
|
|
680
|
+
humanActionRequired: false
|
|
681
|
+
};
|
|
682
|
+
case "show": return {
|
|
683
|
+
summary: "Show one registered agent ID, its current OpenMeld config, and how OpenMeld will wake it.",
|
|
684
|
+
requiredArguments: ["<agent-id>"],
|
|
685
|
+
optionalArguments: [],
|
|
686
|
+
commands: ["openmeld agents show <agent-id> --view agent"],
|
|
687
|
+
stages: ["Resolve the requested agent ID.", "Return the current registration and effective runtime config."],
|
|
688
|
+
notes: [],
|
|
689
|
+
humanActionRequired: false
|
|
690
|
+
};
|
|
691
|
+
case "enable": return {
|
|
692
|
+
summary: "Enable one or more detected agents so new Space @mentions can wake them.",
|
|
693
|
+
...buildBuiltinAgentSelectionOverviewPayload("enable"),
|
|
694
|
+
stages: [
|
|
695
|
+
"Resolve selectable detected agents.",
|
|
696
|
+
"Register or re-enable them in your local OpenMeld config.",
|
|
697
|
+
"Return per-agent enable summary."
|
|
698
|
+
],
|
|
699
|
+
notes: ["Use `openmeld agents detect --view agent` first if you need to discover selectable ids."],
|
|
700
|
+
humanActionRequired: false
|
|
701
|
+
};
|
|
702
|
+
case "disable": return {
|
|
703
|
+
summary: "Disable one or more registered agents so new Space @mentions stop waking them.",
|
|
704
|
+
...buildBuiltinAgentSelectionOverviewPayload("disable"),
|
|
705
|
+
stages: [
|
|
706
|
+
"Resolve requested registered agent ids.",
|
|
707
|
+
"Update enabled=false in your local OpenMeld config.",
|
|
708
|
+
"Return per-agent disable summary."
|
|
709
|
+
],
|
|
710
|
+
notes: [],
|
|
711
|
+
humanActionRequired: false
|
|
712
|
+
};
|
|
713
|
+
case "config": return {
|
|
714
|
+
summary: "Update a registered agent's OpenMeld configuration with explicit flags.",
|
|
715
|
+
requiredArguments: ["<agent-id>", "--enabled true|false"],
|
|
716
|
+
optionalArguments: [],
|
|
717
|
+
commands: ["openmeld agents config <agent-id> --enabled true --view agent", "openmeld agents config <agent-id> --enabled false --view agent"],
|
|
718
|
+
stages: [
|
|
719
|
+
"Resolve the requested registered agent id.",
|
|
720
|
+
"Apply explicit config updates.",
|
|
721
|
+
"Return the updated effective config."
|
|
722
|
+
],
|
|
723
|
+
notes: [],
|
|
724
|
+
humanActionRequired: false
|
|
725
|
+
};
|
|
726
|
+
case "custom_list": return {
|
|
727
|
+
summary: "List custom agent targets stored in your local OpenMeld config and whether they are currently registered.",
|
|
728
|
+
requiredArguments: [],
|
|
729
|
+
optionalArguments: [],
|
|
730
|
+
commands: ["openmeld agents custom list --view agent"],
|
|
731
|
+
stages: ["Load configured custom targets.", "Project whether each target is registered and enabled."],
|
|
732
|
+
notes: [],
|
|
733
|
+
humanActionRequired: false
|
|
734
|
+
};
|
|
735
|
+
case "custom_add": return {
|
|
736
|
+
summary: "Add a custom agent target to your local OpenMeld config with an absolute directory path.",
|
|
737
|
+
requiredArguments: ["--name <name>", "--dir <absolute-path>"],
|
|
738
|
+
optionalArguments: ["--id <id>"],
|
|
739
|
+
commands: ["openmeld agents custom add --name <name> --dir <absolute-path> --view agent", "openmeld agents custom add --id <id> --name <name> --dir <absolute-path> --view agent"],
|
|
740
|
+
stages: [
|
|
741
|
+
"Validate explicit custom target input.",
|
|
742
|
+
"Save the target in your local OpenMeld config.",
|
|
743
|
+
"Return the created custom target."
|
|
744
|
+
],
|
|
745
|
+
notes: [],
|
|
746
|
+
humanActionRequired: false
|
|
747
|
+
};
|
|
748
|
+
case "custom_update": return {
|
|
749
|
+
summary: "Update a custom agent target while keeping its ID stable.",
|
|
750
|
+
requiredArguments: ["--id <id>", "one update: --name <name> | --dir <absolute-path>"],
|
|
751
|
+
optionalArguments: [],
|
|
752
|
+
commands: ["openmeld agents custom update --id <id> --name <name> --view agent", "openmeld agents custom update --id <id> --dir <absolute-path> --view agent"],
|
|
753
|
+
stages: [
|
|
754
|
+
"Resolve the custom target id.",
|
|
755
|
+
"Apply explicit updates.",
|
|
756
|
+
"Return the updated target and changed fields."
|
|
757
|
+
],
|
|
758
|
+
notes: [],
|
|
759
|
+
humanActionRequired: false
|
|
760
|
+
};
|
|
761
|
+
case "custom_remove": return {
|
|
762
|
+
summary: "Remove a custom agent target that is not currently registered.",
|
|
763
|
+
requiredArguments: ["--id <id>"],
|
|
764
|
+
optionalArguments: [],
|
|
765
|
+
commands: ["openmeld agents custom remove --id <id> --view agent"],
|
|
766
|
+
stages: [
|
|
767
|
+
"Resolve the custom target id.",
|
|
768
|
+
"Verify it is not currently registered.",
|
|
769
|
+
"Remove it from your local OpenMeld config and report the result."
|
|
770
|
+
],
|
|
771
|
+
notes: ["Install records are kept after removal; clean them up with OpenMeld skills commands when needed."],
|
|
772
|
+
humanActionRequired: false
|
|
773
|
+
};
|
|
774
|
+
default: return assertNever(command);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
function assertNever(value) {
|
|
778
|
+
throw new Error(`unsupported agents agent overview command: ${String(value)}`);
|
|
779
|
+
}
|
|
780
|
+
//#endregion
|
|
781
|
+
//#region src/ui/visible-window.ts
|
|
782
|
+
function clampNumber(value, min, max) {
|
|
783
|
+
if (!Number.isFinite(value)) return min;
|
|
784
|
+
if (value < min) return min;
|
|
785
|
+
if (value > max) return max;
|
|
786
|
+
return value;
|
|
787
|
+
}
|
|
788
|
+
function resolveAnchoredVisibleWindow(input) {
|
|
789
|
+
if (input.itemCount <= input.maxVisible) return {
|
|
790
|
+
start: 0,
|
|
791
|
+
end: input.itemCount
|
|
792
|
+
};
|
|
793
|
+
const start = clampNumber((input.anchorIndex === null ? 0 : clampNumber(input.anchorIndex, 0, input.itemCount - 1)) - Math.floor(input.maxVisible / 2), 0, input.itemCount - input.maxVisible);
|
|
794
|
+
return {
|
|
795
|
+
start,
|
|
796
|
+
end: Math.min(input.itemCount, start + input.maxVisible)
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
function resolveOffsetVisibleWindow(input) {
|
|
800
|
+
if (input.itemCount <= input.maxVisible) return {
|
|
801
|
+
start: 0,
|
|
802
|
+
end: input.itemCount
|
|
803
|
+
};
|
|
804
|
+
const maxStart = Math.max(0, input.itemCount - input.maxVisible);
|
|
805
|
+
const start = resolveVisibleWindowOffset(input.offset, maxStart);
|
|
806
|
+
return {
|
|
807
|
+
start,
|
|
808
|
+
end: Math.min(input.itemCount, start + input.maxVisible)
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
function resolveVisibleWindowOffset(offset, maxStart) {
|
|
812
|
+
if (!Number.isFinite(offset)) return offset > 0 ? maxStart : 0;
|
|
813
|
+
return clampNumber(offset, 0, maxStart);
|
|
814
|
+
}
|
|
815
|
+
//#endregion
|
|
816
|
+
//#region src/ui/search-select.ts
|
|
817
|
+
const PROMPT_SYMBOL_ACTIVE = pc.green("◇");
|
|
818
|
+
const PROMPT_SYMBOL_CANCELLED = pc.red("■");
|
|
819
|
+
const CURSOR_SYMBOL = pc.cyan("❯");
|
|
820
|
+
const BAR_SYMBOL = pc.dim("│");
|
|
821
|
+
const SINGLE_SELECTED_SYMBOL = pc.green("●");
|
|
822
|
+
const SINGLE_UNSELECTED_SYMBOL = pc.dim("○");
|
|
823
|
+
const MULTI_SELECTED_SYMBOL = pc.green("■");
|
|
824
|
+
const MULTI_UNSELECTED_SYMBOL = pc.dim("□");
|
|
825
|
+
const EMPTY_MATCH_LABEL = "No matches found";
|
|
826
|
+
const DEFAULT_MAX_VISIBLE = 8;
|
|
827
|
+
const PRINTABLE_SEARCH_INPUT_REGEX = /^[\x20-\x7E]+$/;
|
|
828
|
+
function createSearchPromptState(input) {
|
|
829
|
+
const items = input.items.map(sanitizeSearchPromptItem);
|
|
830
|
+
const baseState = {
|
|
831
|
+
mode: input.mode,
|
|
832
|
+
message: sanitizeTerminalDisplayText(input.message, "single-line"),
|
|
833
|
+
noticeLines: (input.noticeLines ?? []).map((line) => sanitizeTerminalDisplayText(line, "single-line")),
|
|
834
|
+
items,
|
|
835
|
+
footerActions: (input.footerActions ?? []).map(sanitizeSearchPromptFooterAction),
|
|
836
|
+
footerActionsResolver: input.footerActionsResolver ?? null,
|
|
837
|
+
workbench: input.workbench === true,
|
|
838
|
+
allowBack: input.allowBack === true,
|
|
839
|
+
backLabel: sanitizeTerminalDisplayText(input.backLabel ?? "Back", "single-line"),
|
|
840
|
+
emptyLabel: sanitizeTerminalDisplayText(input.emptyLabel ?? EMPTY_MATCH_LABEL, "single-line"),
|
|
841
|
+
maxVisible: normalizeMaxVisible(input.maxVisible),
|
|
842
|
+
required: input.required === true,
|
|
843
|
+
readOnly: input.readOnly === true,
|
|
844
|
+
summaryCard: input.summaryCard === void 0 ? null : sanitizeSearchPromptSummaryCard(input.summaryCard),
|
|
845
|
+
selectedValues: dedupeValues(input.initialSelectedValues ?? []),
|
|
846
|
+
query: "",
|
|
847
|
+
activeRowIndex: 0
|
|
848
|
+
};
|
|
849
|
+
return syncSearchPromptState({
|
|
850
|
+
...baseState,
|
|
851
|
+
activeRowIndex: resolveInitialSearchPromptActiveRowIndex(baseState)
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
function sanitizeSearchPromptFooterAction(action) {
|
|
855
|
+
return {
|
|
856
|
+
...action,
|
|
857
|
+
label: sanitizeTerminalDisplayText(action.label, "single-line"),
|
|
858
|
+
...typeof action.icon === "string" ? { icon: sanitizeTerminalDisplayText(action.icon, "single-line") } : {},
|
|
859
|
+
...typeof action.hint === "string" ? { hint: sanitizeTerminalDisplayText(action.hint, "single-line") } : {}
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
function sanitizeSearchPromptItem(item) {
|
|
863
|
+
const normalizedItem = {
|
|
864
|
+
...item,
|
|
865
|
+
label: sanitizeTerminalDisplayText(item.label, "single-line"),
|
|
866
|
+
...typeof item.detail === "string" ? { detail: sanitizeTerminalDisplayText(item.detail, "single-line") } : {},
|
|
867
|
+
...typeof item.hint === "string" ? { hint: sanitizeTerminalDisplayText(item.hint, "single-line") } : {},
|
|
868
|
+
...typeof item.searchText === "string" ? { searchText: sanitizeTerminalDisplayText(item.searchText, "multi-line") } : {}
|
|
869
|
+
};
|
|
870
|
+
return {
|
|
871
|
+
...normalizedItem,
|
|
872
|
+
searchKey: buildSearchKey(normalizedItem)
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
function sanitizeSearchPromptSummaryCard(card) {
|
|
876
|
+
return {
|
|
877
|
+
...card,
|
|
878
|
+
title: sanitizeTerminalDisplayText(card.title, "single-line"),
|
|
879
|
+
rows: card.rows.map((row) => ({
|
|
880
|
+
label: sanitizeTerminalDisplayText(row.label, "single-line"),
|
|
881
|
+
value: sanitizeTerminalDisplayText(row.value, "single-line")
|
|
882
|
+
}))
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
function getSearchPromptViewModel(state) {
|
|
886
|
+
const filteredIndexes = getFilteredItemIndexes(state);
|
|
887
|
+
const footerActions = resolveSearchPromptFooterActions(state);
|
|
888
|
+
const rows = buildSearchPromptRows({
|
|
889
|
+
allowBack: state.allowBack,
|
|
890
|
+
filteredIndexes,
|
|
891
|
+
footerActions,
|
|
892
|
+
workbench: state.workbench
|
|
893
|
+
});
|
|
894
|
+
const activeRow = rows[state.activeRowIndex] ?? null;
|
|
895
|
+
const activeItemFilteredIndex = resolveSearchPromptActiveItemFilteredIndex({
|
|
896
|
+
activeRow,
|
|
897
|
+
filteredIndexes,
|
|
898
|
+
workbench: state.workbench
|
|
899
|
+
});
|
|
900
|
+
const visibleWindow = resolveAnchoredVisibleWindow({
|
|
901
|
+
itemCount: filteredIndexes.length,
|
|
902
|
+
anchorIndex: activeItemFilteredIndex,
|
|
903
|
+
maxVisible: state.maxVisible
|
|
904
|
+
});
|
|
905
|
+
return {
|
|
906
|
+
filteredIndexes,
|
|
907
|
+
rows,
|
|
908
|
+
activeRow,
|
|
909
|
+
activeItemFilteredIndex,
|
|
910
|
+
visibleRows: buildSearchPromptVisibleRows({
|
|
911
|
+
activeRow,
|
|
912
|
+
filteredIndexes,
|
|
913
|
+
footerActions,
|
|
914
|
+
state,
|
|
915
|
+
visibleWindow
|
|
916
|
+
}),
|
|
917
|
+
hiddenBefore: visibleWindow.start,
|
|
918
|
+
hiddenAfter: Math.max(0, filteredIndexes.length - visibleWindow.end)
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
function resolveSearchPromptActiveItemFilteredIndex(input) {
|
|
922
|
+
if (input.activeRow?.kind === "item") return input.activeRow.filteredIndex;
|
|
923
|
+
if (input.filteredIndexes.length === 0) return null;
|
|
924
|
+
return input.filteredIndexes.length - 1;
|
|
925
|
+
}
|
|
926
|
+
function updateSearchPromptState(state, action) {
|
|
927
|
+
switch (action.type) {
|
|
928
|
+
case "move": return moveSearchPromptCursor(state, action.delta);
|
|
929
|
+
case "query-append":
|
|
930
|
+
if (action.text.length === 0) return state;
|
|
931
|
+
return syncSearchPromptState({
|
|
932
|
+
...state,
|
|
933
|
+
query: `${state.query}${action.text}`
|
|
934
|
+
});
|
|
935
|
+
case "query-backspace":
|
|
936
|
+
if (state.query.length === 0) return state;
|
|
937
|
+
return syncSearchPromptState({
|
|
938
|
+
...state,
|
|
939
|
+
query: state.query.slice(0, -1)
|
|
940
|
+
});
|
|
941
|
+
case "query-clear":
|
|
942
|
+
if (state.query.length === 0) return state;
|
|
943
|
+
return syncSearchPromptState({
|
|
944
|
+
...state,
|
|
945
|
+
query: ""
|
|
946
|
+
});
|
|
947
|
+
case "set-query": return syncSearchPromptState({
|
|
948
|
+
...state,
|
|
949
|
+
query: action.query
|
|
950
|
+
});
|
|
951
|
+
case "toggle-active": return toggleSearchPromptActiveSelection(state);
|
|
952
|
+
case "set-selected": return syncSearchPromptState({
|
|
953
|
+
...state,
|
|
954
|
+
selectedValues: dedupeValues(action.selectedValues)
|
|
955
|
+
});
|
|
956
|
+
default: return state;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
function getSearchPromptSelection(state) {
|
|
960
|
+
const viewModel = getSearchPromptViewModel(state);
|
|
961
|
+
if (viewModel.activeRow?.kind === "back") return state.workbench ? {
|
|
962
|
+
status: "back",
|
|
963
|
+
selectedValues: state.selectedValues
|
|
964
|
+
} : { status: "back" };
|
|
965
|
+
if (viewModel.activeRow?.kind === "action") {
|
|
966
|
+
const action = resolveSearchPromptFooterActions(state)[viewModel.activeRow.actionIndex];
|
|
967
|
+
if (!action || action.disabled === true) return null;
|
|
968
|
+
return {
|
|
969
|
+
status: "action",
|
|
970
|
+
action: action.value,
|
|
971
|
+
selectedValues: state.selectedValues
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
if (state.readOnly) return null;
|
|
975
|
+
if (state.workbench) return null;
|
|
976
|
+
if (state.mode === "single") {
|
|
977
|
+
const activeItem = getActiveItem(state);
|
|
978
|
+
if (!activeItem || activeItem.disabled === true) return null;
|
|
979
|
+
return {
|
|
980
|
+
status: "submitted",
|
|
981
|
+
value: activeItem.value
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
if (state.required && state.selectedValues.length === 0) return null;
|
|
985
|
+
return {
|
|
986
|
+
status: "submitted",
|
|
987
|
+
value: state.selectedValues
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
function renderSearchPromptLines(state, status = "active") {
|
|
991
|
+
const lines = [`${status === "cancelled" ? PROMPT_SYMBOL_CANCELLED : PROMPT_SYMBOL_ACTIVE} ${pc.bold(state.message)}`];
|
|
992
|
+
if (status === "submitted") {
|
|
993
|
+
lines.push(`${BAR_SYMBOL} ${pc.dim(buildSubmittedSearchPromptSummary(state) ?? "Done")}`);
|
|
994
|
+
return lines;
|
|
995
|
+
}
|
|
996
|
+
if (status === "cancelled") {
|
|
997
|
+
lines.push(`${BAR_SYMBOL} ${pc.strikethrough(pc.dim("Cancelled"))}`);
|
|
998
|
+
return lines;
|
|
999
|
+
}
|
|
1000
|
+
lines.push(...renderActiveSearchPromptLines(state));
|
|
1001
|
+
lines.push(pc.dim("└"));
|
|
1002
|
+
return lines;
|
|
1003
|
+
}
|
|
1004
|
+
function renderActiveSearchPromptLines(state) {
|
|
1005
|
+
const viewModel = getSearchPromptViewModel(state);
|
|
1006
|
+
const lines = [];
|
|
1007
|
+
if (state.summaryCard) {
|
|
1008
|
+
lines.push(`${BAR_SYMBOL}`);
|
|
1009
|
+
lines.push(...renderSearchPromptSummaryCardLines(state.summaryCard));
|
|
1010
|
+
lines.push(`${BAR_SYMBOL}`);
|
|
1011
|
+
}
|
|
1012
|
+
if (state.noticeLines.length > 0) {
|
|
1013
|
+
lines.push(`${BAR_SYMBOL}`);
|
|
1014
|
+
lines.push(...renderSearchPromptNoticeCardLines(state.noticeLines));
|
|
1015
|
+
lines.push(`${BAR_SYMBOL}`);
|
|
1016
|
+
}
|
|
1017
|
+
lines.push(`${BAR_SYMBOL} ${pc.dim("Search:")} ${state.query}${pc.inverse(" ")}`, renderSearchPromptHintLine({
|
|
1018
|
+
mode: state.mode,
|
|
1019
|
+
allowBack: state.allowBack,
|
|
1020
|
+
readOnly: state.readOnly,
|
|
1021
|
+
workbench: state.workbench
|
|
1022
|
+
}));
|
|
1023
|
+
lines.push(`${BAR_SYMBOL}`);
|
|
1024
|
+
lines.push(...renderSearchPromptBodyLines(state, viewModel));
|
|
1025
|
+
lines.push(...renderSearchPromptOverflowLines(viewModel));
|
|
1026
|
+
if (state.workbench) {
|
|
1027
|
+
lines.push(...renderSearchPromptSelectionSummaryLines(state));
|
|
1028
|
+
lines.push(...renderSearchPromptWorkbenchMenuLines(viewModel));
|
|
1029
|
+
} else {
|
|
1030
|
+
lines.push(...renderSearchPromptActionLines(viewModel));
|
|
1031
|
+
lines.push(...renderSearchPromptBackLines(state, viewModel));
|
|
1032
|
+
lines.push(...renderSearchPromptSelectionSummaryLines(state));
|
|
1033
|
+
}
|
|
1034
|
+
return lines;
|
|
1035
|
+
}
|
|
1036
|
+
function renderSearchPromptBodyLines(state, viewModel) {
|
|
1037
|
+
const itemRows = viewModel.visibleRows.filter((row) => row.kind === "item");
|
|
1038
|
+
if (itemRows.length === 0) return [`${BAR_SYMBOL} ${pc.dim(state.emptyLabel)}`];
|
|
1039
|
+
return itemRows.flatMap((row) => {
|
|
1040
|
+
const lines = [renderSearchPromptItemLine(row, {
|
|
1041
|
+
mode: state.mode,
|
|
1042
|
+
readOnly: state.readOnly
|
|
1043
|
+
})];
|
|
1044
|
+
if (row.item.detail) lines.push(`${BAR_SYMBOL} ${pc.dim(row.item.detail)}`);
|
|
1045
|
+
return lines;
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
function renderSearchPromptOverflowLines(viewModel) {
|
|
1049
|
+
if (viewModel.hiddenBefore === 0 && viewModel.hiddenAfter === 0) return [];
|
|
1050
|
+
const hiddenLabels = [];
|
|
1051
|
+
if (viewModel.hiddenBefore > 0) hiddenLabels.push(`↑ ${String(viewModel.hiddenBefore)} more`);
|
|
1052
|
+
if (viewModel.hiddenAfter > 0) hiddenLabels.push(`↓ ${String(viewModel.hiddenAfter)} more`);
|
|
1053
|
+
return [`${BAR_SYMBOL} ${pc.dim(hiddenLabels.join(" "))}`];
|
|
1054
|
+
}
|
|
1055
|
+
function renderSearchPromptActionLines(viewModel) {
|
|
1056
|
+
const actionRows = viewModel.visibleRows.filter((row) => row.kind === "action");
|
|
1057
|
+
if (actionRows.length === 0) return [];
|
|
1058
|
+
return renderSearchPromptActionBlockLines(actionRows);
|
|
1059
|
+
}
|
|
1060
|
+
function renderSearchPromptWorkbenchMenuLines(viewModel) {
|
|
1061
|
+
const actionRows = viewModel.visibleRows.filter((row) => row.kind === "action");
|
|
1062
|
+
const backRow = viewModel.visibleRows.find((row) => row.kind === "back");
|
|
1063
|
+
if (actionRows.length === 0 && !backRow) return [];
|
|
1064
|
+
const lines = renderSearchPromptActionBlockLines(actionRows);
|
|
1065
|
+
if (backRow) {
|
|
1066
|
+
if (actionRows.length > 0) lines.push(`${BAR_SYMBOL}`);
|
|
1067
|
+
lines.push(renderSearchPromptBackLine(backRow, { workbench: true }));
|
|
1068
|
+
}
|
|
1069
|
+
return lines;
|
|
1070
|
+
}
|
|
1071
|
+
function renderSearchPromptBackLines(state, viewModel) {
|
|
1072
|
+
if (!state.allowBack || state.workbench) return [];
|
|
1073
|
+
const backRow = viewModel.visibleRows.find((row) => row.kind === "back");
|
|
1074
|
+
if (!backRow) return [];
|
|
1075
|
+
return [`${BAR_SYMBOL}`, renderSearchPromptBackLine(backRow)];
|
|
1076
|
+
}
|
|
1077
|
+
function renderSearchPromptSelectionSummaryLines(state) {
|
|
1078
|
+
if (state.mode !== "multiple" || state.readOnly) return [];
|
|
1079
|
+
const lines = [`${BAR_SYMBOL}`, `${BAR_SYMBOL} ${pc.green("Selected:")} ${buildMultiselectSummary(state)}`];
|
|
1080
|
+
if (!state.workbench && (!state.required || state.selectedValues.length > 0)) lines.push(`${BAR_SYMBOL} ${pc.dim("Press ")}${pc.green("`Enter`")}${pc.dim(" to continue.")}`);
|
|
1081
|
+
return lines;
|
|
1082
|
+
}
|
|
1083
|
+
function handleSearchPromptKeypress(input) {
|
|
1084
|
+
const { state, str, key } = input;
|
|
1085
|
+
if (!key) return { type: "ignore" };
|
|
1086
|
+
if (key.ctrl === true && key.name === "c") return {
|
|
1087
|
+
type: "result",
|
|
1088
|
+
result: buildSearchPromptDismissResult({
|
|
1089
|
+
state,
|
|
1090
|
+
status: "cancelled"
|
|
1091
|
+
}),
|
|
1092
|
+
status: "cancelled"
|
|
1093
|
+
};
|
|
1094
|
+
if (key.name === "escape") return handleSearchPromptEscapeKeypress(state);
|
|
1095
|
+
if (isReturnKeypress({
|
|
1096
|
+
key,
|
|
1097
|
+
str
|
|
1098
|
+
})) return handleSearchPromptReturnKeypress(state);
|
|
1099
|
+
const moveDelta = resolveSearchPromptMoveDelta(key.name);
|
|
1100
|
+
if (moveDelta !== null) return {
|
|
1101
|
+
type: "state",
|
|
1102
|
+
state: updateSearchPromptState(state, {
|
|
1103
|
+
type: "move",
|
|
1104
|
+
delta: moveDelta
|
|
1105
|
+
})
|
|
1106
|
+
};
|
|
1107
|
+
if (key.name === "space" && state.mode === "multiple") return {
|
|
1108
|
+
type: "state",
|
|
1109
|
+
state: updateSearchPromptState(state, { type: "toggle-active" })
|
|
1110
|
+
};
|
|
1111
|
+
if (key.name === "backspace") return {
|
|
1112
|
+
type: "state",
|
|
1113
|
+
state: updateSearchPromptState(state, { type: "query-backspace" })
|
|
1114
|
+
};
|
|
1115
|
+
if (key.ctrl === true && key.name === "u") return {
|
|
1116
|
+
type: "state",
|
|
1117
|
+
state: updateSearchPromptState(state, { type: "query-clear" })
|
|
1118
|
+
};
|
|
1119
|
+
const printableInput = normalizePrintableSearchInput(str, state.mode);
|
|
1120
|
+
if (printableInput !== null) return {
|
|
1121
|
+
type: "state",
|
|
1122
|
+
state: updateSearchPromptState(state, {
|
|
1123
|
+
type: "query-append",
|
|
1124
|
+
text: printableInput
|
|
1125
|
+
})
|
|
1126
|
+
};
|
|
1127
|
+
return { type: "ignore" };
|
|
1128
|
+
}
|
|
1129
|
+
function buildSearchPromptDismissResult(input) {
|
|
1130
|
+
if (input.state.workbench) {
|
|
1131
|
+
if (input.status === "back") return {
|
|
1132
|
+
status: "back",
|
|
1133
|
+
selectedValues: input.state.selectedValues
|
|
1134
|
+
};
|
|
1135
|
+
return {
|
|
1136
|
+
status: "cancelled",
|
|
1137
|
+
selectedValues: input.state.selectedValues
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
if (input.status === "back") return { status: "back" };
|
|
1141
|
+
return { status: "cancelled" };
|
|
1142
|
+
}
|
|
1143
|
+
function handleSearchPromptEscapeKeypress(state) {
|
|
1144
|
+
return {
|
|
1145
|
+
type: "result",
|
|
1146
|
+
result: buildSearchPromptDismissResult({
|
|
1147
|
+
state,
|
|
1148
|
+
status: state.allowBack ? "back" : "cancelled"
|
|
1149
|
+
}),
|
|
1150
|
+
status: state.allowBack ? "submitted" : "cancelled",
|
|
1151
|
+
state: state.allowBack ? focusSearchPromptBackRow(state) : state
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
function handleSearchPromptReturnKeypress(state) {
|
|
1155
|
+
if (state.workbench && getSearchPromptViewModel(state).activeRow?.kind === "item") return {
|
|
1156
|
+
type: "state",
|
|
1157
|
+
state: updateSearchPromptState(state, { type: "toggle-active" })
|
|
1158
|
+
};
|
|
1159
|
+
const result = getSearchPromptSelection(state);
|
|
1160
|
+
if (!result) return { type: "ignore" };
|
|
1161
|
+
return {
|
|
1162
|
+
type: "result",
|
|
1163
|
+
result,
|
|
1164
|
+
status: "submitted"
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
function promptSearchSelect(input, streams) {
|
|
1168
|
+
return runInteractiveSearchPrompt(createSearchPromptState({
|
|
1169
|
+
...input,
|
|
1170
|
+
mode: "single"
|
|
1171
|
+
}), streams).then((result) => result);
|
|
1172
|
+
}
|
|
1173
|
+
function promptSearchMultiselect(input, streams) {
|
|
1174
|
+
return runInteractiveSearchPrompt(createSearchPromptState({
|
|
1175
|
+
...input,
|
|
1176
|
+
mode: "multiple"
|
|
1177
|
+
}), streams).then((result) => result);
|
|
1178
|
+
}
|
|
1179
|
+
function promptSearchMultiselectWorkbench(input) {
|
|
1180
|
+
return runInteractiveSearchPrompt(createSearchPromptState({
|
|
1181
|
+
...input,
|
|
1182
|
+
mode: "multiple",
|
|
1183
|
+
workbench: true
|
|
1184
|
+
})).then((result) => result);
|
|
1185
|
+
}
|
|
1186
|
+
function buildSearchKey(item) {
|
|
1187
|
+
return [
|
|
1188
|
+
item.searchText,
|
|
1189
|
+
item.label,
|
|
1190
|
+
item.detail,
|
|
1191
|
+
item.hint,
|
|
1192
|
+
String(item.value)
|
|
1193
|
+
].filter((value) => typeof value === "string").join("\n").trim().toLowerCase();
|
|
1194
|
+
}
|
|
1195
|
+
function getFilteredItemIndexes(state) {
|
|
1196
|
+
const query = state.query.trim().toLowerCase();
|
|
1197
|
+
if (query.length === 0) return state.items.map((_item, index) => index);
|
|
1198
|
+
return state.items.flatMap((item, index) => item.searchKey.includes(query) ? [index] : []);
|
|
1199
|
+
}
|
|
1200
|
+
function syncSearchPromptState(state) {
|
|
1201
|
+
const viewModel = getSearchPromptViewModelInternal(state);
|
|
1202
|
+
const activeRowIndex = viewModel.rows.length === 0 ? 0 : clampNumber(state.activeRowIndex, 0, viewModel.rows.length - 1);
|
|
1203
|
+
return {
|
|
1204
|
+
...state,
|
|
1205
|
+
activeRowIndex
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
function getSearchPromptViewModelInternal(state) {
|
|
1209
|
+
const filteredIndexes = getFilteredItemIndexes(state);
|
|
1210
|
+
const footerActions = resolveSearchPromptFooterActions(state);
|
|
1211
|
+
return {
|
|
1212
|
+
filteredIndexes,
|
|
1213
|
+
rows: buildSearchPromptRows({
|
|
1214
|
+
allowBack: state.allowBack,
|
|
1215
|
+
filteredIndexes,
|
|
1216
|
+
footerActions,
|
|
1217
|
+
workbench: state.workbench
|
|
1218
|
+
})
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
function buildSearchPromptRows(input) {
|
|
1222
|
+
const itemRows = input.filteredIndexes.map((itemIndex, filteredIndex) => ({
|
|
1223
|
+
kind: "item",
|
|
1224
|
+
filteredIndex,
|
|
1225
|
+
itemIndex
|
|
1226
|
+
}));
|
|
1227
|
+
const actionRows = input.footerActions.map((_action, actionIndex) => ({
|
|
1228
|
+
kind: "action",
|
|
1229
|
+
actionIndex
|
|
1230
|
+
}));
|
|
1231
|
+
const rows = input.workbench ? [...itemRows, ...actionRows] : [...itemRows, ...actionRows];
|
|
1232
|
+
if (input.allowBack) rows.push({ kind: "back" });
|
|
1233
|
+
return rows;
|
|
1234
|
+
}
|
|
1235
|
+
function buildSearchPromptVisibleRows(input) {
|
|
1236
|
+
const itemRows = input.filteredIndexes.slice(input.visibleWindow.start, input.visibleWindow.end).map((itemIndex, offset) => {
|
|
1237
|
+
const filteredIndex = input.visibleWindow.start + offset;
|
|
1238
|
+
const item = input.state.items[itemIndex];
|
|
1239
|
+
if (!item) throw new Error("search prompt item is missing");
|
|
1240
|
+
return {
|
|
1241
|
+
kind: "item",
|
|
1242
|
+
filteredIndex,
|
|
1243
|
+
itemIndex,
|
|
1244
|
+
item,
|
|
1245
|
+
active: input.activeRow?.kind === "item" && input.activeRow.filteredIndex === filteredIndex,
|
|
1246
|
+
selected: isItemSelected(input.state, item.value)
|
|
1247
|
+
};
|
|
1248
|
+
});
|
|
1249
|
+
const actionRows = input.footerActions.map((action, actionIndex) => ({
|
|
1250
|
+
kind: "action",
|
|
1251
|
+
action,
|
|
1252
|
+
actionIndex,
|
|
1253
|
+
active: input.activeRow?.kind === "action" && input.activeRow.actionIndex === actionIndex
|
|
1254
|
+
}));
|
|
1255
|
+
const rows = [...itemRows, ...actionRows];
|
|
1256
|
+
if (input.state.allowBack) rows.push({
|
|
1257
|
+
kind: "back",
|
|
1258
|
+
active: input.activeRow?.kind === "back",
|
|
1259
|
+
label: input.state.backLabel
|
|
1260
|
+
});
|
|
1261
|
+
return rows;
|
|
1262
|
+
}
|
|
1263
|
+
function resolveInitialSearchPromptActiveRowIndex(state) {
|
|
1264
|
+
if (!state.workbench) return 0;
|
|
1265
|
+
if (getFilteredItemIndexes(state).length > 0) return 0;
|
|
1266
|
+
return 0;
|
|
1267
|
+
}
|
|
1268
|
+
function moveSearchPromptCursor(state, delta) {
|
|
1269
|
+
const viewModel = getSearchPromptViewModelInternal(state);
|
|
1270
|
+
if (viewModel.rows.length === 0 || delta === 0) return state;
|
|
1271
|
+
return {
|
|
1272
|
+
...state,
|
|
1273
|
+
activeRowIndex: wrapNumber(state.activeRowIndex + delta, 0, viewModel.rows.length - 1)
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
function toggleSearchPromptActiveSelection(state) {
|
|
1277
|
+
if (state.mode !== "multiple") return state;
|
|
1278
|
+
const activeItem = getActiveItem(state);
|
|
1279
|
+
if (!activeItem || activeItem.disabled === true) return state;
|
|
1280
|
+
const selectedValues = new Set(state.selectedValues);
|
|
1281
|
+
if (selectedValues.has(activeItem.value)) selectedValues.delete(activeItem.value);
|
|
1282
|
+
else selectedValues.add(activeItem.value);
|
|
1283
|
+
return {
|
|
1284
|
+
...state,
|
|
1285
|
+
selectedValues: Array.from(selectedValues)
|
|
1286
|
+
};
|
|
1287
|
+
}
|
|
1288
|
+
function getActiveItem(state) {
|
|
1289
|
+
const viewModel = getSearchPromptViewModel(state);
|
|
1290
|
+
if (viewModel.activeRow?.kind !== "item") return null;
|
|
1291
|
+
return state.items[viewModel.activeRow.itemIndex] ?? null;
|
|
1292
|
+
}
|
|
1293
|
+
function isItemSelected(state, value) {
|
|
1294
|
+
return state.selectedValues.includes(value);
|
|
1295
|
+
}
|
|
1296
|
+
function renderSearchPromptItemLine(row, input) {
|
|
1297
|
+
const prefix = row.active ? CURSOR_SYMBOL : " ";
|
|
1298
|
+
if (input.readOnly) {
|
|
1299
|
+
const hint = row.item.hint ? pc.dim(` (${row.item.hint})`) : "";
|
|
1300
|
+
return `${BAR_SYMBOL} ${prefix} ${row.item.label}${hint}`;
|
|
1301
|
+
}
|
|
1302
|
+
const marker = resolveSearchPromptItemMarker({
|
|
1303
|
+
mode: input.mode,
|
|
1304
|
+
active: row.active,
|
|
1305
|
+
selected: row.selected
|
|
1306
|
+
});
|
|
1307
|
+
const label = row.item.label;
|
|
1308
|
+
const hint = row.item.hint ? pc.dim(` (${row.item.hint})`) : "";
|
|
1309
|
+
const renderedLabel = row.item.disabled === true ? pc.dim(label) : label;
|
|
1310
|
+
return `${BAR_SYMBOL} ${prefix} ${marker} ${renderedLabel}${hint}`;
|
|
1311
|
+
}
|
|
1312
|
+
function renderSearchPromptActionLine(row) {
|
|
1313
|
+
const prefix = row.active ? CURSOR_SYMBOL : " ";
|
|
1314
|
+
const icon = typeof row.action.icon === "string" && row.action.icon.length > 0 ? `${row.action.disabled === true || row.action.icon === "·" ? pc.dim(row.action.icon) : pc.cyan(row.action.icon)} ` : "";
|
|
1315
|
+
let label = pc.bold(row.action.label);
|
|
1316
|
+
if (row.action.accent === true) label = pc.green(row.action.label);
|
|
1317
|
+
if (row.action.disabled === true) label = pc.dim(row.action.label);
|
|
1318
|
+
const hint = row.action.hint ? pc.dim(` (${row.action.hint})`) : "";
|
|
1319
|
+
return `${BAR_SYMBOL} ${prefix} ${icon}${label}${hint}`;
|
|
1320
|
+
}
|
|
1321
|
+
function renderSearchPromptActionBlockLines(actionRows) {
|
|
1322
|
+
const lines = [`${BAR_SYMBOL}`];
|
|
1323
|
+
for (const row of actionRows) {
|
|
1324
|
+
if (row.action.separatorBefore === true) lines.push(`${BAR_SYMBOL}`);
|
|
1325
|
+
lines.push(renderSearchPromptActionLine(row));
|
|
1326
|
+
}
|
|
1327
|
+
return lines;
|
|
1328
|
+
}
|
|
1329
|
+
function resolveSearchPromptItemMarker(input) {
|
|
1330
|
+
if (input.mode === "multiple") return input.selected ? MULTI_SELECTED_SYMBOL : MULTI_UNSELECTED_SYMBOL;
|
|
1331
|
+
return input.active ? SINGLE_SELECTED_SYMBOL : SINGLE_UNSELECTED_SYMBOL;
|
|
1332
|
+
}
|
|
1333
|
+
function renderSearchPromptBackLine(row, input) {
|
|
1334
|
+
const prefix = row.active ? CURSOR_SYMBOL : " ";
|
|
1335
|
+
if (input?.workbench === true) return `${BAR_SYMBOL} ${prefix} ${row.label}`;
|
|
1336
|
+
return `${BAR_SYMBOL} ${prefix} ${row.label}`;
|
|
1337
|
+
}
|
|
1338
|
+
function buildSubmittedSearchPromptSummary(state) {
|
|
1339
|
+
const viewModel = getSearchPromptViewModel(state);
|
|
1340
|
+
if (viewModel.activeRow?.kind === "back") return state.backLabel;
|
|
1341
|
+
if (viewModel.activeRow?.kind === "action") return resolveSearchPromptFooterActions(state)[viewModel.activeRow.actionIndex]?.label ?? null;
|
|
1342
|
+
if (state.mode === "single") return getActiveItem(state)?.label ?? null;
|
|
1343
|
+
return buildMultiselectSummary(state);
|
|
1344
|
+
}
|
|
1345
|
+
function buildMultiselectSummary(state) {
|
|
1346
|
+
const labels = state.items.filter((item) => isItemSelected(state, item.value)).map((item) => item.label);
|
|
1347
|
+
if (labels.length === 0) return "(none)";
|
|
1348
|
+
if (labels.length <= 3) return labels.join(", ");
|
|
1349
|
+
return `${labels.slice(0, 3).join(", ")} +${String(labels.length - 3)} more`;
|
|
1350
|
+
}
|
|
1351
|
+
function focusSearchPromptBackRow(state) {
|
|
1352
|
+
const backRowIndex = getSearchPromptViewModel(state).rows.findIndex((row) => row.kind === "back");
|
|
1353
|
+
if (backRowIndex < 0) return state;
|
|
1354
|
+
return {
|
|
1355
|
+
...state,
|
|
1356
|
+
activeRowIndex: backRowIndex
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
function resolveSearchPromptMoveDelta(name) {
|
|
1360
|
+
if (name === "up") return -1;
|
|
1361
|
+
if (name === "down") return 1;
|
|
1362
|
+
return null;
|
|
1363
|
+
}
|
|
1364
|
+
function renderSearchPromptHintLine(input) {
|
|
1365
|
+
if (input.readOnly) return `${BAR_SYMBOL} ${pc.dim(input.allowBack ? "↑↓ browse, type to filter, Esc back" : "↑↓ browse, type to filter")}`;
|
|
1366
|
+
if (input.workbench) return `${BAR_SYMBOL} ${pc.dim("↑↓ move, Press ")}${pc.green("`Space`")}${pc.dim(" or ")}${pc.green("`Enter`")}${pc.dim(input.allowBack ? " to select, choose an action to continue, Esc back" : " to select, choose an action to continue")}`;
|
|
1367
|
+
if (input.mode === "multiple") return `${BAR_SYMBOL} ${pc.dim("↑↓ move, Press ")}${pc.green("`Space`")}${pc.dim(" to select, Press ")}${pc.green("`Enter`")}${pc.dim(input.allowBack ? " to confirm, Esc back" : " to confirm")}`;
|
|
1368
|
+
return `${BAR_SYMBOL} ${pc.dim("↑↓ move, Press ")}${pc.green("`Enter`")}${pc.dim(input.allowBack ? " to select, Esc back" : " to select")}`;
|
|
1369
|
+
}
|
|
1370
|
+
function renderSearchPromptSummaryCardLines(card) {
|
|
1371
|
+
return buildTextInfoCardLines({
|
|
1372
|
+
...card,
|
|
1373
|
+
sanitize: true
|
|
1374
|
+
}).map((line) => `${BAR_SYMBOL} ${line}`);
|
|
1375
|
+
}
|
|
1376
|
+
function renderSearchPromptNoticeCardLines(noticeLines) {
|
|
1377
|
+
if (noticeLines.length === 0) return [];
|
|
1378
|
+
return buildTextInfoCardLines({
|
|
1379
|
+
title: pc.yellow("⚠️ Notification"),
|
|
1380
|
+
rows: noticeLines.map((line) => ({
|
|
1381
|
+
label: "",
|
|
1382
|
+
value: pc.yellow(`⚠️ ${line}`)
|
|
1383
|
+
})),
|
|
1384
|
+
compact: true,
|
|
1385
|
+
minContentWidth: 48
|
|
1386
|
+
}).map((line) => `${BAR_SYMBOL} ${line}`);
|
|
1387
|
+
}
|
|
1388
|
+
function normalizeMaxVisible(value) {
|
|
1389
|
+
const normalized = typeof value === "number" ? Math.trunc(value) : NaN;
|
|
1390
|
+
return Number.isFinite(normalized) && normalized > 0 ? normalized : DEFAULT_MAX_VISIBLE;
|
|
1391
|
+
}
|
|
1392
|
+
function wrapNumber(value, min, max) {
|
|
1393
|
+
if (value < min) return max;
|
|
1394
|
+
if (value > max) return min;
|
|
1395
|
+
return value;
|
|
1396
|
+
}
|
|
1397
|
+
function dedupeValues(values) {
|
|
1398
|
+
return Array.from(new Set(values));
|
|
1399
|
+
}
|
|
1400
|
+
function runInteractiveSearchPrompt(initialState, streams) {
|
|
1401
|
+
return runInteractivePrompt({
|
|
1402
|
+
initialState,
|
|
1403
|
+
fallbackResult: buildSearchPromptFallbackResult(initialState),
|
|
1404
|
+
renderLines: (state, status) => renderSearchPromptLines(state, status),
|
|
1405
|
+
handleKeypress: ({ state, str, key }) => handleSearchPromptKeypress({
|
|
1406
|
+
state,
|
|
1407
|
+
str,
|
|
1408
|
+
key
|
|
1409
|
+
})
|
|
1410
|
+
}, streams);
|
|
1411
|
+
}
|
|
1412
|
+
function buildSearchPromptFallbackResult(state) {
|
|
1413
|
+
return buildSearchPromptDismissResult({
|
|
1414
|
+
state,
|
|
1415
|
+
status: state.allowBack ? "back" : "cancelled"
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
function resolveSearchPromptFooterActions(state) {
|
|
1419
|
+
if (state.footerActionsResolver === null) return state.footerActions;
|
|
1420
|
+
return state.footerActionsResolver({ selectedValues: state.selectedValues }).map(sanitizeSearchPromptFooterAction);
|
|
1421
|
+
}
|
|
1422
|
+
function isPrintableSearchInput(value, mode) {
|
|
1423
|
+
if (typeof value !== "string" || value.length === 0) return false;
|
|
1424
|
+
if (mode === "multiple" && value === " ") return false;
|
|
1425
|
+
return PRINTABLE_SEARCH_INPUT_REGEX.test(value);
|
|
1426
|
+
}
|
|
1427
|
+
function normalizePrintableSearchInput(value, mode) {
|
|
1428
|
+
return typeof value === "string" && isPrintableSearchInput(value, mode) ? value : null;
|
|
1429
|
+
}
|
|
1430
|
+
//#endregion
|
|
1431
|
+
//#region src/ui/transport-mode-display.ts
|
|
1432
|
+
const DISPLAY_NOT_SET_UP = "Not set up";
|
|
1433
|
+
function formatTransportModeDisplay(value) {
|
|
1434
|
+
const normalized = String(value ?? "").trim();
|
|
1435
|
+
if (!normalized || normalized === "not configured") return DISPLAY_NOT_SET_UP;
|
|
1436
|
+
if (normalized === "local-cli") return "Local CLI";
|
|
1437
|
+
if (normalized === "codex-app-server") return "Codex App Server";
|
|
1438
|
+
if (normalized === "acp") return "Agent Client Protocol (ACP)";
|
|
1439
|
+
return normalized;
|
|
1440
|
+
}
|
|
1441
|
+
//#endregion
|
|
1442
|
+
//#region src/commands/agents-text.ts
|
|
1443
|
+
const formatAgentsCopy = (text) => formatOpenMeldCliTextBlock(text);
|
|
1444
|
+
function formatAgentMention(agentId) {
|
|
1445
|
+
return pc.bold(pc.cyan(`@${agentId}`));
|
|
1446
|
+
}
|
|
1447
|
+
function formatAgentsToneText(tone, value) {
|
|
1448
|
+
if (tone === "success") return pc.green(value);
|
|
1449
|
+
if (tone === "warning") return pc.yellow(value);
|
|
1450
|
+
return pc.cyan(value);
|
|
1451
|
+
}
|
|
1452
|
+
function formatStatusTextForHuman(value) {
|
|
1453
|
+
if (value.length === 0) return value;
|
|
1454
|
+
const normalized = value.replaceAll("_", " ");
|
|
1455
|
+
return normalized[0]?.toUpperCase() + normalized.slice(1);
|
|
1456
|
+
}
|
|
1457
|
+
function normalizeOptionalString(value) {
|
|
1458
|
+
if (typeof value !== "string") return null;
|
|
1459
|
+
const trimmed = value.trim();
|
|
1460
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
1461
|
+
}
|
|
1462
|
+
//#endregion
|
|
1463
|
+
//#region src/commands/agents-cards.ts
|
|
1464
|
+
const AGENTS_CARD_MIN_CONTENT_WIDTH = 34;
|
|
1465
|
+
function renderAgentsInfoCard(input) {
|
|
1466
|
+
renderInfoCard({
|
|
1467
|
+
presenter: input.presenter,
|
|
1468
|
+
title: input.title,
|
|
1469
|
+
codePrefix: input.codePrefix,
|
|
1470
|
+
rows: input.rows,
|
|
1471
|
+
minContentWidth: input.minContentWidth ?? AGENTS_CARD_MIN_CONTENT_WIDTH,
|
|
1472
|
+
horizontalPadding: 1,
|
|
1473
|
+
compact: true
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
function formatAgentListItem(input) {
|
|
1477
|
+
const parts = [
|
|
1478
|
+
formatAgentMention(input.agentId),
|
|
1479
|
+
pc.dim("·"),
|
|
1480
|
+
input.displayName
|
|
1481
|
+
];
|
|
1482
|
+
if (input.detail) parts.push(pc.dim(input.detail));
|
|
1483
|
+
return parts.join(" ");
|
|
1484
|
+
}
|
|
1485
|
+
function buildReadyAgentCardRow(agentId, displayName) {
|
|
1486
|
+
return {
|
|
1487
|
+
label: "",
|
|
1488
|
+
value: formatAgentListItem({
|
|
1489
|
+
agentId,
|
|
1490
|
+
displayName
|
|
1491
|
+
})
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
function buildStatusAgentCardRow(input) {
|
|
1495
|
+
return {
|
|
1496
|
+
label: "",
|
|
1497
|
+
value: formatAgentListItem({
|
|
1498
|
+
agentId: input.agentId,
|
|
1499
|
+
displayName: input.displayName,
|
|
1500
|
+
detail: input.detail
|
|
1501
|
+
})
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
function buildTurnedOffAgentCardRow(agentId, displayName) {
|
|
1505
|
+
return {
|
|
1506
|
+
label: "",
|
|
1507
|
+
value: formatAgentListItem({
|
|
1508
|
+
agentId,
|
|
1509
|
+
displayName
|
|
1510
|
+
})
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
function buildReadyCountValue(count, noun) {
|
|
1514
|
+
return `${formatAgentsToneText("success", pc.bold(String(count)))} ${pc.dim(noun)}`;
|
|
1515
|
+
}
|
|
1516
|
+
function buildNotReadyCountValue(count, noun) {
|
|
1517
|
+
return `${formatAgentsToneText("warning", pc.bold(String(count)))} ${pc.dim(noun)}`;
|
|
1518
|
+
}
|
|
1519
|
+
//#endregion
|
|
1520
|
+
//#region src/commands/agents-descriptions.ts
|
|
1521
|
+
function normalizeDescriptionLines(description) {
|
|
1522
|
+
return (Array.isArray(description) ? description : [description]).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
1523
|
+
}
|
|
1524
|
+
function buildInteractiveMenuMessage(input) {
|
|
1525
|
+
return [
|
|
1526
|
+
input.title,
|
|
1527
|
+
...normalizeDescriptionLines(input.description).map((line) => pc.dim(formatAgentsCopy(line))),
|
|
1528
|
+
""
|
|
1529
|
+
].join("\n");
|
|
1530
|
+
}
|
|
1531
|
+
function emitInteractiveDescription(description) {
|
|
1532
|
+
const lines = normalizeDescriptionLines(description);
|
|
1533
|
+
if (lines.length === 0) return;
|
|
1534
|
+
process.stdout.write(`${lines.map((line) => pc.dim(formatAgentsCopy(line))).join("\n")}\n`);
|
|
1535
|
+
}
|
|
1536
|
+
function emitHumanSectionDescription(presenter, input) {
|
|
1537
|
+
if (presenter.view !== "human") return;
|
|
1538
|
+
const lines = normalizeDescriptionLines(input.description);
|
|
1539
|
+
for (const [index, line] of lines.entries()) presenter.line({
|
|
1540
|
+
code: `${input.codePrefix}.description.${String(index)}`,
|
|
1541
|
+
text: pc.dim(formatAgentsCopy(line))
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
function emitHumanSectionHeader(presenter, input) {
|
|
1545
|
+
if (presenter.view !== "human") return;
|
|
1546
|
+
presenter.line({
|
|
1547
|
+
code: `${input.codePrefix}.spacer`,
|
|
1548
|
+
text: ""
|
|
1549
|
+
});
|
|
1550
|
+
presenter.line({
|
|
1551
|
+
code: `${input.codePrefix}.title`,
|
|
1552
|
+
text: input.title
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
//#endregion
|
|
1556
|
+
//#region src/commands/agents-summary-cards.ts
|
|
1557
|
+
const AGENTS_ENABLE_RESULT_DESCRIPTION = "These agents can now reply when someone mentions them in a Space from this computer.";
|
|
1558
|
+
const AGENTS_DISABLE_RESULT_DESCRIPTION = "These agents will stop replying to new mentions in Spaces.";
|
|
1559
|
+
function emitEnableSummaryCardsForHuman(input) {
|
|
1560
|
+
emitHumanSectionDescription(input.presenter, {
|
|
1561
|
+
codePrefix: "agents.enable",
|
|
1562
|
+
description: AGENTS_ENABLE_RESULT_DESCRIPTION
|
|
1563
|
+
});
|
|
1564
|
+
renderAgentsInfoCard({
|
|
1565
|
+
presenter: input.presenter,
|
|
1566
|
+
codePrefix: "agents.enable.summary.card",
|
|
1567
|
+
title: "Agents Turned On",
|
|
1568
|
+
rows: buildEnableSummaryRows(input.summary),
|
|
1569
|
+
minContentWidth: 38
|
|
1570
|
+
});
|
|
1571
|
+
const detailCards = buildEnableDetailCards(input.summary, input.targetStateById);
|
|
1572
|
+
for (const card of detailCards) {
|
|
1573
|
+
input.presenter.line({
|
|
1574
|
+
code: `agents.enable.${card.codeSuffix}.spacer`,
|
|
1575
|
+
text: ""
|
|
1576
|
+
});
|
|
1577
|
+
renderAgentsInfoCard({
|
|
1578
|
+
presenter: input.presenter,
|
|
1579
|
+
codePrefix: `agents.enable.${card.codeSuffix}.card`,
|
|
1580
|
+
title: card.title,
|
|
1581
|
+
rows: card.rows,
|
|
1582
|
+
minContentWidth: 30
|
|
1583
|
+
});
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
function buildEnableSummaryRows(summary) {
|
|
1587
|
+
const rows = [{
|
|
1588
|
+
label: "Status",
|
|
1589
|
+
value: buildEnableSummaryStatus(summary)
|
|
1590
|
+
}];
|
|
1591
|
+
if (summary.registered.length > 0) rows.push({
|
|
1592
|
+
label: "On now",
|
|
1593
|
+
value: String(summary.registered.length)
|
|
1594
|
+
});
|
|
1595
|
+
if (summary.reenabled.length > 0) rows.push({
|
|
1596
|
+
label: "Back on",
|
|
1597
|
+
value: String(summary.reenabled.length)
|
|
1598
|
+
});
|
|
1599
|
+
if (summary.skipped.length > 0) rows.push({
|
|
1600
|
+
label: "Already on",
|
|
1601
|
+
value: String(summary.skipped.length)
|
|
1602
|
+
});
|
|
1603
|
+
if (summary.errors.length > 0) rows.push({
|
|
1604
|
+
label: "Needs attention",
|
|
1605
|
+
value: String(summary.errors.length)
|
|
1606
|
+
});
|
|
1607
|
+
return rows;
|
|
1608
|
+
}
|
|
1609
|
+
function buildEnableSummaryStatus(summary) {
|
|
1610
|
+
const readyCount = summary.registered.length + summary.reenabled.length;
|
|
1611
|
+
const reviewCount = summary.skipped.length + summary.errors.length;
|
|
1612
|
+
if (readyCount > 0 && reviewCount === 0) {
|
|
1613
|
+
if (summary.allMode) return readyCount === 1 ? `1 detected agent is ${formatAgentsToneText("success", "available now")}` : `All detected available agents are ${formatAgentsToneText("success", "on now")}`;
|
|
1614
|
+
return readyCount === 1 ? `1 selected agent is ${formatAgentsToneText("success", "available now")}` : `${readyCount} selected agents are ${formatAgentsToneText("success", "available now")}`;
|
|
1615
|
+
}
|
|
1616
|
+
if (readyCount > 0) return readyCount === 1 ? `1 agent is ${formatAgentsToneText("success", "available now")} ${pc.dim("· review the rest below")}` : `${readyCount} agents are ${formatAgentsToneText("success", "available now")} ${pc.dim("· review the rest below")}`;
|
|
1617
|
+
if (summary.skipped.length > 0 && summary.errors.length === 0) return formatAgentsToneText("info", "Already on");
|
|
1618
|
+
return formatAgentsToneText("warning", "OpenMeld could not turn these on");
|
|
1619
|
+
}
|
|
1620
|
+
function buildEnableDetailCards(summary, targetStateById) {
|
|
1621
|
+
const cards = [];
|
|
1622
|
+
if (summary.registered.length > 0) cards.push({
|
|
1623
|
+
codeSuffix: "enabled_now",
|
|
1624
|
+
title: `Turned On Now (${summary.registered.length})`,
|
|
1625
|
+
rows: summary.registered.map((agentId) => buildReadyAgentCardRow(agentId, resolveEnableResultDisplayName(agentId, targetStateById)))
|
|
1626
|
+
});
|
|
1627
|
+
if (summary.reenabled.length > 0) cards.push({
|
|
1628
|
+
codeSuffix: "reenabled",
|
|
1629
|
+
title: `Turned Back On (${summary.reenabled.length})`,
|
|
1630
|
+
rows: summary.reenabled.map((agentId) => buildReadyAgentCardRow(agentId, resolveEnableResultDisplayName(agentId, targetStateById)))
|
|
1631
|
+
});
|
|
1632
|
+
if (summary.skipped.length > 0) cards.push({
|
|
1633
|
+
codeSuffix: "already_enabled",
|
|
1634
|
+
title: `Already On (${summary.skipped.length})`,
|
|
1635
|
+
rows: summary.skipped.map((item) => ({
|
|
1636
|
+
label: resolveEnableResultDisplayName(item.agentId, targetStateById),
|
|
1637
|
+
value: formatEnableSkippedReasonForHuman(item.reason)
|
|
1638
|
+
}))
|
|
1639
|
+
});
|
|
1640
|
+
if (summary.errors.length > 0) cards.push({
|
|
1641
|
+
codeSuffix: "needs_attention",
|
|
1642
|
+
title: `Needs Attention (${summary.errors.length})`,
|
|
1643
|
+
rows: summary.errors.map((item) => ({
|
|
1644
|
+
label: resolveEnableResultDisplayName(item.agentId, targetStateById),
|
|
1645
|
+
value: formatEnableErrorReasonForHuman(item)
|
|
1646
|
+
}))
|
|
1647
|
+
});
|
|
1648
|
+
return cards;
|
|
1649
|
+
}
|
|
1650
|
+
function resolveEnableResultDisplayName(agentId, targetStateById) {
|
|
1651
|
+
return targetStateById.get(agentId)?.displayName ?? agentId;
|
|
1652
|
+
}
|
|
1653
|
+
function formatEnableSkippedReasonForHuman(reason) {
|
|
1654
|
+
if (reason === "already_enabled") return "Already on in OpenMeld";
|
|
1655
|
+
return formatStatusTextForHuman(reason);
|
|
1656
|
+
}
|
|
1657
|
+
function formatEnableErrorReasonForHuman(input) {
|
|
1658
|
+
if (input.code === "controller.launch.choice_required" && input.installChoices && input.installChoices.length > 0) {
|
|
1659
|
+
const choiceSummary = input.installChoices.map((installChoice) => `${installChoice.installId} (${installChoice.label} · ${installChoice.detail})`).join("; ");
|
|
1660
|
+
const nextStepSummary = (input.nextSteps ?? []).join(" or ");
|
|
1661
|
+
return [
|
|
1662
|
+
input.summary ?? "Needs your choice on this Mac before OpenMeld can continue.",
|
|
1663
|
+
`Choices: ${choiceSummary}`,
|
|
1664
|
+
nextStepSummary ? `Next: ${nextStepSummary}` : null
|
|
1665
|
+
].filter((line) => Boolean(line)).join(" ");
|
|
1666
|
+
}
|
|
1667
|
+
if (input.reason === "not found") return "Not found on this computer";
|
|
1668
|
+
if (input.reason === "will be supported soon") return "Found on this computer, but OpenMeld support is coming soon";
|
|
1669
|
+
if (input.reason === "custom target is not runnable yet") return "Custom targets cannot be enabled yet";
|
|
1670
|
+
if (input.reason === "not selectable in current runtime") return "Not available in this runtime";
|
|
1671
|
+
if (input.reason === "unknown built-in target id") return "Not in the built-in support list";
|
|
1672
|
+
if (input.reason === "needs repair") return "Needs repair on this computer before OpenMeld can start it";
|
|
1673
|
+
if (input.reason === "needs your choice") return "Needs your choice on this Mac before OpenMeld can use it";
|
|
1674
|
+
if (input.code === "agents.config_write_failed") return "OpenMeld could not save this change";
|
|
1675
|
+
return formatStatusTextForHuman(input.reason);
|
|
1676
|
+
}
|
|
1677
|
+
function buildRepairSummaryRows(summary) {
|
|
1678
|
+
const rows = [{
|
|
1679
|
+
label: "Status",
|
|
1680
|
+
value: buildRepairSummaryStatus(summary)
|
|
1681
|
+
}];
|
|
1682
|
+
if (summary.repaired.length > 0) rows.push({
|
|
1683
|
+
label: "Repaired",
|
|
1684
|
+
value: String(summary.repaired.length)
|
|
1685
|
+
});
|
|
1686
|
+
if (summary.skipped.length > 0) rows.push({
|
|
1687
|
+
label: "Already available",
|
|
1688
|
+
value: String(summary.skipped.length)
|
|
1689
|
+
});
|
|
1690
|
+
if (summary.errors.length > 0) rows.push({
|
|
1691
|
+
label: "Needs attention",
|
|
1692
|
+
value: String(summary.errors.length)
|
|
1693
|
+
});
|
|
1694
|
+
return rows;
|
|
1695
|
+
}
|
|
1696
|
+
function buildRepairSummaryStatus(summary) {
|
|
1697
|
+
if (summary.repaired.length > 0 && summary.errors.length === 0) {
|
|
1698
|
+
if (summary.skipped.length === 0) return summary.repaired.length === 1 ? `1 agent is ${formatAgentsToneText("success", "available now")}` : `${String(summary.repaired.length)} agents are ${formatAgentsToneText("success", "available now")}`;
|
|
1699
|
+
return `${String(summary.repaired.length)} agents are ${formatAgentsToneText("success", "available now")} ${pc.dim("· review the rest below")}`;
|
|
1700
|
+
}
|
|
1701
|
+
if (summary.skipped.length > 0 && summary.errors.length === 0) return formatAgentsToneText("info", "Already available");
|
|
1702
|
+
return formatAgentsToneText("warning", "OpenMeld could not repair all selected agents");
|
|
1703
|
+
}
|
|
1704
|
+
function buildRepairDetailCards(summary, targetStateById) {
|
|
1705
|
+
const cards = [];
|
|
1706
|
+
if (summary.repaired.length > 0) cards.push({
|
|
1707
|
+
codeSuffix: "repaired",
|
|
1708
|
+
title: `Available Now (${summary.repaired.length})`,
|
|
1709
|
+
rows: summary.repaired.map((agentId) => buildReadyAgentCardRow(agentId, resolveEnableResultDisplayName(agentId, targetStateById)))
|
|
1710
|
+
});
|
|
1711
|
+
if (summary.skipped.length > 0) cards.push({
|
|
1712
|
+
codeSuffix: "already_ready",
|
|
1713
|
+
title: `Already Available (${summary.skipped.length})`,
|
|
1714
|
+
rows: summary.skipped.map((item) => ({
|
|
1715
|
+
label: resolveEnableResultDisplayName(item.agentId, targetStateById),
|
|
1716
|
+
value: item.reason === "already_ready" ? "Already available on this computer" : formatStatusTextForHuman(item.reason)
|
|
1717
|
+
}))
|
|
1718
|
+
});
|
|
1719
|
+
if (summary.errors.length > 0) cards.push({
|
|
1720
|
+
codeSuffix: "needs_attention",
|
|
1721
|
+
title: `Needs Attention (${summary.errors.length})`,
|
|
1722
|
+
rows: summary.errors.map((item) => ({
|
|
1723
|
+
label: resolveEnableResultDisplayName(item.agentId, targetStateById),
|
|
1724
|
+
value: formatRepairErrorReasonForHuman(item)
|
|
1725
|
+
}))
|
|
1726
|
+
});
|
|
1727
|
+
return cards;
|
|
1728
|
+
}
|
|
1729
|
+
function formatRepairErrorReasonForHuman(input) {
|
|
1730
|
+
if (input.reason === "agent is not registered") return "This agent is not connected in OpenMeld yet";
|
|
1731
|
+
if (input.reason === "custom target is not runnable yet") return "Custom targets cannot be repaired yet";
|
|
1732
|
+
if (input.reason === "unknown built-in target id") return "Not in the built-in support list";
|
|
1733
|
+
return formatEnableErrorReasonForHuman(input);
|
|
1734
|
+
}
|
|
1735
|
+
function emitDisableSummaryCardsForHuman(input) {
|
|
1736
|
+
emitHumanSectionDescription(input.presenter, {
|
|
1737
|
+
codePrefix: "agents.disable",
|
|
1738
|
+
description: AGENTS_DISABLE_RESULT_DESCRIPTION
|
|
1739
|
+
});
|
|
1740
|
+
renderAgentsInfoCard({
|
|
1741
|
+
presenter: input.presenter,
|
|
1742
|
+
codePrefix: "agents.disable.summary.card",
|
|
1743
|
+
title: "Agents Turned Off",
|
|
1744
|
+
rows: buildDisableSummaryRows(input.summary),
|
|
1745
|
+
minContentWidth: 38
|
|
1746
|
+
});
|
|
1747
|
+
const detailCards = buildDisableDetailCards(input.summary, input.displayNameById);
|
|
1748
|
+
for (const card of detailCards) {
|
|
1749
|
+
input.presenter.line({
|
|
1750
|
+
code: `agents.disable.${card.codeSuffix}.spacer`,
|
|
1751
|
+
text: ""
|
|
1752
|
+
});
|
|
1753
|
+
renderAgentsInfoCard({
|
|
1754
|
+
presenter: input.presenter,
|
|
1755
|
+
codePrefix: `agents.disable.${card.codeSuffix}.card`,
|
|
1756
|
+
title: card.title,
|
|
1757
|
+
rows: card.rows,
|
|
1758
|
+
minContentWidth: 30
|
|
1759
|
+
});
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
function buildDisableSummaryRows(summary) {
|
|
1763
|
+
const rows = [{
|
|
1764
|
+
label: "Status",
|
|
1765
|
+
value: buildDisableSummaryStatus(summary)
|
|
1766
|
+
}];
|
|
1767
|
+
if (summary.disabled.length > 0) rows.push({
|
|
1768
|
+
label: "Off now",
|
|
1769
|
+
value: String(summary.disabled.length)
|
|
1770
|
+
});
|
|
1771
|
+
if (summary.skipped.length > 0) rows.push({
|
|
1772
|
+
label: "Already off",
|
|
1773
|
+
value: String(summary.skipped.length)
|
|
1774
|
+
});
|
|
1775
|
+
if (summary.errors.length > 0) rows.push({
|
|
1776
|
+
label: "Needs attention",
|
|
1777
|
+
value: String(summary.errors.length)
|
|
1778
|
+
});
|
|
1779
|
+
return rows;
|
|
1780
|
+
}
|
|
1781
|
+
function buildDisableSummaryStatus(summary) {
|
|
1782
|
+
if (summary.disabled.length > 0 && summary.skipped.length === 0) return summary.disabled.length === 1 ? `1 agent ${formatAgentsToneText("warning", "turned off")}` : `${String(summary.disabled.length)} agents ${formatAgentsToneText("warning", "turned off")}`;
|
|
1783
|
+
if (summary.disabled.length > 0) return summary.disabled.length === 1 ? `1 agent ${formatAgentsToneText("warning", "turned off")} ${pc.dim("· review the rest below")}` : `${String(summary.disabled.length)} agents ${formatAgentsToneText("warning", "turned off")} ${pc.dim("· review the rest below")}`;
|
|
1784
|
+
if (summary.skipped.length > 0 && summary.errors.length === 0) return formatAgentsToneText("info", "Already off");
|
|
1785
|
+
return formatAgentsToneText("warning", "OpenMeld could not turn these off");
|
|
1786
|
+
}
|
|
1787
|
+
function buildDisableDetailCards(summary, displayNameById) {
|
|
1788
|
+
const cards = [];
|
|
1789
|
+
if (summary.disabled.length > 0) cards.push({
|
|
1790
|
+
codeSuffix: "turned_off",
|
|
1791
|
+
title: `Turned Off (${summary.disabled.length})`,
|
|
1792
|
+
rows: summary.disabled.map((agentId) => buildTurnedOffAgentCardRow(agentId, displayNameById.get(agentId) ?? agentId))
|
|
1793
|
+
});
|
|
1794
|
+
if (summary.skipped.length > 0) cards.push({
|
|
1795
|
+
codeSuffix: "already_off",
|
|
1796
|
+
title: `Already Off (${summary.skipped.length})`,
|
|
1797
|
+
rows: summary.skipped.map((item) => ({
|
|
1798
|
+
label: displayNameById.get(item.agentId) ?? item.agentId,
|
|
1799
|
+
value: formatDisableSkippedReasonForHuman(item.reason)
|
|
1800
|
+
}))
|
|
1801
|
+
});
|
|
1802
|
+
if (summary.errors.length > 0) cards.push({
|
|
1803
|
+
codeSuffix: "needs_attention",
|
|
1804
|
+
title: `Needs Attention (${summary.errors.length})`,
|
|
1805
|
+
rows: summary.errors.map((item) => ({
|
|
1806
|
+
label: displayNameById.get(item.agentId) ?? item.agentId,
|
|
1807
|
+
value: formatDisableErrorReasonForHuman(item)
|
|
1808
|
+
}))
|
|
1809
|
+
});
|
|
1810
|
+
return cards;
|
|
1811
|
+
}
|
|
1812
|
+
function formatDisableSkippedReasonForHuman(reason) {
|
|
1813
|
+
if (reason === "already_disabled") return "Already off in OpenMeld";
|
|
1814
|
+
return formatStatusTextForHuman(reason);
|
|
1815
|
+
}
|
|
1816
|
+
function formatDisableErrorReasonForHuman(input) {
|
|
1817
|
+
if (input.reason === "agent is not registered") return "Not currently connected to OpenMeld";
|
|
1818
|
+
if (input.code === "agents.config_write_failed") return "OpenMeld could not save this change";
|
|
1819
|
+
return formatStatusTextForHuman(input.reason);
|
|
1820
|
+
}
|
|
1821
|
+
//#endregion
|
|
1822
|
+
//#region src/commands/agents.ts
|
|
1823
|
+
function isBuiltinEnableChoiceCandidate(value) {
|
|
1824
|
+
return value?.targetKind === "builtin";
|
|
1825
|
+
}
|
|
1826
|
+
function hasAgentRegistration(value) {
|
|
1827
|
+
return value?.registration !== null && value?.registration !== void 0;
|
|
1828
|
+
}
|
|
1829
|
+
const AGENTS_MENU_CHOICE = {
|
|
1830
|
+
quickEnable: "quick_enable",
|
|
1831
|
+
manage: "manage",
|
|
1832
|
+
detect: "detect",
|
|
1833
|
+
custom: "custom",
|
|
1834
|
+
exit: "exit"
|
|
1835
|
+
};
|
|
1836
|
+
const AGENTS_MANAGE_AGENT_ACTION_CHOICE = {
|
|
1837
|
+
showDetails: "show_details",
|
|
1838
|
+
chooseInstall: "choose_install",
|
|
1839
|
+
repair: "repair",
|
|
1840
|
+
toggleEnabled: "toggle_enabled",
|
|
1841
|
+
back: "back"
|
|
1842
|
+
};
|
|
1843
|
+
const CUSTOM_AGENTS_COMING_SOON_OPTION_ID = "__custom_agents_coming_soon__";
|
|
1844
|
+
const NO_REGISTERED_AGENTS_MANAGE_MESSAGE = "No agents are set up on this computer yet. Run `openmeld agents detect`, then turn on the ones you want OpenMeld to use.";
|
|
1845
|
+
const NO_ACTIONABLE_AGENTS_ENABLE_MESSAGE = "No more agents are available to enable. Run `openmeld agents detect` to scan this computer again.";
|
|
1846
|
+
const AGENTS_MENU_DESCRIPTION = "Enable agents on this computer so people in a Space can mention them.";
|
|
1847
|
+
const AGENTS_ENABLE_DESCRIPTION = "Choose which new agents OpenMeld should turn on for Space from this computer.";
|
|
1848
|
+
const AGENTS_SELECT_REGISTERED_DESCRIPTION = "Choose an agent on this computer to review or change.";
|
|
1849
|
+
const AGENTS_MENU_COMMANDS = [
|
|
1850
|
+
...buildBuiltinAgentSelectionExamples({ command: "enable" }),
|
|
1851
|
+
...buildBuiltinAgentSelectionExamples({ command: "repair" }),
|
|
1852
|
+
"openmeld agents detect",
|
|
1853
|
+
"openmeld agents list",
|
|
1854
|
+
"openmeld agents manage",
|
|
1855
|
+
...buildBuiltinAgentSelectionExamples({ command: "disable" }),
|
|
1856
|
+
"openmeld agents show <agent-id>",
|
|
1857
|
+
"openmeld agents config <agent-id> --enabled true|false",
|
|
1858
|
+
"openmeld agents custom list",
|
|
1859
|
+
"openmeld agents custom add --name <name> --dir <dir> [--id <id>]",
|
|
1860
|
+
"openmeld agents custom update --id <id> [--name <name>] [--dir <dir>]",
|
|
1861
|
+
"openmeld agents custom remove --id <id>"
|
|
1862
|
+
];
|
|
1863
|
+
const AGENTS_MANAGE_RESULT_COMMANDS = [
|
|
1864
|
+
"openmeld agents list",
|
|
1865
|
+
"openmeld agents show <agent-id>",
|
|
1866
|
+
...buildBuiltinAgentSelectionExamples({ command: "repair" }),
|
|
1867
|
+
"openmeld agents config <agent-id> --enabled true|false",
|
|
1868
|
+
...buildBuiltinAgentSelectionExamples({ command: "disable" })
|
|
1869
|
+
];
|
|
1870
|
+
function formatBuiltinAgentSelectionAlternatives(command) {
|
|
1871
|
+
return buildBuiltinAgentSelectionExamples({ command }).filter((example) => !example.includes(" --install ")).map((example) => `\`${example}\``).join(" or ");
|
|
1872
|
+
}
|
|
1873
|
+
function buildBuiltinAgentMenuActions(command) {
|
|
1874
|
+
switch (command) {
|
|
1875
|
+
case "enable": return [
|
|
1876
|
+
{
|
|
1877
|
+
command: buildBuiltinAgentSelectionCommand({
|
|
1878
|
+
all: true,
|
|
1879
|
+
command
|
|
1880
|
+
}),
|
|
1881
|
+
description: "Enable every detected agent OpenMeld can use on this computer."
|
|
1882
|
+
},
|
|
1883
|
+
{
|
|
1884
|
+
command: buildBuiltinAgentSelectionCommand({
|
|
1885
|
+
agentIds: ["<id...>"],
|
|
1886
|
+
command
|
|
1887
|
+
}),
|
|
1888
|
+
description: "Enable selected agents so OpenMeld can use them."
|
|
1889
|
+
},
|
|
1890
|
+
{
|
|
1891
|
+
command: buildBuiltinAgentSelectionCommand({
|
|
1892
|
+
agentIds: ["<id>"],
|
|
1893
|
+
command,
|
|
1894
|
+
installId: "<install-id>"
|
|
1895
|
+
}),
|
|
1896
|
+
description: "Enable one agent with the exact local install OpenMeld should use."
|
|
1897
|
+
}
|
|
1898
|
+
];
|
|
1899
|
+
case "repair": return [
|
|
1900
|
+
{
|
|
1901
|
+
command: buildBuiltinAgentSelectionCommand({
|
|
1902
|
+
all: true,
|
|
1903
|
+
command
|
|
1904
|
+
}),
|
|
1905
|
+
description: "Repair every connected builtin agent on this computer that needs attention."
|
|
1906
|
+
},
|
|
1907
|
+
{
|
|
1908
|
+
command: buildBuiltinAgentSelectionCommand({
|
|
1909
|
+
agentIds: ["<id...>"],
|
|
1910
|
+
command
|
|
1911
|
+
}),
|
|
1912
|
+
description: "Refresh how OpenMeld starts selected agents on this computer."
|
|
1913
|
+
},
|
|
1914
|
+
{
|
|
1915
|
+
command: buildBuiltinAgentSelectionCommand({
|
|
1916
|
+
agentIds: ["<id>"],
|
|
1917
|
+
command,
|
|
1918
|
+
installId: "<install-id>"
|
|
1919
|
+
}),
|
|
1920
|
+
description: "Repair one agent with the exact local install to use."
|
|
1921
|
+
}
|
|
1922
|
+
];
|
|
1923
|
+
case "disable": return [{
|
|
1924
|
+
command: buildBuiltinAgentSelectionCommand({
|
|
1925
|
+
all: true,
|
|
1926
|
+
command
|
|
1927
|
+
}),
|
|
1928
|
+
description: "Disable every enabled agent for new mentions in Spaces."
|
|
1929
|
+
}, {
|
|
1930
|
+
command: buildBuiltinAgentSelectionCommand({
|
|
1931
|
+
agentIds: ["<id...>"],
|
|
1932
|
+
command
|
|
1933
|
+
}),
|
|
1934
|
+
description: "Disable selected agents so new mentions in Spaces stop waking them."
|
|
1935
|
+
}];
|
|
1936
|
+
default: throw new Error(`unsupported builtin agent selection command: ${command}`);
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
function formatAgentViewLaunchStatus(status) {
|
|
1940
|
+
return status === "ready" ? "available" : status;
|
|
1941
|
+
}
|
|
1942
|
+
function buildQuickEnableOptionHint(actionableBuiltinCount) {
|
|
1943
|
+
return actionableBuiltinCount === 1 ? "1 new agent can be turned on now" : `${String(actionableBuiltinCount)} new agents can be turned on now`;
|
|
1944
|
+
}
|
|
1945
|
+
function buildManageOptionHint(registeredCount) {
|
|
1946
|
+
if (registeredCount === 0) return "No agents are set up on this computer yet";
|
|
1947
|
+
if (registeredCount === 1) return "Review 1 agent on this computer";
|
|
1948
|
+
return `Review ${String(registeredCount)} agents on this computer`;
|
|
1949
|
+
}
|
|
1950
|
+
function buildDetectedAgentsSubtitle(detectedAgentsCount) {
|
|
1951
|
+
return detectedAgentsCount === 1 ? "1 agent found on this computer" : `${String(detectedAgentsCount)} agents found on this computer`;
|
|
1952
|
+
}
|
|
1953
|
+
function buildEnabledAgentsSubtitle(enabledAgentsCount) {
|
|
1954
|
+
return enabledAgentsCount === 1 ? "1 agent is live for Space" : `${String(enabledAgentsCount)} agents are live for Space`;
|
|
1955
|
+
}
|
|
1956
|
+
async function runAgents(input) {
|
|
1957
|
+
const runtime = await resolveRuntimeContext({
|
|
1958
|
+
interactive: input.interactive,
|
|
1959
|
+
view: input.view,
|
|
1960
|
+
profile: input.profile
|
|
1961
|
+
});
|
|
1962
|
+
const presenter = createPresenter(runtime);
|
|
1963
|
+
await emitAgentsConfigParseFallbackSignal({
|
|
1964
|
+
presenter,
|
|
1965
|
+
view: runtime.resolvedView
|
|
1966
|
+
});
|
|
1967
|
+
emitAgentsOverviewIfAgent({
|
|
1968
|
+
presenter,
|
|
1969
|
+
resolvedView: runtime.resolvedView,
|
|
1970
|
+
command: "menu"
|
|
1971
|
+
});
|
|
1972
|
+
const payload = await buildAgentsMenuPayload();
|
|
1973
|
+
if (runtime.resolvedView === "agent") {
|
|
1974
|
+
presenter.data({
|
|
1975
|
+
code: "agents.menu.result",
|
|
1976
|
+
payload
|
|
1977
|
+
});
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
if (canUseInteractivePrompts(runtime)) {
|
|
1981
|
+
await runInteractiveAgentsMenu({
|
|
1982
|
+
profile: input.profile,
|
|
1983
|
+
view: runtime.resolvedView
|
|
1984
|
+
});
|
|
1985
|
+
return;
|
|
1986
|
+
}
|
|
1987
|
+
presenter.line({
|
|
1988
|
+
code: "agents.menu.header",
|
|
1989
|
+
text: "OpenMeld agents commands:"
|
|
1990
|
+
});
|
|
1991
|
+
for (const command of formatOpenMeldCliCommands(payload.commands)) presenter.line({
|
|
1992
|
+
code: "agents.menu.command",
|
|
1993
|
+
text: `- ${command}`
|
|
1994
|
+
});
|
|
1995
|
+
for (const note of payload.notes) presenter.line({
|
|
1996
|
+
code: "agents.menu.note",
|
|
1997
|
+
text: `- ${formatAgentsCopy(note)}`
|
|
1998
|
+
});
|
|
1999
|
+
}
|
|
2000
|
+
async function runAgentsManage(input) {
|
|
2001
|
+
const runtime = await resolveRuntimeContext({
|
|
2002
|
+
view: input.view,
|
|
2003
|
+
profile: input.profile
|
|
2004
|
+
});
|
|
2005
|
+
const presenter = createPresenter(runtime);
|
|
2006
|
+
await emitAgentsConfigParseFallbackSignal({
|
|
2007
|
+
presenter,
|
|
2008
|
+
view: runtime.resolvedView
|
|
2009
|
+
});
|
|
2010
|
+
emitAgentsOverviewIfAgent({
|
|
2011
|
+
presenter,
|
|
2012
|
+
resolvedView: runtime.resolvedView,
|
|
2013
|
+
command: "manage"
|
|
2014
|
+
});
|
|
2015
|
+
if (runtime.resolvedView === "agent") {
|
|
2016
|
+
presenter.data({
|
|
2017
|
+
code: "agents.manage.result",
|
|
2018
|
+
payload: {
|
|
2019
|
+
interactive: true,
|
|
2020
|
+
supportedView: "human",
|
|
2021
|
+
commands: [...AGENTS_MANAGE_RESULT_COMMANDS]
|
|
2022
|
+
}
|
|
2023
|
+
});
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
2026
|
+
if (!canUseInteractivePrompts(runtime)) {
|
|
2027
|
+
presenter.line({
|
|
2028
|
+
code: "agents.manage.non_interactive",
|
|
2029
|
+
text: formatInlineOpenMeldCliCommands("This menu is interactive. In scripts or non-interactive terminals, use `openmeld agents list`, `openmeld agents show <agent-id>`, `openmeld agents config <agent-id> --enabled true|false`, or `openmeld agents disable --agent <id...>`.")
|
|
2030
|
+
});
|
|
2031
|
+
return;
|
|
2032
|
+
}
|
|
2033
|
+
if (await runInteractiveAgentsManageMenu({
|
|
2034
|
+
view: "human",
|
|
2035
|
+
profile: input.profile
|
|
2036
|
+
}) === "cancelled") cancel("Cancelled.");
|
|
2037
|
+
}
|
|
2038
|
+
async function runInteractiveAgentsMenu(input) {
|
|
2039
|
+
while (true) {
|
|
2040
|
+
const { enableCandidates, registrations } = await resolveAgentsChoiceState();
|
|
2041
|
+
const actionableBuiltinCount = enableCandidates.filter((candidate) => canSelectAgentForNewLocalAgentSetup(candidate) && !candidate.registered).length;
|
|
2042
|
+
const registeredCount = registrations.length;
|
|
2043
|
+
const choice = await select({
|
|
2044
|
+
message: buildInteractiveMenuMessage({
|
|
2045
|
+
title: "OpenMeld agents:",
|
|
2046
|
+
description: AGENTS_MENU_DESCRIPTION
|
|
2047
|
+
}),
|
|
2048
|
+
options: [
|
|
2049
|
+
...actionableBuiltinCount > 0 ? [{
|
|
2050
|
+
value: AGENTS_MENU_CHOICE.quickEnable,
|
|
2051
|
+
label: "Enable available agents",
|
|
2052
|
+
hint: buildQuickEnableOptionHint(actionableBuiltinCount)
|
|
2053
|
+
}] : [],
|
|
2054
|
+
{
|
|
2055
|
+
value: AGENTS_MENU_CHOICE.manage,
|
|
2056
|
+
label: "Review Agents on This Device",
|
|
2057
|
+
hint: buildManageOptionHint(registeredCount)
|
|
2058
|
+
},
|
|
2059
|
+
{
|
|
2060
|
+
value: AGENTS_MENU_CHOICE.detect,
|
|
2061
|
+
label: "Find Agents on This Device",
|
|
2062
|
+
hint: "Check which supported AI tools OpenMeld can see here"
|
|
2063
|
+
},
|
|
2064
|
+
{
|
|
2065
|
+
value: AGENTS_MENU_CHOICE.exit,
|
|
2066
|
+
label: "Exit"
|
|
2067
|
+
}
|
|
2068
|
+
]
|
|
2069
|
+
});
|
|
2070
|
+
if (isCancel(choice)) {
|
|
2071
|
+
cancel("Cancelled.");
|
|
2072
|
+
return;
|
|
2073
|
+
}
|
|
2074
|
+
if (choice === AGENTS_MENU_CHOICE.exit) return;
|
|
2075
|
+
if (await runInteractiveAgentsMenuChoice({
|
|
2076
|
+
input,
|
|
2077
|
+
choice
|
|
2078
|
+
}) === "cancelled") {
|
|
2079
|
+
cancel("Cancelled.");
|
|
2080
|
+
return;
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
async function runInteractiveAgentsMenuChoice(input) {
|
|
2085
|
+
switch (input.choice) {
|
|
2086
|
+
case AGENTS_MENU_CHOICE.detect:
|
|
2087
|
+
await runAgentsDetect(input.input);
|
|
2088
|
+
return "continue";
|
|
2089
|
+
case AGENTS_MENU_CHOICE.quickEnable: return runInteractiveAgentsEnable(input.input);
|
|
2090
|
+
case AGENTS_MENU_CHOICE.manage: return runInteractiveAgentsManageMenu(input.input);
|
|
2091
|
+
case AGENTS_MENU_CHOICE.custom: return runInteractiveCustomAgentsMenu(input.input);
|
|
2092
|
+
default: return "continue";
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
async function buildAgentsMenuPayload() {
|
|
2096
|
+
const { enableCandidates, registrations } = await resolveAgentsChoiceState();
|
|
2097
|
+
const registeredAgents = registrations.map((registration) => ({
|
|
2098
|
+
agentId: registration.agentId,
|
|
2099
|
+
displayName: registration.displayName,
|
|
2100
|
+
enabled: registration.enabled
|
|
2101
|
+
}));
|
|
2102
|
+
return {
|
|
2103
|
+
commands: formatOpenMeldCliCommands([...AGENTS_MENU_COMMANDS]),
|
|
2104
|
+
notes: [
|
|
2105
|
+
"Agents are AI tools on this computer. When you enable one, people in a Space can mention `@agent-id` to wake it and get a reply.",
|
|
2106
|
+
"Use `openmeld agents detect` to look for supported AI tools on this computer.",
|
|
2107
|
+
`Use ${formatBuiltinAgentSelectionAlternatives("enable")} to enable the agents that are available.`,
|
|
2108
|
+
`If OpenMeld can see an agent but cannot start it here, use ${formatBuiltinAgentSelectionAlternatives("repair")}.`,
|
|
2109
|
+
"Use `openmeld agents manage` to review enabled agents and change them later.",
|
|
2110
|
+
"Manage custom agents with `openmeld agents custom`."
|
|
2111
|
+
].map(formatAgentsCopy),
|
|
2112
|
+
actions: [
|
|
2113
|
+
...buildBuiltinAgentMenuActions("enable"),
|
|
2114
|
+
...buildBuiltinAgentMenuActions("repair"),
|
|
2115
|
+
{
|
|
2116
|
+
command: "openmeld agents detect",
|
|
2117
|
+
description: "Look for supported AI tools on this computer."
|
|
2118
|
+
},
|
|
2119
|
+
{
|
|
2120
|
+
command: "openmeld agents list",
|
|
2121
|
+
description: "List the agents people can mention in a Space."
|
|
2122
|
+
},
|
|
2123
|
+
{
|
|
2124
|
+
command: "openmeld agents manage",
|
|
2125
|
+
description: "Review enabled agents and change their settings."
|
|
2126
|
+
},
|
|
2127
|
+
...buildBuiltinAgentMenuActions("disable"),
|
|
2128
|
+
{
|
|
2129
|
+
command: "openmeld agents show <agent-id>",
|
|
2130
|
+
description: "Show one agent's current OpenMeld settings."
|
|
2131
|
+
},
|
|
2132
|
+
{
|
|
2133
|
+
command: "openmeld agents config <agent-id> --enabled true|false",
|
|
2134
|
+
description: "Enable or disable one agent for Space mentions."
|
|
2135
|
+
},
|
|
2136
|
+
{
|
|
2137
|
+
command: "openmeld agents custom list",
|
|
2138
|
+
description: "List custom agent targets stored in the local OpenMeld config."
|
|
2139
|
+
},
|
|
2140
|
+
{
|
|
2141
|
+
command: "openmeld agents custom add --name <name> --dir <dir> [--id <id>]",
|
|
2142
|
+
description: "Add a custom agent target to the local OpenMeld config."
|
|
2143
|
+
},
|
|
2144
|
+
{
|
|
2145
|
+
command: "openmeld agents custom update --id <id> [--name <name>] [--dir <dir>]",
|
|
2146
|
+
description: "Update a custom agent target while keeping its id stable."
|
|
2147
|
+
},
|
|
2148
|
+
{
|
|
2149
|
+
command: "openmeld agents custom remove --id <id>",
|
|
2150
|
+
description: "Remove a custom agent target that is not currently connected."
|
|
2151
|
+
}
|
|
2152
|
+
].map((action) => ({
|
|
2153
|
+
...action,
|
|
2154
|
+
command: formatOpenMeldCliCommands([action.command])[0] ?? action.command
|
|
2155
|
+
})),
|
|
2156
|
+
selection: {
|
|
2157
|
+
enableCandidates: enableCandidates.map((candidate) => ({
|
|
2158
|
+
agentId: candidate.agentId,
|
|
2159
|
+
displayName: candidate.displayName,
|
|
2160
|
+
targetKind: candidate.targetKind,
|
|
2161
|
+
detected: candidate.detected,
|
|
2162
|
+
lifecycleStatus: candidate.lifecycleStatus,
|
|
2163
|
+
registered: candidate.registered,
|
|
2164
|
+
enabled: candidate.enabled,
|
|
2165
|
+
selectable: candidate.selectable,
|
|
2166
|
+
selectableReason: candidate.selectableReason,
|
|
2167
|
+
launchStatus: formatAgentViewLaunchStatus(candidate.launchStatus),
|
|
2168
|
+
launchStatusDetail: candidate.launchStatusDetail,
|
|
2169
|
+
defaultTransportMode: candidate.defaultTransportMode ?? null,
|
|
2170
|
+
transportModes: candidate.transportModes,
|
|
2171
|
+
status: resolveEnableCandidateStatus(candidate),
|
|
2172
|
+
...candidate.customDir ? { customDir: candidate.customDir } : {}
|
|
2173
|
+
})),
|
|
2174
|
+
selectableAgentIds: enableCandidates.filter(canSelectAgentForNewLocalAgentSetup).map((candidate) => candidate.agentId),
|
|
2175
|
+
registeredAgents,
|
|
2176
|
+
disableCandidates: registeredAgents.map((agent) => ({
|
|
2177
|
+
agentId: agent.agentId,
|
|
2178
|
+
displayName: agent.displayName,
|
|
2179
|
+
enabled: agent.enabled
|
|
2180
|
+
})),
|
|
2181
|
+
customAgents: {
|
|
2182
|
+
supported: false,
|
|
2183
|
+
message: formatAgentsCopy("Custom agents are not available in this list yet. Use `openmeld agents custom` for now."),
|
|
2184
|
+
commands: formatOpenMeldCliCommands([
|
|
2185
|
+
"openmeld agents custom list",
|
|
2186
|
+
"openmeld agents custom add --name <name> --dir <dir> [--id <id>]",
|
|
2187
|
+
"openmeld agents custom update --id <id> [--name <name>] [--dir <dir>]",
|
|
2188
|
+
"openmeld agents custom remove --id <id>"
|
|
2189
|
+
])
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
};
|
|
2193
|
+
}
|
|
2194
|
+
async function resolveAgentsChoiceState() {
|
|
2195
|
+
const enableCandidates = await listAgentTargetStates();
|
|
2196
|
+
return {
|
|
2197
|
+
registrations: enableCandidates.flatMap((candidate) => candidate.registration ? [candidate.registration] : []).sort((left, right) => compareAgentControllerRefsForDisplay(left.agentId, right.agentId)),
|
|
2198
|
+
enableCandidates
|
|
2199
|
+
};
|
|
2200
|
+
}
|
|
2201
|
+
function resolveEnableCandidateStatus(candidate) {
|
|
2202
|
+
if (candidate.targetKind === "custom") return "custom_coming_soon";
|
|
2203
|
+
if (!candidate.detected) return "not_detected";
|
|
2204
|
+
if (candidate.lifecycleStatus === "suspended") return "detected_suspended";
|
|
2205
|
+
if (candidate.launchStatus === "needs_choice") return "detected_needs_choice";
|
|
2206
|
+
if (candidate.launchStatus === "needs_repair") return "detected_needs_repair";
|
|
2207
|
+
if (!candidate.selectable) return "detected_not_supported_yet";
|
|
2208
|
+
return "detected_supported";
|
|
2209
|
+
}
|
|
2210
|
+
function canSelectAgentForNewLocalAgentSetup(candidate) {
|
|
2211
|
+
return candidate.targetKind === "builtin" && candidate.lifecycleStatus !== "suspended" && candidate.selectable;
|
|
2212
|
+
}
|
|
2213
|
+
function buildUnifiedEnableTargetHint(candidate) {
|
|
2214
|
+
if (candidate.enabled) return "Already turned on in OpenMeld";
|
|
2215
|
+
if (!candidate.detected) return "Not found on this computer";
|
|
2216
|
+
if (candidate.lifecycleStatus === "suspended") return "Suspended for new OpenMeld profiles";
|
|
2217
|
+
if (candidate.launchStatus === "needs_choice") return "Needs your choice on this Mac before OpenMeld can turn it on";
|
|
2218
|
+
if (!candidate.selectable && candidate.selectableReason === "not_supported_yet") return "Found on this computer, but OpenMeld doesn't support it yet";
|
|
2219
|
+
if (!candidate.selectable && candidate.selectableReason === "needs_repair") return "Needs repair before OpenMeld can start it here";
|
|
2220
|
+
if (!candidate.selectable && candidate.selectableReason) return formatSelectableReasonForHuman(candidate.selectableReason);
|
|
2221
|
+
return "Available to enable now";
|
|
2222
|
+
}
|
|
2223
|
+
function formatSelectableReason(reason) {
|
|
2224
|
+
if (reason === "not_detected") return "not found";
|
|
2225
|
+
if (reason === "not_supported_yet") return "will be supported soon";
|
|
2226
|
+
if (reason === "custom_coming_soon") return "coming soon";
|
|
2227
|
+
if (reason === "needs_choice") return "needs your choice";
|
|
2228
|
+
if (reason === "needs_repair") return "needs repair";
|
|
2229
|
+
if (reason === "suspended") return "suspended";
|
|
2230
|
+
return reason;
|
|
2231
|
+
}
|
|
2232
|
+
function formatSelectableReasonForHuman(reason) {
|
|
2233
|
+
if (reason === "not_detected") return "Not found on this computer";
|
|
2234
|
+
if (reason === "not_supported_yet") return "Found on this computer, but OpenMeld doesn't support it yet";
|
|
2235
|
+
if (reason === "custom_coming_soon") return "Custom agent support is coming soon";
|
|
2236
|
+
if (reason === "needs_choice") return "Needs your choice on this Mac before OpenMeld can use it";
|
|
2237
|
+
if (reason === "needs_repair") return "Needs repair on this computer before OpenMeld can start it";
|
|
2238
|
+
if (reason === "suspended") return "Suspended for new OpenMeld profiles";
|
|
2239
|
+
return formatStatusTextForHuman(reason);
|
|
2240
|
+
}
|
|
2241
|
+
function resolveEnableBlockedReason(state) {
|
|
2242
|
+
if (!state) return "unknown built-in target id";
|
|
2243
|
+
if (state.targetKind === "custom") return "custom target is not runnable yet";
|
|
2244
|
+
if (!state.detected) return "not found";
|
|
2245
|
+
if (state.lifecycleStatus === "suspended") return "suspended for new OpenMeld profiles";
|
|
2246
|
+
if (state.launchStatus === "needs_choice") return "needs your choice";
|
|
2247
|
+
if (!state.selectable && state.selectableReason === "needs_repair") return "needs repair";
|
|
2248
|
+
if (!state.selectable && state.selectableReason) return formatSelectableReason(state.selectableReason);
|
|
2249
|
+
return "not selectable in current runtime";
|
|
2250
|
+
}
|
|
2251
|
+
function buildRegisteredAgentHint(state) {
|
|
2252
|
+
const activationHint = state.registration.enabled ? "Turned on" : "Turned off for new @mentions";
|
|
2253
|
+
if (state.launchStatus === "needs_choice") return buildMetadataDetailLine([activationHint, "Needs your choice on this Mac"]);
|
|
2254
|
+
if (state.launchStatus === "needs_repair") return buildMetadataDetailLine([activationHint, "Needs repair on this computer"]);
|
|
2255
|
+
return buildMetadataDetailLine([activationHint, state.registration.enabled ? "Live for Space @mentions" : null]);
|
|
2256
|
+
}
|
|
2257
|
+
async function runInteractiveAgentsEnable(input) {
|
|
2258
|
+
const { enableCandidates } = await resolveAgentsChoiceState();
|
|
2259
|
+
const actionableCandidates = enableCandidates.filter((candidate) => candidate.targetKind === "builtin").filter((candidate) => canSelectAgentForNewLocalAgentSetup(candidate) && !candidate.registered);
|
|
2260
|
+
if (actionableCandidates.length === 0) {
|
|
2261
|
+
process.stderr.write(`${formatAgentsCopy(NO_ACTIONABLE_AGENTS_ENABLE_MESSAGE)}\n`);
|
|
2262
|
+
return "completed";
|
|
2263
|
+
}
|
|
2264
|
+
const selectedResult = await promptEnableAgentIdsFromUnifiedBuiltins({ candidates: actionableCandidates });
|
|
2265
|
+
if (selectedResult.status === "cancelled") return "cancelled";
|
|
2266
|
+
if (selectedResult.status !== "selected") return "completed";
|
|
2267
|
+
await runAgentsEnable({
|
|
2268
|
+
agent: selectedResult.value,
|
|
2269
|
+
profile: input.profile,
|
|
2270
|
+
view: input.view
|
|
2271
|
+
});
|
|
2272
|
+
return "completed";
|
|
2273
|
+
}
|
|
2274
|
+
async function runInteractiveCustomAgentsMenu(input) {
|
|
2275
|
+
while (true) {
|
|
2276
|
+
const outcome = await handleInteractiveCustomAgentsMenuChoice({
|
|
2277
|
+
input,
|
|
2278
|
+
choiceResult: await promptInteractiveCustomAgentsMenuChoice()
|
|
2279
|
+
});
|
|
2280
|
+
if (outcome === "continue") continue;
|
|
2281
|
+
return outcome;
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
async function runInteractiveAgentsManageMenu(input) {
|
|
2285
|
+
if ((await listRegisteredAgentStates()).length === 0) {
|
|
2286
|
+
process.stderr.write(`${formatAgentsCopy(NO_REGISTERED_AGENTS_MANAGE_MESSAGE)}\n`);
|
|
2287
|
+
return "completed";
|
|
2288
|
+
}
|
|
2289
|
+
while (true) {
|
|
2290
|
+
const selectedAgentResult = await promptSelectRegisteredAgent({
|
|
2291
|
+
message: "Choose an agent",
|
|
2292
|
+
emptyMessage: NO_REGISTERED_AGENTS_MANAGE_MESSAGE
|
|
2293
|
+
});
|
|
2294
|
+
if (selectedAgentResult.status === "cancelled") return "cancelled";
|
|
2295
|
+
if (selectedAgentResult.status !== "selected") return "completed";
|
|
2296
|
+
const selectedState = selectedAgentResult.value;
|
|
2297
|
+
const actionResult = await promptInteractiveAgentsManageAgentAction({
|
|
2298
|
+
agentId: selectedState.agentId,
|
|
2299
|
+
displayName: selectedState.displayName,
|
|
2300
|
+
enabled: selectedState.enabled,
|
|
2301
|
+
launchStatus: selectedState.launchStatus
|
|
2302
|
+
});
|
|
2303
|
+
if (actionResult.status === "cancelled") return "cancelled";
|
|
2304
|
+
if (actionResult.status !== "selected" || actionResult.value === AGENTS_MANAGE_AGENT_ACTION_CHOICE.back) continue;
|
|
2305
|
+
if (actionResult.value === AGENTS_MANAGE_AGENT_ACTION_CHOICE.showDetails) {
|
|
2306
|
+
await runAgentsShow({
|
|
2307
|
+
agentId: selectedState.agentId,
|
|
2308
|
+
profile: input.profile,
|
|
2309
|
+
view: input.view
|
|
2310
|
+
});
|
|
2311
|
+
continue;
|
|
2312
|
+
}
|
|
2313
|
+
if (actionResult.value === AGENTS_MANAGE_AGENT_ACTION_CHOICE.repair) {
|
|
2314
|
+
await runAgentsRepair({
|
|
2315
|
+
agent: [selectedState.agentId],
|
|
2316
|
+
profile: input.profile,
|
|
2317
|
+
view: input.view
|
|
2318
|
+
});
|
|
2319
|
+
continue;
|
|
2320
|
+
}
|
|
2321
|
+
if (actionResult.value === AGENTS_MANAGE_AGENT_ACTION_CHOICE.chooseInstall) {
|
|
2322
|
+
await runAgentsRepair({
|
|
2323
|
+
agent: [selectedState.agentId],
|
|
2324
|
+
changeInstall: true,
|
|
2325
|
+
profile: input.profile,
|
|
2326
|
+
view: input.view
|
|
2327
|
+
});
|
|
2328
|
+
continue;
|
|
2329
|
+
}
|
|
2330
|
+
await runAgentsConfig({
|
|
2331
|
+
agentId: selectedState.agentId,
|
|
2332
|
+
enabled: selectedState.enabled ? "false" : "true",
|
|
2333
|
+
profile: input.profile,
|
|
2334
|
+
view: input.view
|
|
2335
|
+
});
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
async function handleInteractiveCustomAgentsMenuChoice(input) {
|
|
2339
|
+
if (input.choiceResult.status === "cancelled") return "cancelled";
|
|
2340
|
+
if (input.choiceResult.status !== "selected") return "completed";
|
|
2341
|
+
switch (input.choiceResult.value) {
|
|
2342
|
+
case "back": return "completed";
|
|
2343
|
+
case "list":
|
|
2344
|
+
await runAgentsCustomList({
|
|
2345
|
+
profile: input.input.profile,
|
|
2346
|
+
view: input.input.view
|
|
2347
|
+
});
|
|
2348
|
+
return "continue";
|
|
2349
|
+
case "add": return await runInteractiveCustomAgentsAddFlow(input.input) === "cancelled" ? "cancelled" : "continue";
|
|
2350
|
+
case "remove": return await runInteractiveCustomAgentsRemoveFlow(input.input) === "cancelled" ? "cancelled" : "continue";
|
|
2351
|
+
case "edit": return await runInteractiveCustomAgentsEditFlow(input.input) === "cancelled" ? "cancelled" : "continue";
|
|
2352
|
+
default: return "completed";
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
async function promptInteractiveAgentsManageAgentAction(input) {
|
|
2356
|
+
const action = await select({
|
|
2357
|
+
message: buildInteractiveMenuMessage({
|
|
2358
|
+
title: `Agent: ${input.agentId}`,
|
|
2359
|
+
description: `When on, @${input.agentId} in Space can wake ${input.displayName}.`
|
|
2360
|
+
}),
|
|
2361
|
+
options: [
|
|
2362
|
+
{
|
|
2363
|
+
value: AGENTS_MANAGE_AGENT_ACTION_CHOICE.showDetails,
|
|
2364
|
+
label: "View Setup"
|
|
2365
|
+
},
|
|
2366
|
+
...input.launchStatus === "needs_choice" ? [{
|
|
2367
|
+
value: AGENTS_MANAGE_AGENT_ACTION_CHOICE.chooseInstall,
|
|
2368
|
+
label: "Choose install on This Device",
|
|
2369
|
+
hint: "Pick which local install OpenMeld should use"
|
|
2370
|
+
}] : [],
|
|
2371
|
+
...input.launchStatus === "needs_repair" ? [{
|
|
2372
|
+
value: AGENTS_MANAGE_AGENT_ACTION_CHOICE.repair,
|
|
2373
|
+
label: "Repair on This Device",
|
|
2374
|
+
hint: "Refresh how OpenMeld starts it locally"
|
|
2375
|
+
}] : [],
|
|
2376
|
+
...input.launchStatus === "ready" ? [{
|
|
2377
|
+
value: AGENTS_MANAGE_AGENT_ACTION_CHOICE.chooseInstall,
|
|
2378
|
+
label: "Change install on This Device",
|
|
2379
|
+
hint: "Pick a different local install for future replies"
|
|
2380
|
+
}] : [],
|
|
2381
|
+
{
|
|
2382
|
+
value: AGENTS_MANAGE_AGENT_ACTION_CHOICE.toggleEnabled,
|
|
2383
|
+
label: input.enabled ? "Turn Off for Space @mentions" : "Turn On for Space @mentions",
|
|
2384
|
+
hint: input.enabled ? "Stop waking it for new @mentions" : "Allow @mentions to wake it again"
|
|
2385
|
+
},
|
|
2386
|
+
{
|
|
2387
|
+
value: AGENTS_MANAGE_AGENT_ACTION_CHOICE.back,
|
|
2388
|
+
label: "Back to Agents"
|
|
2389
|
+
}
|
|
2390
|
+
]
|
|
2391
|
+
});
|
|
2392
|
+
if (isCancel(action)) return { status: "cancelled" };
|
|
2393
|
+
return {
|
|
2394
|
+
status: "selected",
|
|
2395
|
+
value: action
|
|
2396
|
+
};
|
|
2397
|
+
}
|
|
2398
|
+
async function promptInteractiveCustomAgentsMenuChoice() {
|
|
2399
|
+
const choice = await select({
|
|
2400
|
+
message: "Custom Targets:",
|
|
2401
|
+
options: [
|
|
2402
|
+
{
|
|
2403
|
+
value: "add",
|
|
2404
|
+
label: "Add custom target",
|
|
2405
|
+
hint: "Create a new custom agent target"
|
|
2406
|
+
},
|
|
2407
|
+
{
|
|
2408
|
+
value: "edit",
|
|
2409
|
+
label: "Edit custom target",
|
|
2410
|
+
hint: "Update name or dir (id remains stable)"
|
|
2411
|
+
},
|
|
2412
|
+
{
|
|
2413
|
+
value: "remove",
|
|
2414
|
+
label: "Remove custom target",
|
|
2415
|
+
hint: "Blocked when target is currently registered"
|
|
2416
|
+
},
|
|
2417
|
+
{
|
|
2418
|
+
value: "list",
|
|
2419
|
+
label: "List custom targets",
|
|
2420
|
+
hint: "Show custom targets and current status"
|
|
2421
|
+
},
|
|
2422
|
+
{
|
|
2423
|
+
value: "back",
|
|
2424
|
+
label: "Back"
|
|
2425
|
+
}
|
|
2426
|
+
]
|
|
2427
|
+
});
|
|
2428
|
+
if (isCancel(choice)) return { status: "cancelled" };
|
|
2429
|
+
return {
|
|
2430
|
+
status: "selected",
|
|
2431
|
+
value: choice
|
|
2432
|
+
};
|
|
2433
|
+
}
|
|
2434
|
+
async function runInteractiveCustomAgentsAddFlow(input) {
|
|
2435
|
+
let namePlaceholder = "e.g. OpenMeld Agent Executor";
|
|
2436
|
+
let dirPlaceholder = "/absolute/path/to/skills";
|
|
2437
|
+
let idPlaceholder = "leave empty to auto-generate from dir";
|
|
2438
|
+
while (true) {
|
|
2439
|
+
const nameResult = await promptRequiredInteractiveText({
|
|
2440
|
+
message: "Custom target name",
|
|
2441
|
+
placeholder: namePlaceholder,
|
|
2442
|
+
requiredMessage: "Custom target name is required."
|
|
2443
|
+
});
|
|
2444
|
+
if (nameResult.status === "cancelled") return "cancelled";
|
|
2445
|
+
if (nameResult.status !== "selected") return "completed";
|
|
2446
|
+
namePlaceholder = nameResult.value;
|
|
2447
|
+
const addDetails = await promptInteractiveCustomAgentAddDetails({
|
|
2448
|
+
dirPlaceholder,
|
|
2449
|
+
idPlaceholder
|
|
2450
|
+
});
|
|
2451
|
+
if (addDetails.status === "cancelled") return "cancelled";
|
|
2452
|
+
if (addDetails.status !== "submitted") continue;
|
|
2453
|
+
dirPlaceholder = addDetails.value.dir;
|
|
2454
|
+
if (addDetails.value.idInput) idPlaceholder = addDetails.value.idInput;
|
|
2455
|
+
await runAgentsCustomAdd({
|
|
2456
|
+
name: nameResult.value,
|
|
2457
|
+
dir: addDetails.value.dir,
|
|
2458
|
+
...addDetails.value.id ? { id: addDetails.value.id } : {},
|
|
2459
|
+
profile: input.profile,
|
|
2460
|
+
view: input.view
|
|
2461
|
+
});
|
|
2462
|
+
return "completed";
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
async function runInteractiveCustomAgentsRemoveFlow(input) {
|
|
2466
|
+
const selectedTargetResult = await promptSelectCustomTarget({
|
|
2467
|
+
message: "Select custom target to remove",
|
|
2468
|
+
emptyMessage: "No custom targets available. Add a custom target with `openmeld agents custom add --name <name> --dir <dir>`."
|
|
2469
|
+
});
|
|
2470
|
+
if (selectedTargetResult.status === "cancelled") return "cancelled";
|
|
2471
|
+
if (selectedTargetResult.status !== "selected") return "completed";
|
|
2472
|
+
await runAgentsCustomRemove({
|
|
2473
|
+
id: selectedTargetResult.value.id,
|
|
2474
|
+
profile: input.profile,
|
|
2475
|
+
view: input.view
|
|
2476
|
+
});
|
|
2477
|
+
return "completed";
|
|
2478
|
+
}
|
|
2479
|
+
async function runInteractiveCustomAgentsEditFlow(input) {
|
|
2480
|
+
while (true) {
|
|
2481
|
+
const selectedTargetResult = await promptSelectCustomTarget({
|
|
2482
|
+
message: "Select custom target to edit",
|
|
2483
|
+
emptyMessage: "No custom targets available. Add a custom target with `openmeld agents custom add --name <name> --dir <dir>`."
|
|
2484
|
+
});
|
|
2485
|
+
if (selectedTargetResult.status === "cancelled") return "cancelled";
|
|
2486
|
+
if (selectedTargetResult.status !== "selected") return "completed";
|
|
2487
|
+
const updateRequest = await promptInteractiveCustomAgentUpdate(selectedTargetResult.value);
|
|
2488
|
+
if (updateRequest.status === "cancelled") return "cancelled";
|
|
2489
|
+
if (updateRequest.status !== "submitted") continue;
|
|
2490
|
+
await runAgentsCustomUpdate({
|
|
2491
|
+
id: updateRequest.value.id,
|
|
2492
|
+
..."name" in updateRequest.value ? { name: updateRequest.value.name } : { dir: updateRequest.value.dir },
|
|
2493
|
+
profile: input.profile,
|
|
2494
|
+
view: input.view
|
|
2495
|
+
});
|
|
2496
|
+
return "completed";
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
async function promptInteractiveCustomAgentAddDetails(input) {
|
|
2500
|
+
while (true) {
|
|
2501
|
+
const dirResult = await promptRequiredInteractiveText({
|
|
2502
|
+
message: "Custom target absolute dir",
|
|
2503
|
+
placeholder: input.dirPlaceholder,
|
|
2504
|
+
requiredMessage: "Custom target absolute dir is required.",
|
|
2505
|
+
validate: (value) => isAbsolute(value) ? null : "Custom target absolute dir must be an absolute path."
|
|
2506
|
+
});
|
|
2507
|
+
if (dirResult.status === "cancelled" || dirResult.status === "back") return dirResult;
|
|
2508
|
+
if (dirResult.status !== "selected") continue;
|
|
2509
|
+
const idResult = await promptInteractiveText({
|
|
2510
|
+
message: "Custom target id (optional)",
|
|
2511
|
+
placeholder: input.idPlaceholder,
|
|
2512
|
+
allowEmpty: true
|
|
2513
|
+
});
|
|
2514
|
+
if (idResult.status === "cancelled") return idResult;
|
|
2515
|
+
if (idResult.status === "back") {
|
|
2516
|
+
input.dirPlaceholder = dirResult.value;
|
|
2517
|
+
continue;
|
|
2518
|
+
}
|
|
2519
|
+
if (idResult.status !== "selected") continue;
|
|
2520
|
+
return {
|
|
2521
|
+
status: "submitted",
|
|
2522
|
+
value: {
|
|
2523
|
+
dir: dirResult.value,
|
|
2524
|
+
id: normalizeOptionalString(idResult.value) ?? void 0,
|
|
2525
|
+
idInput: normalizeOptionalString(idResult.value) ?? void 0
|
|
2526
|
+
}
|
|
2527
|
+
};
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
async function promptInteractiveCustomAgentUpdate(selectedTarget) {
|
|
2531
|
+
while (true) {
|
|
2532
|
+
const field = await promptInteractiveCustomAgentEditField();
|
|
2533
|
+
if (field.status === "cancelled" || field.status === "back") return field;
|
|
2534
|
+
if (field.status !== "selected") continue;
|
|
2535
|
+
const valueResult = await promptInteractiveCustomAgentEditValue({
|
|
2536
|
+
field: field.value,
|
|
2537
|
+
selectedTarget
|
|
2538
|
+
});
|
|
2539
|
+
if (valueResult.status === "cancelled") return valueResult;
|
|
2540
|
+
if (valueResult.status === "back") continue;
|
|
2541
|
+
if (valueResult.status !== "selected") continue;
|
|
2542
|
+
return {
|
|
2543
|
+
status: "submitted",
|
|
2544
|
+
value: field.value === "name" ? {
|
|
2545
|
+
id: selectedTarget.id,
|
|
2546
|
+
name: valueResult.value
|
|
2547
|
+
} : {
|
|
2548
|
+
id: selectedTarget.id,
|
|
2549
|
+
dir: valueResult.value
|
|
2550
|
+
}
|
|
2551
|
+
};
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
async function promptInteractiveCustomAgentEditField() {
|
|
2555
|
+
const field = await select({
|
|
2556
|
+
message: "Edit field",
|
|
2557
|
+
options: [
|
|
2558
|
+
{
|
|
2559
|
+
value: "name",
|
|
2560
|
+
label: "Name"
|
|
2561
|
+
},
|
|
2562
|
+
{
|
|
2563
|
+
value: "dir",
|
|
2564
|
+
label: "Dir"
|
|
2565
|
+
},
|
|
2566
|
+
{
|
|
2567
|
+
value: "back",
|
|
2568
|
+
label: "Back"
|
|
2569
|
+
}
|
|
2570
|
+
]
|
|
2571
|
+
});
|
|
2572
|
+
if (isCancel(field)) return { status: "cancelled" };
|
|
2573
|
+
if (field === "back") return { status: "back" };
|
|
2574
|
+
return {
|
|
2575
|
+
status: "selected",
|
|
2576
|
+
value: field
|
|
2577
|
+
};
|
|
2578
|
+
}
|
|
2579
|
+
function promptInteractiveCustomAgentEditValue(input) {
|
|
2580
|
+
return input.field === "name" ? promptRequiredInteractiveText({
|
|
2581
|
+
message: "New name",
|
|
2582
|
+
placeholder: input.selectedTarget.name,
|
|
2583
|
+
requiredMessage: "Custom target name is required."
|
|
2584
|
+
}) : promptRequiredInteractiveText({
|
|
2585
|
+
message: "New absolute dir",
|
|
2586
|
+
placeholder: input.selectedTarget.dir,
|
|
2587
|
+
requiredMessage: "Custom target absolute dir is required.",
|
|
2588
|
+
validate: (nextDir) => isAbsolute(nextDir) ? null : "Custom target absolute dir must be an absolute path."
|
|
2589
|
+
});
|
|
2590
|
+
}
|
|
2591
|
+
async function promptInteractiveText(input) {
|
|
2592
|
+
const value = await promptTextEntry({
|
|
2593
|
+
message: input.message,
|
|
2594
|
+
...input.placeholder ? { placeholder: input.placeholder } : {},
|
|
2595
|
+
allowBack: true,
|
|
2596
|
+
backLabel: "Back",
|
|
2597
|
+
allowEmpty: input.allowEmpty === true,
|
|
2598
|
+
requiredMessage: "Value is required."
|
|
2599
|
+
});
|
|
2600
|
+
if (value.status === "cancelled") return { status: "cancelled" };
|
|
2601
|
+
if (value.status === "back") return { status: "back" };
|
|
2602
|
+
return {
|
|
2603
|
+
status: "selected",
|
|
2604
|
+
value: value.value
|
|
2605
|
+
};
|
|
2606
|
+
}
|
|
2607
|
+
async function promptRequiredInteractiveText(input) {
|
|
2608
|
+
while (true) {
|
|
2609
|
+
const valueResult = await promptInteractiveText({
|
|
2610
|
+
message: input.message,
|
|
2611
|
+
...input.placeholder ? { placeholder: input.placeholder } : {}
|
|
2612
|
+
});
|
|
2613
|
+
if (valueResult.status === "cancelled" || valueResult.status === "back") return valueResult;
|
|
2614
|
+
if (valueResult.status !== "selected") continue;
|
|
2615
|
+
const normalizedValue = normalizeOptionalString(valueResult.value);
|
|
2616
|
+
if (!normalizedValue) {
|
|
2617
|
+
process.stderr.write(`${input.requiredMessage}\n`);
|
|
2618
|
+
continue;
|
|
2619
|
+
}
|
|
2620
|
+
const validationMessage = input.validate?.(normalizedValue) ?? null;
|
|
2621
|
+
if (validationMessage) {
|
|
2622
|
+
process.stderr.write(`${validationMessage}\n`);
|
|
2623
|
+
continue;
|
|
2624
|
+
}
|
|
2625
|
+
return {
|
|
2626
|
+
status: "selected",
|
|
2627
|
+
value: normalizedValue
|
|
2628
|
+
};
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
async function promptSelectCustomTarget(input) {
|
|
2632
|
+
const customTargets = await listAgentCustomTargets();
|
|
2633
|
+
if (customTargets.length === 0) {
|
|
2634
|
+
process.stderr.write(`${formatAgentsCopy(input.emptyMessage)}\n`);
|
|
2635
|
+
return { status: "empty" };
|
|
2636
|
+
}
|
|
2637
|
+
const choice = await promptSearchSelect({
|
|
2638
|
+
message: input.message,
|
|
2639
|
+
items: customTargets.map(buildCustomTargetSearchItem),
|
|
2640
|
+
maxVisible: 8,
|
|
2641
|
+
allowBack: true,
|
|
2642
|
+
backLabel: "Back"
|
|
2643
|
+
});
|
|
2644
|
+
if (choice.status === "cancelled") return { status: "cancelled" };
|
|
2645
|
+
if (choice.status === "back") return { status: "back" };
|
|
2646
|
+
const id = normalizeAgentId(choice.value);
|
|
2647
|
+
const selectedTarget = customTargets.find((target) => target.id === id);
|
|
2648
|
+
if (!selectedTarget) return { status: "empty" };
|
|
2649
|
+
return {
|
|
2650
|
+
status: "selected",
|
|
2651
|
+
value: selectedTarget
|
|
2652
|
+
};
|
|
2653
|
+
}
|
|
2654
|
+
async function promptEnableAgentIdsFromUnifiedBuiltins(input) {
|
|
2655
|
+
const selectableCandidates = input.candidates.filter((candidate) => canSelectAgentForNewLocalAgentSetup(candidate) && !candidate.enabled);
|
|
2656
|
+
if (selectableCandidates.length === 0) {
|
|
2657
|
+
process.stderr.write(`${formatAgentsCopy(NO_ACTIONABLE_AGENTS_ENABLE_MESSAGE)}\n`);
|
|
2658
|
+
return { status: "empty" };
|
|
2659
|
+
}
|
|
2660
|
+
emitInteractiveDescription(AGENTS_ENABLE_DESCRIPTION);
|
|
2661
|
+
const selected = await promptSearchMultiselect({
|
|
2662
|
+
message: "Choose agents to enable",
|
|
2663
|
+
items: [...selectableCandidates.map(buildUnifiedEnableSearchItem), {
|
|
2664
|
+
value: CUSTOM_AGENTS_COMING_SOON_OPTION_ID,
|
|
2665
|
+
label: "Custom agents (coming soon)",
|
|
2666
|
+
detail: formatInlineOpenMeldCliCommands("Use `openmeld agents custom` commands for custom targets for now."),
|
|
2667
|
+
searchText: "Custom agents (coming soon) Use openmeld agents custom commands for custom targets for now",
|
|
2668
|
+
disabled: true
|
|
2669
|
+
}],
|
|
2670
|
+
required: true,
|
|
2671
|
+
maxVisible: 8,
|
|
2672
|
+
allowBack: true,
|
|
2673
|
+
backLabel: "Back"
|
|
2674
|
+
});
|
|
2675
|
+
if (selected.status === "cancelled") return { status: "cancelled" };
|
|
2676
|
+
if (selected.status === "back") return { status: "back" };
|
|
2677
|
+
const agentIds = normalizeAgentIds(selected.value.sort()).filter((agentId) => agentId !== CUSTOM_AGENTS_COMING_SOON_OPTION_ID);
|
|
2678
|
+
if (agentIds.length === 0) return { status: "empty" };
|
|
2679
|
+
return {
|
|
2680
|
+
status: "selected",
|
|
2681
|
+
value: agentIds
|
|
2682
|
+
};
|
|
2683
|
+
}
|
|
2684
|
+
async function listRegisteredAgentStates() {
|
|
2685
|
+
return (await listAgentTargetStates()).filter((state) => Boolean(state.registration));
|
|
2686
|
+
}
|
|
2687
|
+
async function promptSelectRegisteredAgent(input) {
|
|
2688
|
+
const registeredStates = await listRegisteredAgentStates();
|
|
2689
|
+
if (registeredStates.length === 0) {
|
|
2690
|
+
process.stderr.write(`${formatAgentsCopy(input.emptyMessage)}\n`);
|
|
2691
|
+
return { status: "empty" };
|
|
2692
|
+
}
|
|
2693
|
+
emitInteractiveDescription(AGENTS_SELECT_REGISTERED_DESCRIPTION);
|
|
2694
|
+
const choice = await promptSearchSelect({
|
|
2695
|
+
message: input.message,
|
|
2696
|
+
items: registeredStates.map(buildRegisteredAgentSearchItem),
|
|
2697
|
+
maxVisible: 8,
|
|
2698
|
+
allowBack: true,
|
|
2699
|
+
backLabel: "Back"
|
|
2700
|
+
});
|
|
2701
|
+
if (choice.status === "cancelled") return { status: "cancelled" };
|
|
2702
|
+
if (choice.status === "back") return { status: "back" };
|
|
2703
|
+
const agentId = normalizeAgentId(choice.value);
|
|
2704
|
+
if (!agentId) return { status: "empty" };
|
|
2705
|
+
const selectedState = registeredStates.find((state) => state.agentId === agentId);
|
|
2706
|
+
if (!selectedState) return { status: "empty" };
|
|
2707
|
+
return {
|
|
2708
|
+
status: "selected",
|
|
2709
|
+
value: selectedState
|
|
2710
|
+
};
|
|
2711
|
+
}
|
|
2712
|
+
function buildCustomTargetSearchItem(target) {
|
|
2713
|
+
return {
|
|
2714
|
+
value: target.id,
|
|
2715
|
+
label: target.name,
|
|
2716
|
+
detail: buildMetadataDetailLine([`ID ${target.id}`, target.dir]),
|
|
2717
|
+
searchText: [
|
|
2718
|
+
target.name,
|
|
2719
|
+
target.id,
|
|
2720
|
+
target.dir
|
|
2721
|
+
].join(" ")
|
|
2722
|
+
};
|
|
2723
|
+
}
|
|
2724
|
+
function buildUnifiedEnableSearchItem(candidate) {
|
|
2725
|
+
const statusHint = buildUnifiedEnableTargetHint(candidate);
|
|
2726
|
+
return {
|
|
2727
|
+
value: candidate.agentId,
|
|
2728
|
+
label: candidate.displayName,
|
|
2729
|
+
detail: buildMetadataDetailLine([`ID ${candidate.agentId}`, statusHint]),
|
|
2730
|
+
searchText: [
|
|
2731
|
+
candidate.displayName,
|
|
2732
|
+
candidate.agentId,
|
|
2733
|
+
statusHint,
|
|
2734
|
+
candidate.selectableReason ?? ""
|
|
2735
|
+
].join(" "),
|
|
2736
|
+
disabled: !canSelectAgentForNewLocalAgentSetup(candidate) || candidate.enabled
|
|
2737
|
+
};
|
|
2738
|
+
}
|
|
2739
|
+
function buildRegisteredAgentSearchItem(state) {
|
|
2740
|
+
const enabledHint = buildRegisteredAgentHint(state);
|
|
2741
|
+
return {
|
|
2742
|
+
value: state.agentId,
|
|
2743
|
+
label: state.agentId,
|
|
2744
|
+
detail: buildMetadataDetailLine([state.displayName, enabledHint]),
|
|
2745
|
+
searchText: [
|
|
2746
|
+
state.displayName,
|
|
2747
|
+
state.agentId,
|
|
2748
|
+
enabledHint,
|
|
2749
|
+
"space wake mention reply"
|
|
2750
|
+
].join(" ")
|
|
2751
|
+
};
|
|
2752
|
+
}
|
|
2753
|
+
function buildMetadataDetailLine(parts) {
|
|
2754
|
+
return parts.map((part) => String(part ?? "").trim()).filter((part) => part.length > 0).join(" · ");
|
|
2755
|
+
}
|
|
2756
|
+
async function runAgentsDetect(input) {
|
|
2757
|
+
const runtime = await resolveRuntimeContext({
|
|
2758
|
+
view: input.view,
|
|
2759
|
+
profile: input.profile
|
|
2760
|
+
});
|
|
2761
|
+
const inventory = await collectLocalAgentControllerInventory();
|
|
2762
|
+
const detectedAgents = inventory.capabilities.filter((capability) => capability.detected && (capability.lifecycleStatus !== "suspended" || capability.enabled)).map((capability) => ({
|
|
2763
|
+
id: capability.agentId,
|
|
2764
|
+
blockerReasonCodes: capability.blockerReasonCodes,
|
|
2765
|
+
displayName: capability.displayName,
|
|
2766
|
+
launchability: capability.launchability,
|
|
2767
|
+
launchStatus: formatAgentViewLaunchStatus(capability.launchStatus),
|
|
2768
|
+
launchStatusDetail: capability.launchStatusDetail,
|
|
2769
|
+
lifecycleStatus: capability.lifecycleStatus,
|
|
2770
|
+
selectable: capability.selectable
|
|
2771
|
+
}));
|
|
2772
|
+
const payload = {
|
|
2773
|
+
detectedAgents,
|
|
2774
|
+
count: detectedAgents.length,
|
|
2775
|
+
inventorySource: "local_runtime_inventory",
|
|
2776
|
+
source: "builtin_detect"
|
|
2777
|
+
};
|
|
2778
|
+
if (input.json === true) {
|
|
2779
|
+
emitCliJsonEnvelope({
|
|
2780
|
+
schemaVersion: 1,
|
|
2781
|
+
type: "agents.detect",
|
|
2782
|
+
...payload
|
|
2783
|
+
});
|
|
2784
|
+
return;
|
|
2785
|
+
}
|
|
2786
|
+
const presenter = createPresenter(runtime);
|
|
2787
|
+
await emitAgentsConfigParseFallbackSignal({
|
|
2788
|
+
presenter,
|
|
2789
|
+
view: runtime.resolvedView
|
|
2790
|
+
});
|
|
2791
|
+
emitAgentsOverviewIfAgent({
|
|
2792
|
+
presenter,
|
|
2793
|
+
resolvedView: runtime.resolvedView,
|
|
2794
|
+
command: "detect"
|
|
2795
|
+
});
|
|
2796
|
+
if (runtime.resolvedView === "agent") {
|
|
2797
|
+
presenter.data({
|
|
2798
|
+
code: "agents.detect.result",
|
|
2799
|
+
payload
|
|
2800
|
+
});
|
|
2801
|
+
return;
|
|
2802
|
+
}
|
|
2803
|
+
const readyAgents = detectedAgents.filter((agent) => agent.lifecycleStatus !== "suspended" && agent.launchability === "launchable");
|
|
2804
|
+
const needsChoiceAgents = detectedAgents.filter((agent) => agent.lifecycleStatus !== "suspended" && agent.launchability === "needs_choice");
|
|
2805
|
+
const needsRepairAgents = detectedAgents.filter((agent) => agent.lifecycleStatus !== "suspended" && agent.launchability === "needs_repair");
|
|
2806
|
+
const comingSoonAgents = detectedAgents.filter((agent) => agent.lifecycleStatus !== "suspended" && agent.launchability === "not_supported" && !agent.selectable);
|
|
2807
|
+
const suspendedAgents = detectedAgents.filter((agent) => agent.lifecycleStatus === "suspended");
|
|
2808
|
+
const notDetectedAgents = inventory.capabilities.filter((capability) => !capability.detected);
|
|
2809
|
+
if (detectedAgents.length === 0) {
|
|
2810
|
+
renderAgentsInfoCard({
|
|
2811
|
+
presenter,
|
|
2812
|
+
codePrefix: "agents.detect.empty.card",
|
|
2813
|
+
title: "Agents Found Here",
|
|
2814
|
+
rows: [{
|
|
2815
|
+
label: "Found",
|
|
2816
|
+
value: pc.dim("No supported AI agents yet")
|
|
2817
|
+
}, {
|
|
2818
|
+
label: "Next",
|
|
2819
|
+
value: formatInlineOpenMeldCliCommands("Open or install the AI tool you want OpenMeld to use, then run `openmeld agents detect` again.")
|
|
2820
|
+
}],
|
|
2821
|
+
minContentWidth: 36
|
|
2822
|
+
});
|
|
2823
|
+
return;
|
|
2824
|
+
}
|
|
2825
|
+
renderAgentsInfoCard({
|
|
2826
|
+
presenter,
|
|
2827
|
+
codePrefix: "agents.detect.summary.card",
|
|
2828
|
+
title: "Agents Found Here",
|
|
2829
|
+
rows: [
|
|
2830
|
+
{
|
|
2831
|
+
label: "Found",
|
|
2832
|
+
value: buildDetectedAgentsSubtitle(detectedAgents.length)
|
|
2833
|
+
},
|
|
2834
|
+
{
|
|
2835
|
+
label: "Available now",
|
|
2836
|
+
value: buildReadyCountValue(readyAgents.length, "live for Space")
|
|
2837
|
+
},
|
|
2838
|
+
{
|
|
2839
|
+
label: "Needs choice",
|
|
2840
|
+
value: buildNotReadyCountValue(needsChoiceAgents.length, "found, needs your choice")
|
|
2841
|
+
},
|
|
2842
|
+
{
|
|
2843
|
+
label: "Needs repair",
|
|
2844
|
+
value: buildNotReadyCountValue(needsRepairAgents.length, "found, needs repair")
|
|
2845
|
+
},
|
|
2846
|
+
{
|
|
2847
|
+
label: "Coming soon",
|
|
2848
|
+
value: buildNotReadyCountValue(comingSoonAgents.length, "found, not supported yet")
|
|
2849
|
+
},
|
|
2850
|
+
{
|
|
2851
|
+
label: "Suspended",
|
|
2852
|
+
value: buildNotReadyCountValue(suspendedAgents.length, "already configured, not offered for new profiles")
|
|
2853
|
+
},
|
|
2854
|
+
{
|
|
2855
|
+
label: "Not found",
|
|
2856
|
+
value: buildNotReadyCountValue(notDetectedAgents.length, "not found on this computer")
|
|
2857
|
+
}
|
|
2858
|
+
],
|
|
2859
|
+
minContentWidth: 42
|
|
2860
|
+
});
|
|
2861
|
+
if (readyAgents.length > 0) {
|
|
2862
|
+
presenter.line({
|
|
2863
|
+
code: "agents.detect.ready.spacer",
|
|
2864
|
+
text: ""
|
|
2865
|
+
});
|
|
2866
|
+
renderAgentsInfoCard({
|
|
2867
|
+
presenter,
|
|
2868
|
+
codePrefix: "agents.detect.ready.card",
|
|
2869
|
+
title: `Available Now (${readyAgents.length})`,
|
|
2870
|
+
rows: readyAgents.map((agent) => buildReadyAgentCardRow(agent.id, agent.displayName)),
|
|
2871
|
+
minContentWidth: 30
|
|
2872
|
+
});
|
|
2873
|
+
}
|
|
2874
|
+
if (needsRepairAgents.length > 0) {
|
|
2875
|
+
presenter.line({
|
|
2876
|
+
code: "agents.detect.needs_repair.spacer",
|
|
2877
|
+
text: ""
|
|
2878
|
+
});
|
|
2879
|
+
renderAgentsInfoCard({
|
|
2880
|
+
presenter,
|
|
2881
|
+
codePrefix: "agents.detect.needs_repair.card",
|
|
2882
|
+
title: `Needs Repair (${needsRepairAgents.length})`,
|
|
2883
|
+
rows: needsRepairAgents.map((agent) => buildStatusAgentCardRow({
|
|
2884
|
+
agentId: agent.id,
|
|
2885
|
+
displayName: agent.displayName,
|
|
2886
|
+
detail: agent.launchStatusDetail ?? "Repair on this computer before OpenMeld can start it"
|
|
2887
|
+
})),
|
|
2888
|
+
minContentWidth: 36
|
|
2889
|
+
});
|
|
2890
|
+
}
|
|
2891
|
+
if (needsChoiceAgents.length > 0) {
|
|
2892
|
+
presenter.line({
|
|
2893
|
+
code: "agents.detect.needs_choice.spacer",
|
|
2894
|
+
text: ""
|
|
2895
|
+
});
|
|
2896
|
+
renderAgentsInfoCard({
|
|
2897
|
+
presenter,
|
|
2898
|
+
codePrefix: "agents.detect.needs_choice.card",
|
|
2899
|
+
title: `Needs Your Choice (${needsChoiceAgents.length})`,
|
|
2900
|
+
rows: needsChoiceAgents.map((agent) => buildStatusAgentCardRow({
|
|
2901
|
+
agentId: agent.id,
|
|
2902
|
+
displayName: agent.displayName,
|
|
2903
|
+
detail: "Choose which install OpenMeld should use on this Mac"
|
|
2904
|
+
})),
|
|
2905
|
+
minContentWidth: 36
|
|
2906
|
+
});
|
|
2907
|
+
}
|
|
2908
|
+
if (comingSoonAgents.length > 0) {
|
|
2909
|
+
presenter.line({
|
|
2910
|
+
code: "agents.detect.coming_soon.spacer",
|
|
2911
|
+
text: ""
|
|
2912
|
+
});
|
|
2913
|
+
renderAgentsInfoCard({
|
|
2914
|
+
presenter,
|
|
2915
|
+
codePrefix: "agents.detect.coming_soon.card",
|
|
2916
|
+
title: `Coming Soon (${comingSoonAgents.length})`,
|
|
2917
|
+
rows: comingSoonAgents.map((agent) => buildStatusAgentCardRow({
|
|
2918
|
+
agentId: agent.id,
|
|
2919
|
+
displayName: agent.displayName,
|
|
2920
|
+
detail: "OpenMeld found it, but builtin runtime support is not available yet"
|
|
2921
|
+
})),
|
|
2922
|
+
minContentWidth: 36
|
|
2923
|
+
});
|
|
2924
|
+
}
|
|
2925
|
+
if (suspendedAgents.length > 0) {
|
|
2926
|
+
presenter.line({
|
|
2927
|
+
code: "agents.detect.suspended.spacer",
|
|
2928
|
+
text: ""
|
|
2929
|
+
});
|
|
2930
|
+
renderAgentsInfoCard({
|
|
2931
|
+
presenter,
|
|
2932
|
+
codePrefix: "agents.detect.suspended.card",
|
|
2933
|
+
title: `Suspended (${suspendedAgents.length})`,
|
|
2934
|
+
rows: suspendedAgents.map((agent) => buildStatusAgentCardRow({
|
|
2935
|
+
agentId: agent.id,
|
|
2936
|
+
displayName: agent.displayName,
|
|
2937
|
+
detail: "Already configured here, but OpenMeld no longer offers it for new profiles"
|
|
2938
|
+
})),
|
|
2939
|
+
minContentWidth: 46
|
|
2940
|
+
});
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
async function runAgentsList(input) {
|
|
2944
|
+
const runtime = await resolveRuntimeContext({
|
|
2945
|
+
view: input.view,
|
|
2946
|
+
profile: input.profile
|
|
2947
|
+
});
|
|
2948
|
+
const states = await listAgentTargetStates();
|
|
2949
|
+
const registrations = states.filter(hasAgentRegistration).map((state) => buildAgentRegistrationAgentView(state)).sort((left, right) => compareAgentControllerRefsForDisplay(left.agentId, right.agentId));
|
|
2950
|
+
const payload = {
|
|
2951
|
+
registrations,
|
|
2952
|
+
count: registrations.length
|
|
2953
|
+
};
|
|
2954
|
+
if (input.json === true) {
|
|
2955
|
+
emitCliJsonEnvelope({
|
|
2956
|
+
schemaVersion: 1,
|
|
2957
|
+
type: "agents.list",
|
|
2958
|
+
...payload
|
|
2959
|
+
});
|
|
2960
|
+
return;
|
|
2961
|
+
}
|
|
2962
|
+
const presenter = createPresenter(runtime);
|
|
2963
|
+
await emitAgentsConfigParseFallbackSignal({
|
|
2964
|
+
presenter,
|
|
2965
|
+
view: runtime.resolvedView
|
|
2966
|
+
});
|
|
2967
|
+
emitAgentsOverviewIfAgent({
|
|
2968
|
+
presenter,
|
|
2969
|
+
resolvedView: runtime.resolvedView,
|
|
2970
|
+
command: "list"
|
|
2971
|
+
});
|
|
2972
|
+
if (runtime.resolvedView === "agent") {
|
|
2973
|
+
presenter.data({
|
|
2974
|
+
code: "agents.list.result",
|
|
2975
|
+
payload
|
|
2976
|
+
});
|
|
2977
|
+
return;
|
|
2978
|
+
}
|
|
2979
|
+
const enabledStates = states.filter((state) => state.registration?.enabled);
|
|
2980
|
+
const readyEnabledStates = enabledStates.filter((state) => state.launchStatus === "ready");
|
|
2981
|
+
const needsChoiceEnabledStates = enabledStates.filter((state) => state.launchStatus === "needs_choice");
|
|
2982
|
+
const needsRepairEnabledStates = enabledStates.filter((state) => state.launchStatus === "needs_repair");
|
|
2983
|
+
if (readyEnabledStates.length === 0 && needsChoiceEnabledStates.length === 0 && needsRepairEnabledStates.length === 0) {
|
|
2984
|
+
renderAgentsInfoCard({
|
|
2985
|
+
presenter,
|
|
2986
|
+
codePrefix: "agents.list.empty.card",
|
|
2987
|
+
title: "Agents Available for Space",
|
|
2988
|
+
rows: [{
|
|
2989
|
+
label: "Live now",
|
|
2990
|
+
value: pc.dim("No agents are live yet")
|
|
2991
|
+
}, {
|
|
2992
|
+
label: "Next",
|
|
2993
|
+
value: formatInlineOpenMeldCliCommands("Run `openmeld agents detect`, then enable the agents you want OpenMeld to use.")
|
|
2994
|
+
}],
|
|
2995
|
+
minContentWidth: 38
|
|
2996
|
+
});
|
|
2997
|
+
return;
|
|
2998
|
+
}
|
|
2999
|
+
renderAgentsInfoCard({
|
|
3000
|
+
presenter,
|
|
3001
|
+
codePrefix: "agents.list.summary.card",
|
|
3002
|
+
title: "Agents Available for Space",
|
|
3003
|
+
rows: [
|
|
3004
|
+
{
|
|
3005
|
+
label: "Live now",
|
|
3006
|
+
value: buildEnabledAgentsSubtitle(readyEnabledStates.length)
|
|
3007
|
+
},
|
|
3008
|
+
{
|
|
3009
|
+
label: "Needs choice",
|
|
3010
|
+
value: String(needsChoiceEnabledStates.length)
|
|
3011
|
+
},
|
|
3012
|
+
{
|
|
3013
|
+
label: "Needs repair",
|
|
3014
|
+
value: String(needsRepairEnabledStates.length)
|
|
3015
|
+
},
|
|
3016
|
+
{
|
|
3017
|
+
label: "Mention as",
|
|
3018
|
+
value: `${formatAgentMention("agent-id")} ${pc.dim("in Space")}`
|
|
3019
|
+
}
|
|
3020
|
+
],
|
|
3021
|
+
minContentWidth: 38
|
|
3022
|
+
});
|
|
3023
|
+
presenter.line({
|
|
3024
|
+
code: "agents.list.ready.spacer",
|
|
3025
|
+
text: ""
|
|
3026
|
+
});
|
|
3027
|
+
renderAgentsInfoCard({
|
|
3028
|
+
presenter,
|
|
3029
|
+
codePrefix: "agents.list.ready.card",
|
|
3030
|
+
title: `Available Agents (${readyEnabledStates.length})`,
|
|
3031
|
+
rows: readyEnabledStates.map((state) => buildReadyAgentCardRow(state.agentId, state.displayName)),
|
|
3032
|
+
minContentWidth: 30
|
|
3033
|
+
});
|
|
3034
|
+
if (needsRepairEnabledStates.length > 0) {
|
|
3035
|
+
presenter.line({
|
|
3036
|
+
code: "agents.list.needs_repair.spacer",
|
|
3037
|
+
text: ""
|
|
3038
|
+
});
|
|
3039
|
+
renderAgentsInfoCard({
|
|
3040
|
+
presenter,
|
|
3041
|
+
codePrefix: "agents.list.needs_repair.card",
|
|
3042
|
+
title: `Needs Repair (${needsRepairEnabledStates.length})`,
|
|
3043
|
+
rows: needsRepairEnabledStates.map((state) => buildStatusAgentCardRow({
|
|
3044
|
+
agentId: state.agentId,
|
|
3045
|
+
displayName: state.displayName,
|
|
3046
|
+
detail: "Connected in OpenMeld, but OpenMeld can't start it on this computer yet"
|
|
3047
|
+
})),
|
|
3048
|
+
minContentWidth: 36
|
|
3049
|
+
});
|
|
3050
|
+
}
|
|
3051
|
+
if (needsChoiceEnabledStates.length > 0) {
|
|
3052
|
+
presenter.line({
|
|
3053
|
+
code: "agents.list.needs_choice.spacer",
|
|
3054
|
+
text: ""
|
|
3055
|
+
});
|
|
3056
|
+
renderAgentsInfoCard({
|
|
3057
|
+
presenter,
|
|
3058
|
+
codePrefix: "agents.list.needs_choice.card",
|
|
3059
|
+
title: `Needs Your Choice (${needsChoiceEnabledStates.length})`,
|
|
3060
|
+
rows: needsChoiceEnabledStates.map((state) => buildStatusAgentCardRow({
|
|
3061
|
+
agentId: state.agentId,
|
|
3062
|
+
displayName: state.displayName,
|
|
3063
|
+
detail: "Connected in OpenMeld, but OpenMeld still needs one install choice on this Mac"
|
|
3064
|
+
})),
|
|
3065
|
+
minContentWidth: 36
|
|
3066
|
+
});
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
async function runAgentsShow(input) {
|
|
3070
|
+
const runtime = await resolveRuntimeContext({
|
|
3071
|
+
view: input.view,
|
|
3072
|
+
profile: input.profile
|
|
3073
|
+
});
|
|
3074
|
+
const presenter = createPresenter(runtime);
|
|
3075
|
+
await emitAgentsConfigParseFallbackSignal({
|
|
3076
|
+
presenter,
|
|
3077
|
+
view: runtime.resolvedView
|
|
3078
|
+
});
|
|
3079
|
+
emitAgentsOverviewIfAgent({
|
|
3080
|
+
presenter,
|
|
3081
|
+
resolvedView: runtime.resolvedView,
|
|
3082
|
+
command: "show"
|
|
3083
|
+
});
|
|
3084
|
+
const agentId = normalizeAgentId(input.agentId ?? "");
|
|
3085
|
+
if (!agentId) throw new Error("agents.invalid_agent_id: provide a non-empty <agent-id> for show");
|
|
3086
|
+
const state = await resolveAgentTargetState(agentId);
|
|
3087
|
+
if (!hasAgentRegistration(state)) throw new Error(`agents.not_registered: agent is not registered: ${agentId}`);
|
|
3088
|
+
const registration = state.registration;
|
|
3089
|
+
let agentControllerInterface = null;
|
|
3090
|
+
if (state.targetKind === "builtin" && state.enabled) {
|
|
3091
|
+
const builtinState = state;
|
|
3092
|
+
agentControllerInterface = await resolveAgentControllerInterfaceSnapshot({
|
|
3093
|
+
agentControllerRef: buildAgentControllerRef({
|
|
3094
|
+
id: builtinState.agentId,
|
|
3095
|
+
kind: "builtin"
|
|
3096
|
+
}),
|
|
3097
|
+
state: builtinState
|
|
3098
|
+
});
|
|
3099
|
+
}
|
|
3100
|
+
const payload = {
|
|
3101
|
+
agent: buildAgentRegistrationAgentView(state),
|
|
3102
|
+
agentControllerInterface: input.json === true || runtime.resolvedView === "agent" ? projectLegacyAgentControllerInterface(agentControllerInterface) : agentControllerInterface,
|
|
3103
|
+
installChoices: state.installChoices,
|
|
3104
|
+
launchStatus: formatAgentViewLaunchStatus(state.launchStatus),
|
|
3105
|
+
launchStatusDetail: state.launchStatusDetail,
|
|
3106
|
+
effectiveConfig: buildEffectiveConfig(state)
|
|
3107
|
+
};
|
|
3108
|
+
if (input.json === true) {
|
|
3109
|
+
emitCliJsonEnvelope({
|
|
3110
|
+
schemaVersion: 1,
|
|
3111
|
+
type: "agents.show",
|
|
3112
|
+
...payload
|
|
3113
|
+
});
|
|
3114
|
+
return;
|
|
3115
|
+
}
|
|
3116
|
+
if (runtime.resolvedView === "agent") {
|
|
3117
|
+
presenter.data({
|
|
3118
|
+
code: "agents.show.result",
|
|
3119
|
+
payload
|
|
3120
|
+
});
|
|
3121
|
+
return;
|
|
3122
|
+
}
|
|
3123
|
+
renderAgentsInfoCard({
|
|
3124
|
+
presenter,
|
|
3125
|
+
codePrefix: "agents.show.card",
|
|
3126
|
+
title: "Agent in OpenMeld",
|
|
3127
|
+
rows: [
|
|
3128
|
+
{
|
|
3129
|
+
label: "Agent",
|
|
3130
|
+
value: formatAgentMention(registration.agentId)
|
|
3131
|
+
},
|
|
3132
|
+
{
|
|
3133
|
+
label: "App",
|
|
3134
|
+
value: registration.displayName
|
|
3135
|
+
},
|
|
3136
|
+
{
|
|
3137
|
+
label: "Status",
|
|
3138
|
+
value: buildAgentsShowStatusValue({
|
|
3139
|
+
enabled: registration.enabled,
|
|
3140
|
+
launchStatus: state.launchStatus
|
|
3141
|
+
})
|
|
3142
|
+
},
|
|
3143
|
+
{
|
|
3144
|
+
label: "Wake",
|
|
3145
|
+
value: buildAgentsShowWakeValue({
|
|
3146
|
+
agentId: registration.agentId,
|
|
3147
|
+
enabled: registration.enabled,
|
|
3148
|
+
launchStatus: state.launchStatus
|
|
3149
|
+
})
|
|
3150
|
+
},
|
|
3151
|
+
{
|
|
3152
|
+
label: "Connection Type",
|
|
3153
|
+
value: `${resolveTransportModeForDisplay(state)} ${pc.dim("on this computer")}`
|
|
3154
|
+
},
|
|
3155
|
+
...buildAgentControllerInterfaceRows(agentControllerInterface),
|
|
3156
|
+
...state.launchStatusDetail ? [{
|
|
3157
|
+
label: "Why",
|
|
3158
|
+
value: state.launchStatusDetail
|
|
3159
|
+
}] : [],
|
|
3160
|
+
...state.launchStatus === "needs_choice" ? [{
|
|
3161
|
+
label: "Next",
|
|
3162
|
+
value: formatInlineOpenMeldCliCommands(`Run \`openmeld agents repair --agent ${registration.agentId}\` and choose one install`)
|
|
3163
|
+
}] : [],
|
|
3164
|
+
...state.launchStatus === "needs_repair" ? [{
|
|
3165
|
+
label: "Next",
|
|
3166
|
+
value: formatInlineOpenMeldCliCommands(`Run \`openmeld agents repair --agent ${registration.agentId}\``)
|
|
3167
|
+
}] : []
|
|
3168
|
+
],
|
|
3169
|
+
minContentWidth: 34
|
|
3170
|
+
});
|
|
3171
|
+
}
|
|
3172
|
+
function projectLegacyAgentControllerInterface(snapshot) {
|
|
3173
|
+
if (!snapshot?.usageLimits) return snapshot;
|
|
3174
|
+
const { usageLimits: _usageLimits, ...legacySnapshot } = snapshot;
|
|
3175
|
+
return legacySnapshot;
|
|
3176
|
+
}
|
|
3177
|
+
function createSilentAgentsPresenter(runtime) {
|
|
3178
|
+
return {
|
|
3179
|
+
view: runtime.resolvedView,
|
|
3180
|
+
event: () => void 0,
|
|
3181
|
+
line: () => void 0,
|
|
3182
|
+
data: () => void 0
|
|
3183
|
+
};
|
|
3184
|
+
}
|
|
3185
|
+
async function runAgentsEnable(input) {
|
|
3186
|
+
const runtime = await resolveRuntimeContext({
|
|
3187
|
+
interactive: input.interactive,
|
|
3188
|
+
view: input.view,
|
|
3189
|
+
profile: input.profile
|
|
3190
|
+
});
|
|
3191
|
+
const presenter = input.json === true ? createSilentAgentsPresenter(runtime) : createPresenter(runtime);
|
|
3192
|
+
await emitAgentsConfigParseFallbackSignal({
|
|
3193
|
+
presenter,
|
|
3194
|
+
view: runtime.resolvedView
|
|
3195
|
+
});
|
|
3196
|
+
emitAgentsOverviewIfAgent({
|
|
3197
|
+
presenter,
|
|
3198
|
+
resolvedView: runtime.resolvedView,
|
|
3199
|
+
command: "enable",
|
|
3200
|
+
handled: input.presentation === "handoff"
|
|
3201
|
+
});
|
|
3202
|
+
const explicitIds = normalizeAgentIds(input.agent ?? []);
|
|
3203
|
+
const explicitInstallId = normalizeOptionalString(input.install);
|
|
3204
|
+
validateBuiltinAgentSelectionInput({
|
|
3205
|
+
all: input.all,
|
|
3206
|
+
command: "enable",
|
|
3207
|
+
explicitIds,
|
|
3208
|
+
explicitInstallId
|
|
3209
|
+
});
|
|
3210
|
+
const targetStates = await listAgentTargetStates();
|
|
3211
|
+
const targetStateById = new Map(targetStates.map((state) => [state.agentId, state]));
|
|
3212
|
+
const selectableBuiltinIds = new Set(targetStates.filter(canSelectAgentForNewLocalAgentSetup).map((state) => state.agentId));
|
|
3213
|
+
const requested = input.all ? [...selectableBuiltinIds].sort((left, right) => left.localeCompare(right)) : explicitIds;
|
|
3214
|
+
const summary = {
|
|
3215
|
+
requested,
|
|
3216
|
+
registered: [],
|
|
3217
|
+
reenabled: [],
|
|
3218
|
+
skipped: [],
|
|
3219
|
+
errors: [],
|
|
3220
|
+
allMode: Boolean(input.all)
|
|
3221
|
+
};
|
|
3222
|
+
const launchSelections = await collectEnableLaunchActionSelections({
|
|
3223
|
+
explicitInstallId,
|
|
3224
|
+
interactiveChoiceAllowed: input.json === true ? false : resolveInteractiveChoiceAllowed({
|
|
3225
|
+
all: input.all,
|
|
3226
|
+
runtime
|
|
3227
|
+
}),
|
|
3228
|
+
requested,
|
|
3229
|
+
summary,
|
|
3230
|
+
targetStateById
|
|
3231
|
+
});
|
|
3232
|
+
if (launchSelections.status === "cancelled") {
|
|
3233
|
+
cancel("Cancelled.");
|
|
3234
|
+
return {
|
|
3235
|
+
status: "cancelled",
|
|
3236
|
+
summary
|
|
3237
|
+
};
|
|
3238
|
+
}
|
|
3239
|
+
if (launchSelections.status === "back") return {
|
|
3240
|
+
status: "back",
|
|
3241
|
+
summary
|
|
3242
|
+
};
|
|
3243
|
+
await updateAgentRegistrations((current) => applyEnableSelectionUpdates({
|
|
3244
|
+
current,
|
|
3245
|
+
requested: requested.filter((agentId) => !launchSelections.blockedAgentIds.has(agentId)),
|
|
3246
|
+
selectedLaunchContracts: launchSelections.selectedLaunchContracts,
|
|
3247
|
+
selectableBuiltinIds,
|
|
3248
|
+
summary,
|
|
3249
|
+
targetStateById
|
|
3250
|
+
}));
|
|
3251
|
+
if (input.json === true) {
|
|
3252
|
+
emitCliJsonEnvelope({
|
|
3253
|
+
schemaVersion: 1,
|
|
3254
|
+
type: "agents.enable",
|
|
3255
|
+
...summary
|
|
3256
|
+
});
|
|
3257
|
+
return {
|
|
3258
|
+
status: "completed",
|
|
3259
|
+
summary
|
|
3260
|
+
};
|
|
3261
|
+
}
|
|
3262
|
+
if (runtime.resolvedView === "agent") {
|
|
3263
|
+
presenter.data({
|
|
3264
|
+
code: "agents.enable.result",
|
|
3265
|
+
payload: summary
|
|
3266
|
+
});
|
|
3267
|
+
return {
|
|
3268
|
+
status: "completed",
|
|
3269
|
+
summary
|
|
3270
|
+
};
|
|
3271
|
+
}
|
|
3272
|
+
if (input.presentation !== "handoff") emitEnableSummaryCardsForHuman({
|
|
3273
|
+
presenter,
|
|
3274
|
+
summary,
|
|
3275
|
+
targetStateById
|
|
3276
|
+
});
|
|
3277
|
+
return {
|
|
3278
|
+
status: "completed",
|
|
3279
|
+
summary
|
|
3280
|
+
};
|
|
3281
|
+
}
|
|
3282
|
+
async function runAgentsRepair(input) {
|
|
3283
|
+
const runtime = await resolveRuntimeContext({
|
|
3284
|
+
interactive: input.interactive,
|
|
3285
|
+
view: input.view,
|
|
3286
|
+
profile: input.profile
|
|
3287
|
+
});
|
|
3288
|
+
const presenter = createPresenter(runtime);
|
|
3289
|
+
await emitAgentsConfigParseFallbackSignal({
|
|
3290
|
+
presenter,
|
|
3291
|
+
view: runtime.resolvedView
|
|
3292
|
+
});
|
|
3293
|
+
emitAgentsOverviewIfAgent({
|
|
3294
|
+
presenter,
|
|
3295
|
+
resolvedView: runtime.resolvedView,
|
|
3296
|
+
command: "repair",
|
|
3297
|
+
handled: input.presentation === "handoff"
|
|
3298
|
+
});
|
|
3299
|
+
const explicitIds = normalizeAgentIds(input.agent ?? []);
|
|
3300
|
+
const explicitInstallId = normalizeOptionalString(input.install);
|
|
3301
|
+
validateBuiltinAgentSelectionInput({
|
|
3302
|
+
all: input.all,
|
|
3303
|
+
command: "repair",
|
|
3304
|
+
explicitIds,
|
|
3305
|
+
explicitInstallId
|
|
3306
|
+
});
|
|
3307
|
+
const targetStates = await listAgentTargetStates();
|
|
3308
|
+
const targetStateById = new Map(targetStates.map((state) => [state.agentId, state]));
|
|
3309
|
+
const requested = input.all ? targetStates.filter((state) => state.targetKind === "builtin" && state.registered && (state.launchStatus === "needs_choice" || state.launchStatus === "needs_repair")).map((state) => state.agentId) : explicitIds;
|
|
3310
|
+
const summary = {
|
|
3311
|
+
requested,
|
|
3312
|
+
repaired: [],
|
|
3313
|
+
skipped: [],
|
|
3314
|
+
errors: [],
|
|
3315
|
+
allMode: Boolean(input.all)
|
|
3316
|
+
};
|
|
3317
|
+
const repairedLaunchContracts = /* @__PURE__ */ new Map();
|
|
3318
|
+
const repairOutcome = await collectRepairLaunchActions({
|
|
3319
|
+
allowReadyForInstallChange: input.changeInstall === true || explicitInstallId !== null,
|
|
3320
|
+
explicitInstallId,
|
|
3321
|
+
interactiveChoiceAllowed: resolveInteractiveChoiceAllowed({
|
|
3322
|
+
all: input.all,
|
|
3323
|
+
runtime
|
|
3324
|
+
}),
|
|
3325
|
+
repairedLaunchContracts,
|
|
3326
|
+
requested,
|
|
3327
|
+
summary,
|
|
3328
|
+
targetStateById
|
|
3329
|
+
});
|
|
3330
|
+
if (repairOutcome === "cancelled") {
|
|
3331
|
+
cancel("Cancelled.");
|
|
3332
|
+
return {
|
|
3333
|
+
status: "cancelled",
|
|
3334
|
+
summary
|
|
3335
|
+
};
|
|
3336
|
+
}
|
|
3337
|
+
if (repairOutcome === "back") return {
|
|
3338
|
+
status: "back",
|
|
3339
|
+
summary
|
|
3340
|
+
};
|
|
3341
|
+
if (repairedLaunchContracts.size > 0) await updateAgentRegistrations((current) => {
|
|
3342
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
3343
|
+
return sortAgentRegistrations(current.map((registration) => {
|
|
3344
|
+
const launchContract = repairedLaunchContracts.get(registration.agentId);
|
|
3345
|
+
if (!launchContract) return registration;
|
|
3346
|
+
return {
|
|
3347
|
+
...registration,
|
|
3348
|
+
launchContract,
|
|
3349
|
+
updatedAt: nowIso
|
|
3350
|
+
};
|
|
3351
|
+
}));
|
|
3352
|
+
});
|
|
3353
|
+
if (runtime.resolvedView === "agent") {
|
|
3354
|
+
presenter.data({
|
|
3355
|
+
code: "agents.repair.result",
|
|
3356
|
+
payload: summary
|
|
3357
|
+
});
|
|
3358
|
+
return {
|
|
3359
|
+
status: "completed",
|
|
3360
|
+
summary
|
|
3361
|
+
};
|
|
3362
|
+
}
|
|
3363
|
+
if (input.presentation !== "handoff") {
|
|
3364
|
+
renderAgentsInfoCard({
|
|
3365
|
+
presenter,
|
|
3366
|
+
codePrefix: "agents.repair.summary.card",
|
|
3367
|
+
title: "Agents Repaired",
|
|
3368
|
+
rows: buildRepairSummaryRows(summary),
|
|
3369
|
+
minContentWidth: 38
|
|
3370
|
+
});
|
|
3371
|
+
for (const card of buildRepairDetailCards(summary, targetStateById)) {
|
|
3372
|
+
presenter.line({
|
|
3373
|
+
code: `agents.repair.${card.codeSuffix}.spacer`,
|
|
3374
|
+
text: ""
|
|
3375
|
+
});
|
|
3376
|
+
renderAgentsInfoCard({
|
|
3377
|
+
presenter,
|
|
3378
|
+
codePrefix: `agents.repair.${card.codeSuffix}.card`,
|
|
3379
|
+
title: card.title,
|
|
3380
|
+
rows: card.rows,
|
|
3381
|
+
minContentWidth: 32
|
|
3382
|
+
});
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
return {
|
|
3386
|
+
status: "completed",
|
|
3387
|
+
summary
|
|
3388
|
+
};
|
|
3389
|
+
}
|
|
3390
|
+
function buildAgentsShowStatusValue(input) {
|
|
3391
|
+
if (input.launchStatus === "needs_choice") return formatAgentsToneText("warning", "Needs your choice on this Mac");
|
|
3392
|
+
if (input.launchStatus === "needs_repair") return formatAgentsToneText("warning", "Needs repair on this computer");
|
|
3393
|
+
if (input.enabled) return formatAgentsToneText("success", "Available in Space");
|
|
3394
|
+
return formatAgentsToneText("warning", "Turned off in Space");
|
|
3395
|
+
}
|
|
3396
|
+
function buildAgentsShowWakeValue(input) {
|
|
3397
|
+
const mention = formatAgentMention(input.agentId);
|
|
3398
|
+
if (input.launchStatus === "needs_choice") return `New ${mention} mentions will not wake it until you choose one install on this Mac`;
|
|
3399
|
+
if (input.launchStatus === "needs_repair") return `New ${mention} mentions will not wake it until you repair it`;
|
|
3400
|
+
if (input.enabled) return `${mention} can wake it`;
|
|
3401
|
+
return `New ${mention} mentions stay off`;
|
|
3402
|
+
}
|
|
3403
|
+
function buildAgentControllerInterfaceRows(snapshot) {
|
|
3404
|
+
if (!snapshot) return [];
|
|
3405
|
+
return createAgentControllerInterfaceDisplayRows(snapshot);
|
|
3406
|
+
}
|
|
3407
|
+
async function promptBuiltinAgentInstallChoice(input) {
|
|
3408
|
+
const choice = await select({
|
|
3409
|
+
message: buildInteractiveMenuMessage({
|
|
3410
|
+
title: `Choose the ${input.displayName} install OpenMeld should use`,
|
|
3411
|
+
description: `We found more than one ${input.displayName} install on this Mac. Pick the one OpenMeld should launch for replies.`
|
|
3412
|
+
}),
|
|
3413
|
+
options: [...input.installChoices.map((installChoice) => ({
|
|
3414
|
+
value: installChoice.installId,
|
|
3415
|
+
label: installChoice.label,
|
|
3416
|
+
hint: installChoice.detail
|
|
3417
|
+
})), {
|
|
3418
|
+
value: "__back__",
|
|
3419
|
+
label: "Back"
|
|
3420
|
+
}]
|
|
3421
|
+
});
|
|
3422
|
+
if (isCancel(choice)) return { status: "cancelled" };
|
|
3423
|
+
if (choice === "__back__") return { status: "back" };
|
|
3424
|
+
return {
|
|
3425
|
+
status: "selected",
|
|
3426
|
+
value: String(choice)
|
|
3427
|
+
};
|
|
3428
|
+
}
|
|
3429
|
+
function buildInstallChoiceNextSteps(input) {
|
|
3430
|
+
return input.installChoices.map((installChoice) => buildBuiltinAgentSelectionCommand({
|
|
3431
|
+
agentIds: [input.agentId],
|
|
3432
|
+
command: input.command,
|
|
3433
|
+
installId: installChoice.installId
|
|
3434
|
+
}));
|
|
3435
|
+
}
|
|
3436
|
+
function createInstallChoiceError(input) {
|
|
3437
|
+
return {
|
|
3438
|
+
agentId: input.agentId,
|
|
3439
|
+
code: "controller.launch.choice_required",
|
|
3440
|
+
humanActionRequired: true,
|
|
3441
|
+
installChoices: input.installChoices,
|
|
3442
|
+
nextSteps: buildInstallChoiceNextSteps({
|
|
3443
|
+
agentId: input.agentId,
|
|
3444
|
+
command: input.command,
|
|
3445
|
+
installChoices: input.installChoices
|
|
3446
|
+
}),
|
|
3447
|
+
reason: input.reason,
|
|
3448
|
+
summary: input.summary ?? "OpenMeld needs one install choice on this Mac before it can continue."
|
|
3449
|
+
};
|
|
3450
|
+
}
|
|
3451
|
+
async function resolveBuiltinLaunchAction(input) {
|
|
3452
|
+
if (!(input.command === "repair" || Boolean(input.explicitInstallId) || input.state.launchStatus === "needs_choice" || input.state.launchStatus === "needs_repair")) return { status: "not_needed" };
|
|
3453
|
+
let repairResult = await repairBuiltinControllerInstall({
|
|
3454
|
+
agentId: input.state.agentId,
|
|
3455
|
+
detected: input.state.detected,
|
|
3456
|
+
displayName: input.state.displayName,
|
|
3457
|
+
installId: input.explicitInstallId,
|
|
3458
|
+
runtimeSupported: input.state.runtimeSupported
|
|
3459
|
+
});
|
|
3460
|
+
if (!repairResult.ok && repairResult.errorCode === "controller.launch.choice_required" && input.interactiveChoiceAllowed) {
|
|
3461
|
+
const installChoice = await promptBuiltinAgentInstallChoice({
|
|
3462
|
+
displayName: input.state.displayName,
|
|
3463
|
+
installChoices: repairResult.installChoices
|
|
3464
|
+
});
|
|
3465
|
+
if (installChoice.status === "cancelled") return { status: "cancelled" };
|
|
3466
|
+
if (installChoice.status === "back" || installChoice.status === "empty") return { status: "back" };
|
|
3467
|
+
repairResult = await repairBuiltinControllerInstall({
|
|
3468
|
+
agentId: input.state.agentId,
|
|
3469
|
+
detected: input.state.detected,
|
|
3470
|
+
displayName: input.state.displayName,
|
|
3471
|
+
installId: installChoice.value,
|
|
3472
|
+
runtimeSupported: input.state.runtimeSupported
|
|
3473
|
+
});
|
|
3474
|
+
}
|
|
3475
|
+
if (!repairResult.ok) return {
|
|
3476
|
+
error: repairResult.errorCode === "controller.launch.choice_required" ? createInstallChoiceError({
|
|
3477
|
+
agentId: input.state.agentId,
|
|
3478
|
+
command: input.command,
|
|
3479
|
+
installChoices: repairResult.installChoices,
|
|
3480
|
+
reason: repairResult.errorMessage
|
|
3481
|
+
}) : {
|
|
3482
|
+
agentId: input.state.agentId,
|
|
3483
|
+
code: repairResult.errorCode,
|
|
3484
|
+
reason: repairResult.errorMessage
|
|
3485
|
+
},
|
|
3486
|
+
status: "blocked"
|
|
3487
|
+
};
|
|
3488
|
+
return {
|
|
3489
|
+
launchContract: repairResult.launchContract,
|
|
3490
|
+
status: "resolved"
|
|
3491
|
+
};
|
|
3492
|
+
}
|
|
3493
|
+
function resolveInteractiveChoiceAllowed(input) {
|
|
3494
|
+
return input.runtime.resolvedView === "human" && canUseInteractivePrompts(input.runtime) && input.all !== true;
|
|
3495
|
+
}
|
|
3496
|
+
async function collectEnableLaunchActionSelections(input) {
|
|
3497
|
+
const blockedAgentIds = /* @__PURE__ */ new Set();
|
|
3498
|
+
const selectedLaunchContracts = /* @__PURE__ */ new Map();
|
|
3499
|
+
for (const requestedId of input.requested) {
|
|
3500
|
+
const state = input.targetStateById.get(requestedId) ?? null;
|
|
3501
|
+
if (!isBuiltinEnableChoiceCandidate(state)) continue;
|
|
3502
|
+
const launchAction = await resolveBuiltinLaunchAction({
|
|
3503
|
+
command: "enable",
|
|
3504
|
+
explicitInstallId: input.explicitInstallId,
|
|
3505
|
+
interactiveChoiceAllowed: input.interactiveChoiceAllowed,
|
|
3506
|
+
state
|
|
3507
|
+
});
|
|
3508
|
+
if (launchAction.status === "not_needed") continue;
|
|
3509
|
+
if (launchAction.status === "cancelled" || launchAction.status === "back") return { status: launchAction.status };
|
|
3510
|
+
if (launchAction.status === "blocked") {
|
|
3511
|
+
blockedAgentIds.add(state.agentId);
|
|
3512
|
+
input.summary.errors.push(launchAction.error);
|
|
3513
|
+
continue;
|
|
3514
|
+
}
|
|
3515
|
+
selectedLaunchContracts.set(state.agentId, launchAction.launchContract);
|
|
3516
|
+
}
|
|
3517
|
+
return {
|
|
3518
|
+
blockedAgentIds,
|
|
3519
|
+
selectedLaunchContracts,
|
|
3520
|
+
status: "completed"
|
|
3521
|
+
};
|
|
3522
|
+
}
|
|
3523
|
+
async function collectRepairLaunchActions(input) {
|
|
3524
|
+
for (const requestedId of input.requested) {
|
|
3525
|
+
const candidate = resolveRepairCandidate({
|
|
3526
|
+
allowReadyForInstallChange: input.allowReadyForInstallChange,
|
|
3527
|
+
requestedId,
|
|
3528
|
+
targetStateById: input.targetStateById
|
|
3529
|
+
});
|
|
3530
|
+
if (!candidate.ok) {
|
|
3531
|
+
input.summary.errors.push(candidate.error);
|
|
3532
|
+
continue;
|
|
3533
|
+
}
|
|
3534
|
+
if (!(input.allowReadyForInstallChange || input.explicitInstallId) && candidate.state.launchStatus === "ready" && candidate.state.registration?.launchContract) {
|
|
3535
|
+
input.summary.skipped.push({
|
|
3536
|
+
agentId: requestedId,
|
|
3537
|
+
reason: "already_ready"
|
|
3538
|
+
});
|
|
3539
|
+
continue;
|
|
3540
|
+
}
|
|
3541
|
+
const launchAction = await resolveBuiltinLaunchAction({
|
|
3542
|
+
command: "repair",
|
|
3543
|
+
explicitInstallId: input.explicitInstallId,
|
|
3544
|
+
interactiveChoiceAllowed: input.interactiveChoiceAllowed,
|
|
3545
|
+
state: candidate.state
|
|
3546
|
+
});
|
|
3547
|
+
if (launchAction.status === "cancelled" || launchAction.status === "back") return launchAction.status;
|
|
3548
|
+
if (launchAction.status === "blocked") {
|
|
3549
|
+
input.summary.errors.push(launchAction.error);
|
|
3550
|
+
continue;
|
|
3551
|
+
}
|
|
3552
|
+
if (launchAction.status === "not_needed") continue;
|
|
3553
|
+
input.repairedLaunchContracts.set(requestedId, launchAction.launchContract);
|
|
3554
|
+
input.summary.repaired.push(requestedId);
|
|
3555
|
+
}
|
|
3556
|
+
return "completed";
|
|
3557
|
+
}
|
|
3558
|
+
function applyEnableSelectionUpdates(input) {
|
|
3559
|
+
const next = [...input.current];
|
|
3560
|
+
const existingById = new Map(next.map((registration, index) => [registration.agentId, index]));
|
|
3561
|
+
for (const requestedId of input.requested) applySingleEnableSelection({
|
|
3562
|
+
existingById,
|
|
3563
|
+
next,
|
|
3564
|
+
requestedId,
|
|
3565
|
+
selectedLaunchContracts: input.selectedLaunchContracts,
|
|
3566
|
+
selectableBuiltinIds: input.selectableBuiltinIds,
|
|
3567
|
+
summary: input.summary,
|
|
3568
|
+
targetStateById: input.targetStateById
|
|
3569
|
+
});
|
|
3570
|
+
return sortAgentRegistrations(next);
|
|
3571
|
+
}
|
|
3572
|
+
function applySingleEnableSelection(input) {
|
|
3573
|
+
if (!input.selectableBuiltinIds.has(input.requestedId)) {
|
|
3574
|
+
const blockedState = input.targetStateById.get(input.requestedId) ?? null;
|
|
3575
|
+
input.summary.errors.push({
|
|
3576
|
+
agentId: input.requestedId,
|
|
3577
|
+
code: "agents.invalid_agent_id",
|
|
3578
|
+
reason: resolveEnableBlockedReason(blockedState)
|
|
3579
|
+
});
|
|
3580
|
+
return;
|
|
3581
|
+
}
|
|
3582
|
+
const targetState = input.targetStateById.get(input.requestedId);
|
|
3583
|
+
if (targetState?.targetKind !== "builtin") {
|
|
3584
|
+
input.summary.errors.push({
|
|
3585
|
+
agentId: input.requestedId,
|
|
3586
|
+
code: "agents.config_write_failed",
|
|
3587
|
+
reason: "selectable built-in target missing from catalog"
|
|
3588
|
+
});
|
|
3589
|
+
return;
|
|
3590
|
+
}
|
|
3591
|
+
const selectedLaunchContract = input.selectedLaunchContracts.get(targetState.agentId) ?? null;
|
|
3592
|
+
const resolvedLaunchContract = selectedLaunchContract ?? targetState.launchContract ?? void 0;
|
|
3593
|
+
if (targetState.launchStatus === "needs_choice" && !selectedLaunchContract) {
|
|
3594
|
+
input.summary.errors.push(createInstallChoiceError({
|
|
3595
|
+
agentId: targetState.agentId,
|
|
3596
|
+
command: "enable",
|
|
3597
|
+
installChoices: targetState.installChoices,
|
|
3598
|
+
reason: targetState.launchStatusDetail ?? "OpenMeld needs one install choice on this Mac before it can turn this on."
|
|
3599
|
+
}));
|
|
3600
|
+
return;
|
|
3601
|
+
}
|
|
3602
|
+
const existingIndex = input.existingById.get(targetState.agentId);
|
|
3603
|
+
if (existingIndex === void 0) {
|
|
3604
|
+
input.next.push(createRegistrationFromCatalogTarget({
|
|
3605
|
+
...targetState,
|
|
3606
|
+
...resolvedLaunchContract ? { launchContract: resolvedLaunchContract } : {}
|
|
3607
|
+
}));
|
|
3608
|
+
input.existingById.set(targetState.agentId, input.next.length - 1);
|
|
3609
|
+
input.summary.registered.push(targetState.agentId);
|
|
3610
|
+
return;
|
|
3611
|
+
}
|
|
3612
|
+
const existing = input.next[existingIndex];
|
|
3613
|
+
if (!existing) {
|
|
3614
|
+
input.summary.errors.push({
|
|
3615
|
+
agentId: targetState.agentId,
|
|
3616
|
+
code: "agents.config_write_failed",
|
|
3617
|
+
reason: "registration index missing"
|
|
3618
|
+
});
|
|
3619
|
+
return;
|
|
3620
|
+
}
|
|
3621
|
+
if (existing.enabled && selectedLaunchContract) {
|
|
3622
|
+
input.next[existingIndex] = applyTargetRuntimeDefaults({
|
|
3623
|
+
registration: {
|
|
3624
|
+
...existing,
|
|
3625
|
+
launchContract: selectedLaunchContract,
|
|
3626
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3627
|
+
},
|
|
3628
|
+
targetState
|
|
3629
|
+
});
|
|
3630
|
+
input.summary.reenabled.push(targetState.agentId);
|
|
3631
|
+
return;
|
|
3632
|
+
}
|
|
3633
|
+
if (existing.enabled) {
|
|
3634
|
+
input.summary.skipped.push({
|
|
3635
|
+
agentId: targetState.agentId,
|
|
3636
|
+
reason: "already_enabled"
|
|
3637
|
+
});
|
|
3638
|
+
return;
|
|
3639
|
+
}
|
|
3640
|
+
input.next[existingIndex] = applyTargetRuntimeDefaults({
|
|
3641
|
+
registration: {
|
|
3642
|
+
...existing,
|
|
3643
|
+
enabled: true,
|
|
3644
|
+
...resolvedLaunchContract ? { launchContract: resolvedLaunchContract } : {},
|
|
3645
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3646
|
+
},
|
|
3647
|
+
targetState
|
|
3648
|
+
});
|
|
3649
|
+
input.summary.reenabled.push(targetState.agentId);
|
|
3650
|
+
}
|
|
3651
|
+
function applyTargetRuntimeDefaults(input) {
|
|
3652
|
+
return {
|
|
3653
|
+
...input.registration,
|
|
3654
|
+
...input.targetState.defaultStreamingMode ? { streamingMode: input.targetState.defaultStreamingMode } : {},
|
|
3655
|
+
...input.targetState.defaultTransportMode ? { transportMode: input.targetState.defaultTransportMode } : {}
|
|
3656
|
+
};
|
|
3657
|
+
}
|
|
3658
|
+
function resolveRepairCandidate(input) {
|
|
3659
|
+
const state = input.targetStateById.get(input.requestedId) ?? null;
|
|
3660
|
+
if (!isBuiltinEnableChoiceCandidate(state)) {
|
|
3661
|
+
if (!state) return {
|
|
3662
|
+
error: {
|
|
3663
|
+
agentId: input.requestedId,
|
|
3664
|
+
code: "agents.invalid_agent_id",
|
|
3665
|
+
reason: "unknown built-in target id"
|
|
3666
|
+
},
|
|
3667
|
+
ok: false
|
|
3668
|
+
};
|
|
3669
|
+
return {
|
|
3670
|
+
error: {
|
|
3671
|
+
agentId: input.requestedId,
|
|
3672
|
+
code: "agents.invalid_agent_id",
|
|
3673
|
+
reason: "custom target is not runnable yet"
|
|
3674
|
+
},
|
|
3675
|
+
ok: false
|
|
3676
|
+
};
|
|
3677
|
+
}
|
|
3678
|
+
if (!state.registered) return {
|
|
3679
|
+
error: {
|
|
3680
|
+
agentId: input.requestedId,
|
|
3681
|
+
code: "agents.not_registered",
|
|
3682
|
+
reason: "agent is not registered"
|
|
3683
|
+
},
|
|
3684
|
+
ok: false
|
|
3685
|
+
};
|
|
3686
|
+
if (input.allowReadyForInstallChange !== true && !(state.launchStatus === "needs_choice" || state.launchStatus === "needs_repair")) return {
|
|
3687
|
+
error: {
|
|
3688
|
+
agentId: input.requestedId,
|
|
3689
|
+
code: "controller.launch.needs_repair",
|
|
3690
|
+
reason: "already available"
|
|
3691
|
+
},
|
|
3692
|
+
ok: false
|
|
3693
|
+
};
|
|
3694
|
+
return {
|
|
3695
|
+
ok: true,
|
|
3696
|
+
state
|
|
3697
|
+
};
|
|
3698
|
+
}
|
|
3699
|
+
async function runAgentsDisable(input) {
|
|
3700
|
+
const runtime = await resolveRuntimeContext({
|
|
3701
|
+
view: input.view,
|
|
3702
|
+
profile: input.profile
|
|
3703
|
+
});
|
|
3704
|
+
const presenter = input.json === true ? createSilentAgentsPresenter(runtime) : createPresenter(runtime);
|
|
3705
|
+
await emitAgentsConfigParseFallbackSignal({
|
|
3706
|
+
presenter,
|
|
3707
|
+
view: runtime.resolvedView
|
|
3708
|
+
});
|
|
3709
|
+
emitAgentsOverviewIfAgent({
|
|
3710
|
+
presenter,
|
|
3711
|
+
resolvedView: runtime.resolvedView,
|
|
3712
|
+
command: "disable"
|
|
3713
|
+
});
|
|
3714
|
+
const explicitIds = normalizeAgentIds(input.agent ?? []);
|
|
3715
|
+
validateBuiltinAgentSelectionInput({
|
|
3716
|
+
all: input.all,
|
|
3717
|
+
command: "disable",
|
|
3718
|
+
explicitIds,
|
|
3719
|
+
explicitInstallId: null
|
|
3720
|
+
});
|
|
3721
|
+
const registrations = await listAgentRegistrations();
|
|
3722
|
+
const displayNameById = new Map(registrations.map((registration) => [registration.agentId, registration.displayName]));
|
|
3723
|
+
const requested = input.all ? registrations.map((registration) => registration.agentId) : explicitIds;
|
|
3724
|
+
const summary = {
|
|
3725
|
+
requested,
|
|
3726
|
+
disabled: [],
|
|
3727
|
+
skipped: [],
|
|
3728
|
+
errors: [],
|
|
3729
|
+
allMode: Boolean(input.all)
|
|
3730
|
+
};
|
|
3731
|
+
await updateAgentRegistrations((current) => {
|
|
3732
|
+
const next = [...current];
|
|
3733
|
+
const byId = new Map(next.map((registration, index) => [registration.agentId, index]));
|
|
3734
|
+
for (const agentId of requested) {
|
|
3735
|
+
const index = byId.get(agentId);
|
|
3736
|
+
if (index === void 0) {
|
|
3737
|
+
summary.errors.push({
|
|
3738
|
+
agentId,
|
|
3739
|
+
code: "agents.not_registered",
|
|
3740
|
+
reason: "agent is not registered"
|
|
3741
|
+
});
|
|
3742
|
+
continue;
|
|
3743
|
+
}
|
|
3744
|
+
const registration = next[index];
|
|
3745
|
+
if (!registration) {
|
|
3746
|
+
summary.errors.push({
|
|
3747
|
+
agentId,
|
|
3748
|
+
code: "agents.config_write_failed",
|
|
3749
|
+
reason: "registration index missing"
|
|
3750
|
+
});
|
|
3751
|
+
continue;
|
|
3752
|
+
}
|
|
3753
|
+
if (!registration.enabled) {
|
|
3754
|
+
summary.skipped.push({
|
|
3755
|
+
agentId,
|
|
3756
|
+
reason: "already_disabled"
|
|
3757
|
+
});
|
|
3758
|
+
continue;
|
|
3759
|
+
}
|
|
3760
|
+
next[index] = {
|
|
3761
|
+
...registration,
|
|
3762
|
+
enabled: false,
|
|
3763
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3764
|
+
};
|
|
3765
|
+
summary.disabled.push(agentId);
|
|
3766
|
+
}
|
|
3767
|
+
return sortAgentRegistrations(next);
|
|
3768
|
+
});
|
|
3769
|
+
if (input.json === true) {
|
|
3770
|
+
emitCliJsonEnvelope({
|
|
3771
|
+
schemaVersion: 1,
|
|
3772
|
+
type: "agents.disable",
|
|
3773
|
+
...summary
|
|
3774
|
+
});
|
|
3775
|
+
return;
|
|
3776
|
+
}
|
|
3777
|
+
if (runtime.resolvedView === "agent") {
|
|
3778
|
+
presenter.data({
|
|
3779
|
+
code: "agents.disable.result",
|
|
3780
|
+
payload: summary
|
|
3781
|
+
});
|
|
3782
|
+
return;
|
|
3783
|
+
}
|
|
3784
|
+
emitDisableSummaryCardsForHuman({
|
|
3785
|
+
presenter,
|
|
3786
|
+
summary,
|
|
3787
|
+
displayNameById
|
|
3788
|
+
});
|
|
3789
|
+
}
|
|
3790
|
+
async function runAgentsConfig(input) {
|
|
3791
|
+
const runtime = await resolveRuntimeContext({
|
|
3792
|
+
view: input.view,
|
|
3793
|
+
profile: input.profile
|
|
3794
|
+
});
|
|
3795
|
+
const presenter = createPresenter(runtime);
|
|
3796
|
+
await emitAgentsConfigParseFallbackSignal({
|
|
3797
|
+
presenter,
|
|
3798
|
+
view: runtime.resolvedView
|
|
3799
|
+
});
|
|
3800
|
+
emitAgentsOverviewIfAgent({
|
|
3801
|
+
presenter,
|
|
3802
|
+
resolvedView: runtime.resolvedView,
|
|
3803
|
+
command: "config"
|
|
3804
|
+
});
|
|
3805
|
+
const agentId = normalizeAgentId(input.agentId ?? "");
|
|
3806
|
+
if (!agentId) throw new Error("agents.invalid_agent_id: provide a non-empty <agent-id> for config");
|
|
3807
|
+
const enabled = parseAgentEnabledOption(input.enabled);
|
|
3808
|
+
if (enabled === void 0) throw new Error("agents.config_invalid: provide at least one option (--enabled)");
|
|
3809
|
+
const updatedFields = [];
|
|
3810
|
+
const effectiveConfig = (await updateAgentRegistrations((current) => {
|
|
3811
|
+
const index = current.findIndex((item) => item.agentId === agentId);
|
|
3812
|
+
if (index < 0) throw new Error(`agents.not_registered: agent is not registered: ${agentId}`);
|
|
3813
|
+
const next = [...current];
|
|
3814
|
+
const selected = next[index];
|
|
3815
|
+
if (!selected) throw new Error("agents.config_write_failed: registration index missing");
|
|
3816
|
+
const existing = { ...selected };
|
|
3817
|
+
if (enabled !== void 0) {
|
|
3818
|
+
existing.enabled = enabled;
|
|
3819
|
+
updatedFields.push("enabled");
|
|
3820
|
+
}
|
|
3821
|
+
existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3822
|
+
next[index] = existing;
|
|
3823
|
+
return sortAgentRegistrations(next);
|
|
3824
|
+
})).find((item) => item.agentId === agentId) ?? null;
|
|
3825
|
+
if (!effectiveConfig) throw new Error("agents.config_write_failed: config update returned empty");
|
|
3826
|
+
const payload = {
|
|
3827
|
+
agentId,
|
|
3828
|
+
updatedFields,
|
|
3829
|
+
effectiveConfig
|
|
3830
|
+
};
|
|
3831
|
+
if (runtime.resolvedView === "agent") {
|
|
3832
|
+
presenter.data({
|
|
3833
|
+
code: "agents.config.result",
|
|
3834
|
+
payload
|
|
3835
|
+
});
|
|
3836
|
+
return;
|
|
3837
|
+
}
|
|
3838
|
+
renderAgentsInfoCard({
|
|
3839
|
+
presenter,
|
|
3840
|
+
codePrefix: "agents.config.result.card",
|
|
3841
|
+
title: "Agent Updated",
|
|
3842
|
+
rows: [
|
|
3843
|
+
{
|
|
3844
|
+
label: "Agent",
|
|
3845
|
+
value: formatAgentMention(effectiveConfig.agentId)
|
|
3846
|
+
},
|
|
3847
|
+
{
|
|
3848
|
+
label: "App",
|
|
3849
|
+
value: effectiveConfig.displayName
|
|
3850
|
+
},
|
|
3851
|
+
{
|
|
3852
|
+
label: "Status",
|
|
3853
|
+
value: enabled ? formatAgentsToneText("success", "Available for new @mentions") : formatAgentsToneText("warning", "Turned off for new @mentions")
|
|
3854
|
+
},
|
|
3855
|
+
{
|
|
3856
|
+
label: "Wake",
|
|
3857
|
+
value: enabled ? `New ${formatAgentMention(effectiveConfig.agentId)} mentions can wake it` : `New ${formatAgentMention(effectiveConfig.agentId)} mentions stay off`
|
|
3858
|
+
}
|
|
3859
|
+
],
|
|
3860
|
+
minContentWidth: 34
|
|
3861
|
+
});
|
|
3862
|
+
}
|
|
3863
|
+
async function runAgentsCustomList(input) {
|
|
3864
|
+
const runtime = await resolveRuntimeContext({
|
|
3865
|
+
view: input.view,
|
|
3866
|
+
profile: input.profile
|
|
3867
|
+
});
|
|
3868
|
+
const presenter = createPresenter(runtime);
|
|
3869
|
+
await emitAgentsConfigParseFallbackSignal({
|
|
3870
|
+
presenter,
|
|
3871
|
+
view: runtime.resolvedView
|
|
3872
|
+
});
|
|
3873
|
+
emitAgentsOverviewIfAgent({
|
|
3874
|
+
presenter,
|
|
3875
|
+
resolvedView: runtime.resolvedView,
|
|
3876
|
+
command: "custom_list"
|
|
3877
|
+
});
|
|
3878
|
+
const [targets, states] = await Promise.all([listAgentCustomTargets(), listAgentTargetStates()]);
|
|
3879
|
+
const customStateById = new Map(states.filter((state) => state.targetKind === "custom").map((state) => [state.agentId, state]));
|
|
3880
|
+
const payloadTargets = targets.map((target) => {
|
|
3881
|
+
const state = customStateById.get(target.id);
|
|
3882
|
+
return {
|
|
3883
|
+
id: target.id,
|
|
3884
|
+
name: target.name,
|
|
3885
|
+
dir: target.dir,
|
|
3886
|
+
registered: state?.registered ?? false,
|
|
3887
|
+
enabled: state?.enabled ?? false
|
|
3888
|
+
};
|
|
3889
|
+
});
|
|
3890
|
+
const payload = {
|
|
3891
|
+
targets: payloadTargets,
|
|
3892
|
+
count: payloadTargets.length
|
|
3893
|
+
};
|
|
3894
|
+
if (runtime.resolvedView === "agent") {
|
|
3895
|
+
presenter.data({
|
|
3896
|
+
code: "agents.custom.list.result",
|
|
3897
|
+
payload
|
|
3898
|
+
});
|
|
3899
|
+
return;
|
|
3900
|
+
}
|
|
3901
|
+
emitHumanSectionHeader(presenter, {
|
|
3902
|
+
codePrefix: "agents.custom.list",
|
|
3903
|
+
title: "Custom agents"
|
|
3904
|
+
});
|
|
3905
|
+
if (payloadTargets.length === 0) {
|
|
3906
|
+
presenter.line({
|
|
3907
|
+
code: "agents.custom.list.empty",
|
|
3908
|
+
text: "No custom agents configured."
|
|
3909
|
+
});
|
|
3910
|
+
presenter.line({
|
|
3911
|
+
code: "agents.custom.list.hint",
|
|
3912
|
+
text: formatInlineOpenMeldCliCommands("Tip: run `openmeld agents custom add --name <name> --dir <dir>`.")
|
|
3913
|
+
});
|
|
3914
|
+
return;
|
|
3915
|
+
}
|
|
3916
|
+
presenter.line({
|
|
3917
|
+
code: "agents.custom.list.subtitle",
|
|
3918
|
+
text: `Configured custom agents: ${payloadTargets.length}`
|
|
3919
|
+
});
|
|
3920
|
+
for (const target of payloadTargets) presenter.line({
|
|
3921
|
+
code: "agents.custom.list.item",
|
|
3922
|
+
text: `- ${target.name} (${target.id}) • dir=${target.dir} • registered=${target.registered ? "true" : "false"} • enabled=${target.enabled ? "true" : "false"}`
|
|
3923
|
+
});
|
|
3924
|
+
}
|
|
3925
|
+
async function runAgentsCustomAdd(input) {
|
|
3926
|
+
const runtime = await resolveRuntimeContext({
|
|
3927
|
+
view: input.view,
|
|
3928
|
+
profile: input.profile
|
|
3929
|
+
});
|
|
3930
|
+
const presenter = createPresenter(runtime);
|
|
3931
|
+
await emitAgentsConfigParseFallbackSignal({
|
|
3932
|
+
presenter,
|
|
3933
|
+
view: runtime.resolvedView
|
|
3934
|
+
});
|
|
3935
|
+
emitAgentsOverviewIfAgent({
|
|
3936
|
+
presenter,
|
|
3937
|
+
resolvedView: runtime.resolvedView,
|
|
3938
|
+
command: "custom_add"
|
|
3939
|
+
});
|
|
3940
|
+
const name = normalizeOptionalString(input.name);
|
|
3941
|
+
const dir = normalizeOptionalString(input.dir);
|
|
3942
|
+
if (!(name && dir)) throw new Error("agents.custom.invalid_argument: provide --name <name> and --dir <dir>");
|
|
3943
|
+
if (!isAbsolute(dir)) throw new Error("agents.custom.invalid_argument: --dir must be an absolute path");
|
|
3944
|
+
const id = normalizeOptionalString(input.id) ?? void 0;
|
|
3945
|
+
const target = await addAgentCustomTarget({
|
|
3946
|
+
name,
|
|
3947
|
+
dir,
|
|
3948
|
+
...id ? { id } : {}
|
|
3949
|
+
});
|
|
3950
|
+
const payload = { target };
|
|
3951
|
+
if (runtime.resolvedView === "agent") {
|
|
3952
|
+
presenter.data({
|
|
3953
|
+
code: "agents.custom.add.result",
|
|
3954
|
+
payload
|
|
3955
|
+
});
|
|
3956
|
+
return;
|
|
3957
|
+
}
|
|
3958
|
+
emitHumanSectionHeader(presenter, {
|
|
3959
|
+
codePrefix: "agents.custom.add",
|
|
3960
|
+
title: "Custom Agent Added"
|
|
3961
|
+
});
|
|
3962
|
+
presenter.line({
|
|
3963
|
+
code: "agents.custom.add.result",
|
|
3964
|
+
text: `${target.name} (${target.id}) -> ${target.dir}`
|
|
3965
|
+
});
|
|
3966
|
+
}
|
|
3967
|
+
async function runAgentsCustomUpdate(input) {
|
|
3968
|
+
const runtime = await resolveRuntimeContext({
|
|
3969
|
+
view: input.view,
|
|
3970
|
+
profile: input.profile
|
|
3971
|
+
});
|
|
3972
|
+
const presenter = createPresenter(runtime);
|
|
3973
|
+
await emitAgentsConfigParseFallbackSignal({
|
|
3974
|
+
presenter,
|
|
3975
|
+
view: runtime.resolvedView
|
|
3976
|
+
});
|
|
3977
|
+
emitAgentsOverviewIfAgent({
|
|
3978
|
+
presenter,
|
|
3979
|
+
resolvedView: runtime.resolvedView,
|
|
3980
|
+
command: "custom_update"
|
|
3981
|
+
});
|
|
3982
|
+
const id = normalizeAgentId(input.id ?? "");
|
|
3983
|
+
if (!id) throw new Error("agents.custom.invalid_argument: provide --id <id>");
|
|
3984
|
+
const name = normalizeOptionalString(input.name) ?? void 0;
|
|
3985
|
+
const dir = normalizeOptionalString(input.dir) ?? void 0;
|
|
3986
|
+
if (name === void 0 && dir === void 0) throw new Error("agents.custom.invalid_argument: provide at least one option (--name/--dir)");
|
|
3987
|
+
if (dir && !isAbsolute(dir)) throw new Error("agents.custom.invalid_argument: --dir must be an absolute path");
|
|
3988
|
+
const updated = await updateAgentCustomTarget({
|
|
3989
|
+
id,
|
|
3990
|
+
...name ? { name } : {},
|
|
3991
|
+
...dir ? { dir } : {}
|
|
3992
|
+
});
|
|
3993
|
+
const payload = {
|
|
3994
|
+
target: updated.target,
|
|
3995
|
+
updatedFields: updated.updatedFields
|
|
3996
|
+
};
|
|
3997
|
+
if (runtime.resolvedView === "agent") {
|
|
3998
|
+
presenter.data({
|
|
3999
|
+
code: "agents.custom.update.result",
|
|
4000
|
+
payload
|
|
4001
|
+
});
|
|
4002
|
+
return;
|
|
4003
|
+
}
|
|
4004
|
+
emitHumanSectionHeader(presenter, {
|
|
4005
|
+
codePrefix: "agents.custom.update",
|
|
4006
|
+
title: "Custom Agent Updated"
|
|
4007
|
+
});
|
|
4008
|
+
const fields = updated.updatedFields.length > 0 ? updated.updatedFields.join(", ") : "no field changed";
|
|
4009
|
+
presenter.line({
|
|
4010
|
+
code: "agents.custom.update.result",
|
|
4011
|
+
text: `${updated.target.id}: ${fields}`
|
|
4012
|
+
});
|
|
4013
|
+
}
|
|
4014
|
+
async function runAgentsCustomRemove(input) {
|
|
4015
|
+
const runtime = await resolveRuntimeContext({
|
|
4016
|
+
view: input.view,
|
|
4017
|
+
profile: input.profile
|
|
4018
|
+
});
|
|
4019
|
+
const presenter = createPresenter(runtime);
|
|
4020
|
+
await emitAgentsConfigParseFallbackSignal({
|
|
4021
|
+
presenter,
|
|
4022
|
+
view: runtime.resolvedView
|
|
4023
|
+
});
|
|
4024
|
+
emitAgentsOverviewIfAgent({
|
|
4025
|
+
presenter,
|
|
4026
|
+
resolvedView: runtime.resolvedView,
|
|
4027
|
+
command: "custom_remove"
|
|
4028
|
+
});
|
|
4029
|
+
const id = normalizeAgentId(input.id ?? "");
|
|
4030
|
+
if (!id) throw new Error("agents.custom.invalid_argument: provide --id <id>");
|
|
4031
|
+
if ((await listAgentRegistrations()).some((registration) => registration.agentId === id)) throw new Error(`agents.custom.in_use: target is currently registered: ${id}`);
|
|
4032
|
+
const removed = await removeAgentCustomTarget({ id });
|
|
4033
|
+
const payload = {
|
|
4034
|
+
targetId: id,
|
|
4035
|
+
removed,
|
|
4036
|
+
installsPolicy: "kept"
|
|
4037
|
+
};
|
|
4038
|
+
if (runtime.resolvedView === "agent") {
|
|
4039
|
+
presenter.data({
|
|
4040
|
+
code: "agents.custom.remove.result",
|
|
4041
|
+
payload
|
|
4042
|
+
});
|
|
4043
|
+
return;
|
|
4044
|
+
}
|
|
4045
|
+
emitHumanSectionHeader(presenter, {
|
|
4046
|
+
codePrefix: "agents.custom.remove",
|
|
4047
|
+
title: "Custom Agent Removed"
|
|
4048
|
+
});
|
|
4049
|
+
presenter.line({
|
|
4050
|
+
code: "agents.custom.remove.result",
|
|
4051
|
+
text: `${removed.name} (${removed.id}) removed`
|
|
4052
|
+
});
|
|
4053
|
+
presenter.line({
|
|
4054
|
+
code: "agents.custom.remove.note",
|
|
4055
|
+
text: formatInlineOpenMeldCliCommands("Installs records are kept; cleanup via `openmeld skills uninstall`.")
|
|
4056
|
+
});
|
|
4057
|
+
}
|
|
4058
|
+
function buildAgentRegistrationAgentView(state) {
|
|
4059
|
+
const transportProjection = resolveTransportModeProjection(state);
|
|
4060
|
+
return {
|
|
4061
|
+
...state.registration,
|
|
4062
|
+
lifecycleStatus: state.lifecycleStatus,
|
|
4063
|
+
configuredTransportMode: transportProjection.configuredTransportMode,
|
|
4064
|
+
configuredTransportModeStatus: transportProjection.configuredTransportModeStatus,
|
|
4065
|
+
defaultTransportMode: transportProjection.defaultTransportMode,
|
|
4066
|
+
effectiveConfig: buildEffectiveConfig(state),
|
|
4067
|
+
effectiveTransportMode: transportProjection.effectiveTransportMode,
|
|
4068
|
+
supportedTransportModes: transportProjection.supportedTransportModes,
|
|
4069
|
+
transportModeSource: transportProjection.transportModeSource
|
|
4070
|
+
};
|
|
4071
|
+
}
|
|
4072
|
+
function buildEffectiveConfig(state) {
|
|
4073
|
+
const transportProjection = resolveTransportModeProjection(state);
|
|
4074
|
+
return {
|
|
4075
|
+
configuredTransportMode: transportProjection.configuredTransportMode,
|
|
4076
|
+
configuredTransportModeStatus: transportProjection.configuredTransportModeStatus,
|
|
4077
|
+
defaultTransportMode: transportProjection.defaultTransportMode,
|
|
4078
|
+
enabled: state.registration.enabled,
|
|
4079
|
+
effectiveTransportMode: transportProjection.effectiveTransportMode,
|
|
4080
|
+
supportedTransportModes: transportProjection.supportedTransportModes,
|
|
4081
|
+
transportMode: transportProjection.effectiveTransportMode,
|
|
4082
|
+
transportModeSource: transportProjection.transportModeSource
|
|
4083
|
+
};
|
|
4084
|
+
}
|
|
4085
|
+
function emitAgentsOverviewIfAgent(input) {
|
|
4086
|
+
if (input.resolvedView !== "agent" || input.handled) return;
|
|
4087
|
+
emitAgentsAgentOverview({
|
|
4088
|
+
presenter: input.presenter,
|
|
4089
|
+
command: input.command
|
|
4090
|
+
});
|
|
4091
|
+
}
|
|
4092
|
+
function resolveTransportModeForDisplay(state) {
|
|
4093
|
+
return formatTransportModeDisplay(resolveTransportModeProjection(state).effectiveTransportMode);
|
|
4094
|
+
}
|
|
4095
|
+
function resolveTransportModeProjection(state) {
|
|
4096
|
+
const supportedTransportModes = state.transportModes.map((transportMode) => transportMode.mode);
|
|
4097
|
+
const configuredTransportMode = state.registration.transportMode ?? null;
|
|
4098
|
+
let configuredTransportModeStatus;
|
|
4099
|
+
if (configuredTransportMode === null) configuredTransportModeStatus = "missing";
|
|
4100
|
+
else if (supportedTransportModes.length === 0 || supportedTransportModes.includes(configuredTransportMode)) configuredTransportModeStatus = "supported";
|
|
4101
|
+
else configuredTransportModeStatus = "unsupported";
|
|
4102
|
+
if (configuredTransportModeStatus === "supported" && configuredTransportMode) return {
|
|
4103
|
+
configuredTransportMode,
|
|
4104
|
+
configuredTransportModeStatus,
|
|
4105
|
+
defaultTransportMode: state.defaultTransportMode ?? null,
|
|
4106
|
+
effectiveTransportMode: configuredTransportMode,
|
|
4107
|
+
supportedTransportModes,
|
|
4108
|
+
transportModeSource: "registration"
|
|
4109
|
+
};
|
|
4110
|
+
const registryTransportMode = state.defaultTransportMode ?? supportedTransportModes[0] ?? null;
|
|
4111
|
+
if (registryTransportMode) return {
|
|
4112
|
+
configuredTransportMode,
|
|
4113
|
+
configuredTransportModeStatus,
|
|
4114
|
+
defaultTransportMode: state.defaultTransportMode ?? null,
|
|
4115
|
+
effectiveTransportMode: registryTransportMode,
|
|
4116
|
+
supportedTransportModes,
|
|
4117
|
+
transportModeSource: "registry_default"
|
|
4118
|
+
};
|
|
4119
|
+
return {
|
|
4120
|
+
configuredTransportMode,
|
|
4121
|
+
configuredTransportModeStatus,
|
|
4122
|
+
defaultTransportMode: null,
|
|
4123
|
+
effectiveTransportMode: DEFAULT_RUNTIME_AGENT_TRANSPORT_MODE,
|
|
4124
|
+
supportedTransportModes,
|
|
4125
|
+
transportModeSource: "global_default"
|
|
4126
|
+
};
|
|
4127
|
+
}
|
|
4128
|
+
async function emitAgentsConfigParseFallbackSignal(input) {
|
|
4129
|
+
const health = await readAgentRuntimeConfigHealth();
|
|
4130
|
+
if (!health.parseFallback) return;
|
|
4131
|
+
const reason = health.reason ?? "unknown";
|
|
4132
|
+
const data = {
|
|
4133
|
+
reason,
|
|
4134
|
+
strategy: "fallback_to_empty_config"
|
|
4135
|
+
};
|
|
4136
|
+
if (input.view === "agent") {
|
|
4137
|
+
input.presenter.line({
|
|
4138
|
+
code: "agents.config_parse_fallback",
|
|
4139
|
+
text: `runtime config parse fallback applied (${reason})`,
|
|
4140
|
+
data
|
|
4141
|
+
});
|
|
4142
|
+
return;
|
|
4143
|
+
}
|
|
4144
|
+
input.presenter.line({
|
|
4145
|
+
code: "agents.config_parse_fallback",
|
|
4146
|
+
text: `warning: runtime config parse fallback applied (${reason})`,
|
|
4147
|
+
data
|
|
4148
|
+
});
|
|
4149
|
+
}
|
|
4150
|
+
//#endregion
|
|
4151
|
+
export { resolveGatewayWebOrigin as C, buildSpaceWebUrl as S, runDaemonServiceParticipationGate as T, promptSearchSelect as _, runAgentsCustomRemove as a, resolveOffsetVisibleWindow as b, runAgentsDisable as c, runAgentsManage as d, runAgentsRepair as f, promptSearchMultiselectWorkbench as g, promptSearchMultiselect as h, runAgentsCustomList as i, runAgentsEnable as l, formatTransportModeDisplay as m, runAgentsConfig as n, runAgentsCustomUpdate as o, runAgentsShow as p, runAgentsCustomAdd as r, runAgentsDetect as s, runAgents as t, runAgentsList as u, clampNumber as v, resolveSpaceWebGatewayOrigin as w, computeRetryDelayMs as x, resolveAnchoredVisibleWindow as y };
|
|
4152
|
+
|
|
4153
|
+
//# sourceMappingURL=agents-Y-UJ74By.js.map
|