@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,95 @@
|
|
|
1
|
+
// JSON escaping can expand a 64 KiB text result to almost 384 KiB.
|
|
2
|
+
export const limits = { messageBytes: 512 * 1024, argumentBytes: 64 * 1024, historyBytes: 1024 * 1024, callsPerStep: 8 };
|
|
3
|
+
export class AgentFault extends Error {
|
|
4
|
+
code;
|
|
5
|
+
constructor(code, message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.code = code;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function snapshot(value) {
|
|
11
|
+
const copy = structuredClone(value);
|
|
12
|
+
const freeze = (item) => {
|
|
13
|
+
if (item && typeof item === 'object') {
|
|
14
|
+
for (const child of Object.values(item))
|
|
15
|
+
freeze(child);
|
|
16
|
+
Object.freeze(item);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
freeze(copy);
|
|
20
|
+
return copy;
|
|
21
|
+
}
|
|
22
|
+
function object(value) {
|
|
23
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
24
|
+
}
|
|
25
|
+
function text(value) { return typeof value === 'string'; }
|
|
26
|
+
function id(value) { return text(value) && value.trim().length > 0; }
|
|
27
|
+
function json(value, depth = 0) {
|
|
28
|
+
if (depth > 32)
|
|
29
|
+
return false;
|
|
30
|
+
if (value === null || text(value) || typeof value === 'boolean')
|
|
31
|
+
return true;
|
|
32
|
+
if (typeof value === 'number')
|
|
33
|
+
return Number.isFinite(value);
|
|
34
|
+
if (Array.isArray(value))
|
|
35
|
+
return Array.from(value).every(v => json(v, depth + 1));
|
|
36
|
+
return object(value) && Object.getPrototypeOf(value) === Object.prototype
|
|
37
|
+
&& Object.values(value).every(v => json(v, depth + 1));
|
|
38
|
+
}
|
|
39
|
+
export function size(value) { return Buffer.byteLength(JSON.stringify(value), 'utf8'); }
|
|
40
|
+
function bounded(value) { return json(value) && size(value) <= limits.messageBytes; }
|
|
41
|
+
function calls(value) {
|
|
42
|
+
return Array.isArray(value) && value.length <= limits.callsPerStep
|
|
43
|
+
&& value.every(c => object(c) && id(c.id) && id(c.name) && bounded(c.arguments)
|
|
44
|
+
&& size(c.arguments) <= limits.argumentBytes)
|
|
45
|
+
&& new Set(value.map(c => c.id)).size === value.length;
|
|
46
|
+
}
|
|
47
|
+
export function validOutcome(value) {
|
|
48
|
+
return object(value) && bounded(value) && (value.ok === true ? text(value.content)
|
|
49
|
+
: value.ok === false && object(value.error) && id(value.error.code) && text(value.error.message)
|
|
50
|
+
&& (value.error.effect === 'none' || value.error.effect === 'unknown'));
|
|
51
|
+
}
|
|
52
|
+
export function response(value, usedIds) {
|
|
53
|
+
if (!object(value) || !bounded(value) || !text(value.content)
|
|
54
|
+
|| !(value.kind === 'final' || (value.kind === 'tool_calls' && calls(value.calls)
|
|
55
|
+
&& value.calls.length > 0 && value.calls.every(c => !usedIds.has(c.id))))) {
|
|
56
|
+
throw new AgentFault('provider_protocol', 'Invalid, oversized or duplicate model response');
|
|
57
|
+
}
|
|
58
|
+
return snapshot(value);
|
|
59
|
+
}
|
|
60
|
+
export function history(value) {
|
|
61
|
+
const pending = new Map();
|
|
62
|
+
const used = new Set();
|
|
63
|
+
if (!Array.isArray(value) || !json(value) || size(value) > limits.historyBytes) {
|
|
64
|
+
throw new AgentFault('invalid_history', 'History must be bounded JSON messages');
|
|
65
|
+
}
|
|
66
|
+
for (const m of value) {
|
|
67
|
+
if (!object(m) || !bounded(m))
|
|
68
|
+
throw new AgentFault('invalid_history', 'Invalid history message');
|
|
69
|
+
if (m.role === 'tool') {
|
|
70
|
+
if (!id(m.toolCallId) || !id(m.name) || !validOutcome(m.outcome)
|
|
71
|
+
|| pending.get(m.toolCallId) !== m.name || !pending.has(m.toolCallId)) {
|
|
72
|
+
throw new AgentFault('invalid_history', 'Tool results must match exactly one pending call');
|
|
73
|
+
}
|
|
74
|
+
pending.delete(m.toolCallId);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
if (pending.size || !text(m.content))
|
|
78
|
+
throw new AgentFault('invalid_history', 'Unsettled calls or invalid content');
|
|
79
|
+
if (m.role === 'assistant' && calls(m.toolCalls)) {
|
|
80
|
+
for (const c of m.toolCalls) {
|
|
81
|
+
if (used.has(c.id))
|
|
82
|
+
throw new AgentFault('invalid_history', 'Duplicate tool call identity');
|
|
83
|
+
used.add(c.id);
|
|
84
|
+
pending.set(c.id, c.name);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else if (m.role !== 'system' && m.role !== 'user') {
|
|
88
|
+
throw new AgentFault('invalid_history', 'Invalid message role or assistant calls');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (pending.size)
|
|
93
|
+
throw new AgentFault('invalid_history', 'History contains unsettled calls');
|
|
94
|
+
return snapshot([...value]);
|
|
95
|
+
}
|
|
@@ -98,24 +98,39 @@ export function createPluginService(store, host, registry) {
|
|
|
98
98
|
source: `plugin:${pkg.manifest.id}@${pkg.digest}`, required: pkg.manifest.required
|
|
99
99
|
}));
|
|
100
100
|
const refFor = (taskId, pkg) => ({ id: keyFor(taskId, pkg.manifest.id), generation: randomUUID() });
|
|
101
|
+
const executionParams = (taskId, preparationId, pkg, phase) => ({
|
|
102
|
+
pluginId: pkg.manifest.id, digest: pkg.digest,
|
|
103
|
+
environmentRef: `${taskId}/${preparationId}`, trust: "trusted-local", phase
|
|
104
|
+
});
|
|
105
|
+
const executionGrant = (taskId, params, path, plannedUses = new Map()) => store.listCapabilityGrants(taskId).find(candidate => Object.entries(params).every(([key, value]) => candidate.parameterBounds[key]?.includes(value))
|
|
106
|
+
&& (candidate.scope.taskId === undefined || candidate.scope.taskId === taskId)
|
|
107
|
+
&& candidate.scope.projectIds === undefined && candidate.scope.repositories === undefined
|
|
108
|
+
&& candidate.scope.packages === undefined
|
|
109
|
+
&& (candidate.scope.homePath === undefined || candidate.scope.homePath === path)
|
|
110
|
+
&& checkGrant({ ...candidate, usesUsed: candidate.usesUsed + (plannedUses.get(candidate.id) ?? 0) }, { action: "plugin.execute", params, irreversibility: "irreversible" }, new Date()).allowed);
|
|
111
|
+
const environmentAccess = (taskId, preparationId, via) => {
|
|
112
|
+
const prepared = store.getEnvironmentPreparation(taskId, preparationId);
|
|
113
|
+
if (!prepared)
|
|
114
|
+
throw new Error("Plugin environment preparation is missing.");
|
|
115
|
+
for (const resourceId of prepared.resourceRefs) {
|
|
116
|
+
const access = resources.assessLocalAccess(taskId, resourceId, prepared.access, via, prepared.id, true);
|
|
117
|
+
if (access.state !== "authorized")
|
|
118
|
+
return access;
|
|
119
|
+
}
|
|
120
|
+
return { state: "authorized" };
|
|
121
|
+
};
|
|
101
122
|
/** Each execution consumes one use. A live call's bound closure carries its
|
|
102
123
|
* admission; it is never resumed from a durable reservation or sent to code.
|
|
103
124
|
* Subsequent actions still check current authority, not cached permission. */
|
|
104
125
|
const execution = (taskId, preparationId, pkg, phase) => {
|
|
105
|
-
const params =
|
|
106
|
-
environmentRef: `${taskId}/${preparationId}`, trust: "trusted-local", phase };
|
|
126
|
+
const params = executionParams(taskId, preparationId, pkg, phase);
|
|
107
127
|
// This grants the capability to run code without OS effect confinement;
|
|
108
128
|
// it does not claim that every invocation actually has irreversible effects.
|
|
109
129
|
const request = { action: "plugin.execute", params, irreversibility: "irreversible" };
|
|
110
130
|
const reservation = phase === "call" ? undefined : `plugin/${randomUUID()}`;
|
|
111
131
|
const grantId = store.transaction((tx) => {
|
|
112
132
|
const env = environment(taskId, preparationId);
|
|
113
|
-
const grant =
|
|
114
|
-
&& (candidate.scope.taskId === undefined || candidate.scope.taskId === taskId)
|
|
115
|
-
&& candidate.scope.projectIds === undefined && candidate.scope.repositories === undefined
|
|
116
|
-
&& candidate.scope.packages === undefined
|
|
117
|
-
&& (candidate.scope.homePath === undefined || candidate.scope.homePath === env.directory.path)
|
|
118
|
-
&& checkGrant(candidate, request, new Date()).allowed);
|
|
133
|
+
const grant = executionGrant(taskId, params, env.directory.path);
|
|
119
134
|
if (!grant)
|
|
120
135
|
throw new Error(`Explicit trusted-local execution grant unavailable (${phase}); pluginId=${params.pluginId}, digest=${params.digest}, environmentRef=${params.environmentRef}.`);
|
|
121
136
|
tx.saveCapabilityGrant(taskId, recordGrantUse(grant, new Date(), reservation));
|
|
@@ -173,6 +188,81 @@ export function createPluginService(store, host, registry) {
|
|
|
173
188
|
}
|
|
174
189
|
};
|
|
175
190
|
return {
|
|
191
|
+
assess(taskId, descriptor, input, via) {
|
|
192
|
+
let validationId;
|
|
193
|
+
let phase;
|
|
194
|
+
let pkg;
|
|
195
|
+
let preparationId;
|
|
196
|
+
let path;
|
|
197
|
+
if (descriptor.name === "plugin.validate") {
|
|
198
|
+
if (input === undefined)
|
|
199
|
+
return undefined;
|
|
200
|
+
const params = input;
|
|
201
|
+
const prepared = store.getEnvironmentPreparation(taskId, params.preparationId);
|
|
202
|
+
if (!prepared?.directory)
|
|
203
|
+
throw new Error("Plugin validation needs an existing directory preparation.");
|
|
204
|
+
const access = environmentAccess(taskId, prepared.id, via);
|
|
205
|
+
if (access.state !== "authorized")
|
|
206
|
+
return access;
|
|
207
|
+
const directory = resolve(prepared.directory.path, params.directory);
|
|
208
|
+
packagePath(relative(prepared.directory.path, directory));
|
|
209
|
+
// Capture data only. Never run a build, prepare an environment or
|
|
210
|
+
// initialize a Provider just to assess the source's exact grant.
|
|
211
|
+
pkg = readPluginPackage(directory, false);
|
|
212
|
+
preparationId = prepared.id;
|
|
213
|
+
path = prepared.directory.path;
|
|
214
|
+
phase = "validate";
|
|
215
|
+
}
|
|
216
|
+
else if (descriptor.name === "plugin.activate") {
|
|
217
|
+
if (input === undefined)
|
|
218
|
+
return undefined;
|
|
219
|
+
validationId = input.validationId;
|
|
220
|
+
phase = "activate";
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
const selected = active.get(descriptor.provider.id);
|
|
224
|
+
if (!selected || selected.ref.generation !== descriptor.provider.generation)
|
|
225
|
+
return undefined;
|
|
226
|
+
validationId = selected.validationId;
|
|
227
|
+
phase = "call";
|
|
228
|
+
}
|
|
229
|
+
// Inspect captured authorization parameters, not Host availability,
|
|
230
|
+
// filesystem readiness or author code. Execution owns those checks.
|
|
231
|
+
if (validationId !== undefined) {
|
|
232
|
+
const report = store.getPluginValidation(taskId, validationId);
|
|
233
|
+
if (!report)
|
|
234
|
+
throw new Error("Plugin validation not found in this Task.");
|
|
235
|
+
pkg = report.package;
|
|
236
|
+
preparationId = report.preparationId;
|
|
237
|
+
path = report.environment.path;
|
|
238
|
+
}
|
|
239
|
+
if (phase !== "validate") {
|
|
240
|
+
const access = environmentAccess(taskId, preparationId, via);
|
|
241
|
+
if (access.state !== "authorized")
|
|
242
|
+
return access;
|
|
243
|
+
}
|
|
244
|
+
if (pkg.manifest.kind === "declarative")
|
|
245
|
+
return { state: "authorized" };
|
|
246
|
+
const params = executionParams(taskId, preparationId, pkg, phase);
|
|
247
|
+
const phases = phase === "validate" && pkg.manifest.build ? ["build", "validate"] : [phase];
|
|
248
|
+
// Simulate the same ordered selection as execution, without persisting
|
|
249
|
+
// uses. A broad Grant may fund multiple phases only if it has capacity.
|
|
250
|
+
const plannedUses = new Map();
|
|
251
|
+
const missing = phases.filter(value => {
|
|
252
|
+
const grant = executionGrant(taskId, { ...params, phase: value }, path, plannedUses);
|
|
253
|
+
if (!grant)
|
|
254
|
+
return true;
|
|
255
|
+
plannedUses.set(grant.id, (plannedUses.get(grant.id) ?? 0) + 1);
|
|
256
|
+
return false;
|
|
257
|
+
});
|
|
258
|
+
if (!missing.length)
|
|
259
|
+
return { state: "authorized" };
|
|
260
|
+
return { state: "requestable", request: {
|
|
261
|
+
action: "plugin.execute", taskId,
|
|
262
|
+
bounds: { ...Object.fromEntries(Object.entries(params).map(([key, value]) => [key, [value]])), phase: missing },
|
|
263
|
+
via, explanation: "Trusted-local author code needs an exact, irreversible execution Grant. Use the existing authorization source and Grant path; discovery consumes no uses."
|
|
264
|
+
} };
|
|
265
|
+
},
|
|
176
266
|
current: inspect,
|
|
177
267
|
list(taskId) {
|
|
178
268
|
return store.transaction(() => store.listPluginIntents(taskId)
|
|
@@ -42,7 +42,7 @@ export function createProjectResources(store, now = () => new Date()) {
|
|
|
42
42
|
throw new Error(`Local resource not found: ${resourceId}.`);
|
|
43
43
|
return value;
|
|
44
44
|
};
|
|
45
|
-
const
|
|
45
|
+
const findGrant = (taskId, resourceId, access, reservation, reservedOnly = false) => {
|
|
46
46
|
const target = resource(resourceId);
|
|
47
47
|
const grant = store.listCapabilityGrants(taskId).find((candidate) => {
|
|
48
48
|
// A Project scope is visibility, not an exhaustive Resource grant. A
|
|
@@ -57,6 +57,10 @@ export function createProjectResources(store, now = () => new Date()) {
|
|
|
57
57
|
return false;
|
|
58
58
|
return checkGrant(candidate, { action: `resource.local.${access}`, params: { resourceId } }, now(), { skipUsesCheck: reservation !== undefined && candidate.useReservations.includes(reservation) }).allowed;
|
|
59
59
|
});
|
|
60
|
+
return grant;
|
|
61
|
+
};
|
|
62
|
+
const grantFor = (taskId, resourceId, access, reservation, reservedOnly = false) => {
|
|
63
|
+
const grant = findGrant(taskId, resourceId, access, reservation, reservedOnly);
|
|
60
64
|
if (!grant)
|
|
61
65
|
throw new Error(`Resource grant unavailable: ${resourceId}/${access}.`);
|
|
62
66
|
return grant;
|
|
@@ -109,6 +113,24 @@ export function createProjectResources(store, now = () => new Date()) {
|
|
|
109
113
|
});
|
|
110
114
|
};
|
|
111
115
|
return {
|
|
116
|
+
assessLocalAccess(taskId, resourceId, access, via, reservation, reservedOnly = false) {
|
|
117
|
+
const current = task(taskId);
|
|
118
|
+
// Only already-discoverable references may receive request guidance.
|
|
119
|
+
// Do not resolve an arbitrary global ID merely to confirm its existence.
|
|
120
|
+
const known = current.projectBindings.some(({ projectId }) => store.getProject(projectId)?.resourceRefs?.includes(resourceId))
|
|
121
|
+
|| store.listCapabilityGrants(taskId).some(grant => grant.parameterBounds.resourceId?.includes(resourceId))
|
|
122
|
+
|| store.listEnvironmentPreparations(taskId).some(value => value.resourceRefs.includes(resourceId))
|
|
123
|
+
|| store.listEvents(taskId).some(event => event.type === "resource.local.registered" && event.payload.resourceId === resourceId);
|
|
124
|
+
if (!known)
|
|
125
|
+
return { state: "hidden" };
|
|
126
|
+
if (findGrant(taskId, resourceId, access, reservation, reservedOnly))
|
|
127
|
+
return { state: "authorized" };
|
|
128
|
+
return { state: "requestable", request: {
|
|
129
|
+
action: `resource.local.${access}`, taskId, bounds: { resourceId: [resourceId] }, via,
|
|
130
|
+
explanation: "Request this exact resource and access through existing Message/InputRequest and Grant authority. A response alone is not a Grant."
|
|
131
|
+
+ (reservedOnly ? " This environment's adoption reservation is no longer valid; inspect preparation/adoption before reusing it." : "")
|
|
132
|
+
} };
|
|
133
|
+
},
|
|
112
134
|
resolveExecutionEnvironment,
|
|
113
135
|
bindEnvironment(taskId, roleName, preparationId, source = { source: "environment.bind" }) {
|
|
114
136
|
return store.transaction((tx) => {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Presence is not authentication. Even partial transport identity must not
|
|
2
|
+
* silently acquire the authority of an unbound public caller. */
|
|
3
|
+
export function hasManagedIdentity(environment) {
|
|
4
|
+
return ["YUI_SESSION_SCOPE", "YUI_ROLE", "YUI_AGENT_ID", "YUI_NATIVE_SESSION_ID",
|
|
5
|
+
"YUI_TASK_ID", "YUI_SESSION_MANIFEST"].some(key => environment[key] !== undefined);
|
|
6
|
+
}
|
|
@@ -10,11 +10,14 @@ export class SurfaceContributions {
|
|
|
10
10
|
return this.registry.search(context).map((entry) => ({
|
|
11
11
|
...reference(entry), name: entry.surfaces?.cli?.name ?? entry.name,
|
|
12
12
|
help: entry.surfaces?.cli?.help ?? entry.summary, effect: entry.effect,
|
|
13
|
+
...(entry.access === undefined ? {} : { access: entry.access }),
|
|
13
14
|
...(entry.unavailable === undefined ? {} : { unavailable: entry.unavailable })
|
|
14
15
|
}));
|
|
15
16
|
}
|
|
16
17
|
listPanels(context) {
|
|
17
18
|
return this.registry.search(context).flatMap((entry) => {
|
|
19
|
+
if (entry.access?.state === "requestable")
|
|
20
|
+
return [];
|
|
18
21
|
const panel = entry.surfaces?.panel
|
|
19
22
|
?? (entry.effect === "query" ? { kind: "data", title: entry.summary, renderer: "json" } : undefined);
|
|
20
23
|
return panel ? [{
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { hasManagedIdentity } from "../runtime/managedIdentity.js";
|
|
1
2
|
import { usageError } from "../errors/cliError.js";
|
|
2
|
-
import {
|
|
3
|
+
import { currentManagedRuntime } from "../runtime/managedCaller.js";
|
|
3
4
|
const LEADER_ROLE = "leader";
|
|
4
5
|
export function taskActor(environment, taskId) {
|
|
5
6
|
const env = environment ?? {};
|
|
@@ -16,9 +17,7 @@ export function taskActor(environment, taskId) {
|
|
|
16
17
|
return "operator";
|
|
17
18
|
throw usageError("A managed global Session may perform this action only as Operator.");
|
|
18
19
|
}
|
|
19
|
-
if (env
|
|
20
|
-
|| env.YUI_AGENT_ID !== undefined
|
|
21
|
-
|| env[MANAGED_NATIVE_SESSION_ENV] !== undefined) {
|
|
20
|
+
if (hasManagedIdentity(env)) {
|
|
22
21
|
throw usageError("Managed Agent identity is incomplete; refusing to infer user authority.");
|
|
23
22
|
}
|
|
24
23
|
return "user";
|
|
@@ -86,9 +85,7 @@ export function projectActor(environment) {
|
|
|
86
85
|
return "operator";
|
|
87
86
|
throw usageError("A managed global Session may manage Project Knowledge only as Operator.");
|
|
88
87
|
}
|
|
89
|
-
if (env
|
|
90
|
-
|| env.YUI_AGENT_ID !== undefined
|
|
91
|
-
|| env[MANAGED_NATIVE_SESSION_ENV] !== undefined) {
|
|
88
|
+
if (hasManagedIdentity(env)) {
|
|
92
89
|
throw usageError("Managed Agent identity is incomplete; refusing to infer user authority.");
|
|
93
90
|
}
|
|
94
91
|
return "user";
|
|
@@ -124,9 +121,7 @@ export function resolveJobCaller(environment, taskId) {
|
|
|
124
121
|
nativeSessionId: env.CODEX_THREAD_ID ?? env.YUI_NATIVE_SESSION_ID
|
|
125
122
|
};
|
|
126
123
|
}
|
|
127
|
-
if (env
|
|
128
|
-
|| env.YUI_AGENT_ID !== undefined
|
|
129
|
-
|| env[MANAGED_NATIVE_SESSION_ENV] !== undefined) {
|
|
124
|
+
if (hasManagedIdentity(env)) {
|
|
130
125
|
throw usageError("Managed Agent identity is incomplete; refusing to infer user authority.");
|
|
131
126
|
}
|
|
132
127
|
return { scope: "user" };
|
|
@@ -1,43 +1,59 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
+
import { API_SCRIPT } from "./client/api.js";
|
|
3
4
|
import { APP_SCRIPT } from "./client/app.js";
|
|
4
5
|
import { COMPONENTS_SCRIPT } from "./client/components.js";
|
|
6
|
+
import { DETAIL_SCRIPT } from "./client/detail.js";
|
|
7
|
+
import { DOCK_SCRIPT } from "./client/dock.js";
|
|
5
8
|
import { DOM_SCRIPT } from "./client/dom.js";
|
|
9
|
+
import { EVIDENCE_SCRIPT } from "./client/evidence.js";
|
|
6
10
|
import { FORMAT_SCRIPT } from "./client/format.js";
|
|
11
|
+
import { FORMS_SCRIPT } from "./client/forms.js";
|
|
7
12
|
import { I18N_SCRIPT } from "./client/i18n.js";
|
|
8
13
|
import { MARKDOWN_SCRIPT } from "./client/markdown.js";
|
|
14
|
+
import { OVERVIEW_SCRIPT } from "./client/overview.js";
|
|
15
|
+
import { RECORDS_SCRIPT } from "./client/records.js";
|
|
16
|
+
import { SECTIONS_SCRIPT } from "./client/sections.js";
|
|
17
|
+
import { SIDEBAR_SCRIPT } from "./client/sidebar.js";
|
|
9
18
|
import { THEME_SCRIPT } from "./client/theme.js";
|
|
10
|
-
import { VIEW_SCRIPT } from "./client/view.js";
|
|
11
|
-
import { TASK_SURFACE_SCRIPT } from "./client/taskSurface.js";
|
|
12
|
-
import { TASK_SUMMARY_SCRIPT } from "./client/taskSummary.js";
|
|
13
19
|
import { DASHBOARD_HTML } from "./shell.js";
|
|
14
20
|
import { FONT_FACE_STYLES } from "./fonts.js";
|
|
15
21
|
import { FONT_WOFF2_BASE64 } from "./fontData.js";
|
|
16
|
-
import {
|
|
22
|
+
import { BASE_STYLES } from "./styles/base.js";
|
|
23
|
+
import { COMPONENT_STYLES } from "./styles/components.js";
|
|
17
24
|
import { LAYOUT_STYLES } from "./styles/layout.js";
|
|
18
25
|
import { MARKDOWN_STYLES } from "./styles/markdown.js";
|
|
19
26
|
import { RESPONSIVE_STYLES } from "./styles/responsive.js";
|
|
20
27
|
import { TOKEN_STYLES } from "./styles/tokens.js";
|
|
21
|
-
import {
|
|
28
|
+
import { VIEW_STYLES } from "./styles/views.js";
|
|
22
29
|
const require = createRequire(import.meta.url);
|
|
30
|
+
const CSS = "text/css; charset=utf-8";
|
|
31
|
+
const JS = "text/javascript; charset=utf-8";
|
|
23
32
|
export const WEB_ASSETS = Object.freeze({
|
|
24
|
-
"/assets/css/
|
|
25
|
-
"/assets/css/
|
|
26
|
-
"/assets/css/
|
|
27
|
-
"/assets/css/
|
|
28
|
-
"/assets/css/
|
|
29
|
-
"/assets/css/
|
|
30
|
-
"/assets/css/
|
|
31
|
-
"/assets/
|
|
32
|
-
"/assets/js/
|
|
33
|
-
"/assets/js/
|
|
34
|
-
"/assets/js/
|
|
35
|
-
"/assets/js/
|
|
36
|
-
"/assets/js/
|
|
37
|
-
"/assets/js/
|
|
38
|
-
"/assets/js/
|
|
39
|
-
"/assets/js/
|
|
40
|
-
"/assets/
|
|
33
|
+
"/assets/css/fonts.css": { contentType: CSS, body: FONT_FACE_STYLES },
|
|
34
|
+
"/assets/css/tokens.css": { contentType: CSS, body: TOKEN_STYLES },
|
|
35
|
+
"/assets/css/base.css": { contentType: CSS, body: BASE_STYLES },
|
|
36
|
+
"/assets/css/layout.css": { contentType: CSS, body: LAYOUT_STYLES },
|
|
37
|
+
"/assets/css/components.css": { contentType: CSS, body: COMPONENT_STYLES },
|
|
38
|
+
"/assets/css/views.css": { contentType: CSS, body: VIEW_STYLES },
|
|
39
|
+
"/assets/css/markdown.css": { contentType: CSS, body: MARKDOWN_STYLES },
|
|
40
|
+
"/assets/css/responsive.css": { contentType: CSS, body: RESPONSIVE_STYLES },
|
|
41
|
+
"/assets/js/dom.js": { contentType: JS, body: DOM_SCRIPT },
|
|
42
|
+
"/assets/js/format.js": { contentType: JS, body: FORMAT_SCRIPT },
|
|
43
|
+
"/assets/js/i18n.js": { contentType: JS, body: I18N_SCRIPT },
|
|
44
|
+
"/assets/js/markdown.js": { contentType: JS, body: MARKDOWN_SCRIPT },
|
|
45
|
+
"/assets/js/theme.js": { contentType: JS, body: THEME_SCRIPT },
|
|
46
|
+
"/assets/js/api.js": { contentType: JS, body: API_SCRIPT },
|
|
47
|
+
"/assets/js/components.js": { contentType: JS, body: COMPONENTS_SCRIPT },
|
|
48
|
+
"/assets/js/records.js": { contentType: JS, body: RECORDS_SCRIPT },
|
|
49
|
+
"/assets/js/forms.js": { contentType: JS, body: FORMS_SCRIPT },
|
|
50
|
+
"/assets/js/evidence.js": { contentType: JS, body: EVIDENCE_SCRIPT },
|
|
51
|
+
"/assets/js/sidebar.js": { contentType: JS, body: SIDEBAR_SCRIPT },
|
|
52
|
+
"/assets/js/overview.js": { contentType: JS, body: OVERVIEW_SCRIPT },
|
|
53
|
+
"/assets/js/sections.js": { contentType: JS, body: SECTIONS_SCRIPT },
|
|
54
|
+
"/assets/js/detail.js": { contentType: JS, body: DETAIL_SCRIPT },
|
|
55
|
+
"/assets/js/dock.js": { contentType: JS, body: DOCK_SCRIPT },
|
|
56
|
+
"/assets/app.js": { contentType: JS, body: APP_SCRIPT },
|
|
41
57
|
...fontAssets(),
|
|
42
58
|
"/assets/vendor/xterm.mjs": vendorAsset("@xterm/xterm/lib/xterm.mjs", "text/javascript; charset=utf-8"),
|
|
43
59
|
"/assets/vendor/addon-fit.mjs": vendorAsset("@xterm/addon-fit/lib/addon-fit.mjs", "text/javascript; charset=utf-8"),
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
export const API_SCRIPT = String.raw `
|
|
2
|
+
// Loopback API client. Every request carries the page token; a write is keyed
|
|
3
|
+
// so an unresolved submission can never be silently replayed from this page.
|
|
4
|
+
const token = document.querySelector('meta[name="yui-web-token"]').content;
|
|
5
|
+
const unresolved = new Set();
|
|
6
|
+
|
|
7
|
+
export function pageToken() { return token; }
|
|
8
|
+
|
|
9
|
+
export async function requestJson(path, options) {
|
|
10
|
+
const response = await fetch(path, {
|
|
11
|
+
...options,
|
|
12
|
+
headers: {
|
|
13
|
+
accept: "application/json",
|
|
14
|
+
"x-yui-web-token": token,
|
|
15
|
+
...(options && options.headers ? options.headers : {})
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
if (!response.ok) {
|
|
19
|
+
let message = "HTTP " + response.status;
|
|
20
|
+
let disposition = "unknown";
|
|
21
|
+
try {
|
|
22
|
+
const body = await response.json();
|
|
23
|
+
if (body && body.error) message = body.error;
|
|
24
|
+
if (body && body.disposition === "not-submitted") disposition = "not-submitted";
|
|
25
|
+
} catch {}
|
|
26
|
+
throw Object.assign(new Error(message), { disposition, status: response.status });
|
|
27
|
+
}
|
|
28
|
+
return response.json();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function postJson(path, body, signal) {
|
|
32
|
+
return requestJson(path, {
|
|
33
|
+
method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body),
|
|
34
|
+
...(signal ? { signal } : {})
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// A key stays blocked until the server either answers or proves the request
|
|
39
|
+
// was not submitted; an unknown outcome must be inspected, not resent.
|
|
40
|
+
export async function submitMutation(key, path, body) {
|
|
41
|
+
if (unresolved.has(key)) {
|
|
42
|
+
throw Object.assign(new Error("An earlier submission is unresolved; read current facts first."), { disposition: "blocked" });
|
|
43
|
+
}
|
|
44
|
+
unresolved.add(key);
|
|
45
|
+
try {
|
|
46
|
+
const receipt = await postJson(path, body);
|
|
47
|
+
unresolved.delete(key);
|
|
48
|
+
return receipt;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (error.disposition === "not-submitted") unresolved.delete(key);
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function releaseMutation(key) { unresolved.delete(key); }
|
|
56
|
+
|
|
57
|
+
const task = function (taskId) { return "/api/tasks/" + encodeURIComponent(taskId); };
|
|
58
|
+
|
|
59
|
+
export const api = {
|
|
60
|
+
dashboard: function (query) { return requestJson("/api/dashboard?" + query); },
|
|
61
|
+
pageSessions: function (query) {
|
|
62
|
+
return requestJson("/api/dashboard/sessions?" + query, { signal: AbortSignal.timeout(5000) });
|
|
63
|
+
},
|
|
64
|
+
context: function (taskId) { return requestJson(task(taskId) + "/context"); },
|
|
65
|
+
observation: function (taskId) { return requestJson(task(taskId), { signal: AbortSignal.timeout(1500) }); },
|
|
66
|
+
inspect: async function (taskId, ref) {
|
|
67
|
+
const query = new URLSearchParams({ store: ref.store, ref: ref.refId });
|
|
68
|
+
if (ref.digest) query.set("digest", ref.digest);
|
|
69
|
+
let result = await requestJson(task(taskId) + "/inspect?" + query);
|
|
70
|
+
if (!result.contentPage) return result;
|
|
71
|
+
const source = result.contentPage.source;
|
|
72
|
+
const digest = result.contentPage.digest;
|
|
73
|
+
let text = "";
|
|
74
|
+
while (true) {
|
|
75
|
+
const page = result.contentPage;
|
|
76
|
+
if (!page || page.source !== source || page.digest !== digest || page.encoding !== "json"
|
|
77
|
+
|| page.offset !== text.length || typeof page.text !== "string" || !page.text.length) {
|
|
78
|
+
throw new Error("Original read changed or returned an invalid page; refresh its Context reference.");
|
|
79
|
+
}
|
|
80
|
+
text += page.text;
|
|
81
|
+
if (page.complete) return JSON.parse(text);
|
|
82
|
+
if (!page.nextCursor) throw new Error("Original read is incomplete; refresh its Context reference.");
|
|
83
|
+
query.set("cursor", page.nextCursor);
|
|
84
|
+
result = await requestJson(task(taskId) + "/inspect?" + query);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
artifacts: function (taskId) { return requestJson(task(taskId) + "/artifacts"); },
|
|
88
|
+
artifact: function (taskId, path, commit) {
|
|
89
|
+
return requestJson(task(taskId) + "/artifacts?" + new URLSearchParams({ path, commit }));
|
|
90
|
+
},
|
|
91
|
+
evidence: function (taskId) { return requestJson(task(taskId) + "/evidence"); },
|
|
92
|
+
panels: function (taskId) { return requestJson(task(taskId) + "/panels", { signal: AbortSignal.timeout(3000) }); },
|
|
93
|
+
readPanel: function (taskId, ref, input) {
|
|
94
|
+
return postJson(task(taskId) + "/panels", { ref, input }, AbortSignal.timeout(3000));
|
|
95
|
+
},
|
|
96
|
+
sendMessage: function (taskId, body, requestId, intent) {
|
|
97
|
+
return submitMutation(taskId + "/messages", task(taskId) + "/messages",
|
|
98
|
+
{ body, requestId, ...(intent === undefined ? {} : { intent }) });
|
|
99
|
+
},
|
|
100
|
+
control: function (taskId, payload) {
|
|
101
|
+
return submitMutation(taskId + "/control/" + payload.requestId, task(taskId) + "/control", payload);
|
|
102
|
+
},
|
|
103
|
+
updateTask: function (taskId, patch, requestId) {
|
|
104
|
+
return submitMutation(taskId + "/metadata", task(taskId) + "/metadata", { patch, requestId });
|
|
105
|
+
},
|
|
106
|
+
answerInput: function (taskId, inputId, answer) {
|
|
107
|
+
return submitMutation(taskId + "/input/" + inputId,
|
|
108
|
+
task(taskId) + "/inputs/" + encodeURIComponent(inputId) + "/answer", answer);
|
|
109
|
+
},
|
|
110
|
+
globalState: function (role) { return requestJson("/api/roles/" + encodeURIComponent(role) + "/control"); },
|
|
111
|
+
globalControl: function (role, input) {
|
|
112
|
+
return submitMutation("global/" + role, "/api/roles/" + encodeURIComponent(role) + "/control", input);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
`;
|