@zq-silk/yui 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -23
- package/dist/cli/commandCatalog.js +234 -122
- package/dist/cli/completion.js +3 -3
- package/dist/cli/helpRenderer.js +3 -0
- package/dist/cli/interactionPolicy.js +44 -23
- package/dist/cli/interactiveSelection.js +1 -1
- package/dist/cli/invocationRouter.js +1 -1
- package/dist/cli/roleWizard.js +8 -8
- package/dist/cli.js +116 -72
- package/dist/commands/agentCommands.js +5 -5
- package/dist/commands/configCommands.js +351 -104
- package/dist/commands/configOverview.js +60 -0
- package/dist/commands/deliveryGuardPreflight.js +2 -2
- package/dist/commands/globalRoleCommands.js +9 -9
- package/dist/commands/profileCommands.js +8 -8
- package/dist/commands/resourcesCommands.js +6 -5
- package/dist/commands/taskCommands.js +3 -6
- package/dist/commands/taskRoleRuntimeStatus.js +3 -1
- package/dist/commands/telemetryCommands.js +11 -6
- package/dist/config/configCatalog.js +42 -0
- package/dist/config/yuiConfig.js +80 -35
- package/dist/context/sessionBootstrapManifest.js +1 -1
- package/dist/controller/clientRuntime.js +0 -2
- package/dist/controller/controller.js +21 -9
- package/dist/controller/fileSchedulerStoreAdapter.js +20 -9
- package/dist/controller/runtime.js +32 -18
- package/dist/coordination/workMailbox.js +25 -22
- package/dist/doctor/doctor.js +2 -2
- package/dist/resources/autoResourceGc.js +3 -1
- package/dist/review/reviewConfig.js +0 -2
- package/dist/run/providerRetry.js +29 -16
- package/dist/run/providerRetryConfig.js +5 -3
- package/dist/runtime/launchDiagnostics.js +1 -1
- package/dist/scheduler/roleRunStall.js +12 -9
- package/dist/setup/setupCommand.js +153 -492
- package/dist/storage/compatibleTaskStore.js +9 -5
- package/dist/storage/migration/productionRegistry.js +58 -0
- package/dist/storage/sqliteStore.js +9 -2
- package/dist/storage/taskStore.js +21 -2
- package/dist/telemetry/sqliteTelemetryStore.js +9 -1
- package/dist/telemetry/telemetryConfig.js +1 -18
- package/dist/telemetry/telemetryStore.js +2 -2
- package/dist/telemetry/telemetryWiring.js +6 -5
- package/dist/web/webSnapshot.js +5 -3
- package/i18n/README.zh-CN.md +37 -32
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +12 -5
- package/skills/yui-operator/SKILL.md +44 -6
- package/skills/yui-runtime/SKILL.md +1 -1
|
@@ -1,82 +1,97 @@
|
|
|
1
|
+
import { mkdirSync, realpathSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
1
3
|
import { usageError } from "../errors/cliError.js";
|
|
2
|
-
import { DEFAULT_CONTEXT_HARD_TOKENS, DEFAULT_CONTEXT_SOFT_TOKENS, DEFAULT_LEADER_NEXT_ACTION_MODE, DEFAULT_PROVIDER_RETRY_MODE, DEFAULT_RECONCILIATION_INTERVAL_SECONDS, DEFAULT_RESOURCES_GC_MODE, LEADER_NEXT_ACTION_MODES, PROVIDER_RETRY_MODES, reconciliationIntervalMilliseconds, resolveContextBudget,
|
|
4
|
+
import { DEFAULT_CONTEXT_HARD_TOKENS, DEFAULT_CONTEXT_SOFT_TOKENS, DEFAULT_AGENT_LAUNCH_INACTIVITY_TIMEOUT_SECONDS, DEFAULT_CONTROLLER_TASK_CONCURRENCY, DEFAULT_DELIVERY_TIMEOUT_SECONDS, DEFAULT_LEADER_NEXT_ACTION_MODE, DEFAULT_LEADER_SEMANTIC_BUDGET_TURNS, DEFAULT_PROVIDER_RETRY_DELAYS_SECONDS, DEFAULT_PROVIDER_RETRY_MAX_WINDOW_SECONDS, DEFAULT_PROVIDER_RETRY_MODE, DEFAULT_RECONCILIATION_INTERVAL_SECONDS, DEFAULT_RESOURCES_GC_MODE, DEFAULT_RESOURCES_QUARANTINE_TTL_HOURS, DEFAULT_TMUX_HISTORY_LIMIT, LEADER_NEXT_ACTION_MODES, PROVIDER_RETRY_MODES, reconciliationIntervalMilliseconds, resolveAgentLaunchInactivityTimeoutSeconds, resolveControllerTaskConcurrency, resolveContextBudget, resolveDeliveryTimeoutSeconds, resolveLeaderNextActionMode, resolveLeaderSemanticBudgetTurns, resolveProviderRetryAdapters, resolveProviderRetryDelaysSeconds, resolveProviderRetryMaxWindowSeconds, resolveProviderRetryMode, resolveResourcesGcAutoQuarantine, resolveResourcesGcMode, resolveResourcesQuarantineTtlHours, resolveRuntimeHealth, resolveTelemetryEnabled, resolveTelemetryRunCap, resolveTelemetryTerminalKeep, resolveTmuxBin, resolveTmuxHistoryLimit } from "../config/yuiConfig.js";
|
|
5
|
+
import { CONFIG_DEFINITIONS, CONFIG_KEYS, configDefinition, configDefinitionsForDomain } from "../config/configCatalog.js";
|
|
3
6
|
import { resolveTimeZone } from "../output/timePresentation.js";
|
|
4
7
|
import { defaultTableWidth, renderTable } from "../output/table.js";
|
|
5
8
|
import { DEFAULT_DELTA_RECHECK_MAX_CHANGED_FILES, DEFAULT_DELTA_RECHECK_MAX_CHANGED_LINES, REVIEW_FINDING_LEDGER_MODES, REVIEW_DELTA_RECHECK_MODES, REVIEW_TRIGGERS } from "../review/reviewConfig.js";
|
|
6
9
|
/**
|
|
7
|
-
* One uniform key model for every durable
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* settings such as `review` and `leader-next-action` are keys like any other;
|
|
11
|
-
* they deliberately have no separate command trees. The stored YuiConfig
|
|
12
|
-
* shape is unchanged, so existing Homes keep working without migration.
|
|
10
|
+
* One uniform key model for every durable setting. Each catalog domain uses
|
|
11
|
+
* the same show/set/clear contract; strategy settings such as `review` and
|
|
12
|
+
* `leader-next-action` are keys like any other.
|
|
13
13
|
*/
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"git-bin",
|
|
27
|
-
"telemetry-mode",
|
|
28
|
-
"telemetry-terminal-keep",
|
|
29
|
-
"telemetry-run-cap",
|
|
30
|
-
"review"
|
|
31
|
-
];
|
|
32
|
-
const CONFIG_USAGE = "Config usage: yui config show | yui config set <key> <value...> | yui config clear <key>.";
|
|
33
|
-
const CONFIG_SET_USAGE = `Config set usage: yui config set <key> <value...>; keys: ${CONFIG_KEYS.join(", ")}.`;
|
|
34
|
-
const CONFIG_CLEAR_USAGE = `Config clear usage: yui config clear <key>; keys: ${CONFIG_KEYS.join(", ")}.`;
|
|
35
|
-
const TIME_ZONE_SET_USAGE = "Config set usage: yui config set time-zone <IANA timezone>.";
|
|
36
|
-
const RECONCILIATION_SET_USAGE = "Config set usage: yui config set reconciliation-interval-seconds <5-300>.";
|
|
37
|
-
const RESOURCES_GC_MODE_SET_USAGE = "Config set usage: yui config set resources-gc-mode <report|quarantine>.";
|
|
38
|
-
const RESOURCES_GC_AUTO_QUARANTINE_SET_USAGE = "Config set usage: yui config set resources-gc-auto-quarantine <true|false>.";
|
|
39
|
-
const LEADER_NEXT_ACTION_SET_USAGE = `Config set usage: yui config set leader-next-action <${LEADER_NEXT_ACTION_MODES.join("|")}>.`;
|
|
40
|
-
const CONTEXT_BUDGET_SET_USAGE = "Config set usage: yui config set context-budget [--soft-tokens <n>] [--hard-tokens <n>].";
|
|
41
|
-
const REVIEW_SET_USAGE = "Config set usage: yui config set review --role <global-role> --trigger <always|leader|final> "
|
|
14
|
+
export { CONFIG_KEYS } from "../config/configCatalog.js";
|
|
15
|
+
const CONFIG_PROPERTIES = Object.fromEntries(CONFIG_DEFINITIONS.map(({ key, property }) => [
|
|
16
|
+
key,
|
|
17
|
+
property
|
|
18
|
+
]));
|
|
19
|
+
const TIME_ZONE_SET_USAGE = "System config set usage: yui config system set time-zone <IANA timezone>.";
|
|
20
|
+
const RECONCILIATION_SET_USAGE = "Runtime config set usage: yui config runtime set reconciliation-interval-seconds <5-300>.";
|
|
21
|
+
const RESOURCES_GC_MODE_SET_USAGE = "Resources config set usage: yui config resources set resources-gc-mode <report|quarantine>.";
|
|
22
|
+
const RESOURCES_GC_AUTO_QUARANTINE_SET_USAGE = "Resources config set usage: yui config resources set resources-gc-auto-quarantine <true|false>.";
|
|
23
|
+
const LEADER_NEXT_ACTION_SET_USAGE = `Workflow config set usage: yui config workflow set leader-next-action <${LEADER_NEXT_ACTION_MODES.join("|")}>.`;
|
|
24
|
+
const CONTEXT_BUDGET_SET_USAGE = "Workflow config set usage: yui config workflow set context-budget [--soft-tokens <n>] [--hard-tokens <n>].";
|
|
25
|
+
const REVIEW_SET_USAGE = "Workflow config set usage: yui config workflow set review --role <global-role> --trigger <always|leader|final> "
|
|
42
26
|
+ "[--finding-ledger <shadow|enforce>] [--delta-recheck <enabled|disabled>] "
|
|
43
27
|
+ "[--delta-recheck-max-lines <n>] [--delta-recheck-max-files <n>].";
|
|
44
|
-
export function runConfigCommand(args, store) {
|
|
28
|
+
export function runConfigCommand(domain, args, store) {
|
|
29
|
+
const keys = configDefinitionsForDomain(domain).map(({ key }) => key);
|
|
30
|
+
const usage = `${capitalize(domain)} config usage: yui config ${domain} show | yui config ${domain} set <key> <value...> | yui config ${domain} clear <key>.`;
|
|
45
31
|
const [command, ...rest] = args;
|
|
46
32
|
if (command === "show") {
|
|
47
33
|
if (rest.length !== 0)
|
|
48
|
-
throw usageError(
|
|
34
|
+
throw usageError(`${capitalize(domain)} config show usage: yui config ${domain} show.`);
|
|
49
35
|
const config = store.getConfig();
|
|
50
36
|
reconciliationIntervalMilliseconds(config.reconciliationIntervalSeconds);
|
|
51
|
-
return { output: renderConfigShow(config), data: effectiveConfigData(config) };
|
|
37
|
+
return { output: renderConfigShow(domain, config), data: effectiveConfigData(config, domain) };
|
|
52
38
|
}
|
|
53
39
|
if (command === "set")
|
|
54
|
-
return { output: runConfigSet(rest, store) };
|
|
40
|
+
return { output: runConfigSet(domain, keys, rest, store) };
|
|
55
41
|
if (command === "clear")
|
|
56
|
-
return { output: runConfigClear(rest, store) };
|
|
57
|
-
throw usageError(command === undefined ?
|
|
42
|
+
return { output: runConfigClear(domain, keys, rest, store) };
|
|
43
|
+
throw usageError(command === undefined ? `${capitalize(domain)} config command is required.` : `Unknown command: config ${domain} ${command}`, usage);
|
|
58
44
|
}
|
|
59
45
|
/**
|
|
60
|
-
* `
|
|
46
|
+
* A domain `show` is row/column data, so it renders through the shared
|
|
61
47
|
* `renderTable` like every other list command. The column contract follows
|
|
62
48
|
* the shared rules: left-aligned cells, widths fitted between min/max,
|
|
63
49
|
* terminal width from `defaultTableWidth()`, and empty values as empty cells.
|
|
64
50
|
*/
|
|
65
|
-
function renderConfigShow(config) {
|
|
66
|
-
return renderTable(
|
|
51
|
+
function renderConfigShow(domain, config) {
|
|
52
|
+
return renderTable(`Yui ${domain} configuration`, [
|
|
67
53
|
{ header: "Setting", minWidth: 20, maxWidth: 32 },
|
|
68
54
|
{ header: "Value", minWidth: 10, maxWidth: 60 }
|
|
69
|
-
], CONFIG_KEY_HANDLERS
|
|
55
|
+
], CONFIG_KEY_HANDLERS
|
|
56
|
+
.filter(({ key }) => configDefinition(key)?.domain === domain)
|
|
57
|
+
.map((handler) => [handler.showLabel, handler.showValue(config)]), defaultTableWidth());
|
|
70
58
|
}
|
|
71
|
-
/** Effective values
|
|
72
|
-
function effectiveConfigData(config) {
|
|
73
|
-
|
|
59
|
+
/** Effective values for every user-configurable system field. */
|
|
60
|
+
export function effectiveConfigData(config, domain) {
|
|
61
|
+
const budget = resolveContextBudget(config.contextBudget);
|
|
62
|
+
const health = resolveRuntimeHealth(config.runtimeHealth);
|
|
63
|
+
const all = {
|
|
64
|
+
schemaVersion: config.schemaVersion,
|
|
65
|
+
defaultAgent: config.defaultAgent ?? null,
|
|
66
|
+
defaultWorkspace: config.defaultWorkspace ?? null,
|
|
74
67
|
timeZone: resolveTimeZone(config.timeZone),
|
|
68
|
+
currentTaskId: config.currentTaskId ?? null,
|
|
69
|
+
lastTaskId: config.lastTaskId ?? null,
|
|
75
70
|
reconciliationIntervalSeconds: config.reconciliationIntervalSeconds
|
|
76
71
|
?? DEFAULT_RECONCILIATION_INTERVAL_SECONDS,
|
|
77
72
|
leaderNextActionMode: resolveLeaderNextActionMode(config.leaderNextActionMode),
|
|
73
|
+
contextBudget: { softTokens: budget.softTokens, hardTokens: budget.hardTokens },
|
|
78
74
|
resourcesGcMode: resolveResourcesGcMode(config.resourcesGcMode),
|
|
79
75
|
resourcesGcAutoQuarantine: resolveResourcesGcAutoQuarantine(config.resourcesGcAutoQuarantine),
|
|
76
|
+
resourcesQuarantineTtlHours: resolveResourcesQuarantineTtlHours(config.resourcesQuarantineTtlHours),
|
|
77
|
+
providerRetryMode: resolveProviderRetryMode(config.providerRetryMode),
|
|
78
|
+
providerRetryAdapters: resolveProviderRetryAdapters(config.providerRetryAdapters),
|
|
79
|
+
providerRetryDelaysSeconds: resolveProviderRetryDelaysSeconds(config.providerRetryDelaysSeconds),
|
|
80
|
+
providerRetryMaxWindowSeconds: resolveProviderRetryMaxWindowSeconds(config.providerRetryMaxWindowSeconds),
|
|
81
|
+
runtimeHealth: {
|
|
82
|
+
quietAfterSeconds: health.quietAfterMs / 1_000,
|
|
83
|
+
diagnosticAfterSeconds: health.diagnosticAfterMs / 1_000,
|
|
84
|
+
stallAfterSeconds: health.stallWindowMs / 1_000
|
|
85
|
+
},
|
|
86
|
+
controllerTaskConcurrency: resolveControllerTaskConcurrency(config.controllerTaskConcurrency),
|
|
87
|
+
agentLaunchInactivityTimeoutSeconds: resolveAgentLaunchInactivityTimeoutSeconds(config.agentLaunchInactivityTimeoutSeconds),
|
|
88
|
+
deliveryTimeoutSeconds: resolveDeliveryTimeoutSeconds(config.deliveryTimeoutSeconds),
|
|
89
|
+
leaderSemanticBudgetTurns: resolveLeaderSemanticBudgetTurns(config.leaderSemanticBudgetTurns),
|
|
90
|
+
tmuxBin: resolveTmuxBin(config.tmuxBin),
|
|
91
|
+
tmuxHistoryLimit: resolveTmuxHistoryLimit(config.tmuxHistoryLimit),
|
|
92
|
+
telemetryEnabled: resolveTelemetryEnabled(config.telemetryEnabled),
|
|
93
|
+
telemetryTerminalKeep: resolveTelemetryTerminalKeep(config.telemetryTerminalKeep),
|
|
94
|
+
telemetryRunCap: resolveTelemetryRunCap(config.telemetryRunCap),
|
|
80
95
|
review: config.review === undefined
|
|
81
96
|
? null
|
|
82
97
|
: {
|
|
@@ -88,31 +103,48 @@ function effectiveConfigData(config) {
|
|
|
88
103
|
?? DEFAULT_DELTA_RECHECK_MAX_CHANGED_LINES,
|
|
89
104
|
deltaRecheckMaxChangedFiles: config.review.deltaRecheckMaxChangedFiles
|
|
90
105
|
?? DEFAULT_DELTA_RECHECK_MAX_CHANGED_FILES
|
|
91
|
-
}
|
|
106
|
+
},
|
|
107
|
+
completionInstallations: config.completionInstallations ?? {}
|
|
108
|
+
};
|
|
109
|
+
const definitions = configDefinitionsForDomain(domain);
|
|
110
|
+
return {
|
|
111
|
+
...Object.fromEntries(definitions.map(({ property }) => [property, all[property]])),
|
|
112
|
+
valueSources: Object.fromEntries(definitions.map(({ key }) => {
|
|
113
|
+
const typedKey = key;
|
|
114
|
+
return [
|
|
115
|
+
key,
|
|
116
|
+
config[CONFIG_PROPERTIES[typedKey]] === undefined ? "default" : "stored"
|
|
117
|
+
];
|
|
118
|
+
}))
|
|
92
119
|
};
|
|
93
120
|
}
|
|
94
|
-
function runConfigSet(args, store) {
|
|
121
|
+
function runConfigSet(domain, keys, args, store) {
|
|
95
122
|
const [key, ...values] = args;
|
|
123
|
+
const usage = `${capitalize(domain)} config set usage: yui config ${domain} set <key> <value...>; keys: ${keys.join(", ")}.`;
|
|
96
124
|
if (key === undefined)
|
|
97
|
-
throw usageError(
|
|
125
|
+
throw usageError(usage);
|
|
98
126
|
const handler = CONFIG_KEY_HANDLERS.find((entry) => entry.key === key);
|
|
99
|
-
if (handler === undefined) {
|
|
100
|
-
throw usageError(`Unknown config key: ${key}`,
|
|
127
|
+
if (handler === undefined || !keys.includes(key)) {
|
|
128
|
+
throw usageError(`Unknown ${domain} config key: ${key}`, usage);
|
|
101
129
|
}
|
|
102
130
|
return handler.set(values, store);
|
|
103
131
|
}
|
|
104
|
-
function runConfigClear(args, store) {
|
|
132
|
+
function runConfigClear(domain, keys, args, store) {
|
|
105
133
|
const [key, ...rest] = args;
|
|
134
|
+
const usage = `${capitalize(domain)} config clear usage: yui config ${domain} clear <key>; keys: ${keys.join(", ")}.`;
|
|
106
135
|
if (key === undefined)
|
|
107
|
-
throw usageError(
|
|
136
|
+
throw usageError(usage);
|
|
108
137
|
if (rest.length !== 0)
|
|
109
|
-
throw usageError(
|
|
138
|
+
throw usageError(`${capitalize(domain)} config clear usage: yui config ${domain} clear ${key}.`);
|
|
110
139
|
const handler = CONFIG_KEY_HANDLERS.find((entry) => entry.key === key);
|
|
111
|
-
if (handler === undefined) {
|
|
112
|
-
throw usageError(`Unknown config key: ${key}`,
|
|
140
|
+
if (handler === undefined || !keys.includes(key)) {
|
|
141
|
+
throw usageError(`Unknown ${domain} config key: ${key}`, usage);
|
|
113
142
|
}
|
|
114
143
|
return handler.clear(store);
|
|
115
144
|
}
|
|
145
|
+
function capitalize(value) {
|
|
146
|
+
return `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
|
|
147
|
+
}
|
|
116
148
|
function saveConfigKey(store, update) {
|
|
117
149
|
store.transaction((tx) => {
|
|
118
150
|
tx.saveConfig(update(tx.getConfig()));
|
|
@@ -152,6 +184,49 @@ function parsePositiveIntegerOption(value, usage) {
|
|
|
152
184
|
return Number(value);
|
|
153
185
|
}
|
|
154
186
|
const CONFIG_KEY_HANDLERS = [
|
|
187
|
+
{
|
|
188
|
+
key: "default-agent",
|
|
189
|
+
showLabel: "Default Agent",
|
|
190
|
+
showValue: (config) => config.defaultAgent ?? "not configured",
|
|
191
|
+
set(args, store) {
|
|
192
|
+
if (args.length !== 1 || args[0].trim().length === 0) {
|
|
193
|
+
throw usageError("System config set usage: yui config system set default-agent <agent-id>.");
|
|
194
|
+
}
|
|
195
|
+
const defaultAgent = args[0].trim();
|
|
196
|
+
if (store.getConfiguredAgent?.(defaultAgent) === null) {
|
|
197
|
+
throw usageError(`Configured Agent not found: ${defaultAgent}.`);
|
|
198
|
+
}
|
|
199
|
+
saveConfigKey(store, (config) => ({ ...config, defaultAgent }));
|
|
200
|
+
return `Default Agent set to ${defaultAgent}\n`;
|
|
201
|
+
},
|
|
202
|
+
clear(store) {
|
|
203
|
+
saveConfigKey(store, (config) => {
|
|
204
|
+
const { defaultAgent: _removed, ...rest } = config;
|
|
205
|
+
return rest;
|
|
206
|
+
});
|
|
207
|
+
return "Default Agent cleared\n";
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
key: "default-workspace",
|
|
212
|
+
showLabel: "Default workspace",
|
|
213
|
+
showValue: (config) => config.defaultWorkspace ?? "not configured",
|
|
214
|
+
set(args, store) {
|
|
215
|
+
if (args.length !== 1 || !isAbsolute(args[0])) {
|
|
216
|
+
throw usageError("System config set usage: yui config system set default-workspace <absolute-path>.");
|
|
217
|
+
}
|
|
218
|
+
const defaultWorkspace = resolveDefaultWorkspace(args[0], store);
|
|
219
|
+
saveConfigKey(store, (config) => ({ ...config, defaultWorkspace }));
|
|
220
|
+
return `Default workspace set to ${defaultWorkspace}\n`;
|
|
221
|
+
},
|
|
222
|
+
clear(store) {
|
|
223
|
+
saveConfigKey(store, (config) => {
|
|
224
|
+
const { defaultWorkspace: _removed, ...rest } = config;
|
|
225
|
+
return rest;
|
|
226
|
+
});
|
|
227
|
+
return "Default workspace cleared\n";
|
|
228
|
+
}
|
|
229
|
+
},
|
|
155
230
|
{
|
|
156
231
|
key: "time-zone",
|
|
157
232
|
showLabel: "Time zone",
|
|
@@ -216,6 +291,26 @@ const CONFIG_KEY_HANDLERS = [
|
|
|
216
291
|
return `Leader next-action mode reset to ${DEFAULT_LEADER_NEXT_ACTION_MODE}\n`;
|
|
217
292
|
}
|
|
218
293
|
},
|
|
294
|
+
{
|
|
295
|
+
key: "leader-semantic-budget-turns",
|
|
296
|
+
showLabel: "Leader semantic budget",
|
|
297
|
+
showValue: (config) => `${resolveLeaderSemanticBudgetTurns(config.leaderSemanticBudgetTurns)} turns`,
|
|
298
|
+
set(args, store) {
|
|
299
|
+
const usage = "Workflow config set usage: yui config workflow set leader-semantic-budget-turns <1-20>.";
|
|
300
|
+
if (args.length !== 1)
|
|
301
|
+
throw usageError(usage);
|
|
302
|
+
const leaderSemanticBudgetTurns = validatedConfigValue(() => resolveLeaderSemanticBudgetTurns(Number(args[0])), usage);
|
|
303
|
+
saveConfigKey(store, (config) => ({ ...config, leaderSemanticBudgetTurns }));
|
|
304
|
+
return `Leader semantic budget set to ${leaderSemanticBudgetTurns} turns\n`;
|
|
305
|
+
},
|
|
306
|
+
clear(store) {
|
|
307
|
+
saveConfigKey(store, (config) => {
|
|
308
|
+
const { leaderSemanticBudgetTurns: _removed, ...rest } = config;
|
|
309
|
+
return rest;
|
|
310
|
+
});
|
|
311
|
+
return `Leader semantic budget reset to ${DEFAULT_LEADER_SEMANTIC_BUDGET_TURNS} turns\n`;
|
|
312
|
+
}
|
|
313
|
+
},
|
|
219
314
|
{
|
|
220
315
|
key: "context-budget",
|
|
221
316
|
showLabel: "Context budget",
|
|
@@ -296,14 +391,135 @@ const CONFIG_KEY_HANDLERS = [
|
|
|
296
391
|
return "Resources GC auto-quarantine reset to off\n";
|
|
297
392
|
}
|
|
298
393
|
},
|
|
394
|
+
{
|
|
395
|
+
key: "resources-quarantine-ttl-hours",
|
|
396
|
+
showLabel: "Resource quarantine TTL",
|
|
397
|
+
showValue: (config) => `${resolveResourcesQuarantineTtlHours(config.resourcesQuarantineTtlHours)} hours`,
|
|
398
|
+
set(args, store) {
|
|
399
|
+
const usage = "Resources config set usage: yui config resources set resources-quarantine-ttl-hours <1-720>.";
|
|
400
|
+
if (args.length !== 1)
|
|
401
|
+
throw usageError(usage);
|
|
402
|
+
const resourcesQuarantineTtlHours = validatedConfigValue(() => resolveResourcesQuarantineTtlHours(Number(args[0])), usage);
|
|
403
|
+
saveConfigKey(store, (config) => ({ ...config, resourcesQuarantineTtlHours }));
|
|
404
|
+
return `Resource quarantine TTL set to ${resourcesQuarantineTtlHours} hours\n`;
|
|
405
|
+
},
|
|
406
|
+
clear(store) {
|
|
407
|
+
saveConfigKey(store, (config) => {
|
|
408
|
+
const { resourcesQuarantineTtlHours: _removed, ...rest } = config;
|
|
409
|
+
return rest;
|
|
410
|
+
});
|
|
411
|
+
return `Resource quarantine TTL reset to ${DEFAULT_RESOURCES_QUARANTINE_TTL_HOURS} hours\n`;
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
key: "controller-task-concurrency",
|
|
416
|
+
showLabel: "Controller Task concurrency",
|
|
417
|
+
showValue: (config) => String(resolveControllerTaskConcurrency(config.controllerTaskConcurrency)),
|
|
418
|
+
set(args, store) {
|
|
419
|
+
const usage = "Runtime config set usage: yui config runtime set controller-task-concurrency <1-32>.";
|
|
420
|
+
if (args.length !== 1)
|
|
421
|
+
throw usageError(usage);
|
|
422
|
+
const controllerTaskConcurrency = validatedConfigValue(() => resolveControllerTaskConcurrency(Number(args[0])), usage);
|
|
423
|
+
saveConfigKey(store, (config) => ({ ...config, controllerTaskConcurrency }));
|
|
424
|
+
return `Controller Task concurrency set to ${controllerTaskConcurrency}\n`;
|
|
425
|
+
},
|
|
426
|
+
clear(store) {
|
|
427
|
+
saveConfigKey(store, (config) => {
|
|
428
|
+
const { controllerTaskConcurrency: _removed, ...rest } = config;
|
|
429
|
+
return rest;
|
|
430
|
+
});
|
|
431
|
+
return `Controller Task concurrency reset to ${DEFAULT_CONTROLLER_TASK_CONCURRENCY}\n`;
|
|
432
|
+
}
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
key: "runtime-health",
|
|
436
|
+
showLabel: "Runtime health thresholds",
|
|
437
|
+
showValue: (config) => {
|
|
438
|
+
const health = resolveRuntimeHealth(config.runtimeHealth);
|
|
439
|
+
return `quiet ${health.quietAfterMs / 1_000}s / diagnostic ${health.diagnosticAfterMs / 1_000}s / stall ${health.stallWindowMs / 1_000}s`;
|
|
440
|
+
},
|
|
441
|
+
set(args, store) {
|
|
442
|
+
const usage = "Runtime config set usage: yui config runtime set runtime-health --quiet-after-seconds <n> --diagnostic-after-seconds <n> --stall-after-seconds <n>.";
|
|
443
|
+
if (args.length !== 6)
|
|
444
|
+
throw usageError(usage);
|
|
445
|
+
const options = new Map();
|
|
446
|
+
for (let index = 0; index < args.length; index += 2) {
|
|
447
|
+
const name = args[index];
|
|
448
|
+
const value = Number(args[index + 1]);
|
|
449
|
+
if (!["--quiet-after-seconds", "--diagnostic-after-seconds", "--stall-after-seconds"].includes(name)
|
|
450
|
+
|| options.has(name))
|
|
451
|
+
throw usageError(usage);
|
|
452
|
+
options.set(name, value);
|
|
453
|
+
}
|
|
454
|
+
const resolved = validatedConfigValue(() => resolveRuntimeHealth({
|
|
455
|
+
quietAfterSeconds: options.get("--quiet-after-seconds"),
|
|
456
|
+
diagnosticAfterSeconds: options.get("--diagnostic-after-seconds"),
|
|
457
|
+
stallAfterSeconds: options.get("--stall-after-seconds")
|
|
458
|
+
}), usage);
|
|
459
|
+
const runtimeHealth = {
|
|
460
|
+
quietAfterSeconds: resolved.quietAfterMs / 1_000,
|
|
461
|
+
diagnosticAfterSeconds: resolved.diagnosticAfterMs / 1_000,
|
|
462
|
+
stallAfterSeconds: resolved.stallWindowMs / 1_000
|
|
463
|
+
};
|
|
464
|
+
saveConfigKey(store, (config) => ({ ...config, runtimeHealth }));
|
|
465
|
+
return `Runtime health thresholds set to quiet ${runtimeHealth.quietAfterSeconds}s / diagnostic ${runtimeHealth.diagnosticAfterSeconds}s / stall ${runtimeHealth.stallAfterSeconds}s\n`;
|
|
466
|
+
},
|
|
467
|
+
clear(store) {
|
|
468
|
+
saveConfigKey(store, (config) => {
|
|
469
|
+
const { runtimeHealth: _removed, ...rest } = config;
|
|
470
|
+
return rest;
|
|
471
|
+
});
|
|
472
|
+
return "Runtime health thresholds reset to quiet 300s / diagnostic 600s / stall 1800s\n";
|
|
473
|
+
}
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
key: "agent-launch-inactivity-timeout-seconds",
|
|
477
|
+
showLabel: "Agent launch inactivity timeout",
|
|
478
|
+
showValue: (config) => `${resolveAgentLaunchInactivityTimeoutSeconds(config.agentLaunchInactivityTimeoutSeconds)} seconds`,
|
|
479
|
+
set(args, store) {
|
|
480
|
+
const usage = "Runtime config set usage: yui config runtime set agent-launch-inactivity-timeout-seconds <15-3600>.";
|
|
481
|
+
if (args.length !== 1)
|
|
482
|
+
throw usageError(usage);
|
|
483
|
+
const agentLaunchInactivityTimeoutSeconds = validatedConfigValue(() => resolveAgentLaunchInactivityTimeoutSeconds(Number(args[0])), usage);
|
|
484
|
+
saveConfigKey(store, (config) => ({ ...config, agentLaunchInactivityTimeoutSeconds }));
|
|
485
|
+
return `Agent launch inactivity timeout set to ${agentLaunchInactivityTimeoutSeconds} seconds\n`;
|
|
486
|
+
},
|
|
487
|
+
clear(store) {
|
|
488
|
+
saveConfigKey(store, (config) => {
|
|
489
|
+
const { agentLaunchInactivityTimeoutSeconds: _removed, ...rest } = config;
|
|
490
|
+
return rest;
|
|
491
|
+
});
|
|
492
|
+
return `Agent launch inactivity timeout reset to ${DEFAULT_AGENT_LAUNCH_INACTIVITY_TIMEOUT_SECONDS} seconds\n`;
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
key: "delivery-timeout-seconds",
|
|
497
|
+
showLabel: "Delivery timeout",
|
|
498
|
+
showValue: (config) => `${resolveDeliveryTimeoutSeconds(config.deliveryTimeoutSeconds)} seconds`,
|
|
499
|
+
set(args, store) {
|
|
500
|
+
const usage = "Runtime config set usage: yui config runtime set delivery-timeout-seconds <5-600>.";
|
|
501
|
+
if (args.length !== 1)
|
|
502
|
+
throw usageError(usage);
|
|
503
|
+
const deliveryTimeoutSeconds = validatedConfigValue(() => resolveDeliveryTimeoutSeconds(Number(args[0])), usage);
|
|
504
|
+
saveConfigKey(store, (config) => ({ ...config, deliveryTimeoutSeconds }));
|
|
505
|
+
return `Delivery timeout set to ${deliveryTimeoutSeconds} seconds\n`;
|
|
506
|
+
},
|
|
507
|
+
clear(store) {
|
|
508
|
+
saveConfigKey(store, (config) => {
|
|
509
|
+
const { deliveryTimeoutSeconds: _removed, ...rest } = config;
|
|
510
|
+
return rest;
|
|
511
|
+
});
|
|
512
|
+
return `Delivery timeout reset to ${DEFAULT_DELIVERY_TIMEOUT_SECONDS} seconds\n`;
|
|
513
|
+
}
|
|
514
|
+
},
|
|
299
515
|
{
|
|
300
516
|
key: "provider-retry-mode",
|
|
301
517
|
showLabel: "Provider retry mode",
|
|
302
518
|
showValue: (config) => resolveProviderRetryMode(config.providerRetryMode),
|
|
303
519
|
set(args, store) {
|
|
304
520
|
if (args.length !== 1)
|
|
305
|
-
throw usageError(`
|
|
306
|
-
const mode = validatedConfigValue(() => resolveProviderRetryMode(args[0]), `
|
|
521
|
+
throw usageError(`Runtime config set usage: yui config runtime set provider-retry-mode <${PROVIDER_RETRY_MODES.join("|")}>.`);
|
|
522
|
+
const mode = validatedConfigValue(() => resolveProviderRetryMode(args[0]), `Runtime config set usage: yui config runtime set provider-retry-mode <${PROVIDER_RETRY_MODES.join("|")}>.`);
|
|
307
523
|
saveConfigKey(store, (config) => ({ ...config, providerRetryMode: mode }));
|
|
308
524
|
return `Provider retry mode set to ${mode}\n`;
|
|
309
525
|
},
|
|
@@ -321,11 +537,11 @@ const CONFIG_KEY_HANDLERS = [
|
|
|
321
537
|
showValue: (config) => resolveProviderRetryAdapters(config.providerRetryAdapters).join(", ") || "none",
|
|
322
538
|
set(args, store) {
|
|
323
539
|
if (args.length !== 1)
|
|
324
|
-
throw usageError("
|
|
540
|
+
throw usageError("Runtime config set usage: yui config runtime set provider-retry-adapters <all|claude,codex|off>.");
|
|
325
541
|
const raw = args[0].trim().toLowerCase();
|
|
326
542
|
const adapters = raw === "off" || raw === "" || raw === "0"
|
|
327
543
|
? []
|
|
328
|
-
: validatedConfigValue(() => resolveProviderRetryAdapters(raw.split(",")), "
|
|
544
|
+
: validatedConfigValue(() => resolveProviderRetryAdapters(raw.split(",")), "Runtime config set usage: yui config runtime set provider-retry-adapters <all|claude,codex|off>.");
|
|
329
545
|
saveConfigKey(store, (config) => ({ ...config, providerRetryAdapters: adapters }));
|
|
330
546
|
return `Provider retry adapters set to ${adapters.join(", ") || "none"}\n`;
|
|
331
547
|
},
|
|
@@ -338,41 +554,43 @@ const CONFIG_KEY_HANDLERS = [
|
|
|
338
554
|
}
|
|
339
555
|
},
|
|
340
556
|
{
|
|
341
|
-
key: "provider-retry-
|
|
342
|
-
showLabel: "Provider retry
|
|
343
|
-
showValue: (config) => `${
|
|
557
|
+
key: "provider-retry-delays-seconds",
|
|
558
|
+
showLabel: "Provider retry delays",
|
|
559
|
+
showValue: (config) => `${resolveProviderRetryDelaysSeconds(config.providerRetryDelaysSeconds).join(", ")} seconds`,
|
|
344
560
|
set(args, store) {
|
|
561
|
+
const usage = "Runtime config set usage: yui config runtime set provider-retry-delays-seconds <comma-separated-seconds>.";
|
|
345
562
|
if (args.length !== 1)
|
|
346
|
-
throw usageError(
|
|
347
|
-
const
|
|
348
|
-
saveConfigKey(store, (config) => ({ ...config,
|
|
349
|
-
return `Provider retry
|
|
563
|
+
throw usageError(usage);
|
|
564
|
+
const providerRetryDelaysSeconds = validatedConfigValue(() => resolveProviderRetryDelaysSeconds(args[0].split(",").map(Number)), usage);
|
|
565
|
+
saveConfigKey(store, (config) => ({ ...config, providerRetryDelaysSeconds }));
|
|
566
|
+
return `Provider retry delays set to ${providerRetryDelaysSeconds.join(", ")} seconds\n`;
|
|
350
567
|
},
|
|
351
568
|
clear(store) {
|
|
352
569
|
saveConfigKey(store, (config) => {
|
|
353
|
-
const {
|
|
570
|
+
const { providerRetryDelaysSeconds: _removed, ...rest } = config;
|
|
354
571
|
return rest;
|
|
355
572
|
});
|
|
356
|
-
return
|
|
573
|
+
return `Provider retry delays reset to ${DEFAULT_PROVIDER_RETRY_DELAYS_SECONDS.join(", ")} seconds\n`;
|
|
357
574
|
}
|
|
358
575
|
},
|
|
359
576
|
{
|
|
360
|
-
key: "
|
|
361
|
-
showLabel: "
|
|
362
|
-
showValue: (config) => (
|
|
577
|
+
key: "provider-retry-max-window-seconds",
|
|
578
|
+
showLabel: "Provider retry max window",
|
|
579
|
+
showValue: (config) => `${resolveProviderRetryMaxWindowSeconds(config.providerRetryMaxWindowSeconds)} seconds`,
|
|
363
580
|
set(args, store) {
|
|
581
|
+
const usage = "Runtime config set usage: yui config runtime set provider-retry-max-window-seconds <positive-seconds>.";
|
|
364
582
|
if (args.length !== 1)
|
|
365
|
-
throw usageError(
|
|
366
|
-
const
|
|
367
|
-
saveConfigKey(store, (config) => ({ ...config,
|
|
368
|
-
return `
|
|
583
|
+
throw usageError(usage);
|
|
584
|
+
const providerRetryMaxWindowSeconds = validatedConfigValue(() => resolveProviderRetryMaxWindowSeconds(Number(args[0])), usage);
|
|
585
|
+
saveConfigKey(store, (config) => ({ ...config, providerRetryMaxWindowSeconds }));
|
|
586
|
+
return `Provider retry max window set to ${providerRetryMaxWindowSeconds} seconds\n`;
|
|
369
587
|
},
|
|
370
588
|
clear(store) {
|
|
371
589
|
saveConfigKey(store, (config) => {
|
|
372
|
-
const {
|
|
590
|
+
const { providerRetryMaxWindowSeconds: _removed, ...rest } = config;
|
|
373
591
|
return rest;
|
|
374
592
|
});
|
|
375
|
-
return
|
|
593
|
+
return `Provider retry max window reset to ${DEFAULT_PROVIDER_RETRY_MAX_WINDOW_SECONDS} seconds\n`;
|
|
376
594
|
}
|
|
377
595
|
},
|
|
378
596
|
{
|
|
@@ -381,8 +599,8 @@ const CONFIG_KEY_HANDLERS = [
|
|
|
381
599
|
showValue: (config) => resolveTmuxBin(config.tmuxBin),
|
|
382
600
|
set(args, store) {
|
|
383
601
|
if (args.length !== 1)
|
|
384
|
-
throw usageError("
|
|
385
|
-
const tmuxBin = validatedConfigValue(() => resolveTmuxBin(args[0]), "
|
|
602
|
+
throw usageError("Tools config set usage: yui config tools set tmux-bin <path>.");
|
|
603
|
+
const tmuxBin = validatedConfigValue(() => resolveTmuxBin(args[0]), "Tools config set usage: yui config tools set tmux-bin <path>.");
|
|
386
604
|
saveConfigKey(store, (config) => ({ ...config, tmuxBin }));
|
|
387
605
|
return `Tmux bin set to ${tmuxBin}\n`;
|
|
388
606
|
},
|
|
@@ -395,41 +613,43 @@ const CONFIG_KEY_HANDLERS = [
|
|
|
395
613
|
}
|
|
396
614
|
},
|
|
397
615
|
{
|
|
398
|
-
key: "
|
|
399
|
-
showLabel: "
|
|
400
|
-
showValue: (config) =>
|
|
616
|
+
key: "tmux-history-limit",
|
|
617
|
+
showLabel: "Tmux history limit",
|
|
618
|
+
showValue: (config) => String(resolveTmuxHistoryLimit(config.tmuxHistoryLimit)),
|
|
401
619
|
set(args, store) {
|
|
620
|
+
const usage = "Tools config set usage: yui config tools set tmux-history-limit <1000-1000000>.";
|
|
402
621
|
if (args.length !== 1)
|
|
403
|
-
throw usageError(
|
|
404
|
-
const
|
|
405
|
-
saveConfigKey(store, (config) => ({ ...config,
|
|
406
|
-
return `
|
|
622
|
+
throw usageError(usage);
|
|
623
|
+
const tmuxHistoryLimit = validatedConfigValue(() => resolveTmuxHistoryLimit(Number(args[0])), usage);
|
|
624
|
+
saveConfigKey(store, (config) => ({ ...config, tmuxHistoryLimit }));
|
|
625
|
+
return `Tmux history limit set to ${tmuxHistoryLimit}\n`;
|
|
407
626
|
},
|
|
408
627
|
clear(store) {
|
|
409
628
|
saveConfigKey(store, (config) => {
|
|
410
|
-
const {
|
|
629
|
+
const { tmuxHistoryLimit: _removed, ...rest } = config;
|
|
411
630
|
return rest;
|
|
412
631
|
});
|
|
413
|
-
return
|
|
632
|
+
return `Tmux history limit reset to ${DEFAULT_TMUX_HISTORY_LIMIT}\n`;
|
|
414
633
|
}
|
|
415
634
|
},
|
|
416
635
|
{
|
|
417
|
-
key: "telemetry-
|
|
418
|
-
showLabel: "
|
|
419
|
-
showValue: (config) =>
|
|
636
|
+
key: "telemetry-enabled",
|
|
637
|
+
showLabel: "Diagnostic telemetry",
|
|
638
|
+
showValue: (config) => (resolveTelemetryEnabled(config.telemetryEnabled) ? "on" : "off"),
|
|
420
639
|
set(args, store) {
|
|
640
|
+
const usage = "Tools config set usage: yui config tools set telemetry-enabled <true|false>.";
|
|
421
641
|
if (args.length !== 1)
|
|
422
|
-
throw usageError(
|
|
423
|
-
const
|
|
424
|
-
saveConfigKey(store, (config) => ({ ...config,
|
|
425
|
-
return `
|
|
642
|
+
throw usageError(usage);
|
|
643
|
+
const telemetryEnabled = validatedConfigValue(() => resolveTelemetryEnabled(parseBooleanConfigValue(args[0])), usage);
|
|
644
|
+
saveConfigKey(store, (config) => ({ ...config, telemetryEnabled }));
|
|
645
|
+
return `Diagnostic telemetry set to ${telemetryEnabled ? "on" : "off"}\n`;
|
|
426
646
|
},
|
|
427
647
|
clear(store) {
|
|
428
648
|
saveConfigKey(store, (config) => {
|
|
429
|
-
const {
|
|
649
|
+
const { telemetryEnabled: _removed, ...rest } = config;
|
|
430
650
|
return rest;
|
|
431
651
|
});
|
|
432
|
-
return "
|
|
652
|
+
return "Diagnostic telemetry reset to off\n";
|
|
433
653
|
}
|
|
434
654
|
},
|
|
435
655
|
{
|
|
@@ -438,8 +658,8 @@ const CONFIG_KEY_HANDLERS = [
|
|
|
438
658
|
showValue: (config) => String(resolveTelemetryTerminalKeep(config.telemetryTerminalKeep)),
|
|
439
659
|
set(args, store) {
|
|
440
660
|
if (args.length !== 1)
|
|
441
|
-
throw usageError("
|
|
442
|
-
const telemetryTerminalKeep = validatedConfigValue(() => resolveTelemetryTerminalKeep(Number(args[0])), "
|
|
661
|
+
throw usageError("Tools config set usage: yui config tools set telemetry-terminal-keep <n>.");
|
|
662
|
+
const telemetryTerminalKeep = validatedConfigValue(() => resolveTelemetryTerminalKeep(Number(args[0])), "Tools config set usage: yui config tools set telemetry-terminal-keep <n>.");
|
|
443
663
|
saveConfigKey(store, (config) => ({ ...config, telemetryTerminalKeep }));
|
|
444
664
|
return `Telemetry terminal keep set to ${telemetryTerminalKeep}\n`;
|
|
445
665
|
},
|
|
@@ -457,8 +677,8 @@ const CONFIG_KEY_HANDLERS = [
|
|
|
457
677
|
showValue: (config) => String(resolveTelemetryRunCap(config.telemetryRunCap)),
|
|
458
678
|
set(args, store) {
|
|
459
679
|
if (args.length !== 1)
|
|
460
|
-
throw usageError("
|
|
461
|
-
const telemetryRunCap = validatedConfigValue(() => resolveTelemetryRunCap(Number(args[0])), "
|
|
680
|
+
throw usageError("Tools config set usage: yui config tools set telemetry-run-cap <n>.");
|
|
681
|
+
const telemetryRunCap = validatedConfigValue(() => resolveTelemetryRunCap(Number(args[0])), "Tools config set usage: yui config tools set telemetry-run-cap <n>.");
|
|
462
682
|
saveConfigKey(store, (config) => ({ ...config, telemetryRunCap }));
|
|
463
683
|
return `Telemetry run cap set to ${telemetryRunCap}\n`;
|
|
464
684
|
},
|
|
@@ -548,3 +768,30 @@ const CONFIG_KEY_HANDLERS = [
|
|
|
548
768
|
}
|
|
549
769
|
}
|
|
550
770
|
];
|
|
771
|
+
const configuredHandlerKeys = new Set(CONFIG_KEY_HANDLERS.map(({ key }) => key));
|
|
772
|
+
const missingConfigHandlers = CONFIG_KEYS.filter((key) => !configuredHandlerKeys.has(key));
|
|
773
|
+
const duplicateConfigHandlers = CONFIG_KEY_HANDLERS
|
|
774
|
+
.map(({ key }) => key)
|
|
775
|
+
.filter((key, index, keys) => keys.indexOf(key) !== index);
|
|
776
|
+
if (missingConfigHandlers.length > 0 || duplicateConfigHandlers.length > 0) {
|
|
777
|
+
throw new Error(`Config handler/catalog drift: missing=${missingConfigHandlers.join(",") || "none"}; `
|
|
778
|
+
+ `duplicate=${duplicateConfigHandlers.join(",") || "none"}.`);
|
|
779
|
+
}
|
|
780
|
+
function resolveDefaultWorkspace(value, store) {
|
|
781
|
+
const requested = resolve(value);
|
|
782
|
+
if (store.rootDirectory === undefined)
|
|
783
|
+
return requested;
|
|
784
|
+
const requestedHome = resolve(store.rootDirectory());
|
|
785
|
+
assertWorkspaceOutsideHome(requested, requestedHome);
|
|
786
|
+
mkdirSync(requested, { recursive: true, mode: 0o700 });
|
|
787
|
+
const workspace = realpathSync(requested);
|
|
788
|
+
const home = realpathSync(requestedHome);
|
|
789
|
+
assertWorkspaceOutsideHome(workspace, home);
|
|
790
|
+
return workspace;
|
|
791
|
+
}
|
|
792
|
+
function assertWorkspaceOutsideHome(workspace, home) {
|
|
793
|
+
const fromHome = relative(home, workspace);
|
|
794
|
+
if (fromHome === "" || (!fromHome.startsWith("..") && !isAbsolute(fromHome))) {
|
|
795
|
+
throw usageError("Default workspace must be outside YUI_HOME.");
|
|
796
|
+
}
|
|
797
|
+
}
|