@zq-silk/yui 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commandCatalog.js +294 -286
- package/dist/cli/commandDiscovery.js +69 -0
- package/dist/cli/completion.js +22 -227
- package/dist/cli/dynamicCompletion.js +12 -6
- package/dist/cli/invocationAuthority.js +2 -2
- package/dist/cli/invocationRouter.js +18 -7
- package/dist/cli.js +71 -23
- package/dist/commands/capabilityCommands.js +5 -5
- package/dist/commands/globalRoleCommands.js +2 -1
- package/dist/commands/taskCommands.js +11 -0
- package/dist/context/taskContext.js +6 -2
- package/dist/kernel/accessAssessment.js +1 -0
- package/dist/kernel/builtinCapabilities.js +28 -1
- package/dist/kernel/capabilityRegistry.js +64 -19
- package/dist/nativeAgent/agent.js +173 -0
- package/dist/nativeAgent/contracts.js +1 -0
- package/dist/nativeAgent/demo.js +29 -0
- package/dist/nativeAgent/index.js +3 -0
- package/dist/nativeAgent/mockProvider.js +43 -0
- package/dist/nativeAgent/textTools.js +156 -0
- package/dist/nativeAgent/validation.js +95 -0
- package/dist/plugins/pluginService.js +98 -8
- package/dist/resources/projectResourceService.js +23 -1
- package/dist/runtime/managedIdentity.js +6 -0
- package/dist/surface/surfaceContributions.js +3 -0
- package/dist/task/taskAuthority.js +5 -10
- package/dist/web/assets/assetManifest.js +38 -22
- package/dist/web/assets/client/api.js +115 -0
- package/dist/web/assets/client/app.js +555 -863
- package/dist/web/assets/client/components.js +323 -897
- package/dist/web/assets/client/detail.js +333 -0
- package/dist/web/assets/client/dock.js +225 -0
- package/dist/web/assets/client/dom.js +55 -3
- package/dist/web/assets/client/evidence.js +172 -0
- package/dist/web/assets/client/format.js +25 -14
- package/dist/web/assets/client/forms.js +230 -0
- package/dist/web/assets/client/i18n.js +1020 -791
- package/dist/web/assets/client/overview.js +115 -0
- package/dist/web/assets/client/records.js +63 -0
- package/dist/web/assets/client/sections.js +313 -0
- package/dist/web/assets/client/sidebar.js +117 -0
- package/dist/web/assets/client/theme.js +61 -22
- package/dist/web/assets/icons.js +39 -0
- package/dist/web/assets/shell.js +124 -118
- package/dist/web/assets/styles/base.js +41 -0
- package/dist/web/assets/styles/components.js +167 -0
- package/dist/web/assets/styles/layout.js +49 -77
- package/dist/web/assets/styles/markdown.js +16 -24
- package/dist/web/assets/styles/responsive.js +33 -44
- package/dist/web/assets/styles/tokens.js +65 -89
- package/dist/web/assets/styles/views.js +289 -0
- package/dist/web/webServer.js +2 -1
- package/docs/release-workflow.md +23 -0
- package/docs/release-workflow.zh-CN.md +16 -0
- package/package.json +1 -1
- package/skills/yui-runtime/SKILL.md +26 -0
- package/dist/web/assets/client/taskSummary.js +0 -350
- package/dist/web/assets/client/taskSurface.js +0 -616
- package/dist/web/assets/client/view.js +0 -608
- package/dist/web/assets/styles/cards.js +0 -247
- package/dist/web/assets/styles/widgets.js +0 -168
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { ROOT_COMMAND, findCommandNode } from "./commandCatalog.js";
|
|
2
|
+
import { hasManagedIdentity } from "../runtime/managedIdentity.js";
|
|
3
|
+
/** Offline presentation hints only. Execution still authenticates the original
|
|
4
|
+
* caller, including stale/incomplete identities, at its existing boundary. */
|
|
5
|
+
export function discoveryAudience(env) {
|
|
6
|
+
if (!hasManagedIdentity(env))
|
|
7
|
+
return "public";
|
|
8
|
+
if (!env.YUI_ROLE || !env.YUI_NATIVE_SESSION_ID)
|
|
9
|
+
return "unbound";
|
|
10
|
+
if (env.YUI_SESSION_SCOPE === "global")
|
|
11
|
+
return env.YUI_ROLE === "operator" ? "operator" : "global";
|
|
12
|
+
if (env.YUI_SESSION_SCOPE === "task" && env.YUI_TASK_ID)
|
|
13
|
+
return env.YUI_ROLE === "leader" ? "leader" : "assignment";
|
|
14
|
+
return "unbound";
|
|
15
|
+
}
|
|
16
|
+
export function discoveryCommandTree(env) {
|
|
17
|
+
const audience = discoveryAudience(env);
|
|
18
|
+
const project = (node) => {
|
|
19
|
+
const children = node.children.map(project).filter((child) => child !== undefined);
|
|
20
|
+
const eligible = node.discovery?.surface !== "runtime" && node.discovery?.audiences.includes(audience);
|
|
21
|
+
if (!children.length && (!eligible || node.kind === "group") && node !== ROOT_COMMAND)
|
|
22
|
+
return undefined;
|
|
23
|
+
const executable = eligible && node.kind !== "group";
|
|
24
|
+
const kind = children.length ? executable ? "hybrid" : "group" : node.kind;
|
|
25
|
+
const template = `${node.path.join(" ")}${kind === "group" ? " <command>" : ""}`;
|
|
26
|
+
const omittedOptions = Object.entries(node.discovery?.optionAudiences ?? {})
|
|
27
|
+
.filter(([, audiences]) => !audiences.includes(audience)).map(([option]) => option);
|
|
28
|
+
const text = (value) => omittedOptions.reduce((line, option) => line.replace(new RegExp(`\\s*\\[${option}(?:\\s+[^\\]]*)?\\]`, "g"), ""), value);
|
|
29
|
+
const options = node.options.filter(option => !omittedOptions.includes(option));
|
|
30
|
+
// Group prose often contains concrete commands for several audiences.
|
|
31
|
+
// Generate its usage from the same retained children, not unfiltered text.
|
|
32
|
+
return {
|
|
33
|
+
...node, children, hidden: false, kind,
|
|
34
|
+
usage: kind === "group" ? [template] : node.usage.map(text),
|
|
35
|
+
examples: kind === "group" ? children.slice(0, 3).map(child => child.usage[0]) : node.examples.map(text),
|
|
36
|
+
options, optionValues: Object.fromEntries(Object.entries(node.optionValues).filter(([option]) => options.includes(option))),
|
|
37
|
+
sections: node.sections.map(section => ({ ...section,
|
|
38
|
+
entries: section.entries.filter(entry => children.some(child => child.name === entry) || (executable && node.values.some(value => value.name === entry)))
|
|
39
|
+
})).filter(section => section.entries.length > 0),
|
|
40
|
+
...(executable ? {} : { options: [], values: [], optionValues: {}, argumentValues: {} })
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
const tree = project(ROOT_COMMAND);
|
|
44
|
+
if (audience === "public") {
|
|
45
|
+
const sections = [
|
|
46
|
+
{ id: "start", title: "Start work", entries: ["operator", "project"] },
|
|
47
|
+
{ id: "progress", title: "Inspect progress", entries: ["task", "jobs", "execution", "telemetry"] },
|
|
48
|
+
{ id: "participate", title: "Participate and control", entries: ["role", "session"] },
|
|
49
|
+
{ id: "maintain", title: "Configure and maintain", entries: ["help", "version", "setup", "start", "doctor", "config", "web", "controller", "resources", "update", "upgrade", "release"] }
|
|
50
|
+
];
|
|
51
|
+
return { ...tree, sections: sections.map(section => ({ ...section,
|
|
52
|
+
entries: section.entries.filter(entry => tree.children.some(child => child.name === entry))
|
|
53
|
+
})) };
|
|
54
|
+
}
|
|
55
|
+
// Help accepts catalog paths, and config describe accepts only retained domains.
|
|
56
|
+
const config = findCommandNode(["config"], tree);
|
|
57
|
+
if (config) {
|
|
58
|
+
const domains = config.children.filter(child => !["show", "describe"].includes(child.name)).map(child => child.name);
|
|
59
|
+
return {
|
|
60
|
+
...tree, children: tree.children.map(child => child !== config ? child : {
|
|
61
|
+
...config, children: config.children.map(node => node.name !== "describe" ? node : {
|
|
62
|
+
...node, argumentValues: { 0: domains }, usage: ["yui config describe [domain]"],
|
|
63
|
+
examples: ["yui config describe"]
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return tree;
|
|
69
|
+
}
|
package/dist/cli/completion.js
CHANGED
|
@@ -1,235 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/** Resolve the current caller on every completion, never the installer's view.
|
|
2
|
+
* Command names are local; target selectors use authenticated scoped reads. */
|
|
3
3
|
export function renderCompletion(shell) {
|
|
4
|
-
const entries = collectEntries(ROOT_COMMAND);
|
|
5
4
|
switch (shell) {
|
|
6
|
-
case "bash": return
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const visit = (node) => {
|
|
15
|
-
if (!node.hidden && !node.commandPathArguments) {
|
|
16
|
-
const policy = findInteractionPolicy(node);
|
|
17
|
-
entries.push({
|
|
18
|
-
path: node.path.slice(1).join(" "),
|
|
19
|
-
immediate: orderedImmediateTokens(node),
|
|
20
|
-
options: node.options,
|
|
21
|
-
dynamicArguments: policy?.selectors.flatMap((selector) => selector.argumentIndex === undefined ? [] : [selector.argumentIndex]) ?? [],
|
|
22
|
-
dynamicOptions: policy?.selectors.flatMap((selector) => selector.option === undefined ? [] : [selector.option]) ?? []
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
visibleChildren(node).forEach(visit);
|
|
26
|
-
};
|
|
27
|
-
visit(root);
|
|
28
|
-
return entries;
|
|
29
|
-
}
|
|
30
|
-
function manifest(prefix) {
|
|
31
|
-
return listPublicCommandPaths().map((path) => `${prefix} ${path}`).join("\n");
|
|
32
|
-
}
|
|
33
|
-
function renderBash(entries) {
|
|
34
|
-
const functionName = "_yui";
|
|
35
|
-
return `${manifest("# yui command:")}
|
|
36
|
-
${functionName}() {
|
|
37
|
-
local current path previous dynamic_candidate dynamic=false
|
|
38
|
-
local -a candidates dynamic_candidates
|
|
39
|
-
current="\${COMP_WORDS[COMP_CWORD]}"
|
|
40
|
-
path=""
|
|
41
|
-
if (( COMP_CWORD > 1 )); then
|
|
42
|
-
path="\${COMP_WORDS[*]:1:COMP_CWORD-1}"
|
|
43
|
-
fi
|
|
44
|
-
previous="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
45
|
-
if [[ "$current" != -* ]]; then
|
|
46
|
-
case "$COMP_CWORD:$path" in
|
|
47
|
-
${bashDynamicArgumentCases(entries)}
|
|
48
|
-
esac
|
|
49
|
-
fi
|
|
50
|
-
if [[ "$dynamic" == false ]]; then
|
|
51
|
-
case "$previous:$path" in
|
|
52
|
-
${bashDynamicOptionCases(entries)}
|
|
53
|
-
esac
|
|
54
|
-
fi
|
|
55
|
-
if [[ "$dynamic" == true ]]; then
|
|
56
|
-
while IFS= read -r dynamic_candidate; do
|
|
57
|
-
[[ -n "$dynamic_candidate" ]] && dynamic_candidates+=("$dynamic_candidate")
|
|
58
|
-
done < <(command yui config completion candidates "$current" -- "\${COMP_WORDS[@]:1:COMP_CWORD-1}" 2>/dev/null)
|
|
59
|
-
candidates=("\${dynamic_candidates[@]}")
|
|
60
|
-
elif [[ "$current" == -* ]]; then
|
|
61
|
-
case "$path" in
|
|
62
|
-
${bashStaticOptionCases(entries)}
|
|
63
|
-
*) candidates=();;
|
|
64
|
-
esac
|
|
65
|
-
else
|
|
66
|
-
case "$path" in
|
|
67
|
-
${entries.map((entry) => ` ${bashPattern(entry.path)}) candidates=(${[...entry.immediate, ...entry.options].map(shellQuote).join(" ")});;`).join("\n")}
|
|
68
|
-
*) candidates=();;
|
|
69
|
-
esac
|
|
70
|
-
fi
|
|
71
|
-
COMPREPLY=( $(compgen -W "\${candidates[*]}" -- "$current") )
|
|
72
|
-
}
|
|
73
|
-
complete -F ${functionName} yui
|
|
5
|
+
case "bash": return `_yui() {
|
|
6
|
+
local candidate current="\${COMP_WORDS[COMP_CWORD]}"
|
|
7
|
+
COMPREPLY=()
|
|
8
|
+
while IFS= read -r candidate; do
|
|
9
|
+
[[ -n "$candidate" ]] && COMPREPLY+=("$candidate")
|
|
10
|
+
done < <(command yui config completion candidates "$current" -- "\${COMP_WORDS[@]:1:COMP_CWORD-1}" 2>/dev/null)
|
|
11
|
+
}
|
|
12
|
+
complete -F _yui yui
|
|
74
13
|
`;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return `#compdef yui
|
|
78
|
-
${manifest("# yui command:")}
|
|
79
|
-
local current command_path previous dynamic_output dynamic=false
|
|
14
|
+
case "zsh": return `#compdef yui
|
|
15
|
+
local output
|
|
80
16
|
local -a candidates
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (( CURRENT > 2 )); then
|
|
84
|
-
command_path="\${(j: :)words[2,CURRENT-1]}"
|
|
85
|
-
fi
|
|
86
|
-
previous="$words[CURRENT-1]"
|
|
87
|
-
if [[ "$current" != -* ]]; then
|
|
88
|
-
case "$CURRENT:$command_path" in
|
|
89
|
-
${zshDynamicArgumentCases(entries)}
|
|
90
|
-
esac
|
|
91
|
-
fi
|
|
92
|
-
if [[ "$dynamic" == false ]]; then
|
|
93
|
-
case "$previous:$command_path" in
|
|
94
|
-
${zshDynamicOptionCases(entries)}
|
|
95
|
-
esac
|
|
96
|
-
fi
|
|
97
|
-
if [[ "$dynamic" == true ]]; then
|
|
98
|
-
dynamic_output="$(command yui config completion candidates "$current" -- "\${(@)words[2,CURRENT-1]}" 2>/dev/null)"
|
|
99
|
-
candidates=("\${(@f)dynamic_output}")
|
|
100
|
-
elif [[ "$current" == -* ]]; then
|
|
101
|
-
case "$command_path" in
|
|
102
|
-
${zshStaticOptionCases(entries)}
|
|
103
|
-
*) candidates=();;
|
|
104
|
-
esac
|
|
105
|
-
else
|
|
106
|
-
case "$command_path" in
|
|
107
|
-
${entries.map((entry) => ` ${zshPattern(entry.path)}) candidates=(${[...entry.immediate, ...entry.options].map(shellQuote).join(" ")});;`).join("\n")}
|
|
108
|
-
*) candidates=();;
|
|
109
|
-
esac
|
|
110
|
-
fi
|
|
17
|
+
output="$(command yui config completion candidates "$words[CURRENT]" -- "\${(@)words[2,CURRENT-1]}" 2>/dev/null)"
|
|
18
|
+
candidates=("\${(@f)output}")
|
|
111
19
|
(( \${#candidates[@]} > 0 )) && compadd -- "$candidates[@]"
|
|
112
20
|
`;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
"
|
|
121
|
-
" test (count $actual) -eq (count $argv); or return 1",
|
|
122
|
-
" for index in (seq (count $argv))",
|
|
123
|
-
" test \"$actual[$index]\" = \"$argv[$index]\"; or return 1",
|
|
124
|
-
" end",
|
|
125
|
-
"end",
|
|
126
|
-
`function ${prefix}_at_command_options`,
|
|
127
|
-
" string match -q -- '-*' (commandline -ct); or return 1",
|
|
128
|
-
" set -l actual (commandline -opc)",
|
|
129
|
-
" set -e actual[1]",
|
|
130
|
-
" test (count $actual) -ge (count $argv); or return 1",
|
|
131
|
-
" for index in (seq (count $argv))",
|
|
132
|
-
" test \"$actual[$index]\" = \"$argv[$index]\"; or return 1",
|
|
133
|
-
" end",
|
|
134
|
-
"end",
|
|
135
|
-
`function ${prefix}_needs_dynamic`,
|
|
136
|
-
" set -l mode $argv[1]",
|
|
137
|
-
" set -l expected $argv[2]",
|
|
138
|
-
" set -l expected_path $argv[3..-1]",
|
|
139
|
-
" set -l actual (commandline -opc)",
|
|
140
|
-
" set -e actual[1]",
|
|
141
|
-
" test (count $actual) -ge (count $expected_path); or return 1",
|
|
142
|
-
" for index in (seq (count $expected_path))",
|
|
143
|
-
" test \"$actual[$index]\" = \"$expected_path[$index]\"; or return 1",
|
|
144
|
-
" end",
|
|
145
|
-
" if test \"$mode\" = argument",
|
|
146
|
-
" string match -q -- '-*' (commandline -ct); and return 1",
|
|
147
|
-
" test (count $actual) -eq \"$expected\"",
|
|
148
|
-
" else",
|
|
149
|
-
" test \"$actual[-1]\" = \"$expected\"",
|
|
150
|
-
" end",
|
|
151
|
-
"end",
|
|
152
|
-
`function ${prefix}_dynamic`,
|
|
153
|
-
" set -l words (commandline -opc)",
|
|
154
|
-
" yui config completion candidates (commandline -ct) -- $words[2..-1] 2>/dev/null",
|
|
155
|
-
"end"
|
|
156
|
-
];
|
|
157
|
-
for (const entry of entries) {
|
|
158
|
-
for (const argument of entry.dynamicArguments) {
|
|
159
|
-
const condition = `${prefix}_needs_dynamic argument ${argument} ${fishPath(entry.path)}`;
|
|
160
|
-
lines.push(`complete -c yui -f -n ${fishQuote(condition)} -a '(${prefix}_dynamic)'`);
|
|
161
|
-
}
|
|
162
|
-
for (const option of entry.dynamicOptions) {
|
|
163
|
-
const condition = `${prefix}_needs_dynamic option ${option} ${fishPath(entry.path)}`;
|
|
164
|
-
lines.push(`complete -c yui -f -n ${fishQuote(condition)} -a '(${prefix}_dynamic)'`);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
for (const entry of entries) {
|
|
168
|
-
if (entry.immediate.length > 0) {
|
|
169
|
-
const condition = entry.path.length === 0
|
|
170
|
-
? "__fish_use_subcommand"
|
|
171
|
-
: `${prefix}_at_path ${fishPath(entry.path)}${fishInitialDynamicExclusion(entry, prefix)}`;
|
|
172
|
-
lines.push(`complete -c yui -f -n ${fishQuote(condition)} -a '${escapeSingle(entry.immediate.join(" "))}'`);
|
|
173
|
-
}
|
|
174
|
-
if (entry.options.length > 0) {
|
|
175
|
-
const path = fishPath(entry.path);
|
|
176
|
-
const exact = `${prefix}_at_path ${path}${fishInitialDynamicExclusion(entry, prefix)}`;
|
|
177
|
-
const condition = `begin; ${exact}; end; or ${prefix}_at_command_options ${path}`;
|
|
178
|
-
lines.push(`complete -c yui -f -n ${fishQuote(condition)} -a '${escapeSingle(entry.options.join(" "))}'`);
|
|
179
|
-
}
|
|
21
|
+
case "fish": return `function __yui_candidates
|
|
22
|
+
set -l words (commandline -opc)
|
|
23
|
+
set -l current (commandline -ct)
|
|
24
|
+
command yui config completion candidates "$current" -- $words[2..-1] 2>/dev/null
|
|
25
|
+
end
|
|
26
|
+
complete -c yui -f -a '(__yui_candidates)'
|
|
27
|
+
`;
|
|
28
|
+
default: throw new Error("Completion shell must be one of bash, zsh, fish.");
|
|
180
29
|
}
|
|
181
|
-
return `${lines.join("\n")}\n`;
|
|
182
|
-
}
|
|
183
|
-
function bashDynamicArgumentCases(entries) {
|
|
184
|
-
return entries.flatMap((entry) => entry.dynamicArguments.map((argument) => ` ${dynamicArgumentPattern(argument + 1, entry.path)}) dynamic=true;;`)).join("\n");
|
|
185
|
-
}
|
|
186
|
-
function zshDynamicArgumentCases(entries) {
|
|
187
|
-
return entries.flatMap((entry) => entry.dynamicArguments.map((argument) => ` ${dynamicArgumentPattern(argument + 2, entry.path)}) dynamic=true;;`)).join("\n");
|
|
188
|
-
}
|
|
189
|
-
function dynamicArgumentPattern(position, path) {
|
|
190
|
-
return `${position}:${shellQuote(path)}|${position}:${shellQuote(`${path} `)}*`;
|
|
191
|
-
}
|
|
192
|
-
function bashDynamicOptionCases(entries) {
|
|
193
|
-
return entries.flatMap((entry) => entry.dynamicOptions.map((option) => ` ${dynamicOptionPattern(option, entry.path)}) dynamic=true;;`)).join("\n");
|
|
194
|
-
}
|
|
195
|
-
function zshDynamicOptionCases(entries) {
|
|
196
|
-
return entries.flatMap((entry) => entry.dynamicOptions.map((option) => ` ${dynamicOptionPattern(option, entry.path)}) dynamic=true;;`)).join("\n");
|
|
197
|
-
}
|
|
198
|
-
function dynamicOptionPattern(option, path) {
|
|
199
|
-
return `${shellQuote(`${option}:${path}`)}|${shellQuote(`${option}:${path} `)}*`;
|
|
200
|
-
}
|
|
201
|
-
function bashStaticOptionCases(entries) {
|
|
202
|
-
return staticOptionEntries(entries).map((entry) => ` ${commandPrefixPattern(entry.path)}) candidates=(${entry.options.map(shellQuote).join(" ")});;`).join("\n");
|
|
203
|
-
}
|
|
204
|
-
function zshStaticOptionCases(entries) {
|
|
205
|
-
return staticOptionEntries(entries).map((entry) => ` ${commandPrefixPattern(entry.path)}) candidates=(${entry.options.map(shellQuote).join(" ")});;`).join("\n");
|
|
206
|
-
}
|
|
207
|
-
function staticOptionEntries(entries) {
|
|
208
|
-
return entries.filter((entry) => entry.options.length > 0)
|
|
209
|
-
.sort((left, right) => right.path.split(" ").length - left.path.split(" ").length);
|
|
210
|
-
}
|
|
211
|
-
function commandPrefixPattern(path) {
|
|
212
|
-
return `${shellQuote(path)}|${shellQuote(`${path} `)}*`;
|
|
213
|
-
}
|
|
214
|
-
function fishInitialDynamicExclusion(entry, prefix) {
|
|
215
|
-
const initial = entry.dynamicArguments.filter((position) => position === entry.path.split(" ").filter(Boolean).length);
|
|
216
|
-
return initial.map((position) => `; and not ${prefix}_needs_dynamic argument ${position} ${fishPath(entry.path)}`).join("");
|
|
217
|
-
}
|
|
218
|
-
function fishPath(path) {
|
|
219
|
-
return path.split(" ").filter(Boolean).join(" ");
|
|
220
|
-
}
|
|
221
|
-
function bashPattern(path) {
|
|
222
|
-
return path.length === 0 ? '""' : shellQuote(path);
|
|
223
|
-
}
|
|
224
|
-
function zshPattern(path) {
|
|
225
|
-
return path.length === 0 ? '""' : shellQuote(path);
|
|
226
|
-
}
|
|
227
|
-
function shellQuote(value) {
|
|
228
|
-
return `'${escapeSingle(value)}'`;
|
|
229
|
-
}
|
|
230
|
-
function fishQuote(value) {
|
|
231
|
-
return `'${escapeSingle(value)}'`;
|
|
232
|
-
}
|
|
233
|
-
function escapeSingle(value) {
|
|
234
|
-
return value.replaceAll("'", "'\\''");
|
|
235
30
|
}
|
|
@@ -5,7 +5,16 @@ export async function resolveCompletionCandidates(input) {
|
|
|
5
5
|
const words = input.words[0] === "yui"
|
|
6
6
|
? input.words.slice(1)
|
|
7
7
|
: [...input.words];
|
|
8
|
-
const
|
|
8
|
+
const root = input.root ?? ROOT_COMMAND;
|
|
9
|
+
if (words[0] === "help") {
|
|
10
|
+
const { node, consumed } = resolveCommand(words.slice(1), root);
|
|
11
|
+
return consumed === words.length - 1 ? prefix(visibleChildren(node).map(child => child.name), input.current) : [];
|
|
12
|
+
}
|
|
13
|
+
const { node, consumed } = resolveCommand(words, root);
|
|
14
|
+
if (node.kind === "group" && consumed < words.length)
|
|
15
|
+
return [];
|
|
16
|
+
if (input.current.startsWith("-"))
|
|
17
|
+
return prefix(node.options, input.current);
|
|
9
18
|
if (consumed === words.length && node.children.length > 0) {
|
|
10
19
|
return prefix(visibleChildren(node).map((child) => child.name), input.current);
|
|
11
20
|
}
|
|
@@ -13,9 +22,6 @@ export async function resolveCompletionCandidates(input) {
|
|
|
13
22
|
if (previous !== undefined && Object.hasOwn(node.optionValues, previous)) {
|
|
14
23
|
return prefix(node.optionValues[previous] ?? [], input.current);
|
|
15
24
|
}
|
|
16
|
-
if (input.current.startsWith("-")) {
|
|
17
|
-
return prefix(node.options, input.current);
|
|
18
|
-
}
|
|
19
25
|
const argumentPosition = words.length;
|
|
20
26
|
const policy = findInteractionPolicy(node);
|
|
21
27
|
const selector = policy?.selectors.find((candidate) => selectorApplies(candidate, previous, argumentPosition));
|
|
@@ -31,8 +37,8 @@ function selectorApplies(selector, previous, argumentPosition) {
|
|
|
31
37
|
return selector.option === previous;
|
|
32
38
|
return selector.argumentIndex === argumentPosition;
|
|
33
39
|
}
|
|
34
|
-
function resolveCommand(words) {
|
|
35
|
-
let node =
|
|
40
|
+
function resolveCommand(words, root) {
|
|
41
|
+
let node = root;
|
|
36
42
|
let consumed = 0;
|
|
37
43
|
while (consumed < words.length && node.kind !== "leaf") {
|
|
38
44
|
const child = findChild(node, words[consumed] ?? "");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { usageError } from "../errors/cliError.js";
|
|
2
|
+
import { hasManagedIdentity } from "../runtime/managedIdentity.js";
|
|
2
3
|
import { requireManagedGlobalCaller } from "../runtime/managedCaller.js";
|
|
3
4
|
import { routeInvocation } from "./invocationRouter.js";
|
|
4
5
|
import { findInteractionPolicy } from "./interactionPolicy.js";
|
|
@@ -17,8 +18,7 @@ export function assertConfigurationAuthority(args, store, env) {
|
|
|
17
18
|
requireManagedGlobalCaller(store, env);
|
|
18
19
|
return;
|
|
19
20
|
}
|
|
20
|
-
if (env
|
|
21
|
-
|| env.YUI_AGENT_ID !== undefined || env.YUI_NATIVE_SESSION_ID !== undefined) {
|
|
21
|
+
if (hasManagedIdentity(env)) {
|
|
22
22
|
throw usageError("Home configuration and resource mutations require the user or current Operator, not a Task Assignment.");
|
|
23
23
|
}
|
|
24
24
|
}
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
import { findChild, ROOT_COMMAND } from "./commandCatalog.js";
|
|
2
|
-
export function routeInvocation(args) {
|
|
2
|
+
export function routeInvocation(args, discoveryRoot = ROOT_COMMAND) {
|
|
3
3
|
if (args.length === 0)
|
|
4
4
|
return { kind: "execute", node: ROOT_COMMAND };
|
|
5
5
|
if (args[0] === "help")
|
|
6
|
-
return resolveHelpPath(args.slice(1));
|
|
7
|
-
|
|
6
|
+
return resolveHelpPath(args.slice(1), discoveryRoot);
|
|
7
|
+
const result = resolveExecutionPath(args);
|
|
8
|
+
if (result.kind === "execute" || result.kind === "help")
|
|
9
|
+
return result;
|
|
10
|
+
const path = result.helpNode.path.slice(1);
|
|
11
|
+
let helpNode = discoveryRoot;
|
|
12
|
+
for (const segment of path) {
|
|
13
|
+
const child = findChild(helpNode, segment);
|
|
14
|
+
if (!child)
|
|
15
|
+
break;
|
|
16
|
+
helpNode = child;
|
|
17
|
+
}
|
|
18
|
+
return { ...result, helpNode };
|
|
8
19
|
}
|
|
9
|
-
function resolveHelpPath(path) {
|
|
20
|
+
function resolveHelpPath(path, root) {
|
|
10
21
|
if (path.length === 0)
|
|
11
|
-
return { kind: "help", node:
|
|
12
|
-
let node =
|
|
13
|
-
let nearestGroup =
|
|
22
|
+
return { kind: "help", node: root };
|
|
23
|
+
let node = root;
|
|
24
|
+
let nearestGroup = root;
|
|
14
25
|
for (const segment of path) {
|
|
15
26
|
const child = findChild(node, segment);
|
|
16
27
|
if (child === undefined || child.hidden) {
|
package/dist/cli.js
CHANGED
|
@@ -12,6 +12,8 @@ import { nativeAgentEnvironmentNames } from "./agent/launchEnvironment.js";
|
|
|
12
12
|
import { listArtifactsCapability, readArtifactCapability, saveArtifactCapability } from "./artifacts/artifactCapability.js";
|
|
13
13
|
import { renderAgentConfigurationResolutionNotice } from "./cli/agentConfigurationPicker.js";
|
|
14
14
|
import { describeCommandTree, findCommandNode } from "./cli/commandCatalog.js";
|
|
15
|
+
import { discoveryCommandTree } from "./cli/commandDiscovery.js";
|
|
16
|
+
import { contextRecordReader } from "./context/taskContext.js";
|
|
15
17
|
import { renderCompletion } from "./cli/completion.js";
|
|
16
18
|
import { runCompletionWizard } from "./cli/completionWizard.js";
|
|
17
19
|
import { resolveCompletionCandidates } from "./cli/dynamicCompletion.js";
|
|
@@ -21,6 +23,7 @@ import { routeInvocation } from "./cli/invocationRouter.js";
|
|
|
21
23
|
import { operatorOfflineCommand, taskDiagnosticTarget } from "./cli/managedDiagnostics.js";
|
|
22
24
|
import { assertConfigurationAuthority, assertTaskInvocationScope } from "./cli/invocationAuthority.js";
|
|
23
25
|
import { requireManagedGlobalCaller, ManagedRuntimeDriftError } from "./runtime/managedCaller.js";
|
|
26
|
+
import { hasManagedIdentity } from "./runtime/managedIdentity.js";
|
|
24
27
|
import { resolveOperatorWizardArguments } from "./cli/operatorWizard.js";
|
|
25
28
|
import { resolveGlobalRoleAgentConfigurationArguments, resolveRoleWizardArguments } from "./cli/roleWizard.js";
|
|
26
29
|
import { runUpdateCommand } from "./cli/updateCommand.js";
|
|
@@ -153,11 +156,14 @@ function failureContext() {
|
|
|
153
156
|
}
|
|
154
157
|
export async function main() {
|
|
155
158
|
const home = resolveYuiHome(process.env);
|
|
159
|
+
const discovery = discoveryCommandTree(process.env);
|
|
156
160
|
invocationHome = home;
|
|
157
161
|
const routedForFence = args.length === 0 ? undefined : routeInvocation(args);
|
|
158
162
|
const managedInvocation = process.env.YUI_SESSION_SCOPE === "task"
|
|
159
163
|
|| process.env.YUI_SESSION_SCOPE === "global";
|
|
160
164
|
const homeFreeInvocation = args.length === 0
|
|
165
|
+
|| (args[0] === "config" && (args[1] === "describe"
|
|
166
|
+
|| (args[1] === "completion" && args[2] === "candidates")))
|
|
161
167
|
|| (args[0] === "version" && args.length === 1)
|
|
162
168
|
|| routedForFence?.kind === "help"
|
|
163
169
|
|| routedForFence?.kind === "path-error"
|
|
@@ -187,16 +193,16 @@ export async function main() {
|
|
|
187
193
|
return;
|
|
188
194
|
}
|
|
189
195
|
if (args.length === 0) {
|
|
190
|
-
emit(renderCommandHelp(
|
|
196
|
+
emit(renderCommandHelp(discovery, VERSION), false, jsonOutput ? describeCommandTree(discovery) : undefined);
|
|
191
197
|
return;
|
|
192
198
|
}
|
|
193
199
|
if (args[0] === "version" && args.length === 1) {
|
|
194
200
|
emit(VERSION, true, yuiVersionIdentity());
|
|
195
201
|
return;
|
|
196
202
|
}
|
|
197
|
-
const invocation = routeInvocation(args);
|
|
203
|
+
const invocation = routeInvocation(args, discovery);
|
|
198
204
|
if (invocation.kind === "help") {
|
|
199
|
-
emit(renderCommandHelp(invocation.node, VERSION), true);
|
|
205
|
+
emit(renderCommandHelp(invocation.node, VERSION), true, jsonOutput ? describeCommandTree(invocation.node) : undefined);
|
|
200
206
|
return;
|
|
201
207
|
}
|
|
202
208
|
if (invocation.kind === "path-error") {
|
|
@@ -207,11 +213,22 @@ export async function main() {
|
|
|
207
213
|
}
|
|
208
214
|
if (args[0] === "version")
|
|
209
215
|
throw usageError("Version usage: yui version");
|
|
216
|
+
// Offline discovery is not an execution credential. Target selectors enter
|
|
217
|
+
// the original authenticated path lazily, only when stored data is needed.
|
|
218
|
+
if (args[0] === "config" && args[1] === "completion" && args[2] === "candidates") {
|
|
219
|
+
await completionCommand(home, invocation.node);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (args[0] === "config" && args[1] === "describe") {
|
|
223
|
+
const target = findCommandNode(args[2] === undefined ? ["config"] : ["config", args[2]], discovery);
|
|
224
|
+
if (args.length > 3 || !target)
|
|
225
|
+
throw usageError("Config describe usage: yui config describe [visible-domain].");
|
|
226
|
+
emit(renderCommandHelp(target, VERSION), false, describeCommandTree(target));
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
210
229
|
const { contract: taskFinalReviewContract, verifiedStore } = await preflightManagedTaskControlPlane();
|
|
211
230
|
assertTaskInvocationScope(args, process.env);
|
|
212
|
-
if (["config", "resources"].includes(args[0] ?? "") && (
|
|
213
|
-
|| process.env.YUI_ROLE !== undefined || process.env.YUI_AGENT_ID !== undefined
|
|
214
|
-
|| process.env.YUI_NATIVE_SESSION_ID !== undefined)) {
|
|
231
|
+
if (["config", "resources"].includes(args[0] ?? "") && hasManagedIdentity(process.env)) {
|
|
215
232
|
const ownedStore = verifiedStore === undefined ? openCurrentTaskStore(home) : undefined;
|
|
216
233
|
try {
|
|
217
234
|
assertConfigurationAuthority(args, verifiedStore ?? ownedStore, process.env);
|
|
@@ -268,21 +285,6 @@ export async function main() {
|
|
|
268
285
|
await completionCommand(home, invocation.node);
|
|
269
286
|
return;
|
|
270
287
|
}
|
|
271
|
-
if (args[0] === "config" && args[1] === "describe") {
|
|
272
|
-
const describeNode = findCommandNode(["config", "describe"]);
|
|
273
|
-
if (describeNode === undefined)
|
|
274
|
-
throw new Error("Config describe command is missing from the catalog.");
|
|
275
|
-
const domain = args[2];
|
|
276
|
-
const domains = describeNode.argumentValues[0] ?? [];
|
|
277
|
-
if (args.length > 3 || (domain !== undefined && !domains.includes(domain))) {
|
|
278
|
-
throw usageError(`Config describe usage: ${describeNode.usage.join(" | ")}.`);
|
|
279
|
-
}
|
|
280
|
-
const target = findCommandNode(domain === undefined ? ["config"] : ["config", domain]);
|
|
281
|
-
if (target === undefined)
|
|
282
|
-
throw new Error(`Config domain is missing from the catalog: ${domain}.`);
|
|
283
|
-
emit(renderCommandHelp(target, VERSION), false, describeCommandTree(target));
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
288
|
if (args[0] === "capability") {
|
|
287
289
|
const result = await runCapabilityCommand(args.slice(1), home, process.env);
|
|
288
290
|
emit(JSON.stringify(result, null, 2), false, result);
|
|
@@ -1959,6 +1961,9 @@ async function preflightManagedTaskControlPlane() {
|
|
|
1959
1961
|
if (process.env.YUI_SESSION_SCOPE === "global") {
|
|
1960
1962
|
return await preflightManagedGlobalControlPlane();
|
|
1961
1963
|
}
|
|
1964
|
+
if (hasManagedIdentity(process.env)) {
|
|
1965
|
+
throw usageError("Managed Agent identity is incomplete; refusing to infer user authority.");
|
|
1966
|
+
}
|
|
1962
1967
|
if (taskFinalReviewInvocation.request !== undefined) {
|
|
1963
1968
|
throw new Error("Task final-review contract may only be established from the Task Leader's managed Session.");
|
|
1964
1969
|
}
|
|
@@ -2594,10 +2599,23 @@ async function completionCommand(home, node) {
|
|
|
2594
2599
|
if (prefix === undefined || separator !== 4) {
|
|
2595
2600
|
throw usageError("Completion candidates usage: yui config completion candidates <prefix> -- <words...>");
|
|
2596
2601
|
}
|
|
2602
|
+
let selection;
|
|
2597
2603
|
const candidates = await resolveCompletionCandidates({
|
|
2598
2604
|
current: prefix,
|
|
2599
2605
|
words: args.slice(separator + 1),
|
|
2600
|
-
|
|
2606
|
+
root: discoveryCommandTree(process.env),
|
|
2607
|
+
ports: { call: async (method, params) => {
|
|
2608
|
+
if (!selection) {
|
|
2609
|
+
assertCliHomeReleaseFence({
|
|
2610
|
+
home, packageRoot: fileURLToPath(new URL("../", import.meta.url)),
|
|
2611
|
+
entryPath: fileURLToPath(import.meta.url), args
|
|
2612
|
+
});
|
|
2613
|
+
await preflightManagedTaskControlPlane();
|
|
2614
|
+
assertTaskInvocationScope(args.slice(separator + 1), process.env);
|
|
2615
|
+
selection = completionSelectionPorts(home);
|
|
2616
|
+
}
|
|
2617
|
+
return selection.call(method, params);
|
|
2618
|
+
} }
|
|
2601
2619
|
});
|
|
2602
2620
|
process.stdout.write(candidates.length === 0 ? "" : `${candidates.join("\n")}\n`);
|
|
2603
2621
|
return;
|
|
@@ -2940,7 +2958,37 @@ function completionSelectionPorts(home) {
|
|
|
2940
2958
|
return { call: () => [] };
|
|
2941
2959
|
}
|
|
2942
2960
|
const store = readableStore(home);
|
|
2943
|
-
|
|
2961
|
+
const ports = selectionPorts(store, new AgentConfigurationCatalogService(home, { environment: process.env }));
|
|
2962
|
+
if (process.env.YUI_SESSION_SCOPE !== "task")
|
|
2963
|
+
return ports;
|
|
2964
|
+
const taskId = process.env.YUI_TASK_ID;
|
|
2965
|
+
const readable = contextRecordReader(store, taskId, process.env);
|
|
2966
|
+
const families = {
|
|
2967
|
+
"task.list": "task", "project.list": "project-policy", "task.role.list": "role",
|
|
2968
|
+
"task.work.list": "work-item", "task.message.list": "task-message", "task.input.list": "input-request",
|
|
2969
|
+
"task.turn.list": "run", "task.decision.list": "task-decision", "task.milestone.list": "task-milestone",
|
|
2970
|
+
"task.event.list": "task-event", "task.change-set.list": "change-set",
|
|
2971
|
+
"task.integration.list": "integration-attempt"
|
|
2972
|
+
};
|
|
2973
|
+
return { call: async (method, params) => {
|
|
2974
|
+
if (params.taskId !== undefined && params.taskId !== taskId)
|
|
2975
|
+
return [];
|
|
2976
|
+
const family = families[method];
|
|
2977
|
+
if (!family)
|
|
2978
|
+
return []; // Completion never needs broader authority than Context.
|
|
2979
|
+
const scopedParams = method === "task.input.list" ? { ...params, taskId } : params;
|
|
2980
|
+
const values = await ports.call(method, scopedParams);
|
|
2981
|
+
if (!Array.isArray(values))
|
|
2982
|
+
return [];
|
|
2983
|
+
return values.filter(value => {
|
|
2984
|
+
const id = String(value.id ?? value.name ?? "");
|
|
2985
|
+
if (method === "task.list" && id !== taskId)
|
|
2986
|
+
return false;
|
|
2987
|
+
if (method === "project.list" && !store.getTask(taskId)?.projectBindings.some(binding => binding.projectId === id))
|
|
2988
|
+
return false;
|
|
2989
|
+
return readable(family, id);
|
|
2990
|
+
});
|
|
2991
|
+
} };
|
|
2944
2992
|
}
|
|
2945
2993
|
function emit(output, literal = false, data) {
|
|
2946
2994
|
const normalized = literal ? output.trimEnd() : output.trimEnd();
|
|
@@ -3,7 +3,7 @@ import { resolveJobCaller } from "../task/taskAuthority.js";
|
|
|
3
3
|
import { callFileTaskController } from "../controller/clientRuntime.js";
|
|
4
4
|
export async function runCapabilityCommand(args, home, environment) {
|
|
5
5
|
const [action, ...rest] = args;
|
|
6
|
-
const usage = "yui capability search [query] --task <id> | commands --task <id> | panels --task <id> | describe <name> --task <id> [--provider <id>] [--version <version>] | call <name> --task <id> --input <json> [--provider <id>] [--version <version>] [--request-id <id>]";
|
|
6
|
+
const usage = "yui capability search [query] --task <id> | commands --task <id> | panels --task <id> | describe <name> --task <id> [--input <json>] [--provider <id>] [--version <version>] | call <name> --task <id> --input <json> [--provider <id>] [--version <version>] [--request-id <id>]";
|
|
7
7
|
if (!["search", "commands", "panels", "describe", "call"].includes(action))
|
|
8
8
|
throw usageError(usage);
|
|
9
9
|
const surfaceList = action === "commands" || action === "panels";
|
|
@@ -29,12 +29,12 @@ export async function runCapabilityCommand(args, home, environment) {
|
|
|
29
29
|
throw usageError(usage);
|
|
30
30
|
if ((action === "search" || surfaceList) && [...flags.keys()].some((key) => key !== "--task"))
|
|
31
31
|
throw usageError(usage);
|
|
32
|
-
if (action === "describe" &&
|
|
32
|
+
if (action === "describe" && flags.has("--request-id"))
|
|
33
33
|
throw usageError(usage);
|
|
34
34
|
let input;
|
|
35
|
-
if (action === "call")
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
if (action === "call" && !flags.has("--input"))
|
|
36
|
+
throw usageError("Capability call requires --input <json>.", usage);
|
|
37
|
+
if (flags.has("--input")) {
|
|
38
38
|
try {
|
|
39
39
|
input = JSON.parse(flags.get("--input"));
|
|
40
40
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { roleNotFound, usageError } from "../errors/cliError.js";
|
|
2
|
+
import { hasManagedIdentity } from "../runtime/managedIdentity.js";
|
|
2
3
|
import { boundedDocument, recordPage, readOptions, readDigest, messageReceipt } from "../output/boundedRead.js";
|
|
3
4
|
import { controlProviderRetry, providerRetryProjection, renderProviderRetry } from "../runtime/providerRetry.js";
|
|
4
5
|
import { settleGlobalRetryInput } from "../message/globalProviderRetry.js";
|
|
@@ -50,7 +51,7 @@ function assertGlobalInputAuthority(store, options, target) {
|
|
|
50
51
|
throw usageError("Global input mutation requires the current native caller Session.");
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
|
-
else if (env
|
|
54
|
+
else if (hasManagedIdentity(env)) {
|
|
54
55
|
throw usageError("Incomplete managed identity cannot acquire user authority.");
|
|
55
56
|
}
|
|
56
57
|
}
|