@zq-silk/yui 2.2.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -3
- package/dist/artifacts/artifactCapability.js +39 -10
- package/dist/artifacts/imageMetadata.js +120 -0
- package/dist/artifacts/managedGit.js +7 -4
- package/dist/artifacts/taskArtifactRepository.js +6 -0
- package/dist/cli/commandCatalog.js +25 -11
- package/dist/cli/interactionPolicy.js +4 -0
- package/dist/cli/invocationAuthority.js +1 -1
- package/dist/commands/executionAuditCommands.js +5 -0
- package/dist/commands/globalRoleCommands.js +41 -3
- package/dist/commands/projectCommands.js +89 -94
- package/dist/commands/roleSkillValidation.js +4 -8
- package/dist/commands/taskCommands.js +30 -9
- package/dist/commands/taskIntegrationCommands.js +5 -16
- package/dist/commands/taskUsageCommand.js +38 -0
- package/dist/context/roleSessionContext.js +18 -2
- package/dist/context/runContextPack.js +5 -1
- package/dist/context/sessionBootstrapManifest.js +10 -1
- package/dist/context/skillCatalog.js +108 -0
- package/dist/context/skillPackage.js +161 -0
- package/dist/context/taskContext.js +3 -2
- package/dist/context/taskSearch.js +127 -0
- package/dist/controlPlaneCli.js +51 -39
- package/dist/controller/clientRuntime.js +3 -2
- package/dist/controller/fileSchedulerStoreAdapter.js +32 -6
- package/dist/controller/jobControl.js +2 -0
- package/dist/controller/nativeHostInput.js +37 -0
- package/dist/controller/runtime.js +93 -2
- package/dist/controller/runtimeEventInbox.js +2 -2
- package/dist/controller/runtimeEventProcessor.js +3 -3
- package/dist/doctor/doctor.js +10 -0
- package/dist/execution/reviewMainRun.js +1 -0
- package/dist/execution/workItemMainRun.js +1 -0
- package/dist/executor/codexConfigConflict.js +6 -4
- package/dist/executor/effectiveLaunch.js +24 -1
- package/dist/executor/fileRoleLaunchPlanner.js +13 -5
- package/dist/integration/gitIntegrationService.js +15 -79
- package/dist/integration/integrationSourceApplication.js +4 -2
- package/dist/job/httpPreview.js +61 -0
- package/dist/job/jobCleanupInspection.js +67 -0
- package/dist/job/jobRunner.js +12 -2
- package/dist/kernel/builtinCapabilities.js +3 -1
- package/dist/message/message.js +15 -1
- package/dist/output/rolePresentation.js +4 -1
- package/dist/repository/executionLaneGitSnapshot.js +2 -1
- package/dist/repository/gitCompatibility.js +62 -0
- package/dist/repository/gitWorkspace.js +86 -80
- package/dist/repository/gitWorktreeInventory.js +121 -0
- package/dist/repository/project.js +74 -25
- package/dist/repository/taskWorkspaceCoordinator.js +72 -145
- package/dist/repository/taskWorkspacePreparer.js +39 -43
- package/dist/repository/workspaceCleanupInspection.js +88 -7
- package/dist/resources/resourceDiscovery.js +2 -2
- package/dist/resources/resourceGc.js +7 -4
- package/dist/resources/resourceRegistrar.js +18 -11
- package/dist/runtime/agentEndpoint.js +8 -0
- package/dist/runtime/agentHost.js +170 -3
- package/dist/runtime/codexAppServerRuntime.js +1 -1
- package/dist/runtime/codexNativeAccess.js +175 -0
- package/dist/runtime/controlledCodexTui.js +116 -0
- package/dist/runtime/integrationRuntimeIsolation.js +21 -0
- package/dist/runtime/publicReply.js +23 -0
- package/dist/runtime/runtimeObservation.js +48 -0
- package/dist/runtime/structuredProviderHost.js +16 -7
- package/dist/runtime/taskCostMetrics.js +133 -0
- package/dist/runtime/taskRuntimeIsolation.js +91 -21
- package/dist/runtime/taskUsageMetrics.js +11 -2
- package/dist/runtime/taskUsageQuery.js +38 -0
- package/dist/scheduler/taskExecutionProjection.js +2 -0
- package/dist/scheduler/taskObservabilityProjection.js +9 -1
- package/dist/storage/homeLayout.js +13 -0
- package/dist/storage/sqliteSchema.js +44 -0
- package/dist/storage/sqliteStore.js +53 -0
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +2 -0
- package/dist/storage/taskSearch.js +35 -0
- package/dist/storage/upgrade/projectKnowledgeMigration.js +52 -0
- package/dist/task/archiveDiagnostics.js +26 -8
- package/dist/task/archivePreflight.js +26 -9
- package/dist/task/leaderArchive.js +9 -15
- package/dist/task/ordinaryArchive.js +42 -0
- package/dist/tmux/tmuxManager.js +8 -8
- package/dist/web/assets/assetManifest.js +11 -51
- package/dist/web/assets/client/app/catalog.js +215 -0
- package/dist/web/assets/client/app/main.js +148 -0
- package/dist/web/assets/client/app/selection.js +139 -0
- package/dist/web/assets/client/app/settings.js +326 -0
- package/dist/web/assets/client/app/taskView.js +280 -0
- package/dist/web/assets/client/domain/context.js +96 -0
- package/dist/web/assets/client/domain/records.js +126 -0
- package/dist/web/assets/client/domain/runs.js +119 -0
- package/dist/web/assets/client/domain/taskForms.js +197 -0
- package/dist/web/assets/client/domain/vocab.js +69 -0
- package/dist/web/assets/client/domain/work.js +116 -0
- package/dist/web/assets/client/i18n/en.js +900 -0
- package/dist/web/assets/client/i18n/zh-CN.js +898 -0
- package/dist/web/assets/client/index.js +114 -0
- package/dist/web/assets/client/layout/resizer.js +37 -0
- package/dist/web/assets/client/layout/router.js +35 -0
- package/dist/web/assets/client/layout/shortcuts.js +45 -0
- package/dist/web/assets/client/layout/sizing.js +95 -0
- package/dist/web/assets/client/layout/workspace.js +247 -0
- package/dist/web/assets/client/{api.js → lib/api.js} +28 -4
- package/dist/web/assets/client/{dom.js → lib/dom.js} +24 -12
- package/dist/web/assets/client/{format.js → lib/format.js} +7 -0
- package/dist/web/assets/client/lib/i18n.js +54 -0
- package/dist/web/assets/client/lib/markdown.js +167 -0
- package/dist/web/assets/client/lib/prefs.js +28 -0
- package/dist/web/assets/client/lib/theme.js +68 -0
- package/dist/web/assets/client/ui/containers.js +101 -0
- package/dist/web/assets/client/ui/controls.js +51 -0
- package/dist/web/assets/client/ui/forms.js +48 -0
- package/dist/web/assets/client/ui/metrics.js +137 -0
- package/dist/web/assets/client/ui/primitives.js +92 -0
- package/dist/web/assets/client/ui/text.js +33 -0
- package/dist/web/assets/client/ui/toast.js +12 -0
- package/dist/web/assets/client/views/bodySearch.js +172 -0
- package/dist/web/assets/client/views/createTask.js +85 -0
- package/dist/web/assets/client/views/dock/conversation.js +612 -0
- package/dist/web/assets/client/views/dock/discussion.js +173 -0
- package/dist/web/assets/client/views/dock/preview.js +129 -0
- package/dist/web/assets/client/views/dock/terminal.js +199 -0
- package/dist/web/assets/client/views/globalInput.js +67 -0
- package/dist/web/assets/client/{overview.js → views/overview.js} +62 -56
- package/dist/web/assets/client/{sidebar.js → views/sidebar.js} +56 -62
- package/dist/web/assets/client/views/skills.js +152 -0
- package/dist/web/assets/client/views/task/actions.js +36 -0
- package/dist/web/assets/client/views/task/delivery.js +185 -0
- package/dist/web/assets/client/views/task/deliveryResults.js +159 -0
- package/dist/web/assets/client/views/task/files.js +272 -0
- package/dist/web/assets/client/views/task/observation.js +153 -0
- package/dist/web/assets/client/views/task/overview.js +161 -0
- package/dist/web/assets/client/views/task/page.js +131 -0
- package/dist/web/assets/client/views/task/records.js +163 -0
- package/dist/web/assets/client/views/task/runtime.js +130 -0
- package/dist/web/assets/client/views/task/timeline.js +190 -0
- package/dist/web/assets/shared/geometry.js +19 -0
- package/dist/web/assets/{icons.js → shared/icons.js} +4 -0
- package/dist/web/assets/shared/taskTabs.js +8 -0
- package/dist/web/assets/shared/themes.js +93 -0
- package/dist/web/assets/shell/dialogs.js +63 -0
- package/dist/web/assets/shell/dock.js +40 -0
- package/dist/web/assets/shell/index.js +40 -0
- package/dist/web/assets/shell/settings.js +34 -0
- package/dist/web/assets/shell/sidebar.js +45 -0
- package/dist/web/assets/styles/index.js +41 -0
- package/dist/web/assets/styles/{responsive.js → layout/responsive.js} +15 -13
- package/dist/web/assets/styles/{layout.js → layout/workspace.js} +17 -24
- package/dist/web/assets/styles/markdown.js +22 -3
- package/dist/web/assets/styles/tokens.js +10 -63
- package/dist/web/assets/styles/ui/controls.js +76 -0
- package/dist/web/assets/styles/ui/forms.js +46 -0
- package/dist/web/assets/styles/ui/surfaces.js +122 -0
- package/dist/web/assets/styles/views/delivery.js +29 -0
- package/dist/web/assets/styles/views/dock.js +71 -0
- package/dist/web/assets/styles/views/page.js +32 -0
- package/dist/web/assets/styles/views/records.js +70 -0
- package/dist/web/assets/styles/views/settings.js +26 -0
- package/dist/web/assets/styles/views/sidebar.js +74 -0
- package/dist/web/assets/styles/views/task.js +80 -0
- package/dist/web/assets/styles/views/terminal.js +38 -0
- package/dist/web/taskPreviews.js +205 -0
- package/dist/web/tmuxWebTerminal.js +16 -9
- package/dist/web/webConversation.js +373 -0
- package/dist/web/webMaterials.js +155 -0
- package/dist/web/webServer.js +306 -18
- package/dist/web/webSettings.js +331 -0
- package/dist/web/webSkills.js +86 -0
- package/dist/web/webSnapshot.js +40 -8
- package/dist/web/webTaskSurface.js +114 -9
- package/dist/workspace/gitChangeSetCapture.js +3 -1
- package/docs/managed-turn-and-session-runtime.md +9 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +7 -0
- package/docs/observability/README.md +13 -3
- package/docs/observability/README.zh-CN.md +10 -3
- package/docs/release-workflow.md +53 -0
- package/docs/release-workflow.zh-CN.md +40 -0
- package/docs/storage-baseline.md +38 -1
- package/docs/storage-baseline.zh-CN.md +22 -0
- package/docs/task-delivery.md +36 -8
- package/docs/task-delivery.zh-CN.md +24 -7
- package/docs/task-discovery.md +28 -0
- package/docs/testing/verification-levels.md +2 -2
- package/i18n/README.zh-CN.md +4 -3
- package/package.json +1 -1
- package/skills/yui-leader/references/execution.md +2 -1
- package/skills/yui-leader/references/integration.md +20 -0
- package/skills/yui-operator/SKILL.md +4 -0
- package/skills/yui-reviewer/SKILL.md +3 -0
- package/skills/yui-runtime/SKILL.md +4 -0
- package/skills/yui-runtime/references/publication.md +20 -4
- package/skills/yui-worker/SKILL.md +3 -0
- package/dist/web/assets/client/app.js +0 -804
- package/dist/web/assets/client/components.js +0 -418
- package/dist/web/assets/client/detail.js +0 -333
- package/dist/web/assets/client/dock.js +0 -225
- package/dist/web/assets/client/evidence.js +0 -172
- package/dist/web/assets/client/forms.js +0 -230
- package/dist/web/assets/client/i18n.js +0 -1161
- package/dist/web/assets/client/markdown.js +0 -114
- package/dist/web/assets/client/records.js +0 -63
- package/dist/web/assets/client/sections.js +0 -313
- package/dist/web/assets/client/theme.js +0 -71
- package/dist/web/assets/shell.js +0 -161
- package/dist/web/assets/styles/components.js +0 -169
- package/dist/web/assets/styles/views.js +0 -292
package/README.md
CHANGED
|
@@ -46,7 +46,7 @@ For the self-built coding Agent (without a Controller or Yui Home), see
|
|
|
46
46
|
[`yui agent`](docs/native-agent.md). Its explicit configuration and product state
|
|
47
47
|
are separate from the orchestration setup below.
|
|
48
48
|
|
|
49
|
-
You need Linux x64 (glibc) or macOS (x64 or Apple Silicon), Git, tmux, and
|
|
49
|
+
You need Linux x64 (glibc) or macOS (x64 or Apple Silicon), Git 2.29.0 or newer, tmux, and
|
|
50
50
|
Node.js `^20.17.0`, `^22.9.0`, `^24.0.0` or `^26.0.0`. On macOS install tmux with
|
|
51
51
|
`brew install tmux`. For the simplest setup, have Codex CLI or Claude Code CLI
|
|
52
52
|
installed and ready to use with your own account. Yui coordinates those
|
|
@@ -162,8 +162,24 @@ The task list remains at the left, task details occupy the primary reading area,
|
|
|
162
162
|
and recorded Task messages live in a secondary discussion pane. You can resize,
|
|
163
163
|
swap or hide that pane; on narrow screens it opens over the detail. Native Sessions
|
|
164
164
|
remain separate from recorded Task messages.
|
|
165
|
-
|
|
166
|
-
|
|
165
|
+
For a current controlled Codex Operator or Task Leader, **Conversation** and
|
|
166
|
+
**Terminal** are two views of the same Thread. Terminal opens the native Codex
|
|
167
|
+
TUI through the existing Host; it does not start another Agent. Only one browser
|
|
168
|
+
terminal holds the writer at a time. Returning to Conversation or closing the
|
|
169
|
+
page detaches that browser, without stopping the Agent. Conversation drafts and
|
|
170
|
+
the browser's default-view preference survive switching. Native questions and
|
|
171
|
+
approvals answer the original Codex request; Leader Task questions remain on
|
|
172
|
+
the separate InputRequest channel. The default-view preference is shared with
|
|
173
|
+
Settings; saving it does not switch the current view or Session.
|
|
174
|
+
MCP forms and URL confirmations can be completed in Terminal or declined in
|
|
175
|
+
Conversation. Native additional input is saved as a user Message with its exact
|
|
176
|
+
steer receipt before submission.
|
|
177
|
+
Attachments and native configuration changes
|
|
178
|
+
are not supported by this managed text-input surface.
|
|
179
|
+
The detail has four tabs: Overview (user input, progress and key Decisions),
|
|
180
|
+
Delivery (WorkItems, Reviews, Integration and remote delivery, read on demand),
|
|
181
|
+
Runtime (Roles, native Session activity, open execution and usage) and Records
|
|
182
|
+
(a timeline of Decisions, milestones, executions and questions). See
|
|
167
183
|
[attention and progress](docs/observability/README.md#web-attention-and-progress).
|
|
168
184
|
See [Web permissions](docs/architecture/capabilities-and-resources.md#cli-and-web)
|
|
169
185
|
and [input timing](docs/managed-turn-and-session-runtime.md#input-timing-queue-steer-and-interrupt).
|
|
@@ -2,6 +2,7 @@ import { openTaskArtifactRepository } from "./taskArtifactRepository.js";
|
|
|
2
2
|
import { saveArtifactFile } from "./gitArtifactRef.js";
|
|
3
3
|
import { requireCommitId } from "./managedGit.js";
|
|
4
4
|
import { safeRelativeArtifactPath } from "./artifactPaths.js";
|
|
5
|
+
import { isRasterArtifact, MAX_IMAGE_BYTES, rasterMetadata } from "./imageMetadata.js";
|
|
5
6
|
/**
|
|
6
7
|
* Capability-shaped adapter over the per-Task local Git artifact repository.
|
|
7
8
|
*
|
|
@@ -13,11 +14,8 @@ import { safeRelativeArtifactPath } from "./artifactPaths.js";
|
|
|
13
14
|
* self-certifying commit-pinned reference (`taskId + commit + relativePath`),
|
|
14
15
|
* which is the identity used for frozen Candidate/Review/completion evidence.
|
|
15
16
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* very large files are out of scope for this first version by contract (§3.5)
|
|
19
|
-
* and are rejected or referenced through their existing resource boundary, not
|
|
20
|
-
* carried here.
|
|
17
|
+
* Text is UTF-8 (8 MiB). Static raster images use explicit base64 (4 MiB
|
|
18
|
+
* decoded), with the same immutable repository and image limits as Web.
|
|
21
19
|
*/
|
|
22
20
|
/** Text cap for a single artifact file, mirroring the former immutable-artifact limit. */
|
|
23
21
|
const MAX_ARTIFACT_TEXT_BYTES = 8 * 1024 * 1024;
|
|
@@ -35,7 +33,23 @@ function requireTextContent(content) {
|
|
|
35
33
|
* commit exactly that path, and return the resulting commit-pinned reference.
|
|
36
34
|
*/
|
|
37
35
|
export async function saveArtifactCapability(home, taskId, input) {
|
|
38
|
-
|
|
36
|
+
if (input.encoding !== undefined && input.encoding !== "utf8" && input.encoding !== "base64") {
|
|
37
|
+
throw new Error("Artifact encoding must be utf8 or base64.");
|
|
38
|
+
}
|
|
39
|
+
let bytes;
|
|
40
|
+
if (input.encoding === "base64") {
|
|
41
|
+
if (!isRasterArtifact(input.relativePath) || typeof input.content !== "string"
|
|
42
|
+
|| input.content.length > Math.ceil(MAX_IMAGE_BYTES / 3) * 4) {
|
|
43
|
+
throw new Error("Base64 artifacts require a static PNG, JPEG or WebP path and at most 4 MiB.");
|
|
44
|
+
}
|
|
45
|
+
bytes = Buffer.from(input.content, "base64");
|
|
46
|
+
if (bytes.toString("base64") !== input.content)
|
|
47
|
+
throw new Error("Artifact image must use canonical base64.");
|
|
48
|
+
rasterMetadata(bytes);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
bytes = requireTextContent(input.content);
|
|
52
|
+
}
|
|
39
53
|
return saveArtifactFile(home, taskId, {
|
|
40
54
|
relativePath: input.relativePath,
|
|
41
55
|
bytes,
|
|
@@ -45,8 +59,8 @@ export async function saveArtifactCapability(home, taskId, input) {
|
|
|
45
59
|
}
|
|
46
60
|
/**
|
|
47
61
|
* `artifact.read`: read `relativePath` at HEAD, or at a pinned `commit` for
|
|
48
|
-
* frozen evidence.
|
|
49
|
-
*
|
|
62
|
+
* frozen evidence. Images return explicit base64 and bounded raster metadata;
|
|
63
|
+
* text returns UTF-8. Both carry the resolved commit and exact digest.
|
|
50
64
|
*/
|
|
51
65
|
export async function readArtifactCapability(home, taskId, input) {
|
|
52
66
|
const repo = openTaskArtifactRepository(home, taskId);
|
|
@@ -54,12 +68,27 @@ export async function readArtifactCapability(home, taskId, input) {
|
|
|
54
68
|
throw new Error(`Artifact repository is unavailable for ${taskId}.`);
|
|
55
69
|
const safeRelative = safeRelativeArtifactPath(input.relativePath);
|
|
56
70
|
const content = await repo.read(safeRelative, input.commit === undefined ? undefined : requireCommitId(input.commit));
|
|
71
|
+
const image = isRasterArtifact(safeRelative) ? rasterMetadata(content.bytes) : null;
|
|
72
|
+
let text;
|
|
73
|
+
if (image)
|
|
74
|
+
text = content.bytes.toString("base64");
|
|
75
|
+
else {
|
|
76
|
+
try {
|
|
77
|
+
text = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(content.bytes);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
throw new Error("Artifact is not readable UTF-8 text or a supported raster image.");
|
|
81
|
+
}
|
|
82
|
+
if (text.includes("\0"))
|
|
83
|
+
throw new Error("Binary artifact is not supported as text.");
|
|
84
|
+
}
|
|
57
85
|
return {
|
|
58
86
|
taskId,
|
|
59
87
|
relativePath: content.relativePath,
|
|
60
88
|
commit: content.commit,
|
|
61
|
-
content:
|
|
62
|
-
digest: content.digest
|
|
89
|
+
content: text,
|
|
90
|
+
digest: content.digest,
|
|
91
|
+
...(image ? { encoding: "base64", ...image } : {})
|
|
63
92
|
};
|
|
64
93
|
}
|
|
65
94
|
/**
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { extname } from "node:path";
|
|
2
|
+
export const MAX_IMAGE_BYTES = 4 * 1024 * 1024;
|
|
3
|
+
export function isRasterArtifact(path) {
|
|
4
|
+
return [".png", ".jpg", ".jpeg", ".webp"].includes(extname(path).toLowerCase());
|
|
5
|
+
}
|
|
6
|
+
/** Bounded raster metadata inspection, not a decoder. The viewer also reports
|
|
7
|
+
* browser decode errors. Never admit SVG/HTML or trust a caller's MIME type. */
|
|
8
|
+
export function rasterMetadata(bytes) {
|
|
9
|
+
if (bytes.length > MAX_IMAGE_BYTES)
|
|
10
|
+
throw new Error("Image exceeds 4 MiB.");
|
|
11
|
+
let mime, width = 0, height = 0;
|
|
12
|
+
if (bytes.length >= 33 && bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))
|
|
13
|
+
&& bytes.toString("ascii", 12, 16) === "IHDR" && bytes.readUInt32BE(8) === 13) {
|
|
14
|
+
mime = "image/png";
|
|
15
|
+
width = bytes.readUInt32BE(16);
|
|
16
|
+
height = bytes.readUInt32BE(20);
|
|
17
|
+
let end = false;
|
|
18
|
+
for (let offset = 8; offset + 12 <= bytes.length;) {
|
|
19
|
+
const length = bytes.readUInt32BE(offset);
|
|
20
|
+
const type = bytes.toString("ascii", offset + 4, offset + 8);
|
|
21
|
+
if (offset + length + 12 > bytes.length)
|
|
22
|
+
throw new Error("Truncated PNG image.");
|
|
23
|
+
if (type === "acTL")
|
|
24
|
+
throw new Error("Animated images are unsupported; save a static frame.");
|
|
25
|
+
offset += length + 12;
|
|
26
|
+
if (type === "IEND") {
|
|
27
|
+
end = length === 0 && offset === bytes.length;
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (!end)
|
|
32
|
+
throw new Error("Incomplete PNG image.");
|
|
33
|
+
}
|
|
34
|
+
else if (bytes.length >= 4 && bytes[0] === 255 && bytes[1] === 216) {
|
|
35
|
+
mime = "image/jpeg";
|
|
36
|
+
let offset = 2;
|
|
37
|
+
while (offset + 4 <= bytes.length) {
|
|
38
|
+
if (bytes[offset++] !== 255)
|
|
39
|
+
throw new Error("Invalid JPEG image marker.");
|
|
40
|
+
while (bytes[offset] === 255)
|
|
41
|
+
offset++;
|
|
42
|
+
const marker = bytes[offset++];
|
|
43
|
+
if (marker === 0xda || marker === 0xd9)
|
|
44
|
+
break;
|
|
45
|
+
if (offset + 2 > bytes.length)
|
|
46
|
+
throw new Error("Truncated JPEG image.");
|
|
47
|
+
const length = bytes.readUInt16BE(offset);
|
|
48
|
+
if (length < 2 || offset + length > bytes.length)
|
|
49
|
+
throw new Error("Truncated JPEG image.");
|
|
50
|
+
if ([0xc0, 0xc1, 0xc2].includes(marker)) {
|
|
51
|
+
if (length < 8)
|
|
52
|
+
throw new Error("Invalid JPEG image frame.");
|
|
53
|
+
if (width || height)
|
|
54
|
+
throw new Error("Multiple JPEG image frames are unsupported.");
|
|
55
|
+
height = bytes.readUInt16BE(offset + 3);
|
|
56
|
+
width = bytes.readUInt16BE(offset + 5);
|
|
57
|
+
}
|
|
58
|
+
offset += length;
|
|
59
|
+
}
|
|
60
|
+
if (bytes[bytes.length - 2] !== 255 || bytes[bytes.length - 1] !== 217)
|
|
61
|
+
throw new Error("Incomplete JPEG image.");
|
|
62
|
+
}
|
|
63
|
+
else if (bytes.length >= 30 && bytes.toString("ascii", 0, 4) === "RIFF"
|
|
64
|
+
&& bytes.toString("ascii", 8, 12) === "WEBP" && bytes.readUInt32LE(4) + 8 === bytes.length) {
|
|
65
|
+
mime = "image/webp";
|
|
66
|
+
const kind = bytes.toString("ascii", 12, 16);
|
|
67
|
+
if (kind === "VP8X") {
|
|
68
|
+
if (bytes[20] & 2)
|
|
69
|
+
throw new Error("Animated images are unsupported; save a static frame.");
|
|
70
|
+
width = 1 + bytes.readUIntLE(24, 3);
|
|
71
|
+
height = 1 + bytes.readUIntLE(27, 3);
|
|
72
|
+
}
|
|
73
|
+
else if (kind === "VP8 " && bytes.subarray(23, 26).equals(Buffer.from([157, 1, 42]))) {
|
|
74
|
+
width = bytes.readUInt16LE(26) & 0x3fff;
|
|
75
|
+
height = bytes.readUInt16LE(28) & 0x3fff;
|
|
76
|
+
}
|
|
77
|
+
else if (kind === "VP8L" && bytes[20] === 47) {
|
|
78
|
+
width = 1 + (bytes.readUInt32LE(21) & 0x3fff);
|
|
79
|
+
height = 1 + ((bytes.readUInt32LE(21) >>> 14) & 0x3fff);
|
|
80
|
+
}
|
|
81
|
+
let frames = 0;
|
|
82
|
+
for (let offset = 12; offset < bytes.length;) {
|
|
83
|
+
if (offset + 8 > bytes.length)
|
|
84
|
+
throw new Error("Truncated WebP image.");
|
|
85
|
+
const length = bytes.readUInt32LE(offset + 4);
|
|
86
|
+
const type = bytes.toString("ascii", offset, offset + 4);
|
|
87
|
+
if (type === "ANIM" || type === "ANMF")
|
|
88
|
+
throw new Error("Animated images are unsupported; save a static frame.");
|
|
89
|
+
if (offset + 8 + length > bytes.length)
|
|
90
|
+
throw new Error("Truncated WebP image.");
|
|
91
|
+
if (type === "VP8 " || type === "VP8L") {
|
|
92
|
+
let frameWidth = 0, frameHeight = 0;
|
|
93
|
+
if (type === "VP8 " && length >= 10
|
|
94
|
+
&& bytes.subarray(offset + 11, offset + 14).equals(Buffer.from([157, 1, 42]))) {
|
|
95
|
+
frameWidth = bytes.readUInt16LE(offset + 14) & 0x3fff;
|
|
96
|
+
frameHeight = bytes.readUInt16LE(offset + 16) & 0x3fff;
|
|
97
|
+
}
|
|
98
|
+
else if (type === "VP8L" && length >= 5 && bytes[offset + 8] === 47) {
|
|
99
|
+
frameWidth = 1 + (bytes.readUInt32LE(offset + 9) & 0x3fff);
|
|
100
|
+
frameHeight = 1 + ((bytes.readUInt32LE(offset + 9) >>> 14) & 0x3fff);
|
|
101
|
+
}
|
|
102
|
+
if (frameWidth !== width || frameHeight !== height || ++frames > 1) {
|
|
103
|
+
throw new Error("Invalid WebP image frame dimensions.");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
offset += 8 + length + (length % 2);
|
|
107
|
+
if (offset > bytes.length)
|
|
108
|
+
throw new Error("Truncated WebP image.");
|
|
109
|
+
}
|
|
110
|
+
if (frames !== 1)
|
|
111
|
+
throw new Error("WebP image has no readable frame.");
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
throw new Error("Unsupported or damaged image. Use static PNG, JPEG or WebP (not SVG/GIF).");
|
|
115
|
+
}
|
|
116
|
+
if (!width || !height || width > 8192 || height > 8192 || width * height > 16_000_000) {
|
|
117
|
+
throw new Error("Unsupported image dimensions: maximum 8192 per side and 16 million pixels.");
|
|
118
|
+
}
|
|
119
|
+
return { mime, width, height };
|
|
120
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
|
+
import { supportedGit } from "../repository/gitCompatibility.js";
|
|
3
4
|
const executeFile = promisify(execFile);
|
|
4
5
|
/**
|
|
5
6
|
* Hardened, isolated Git runner for per-Task artifact repositories.
|
|
@@ -44,7 +45,7 @@ const HARDENING_FLAGS = Object.freeze([
|
|
|
44
45
|
"-c", "protocol.file.allow=never",
|
|
45
46
|
// Hooks / filters / external programs — no committed script may execute.
|
|
46
47
|
"-c", `core.hooksPath=${NULL_DEVICE}`,
|
|
47
|
-
"-c", "core.fsmonitor=
|
|
48
|
+
"-c", "core.fsmonitor=",
|
|
48
49
|
"-c", "core.pager=cat",
|
|
49
50
|
"-c", "core.editor=false",
|
|
50
51
|
// Signing — never shell out to a signing program.
|
|
@@ -77,8 +78,9 @@ function managedGitEnvironment() {
|
|
|
77
78
|
PATH: process.env.PATH ?? "/usr/bin:/bin",
|
|
78
79
|
LANG: "C.UTF-8",
|
|
79
80
|
LC_ALL: "C.UTF-8",
|
|
80
|
-
//
|
|
81
|
-
//
|
|
81
|
+
// Older supported Git does not recognize GIT_CONFIG_GLOBAL/SYSTEM.
|
|
82
|
+
// A neutral HOME and absent XDG_CONFIG_HOME also block its global lookup;
|
|
83
|
+
// GIT_CONFIG_NOSYSTEM is understood by all supported versions.
|
|
82
84
|
HOME: process.platform === "win32" ? (process.env.USERPROFILE ?? "") : "/nonexistent",
|
|
83
85
|
GIT_CONFIG_NOSYSTEM: "1",
|
|
84
86
|
GIT_CONFIG_GLOBAL: NULL_DEVICE,
|
|
@@ -122,8 +124,9 @@ function assertSafeArguments(args) {
|
|
|
122
124
|
*/
|
|
123
125
|
async function spawnManagedGit(repoPath, args, options) {
|
|
124
126
|
assertSafeArguments(args);
|
|
127
|
+
const program = supportedGit();
|
|
125
128
|
try {
|
|
126
|
-
const result = await executeFile(
|
|
129
|
+
const result = await executeFile(program.path, [...HARDENING_FLAGS, "-C", repoPath, ...args], {
|
|
127
130
|
encoding: "buffer",
|
|
128
131
|
env: managedGitEnvironment(),
|
|
129
132
|
maxBuffer: options?.maxBuffer ?? 16 * 1024 * 1024,
|
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
|
+
import { supportedGit } from "../repository/gitCompatibility.js";
|
|
5
6
|
import { acquireArtifactCommitLock } from "./artifactCommitLock.js";
|
|
6
7
|
import { resolveContainedArtifactPath, safeRelativeArtifactPath, taskArtifactRepoPath } from "./artifactPaths.js";
|
|
7
8
|
import { ManagedGitError, externalProgramConfigViolations, managedGit, managedGitBuffer, managedGitSucceeds, requireCommitId } from "./managedGit.js";
|
|
@@ -96,6 +97,7 @@ export function openTaskArtifactRepository(home, taskId) {
|
|
|
96
97
|
return currentHead(repoPath);
|
|
97
98
|
};
|
|
98
99
|
const ensure = async () => {
|
|
100
|
+
supportedGit();
|
|
99
101
|
if (exists()) {
|
|
100
102
|
await assertNoRemote(taskId, repoPath);
|
|
101
103
|
return;
|
|
@@ -189,6 +191,10 @@ export function openTaskArtifactRepository(home, taskId) {
|
|
|
189
191
|
if (objectType !== "blob") {
|
|
190
192
|
throw new Error(`Artifact ${safeRelative} at ${pinned} is not a file (${objectType}).`);
|
|
191
193
|
}
|
|
194
|
+
const size = Number((await managedGit(repoPath, ["cat-file", "-s", objectSpec])).trim());
|
|
195
|
+
if (!Number.isSafeInteger(size) || size > MAX_ARTIFACT_BYTES) {
|
|
196
|
+
throw new Error(`Artifact ${safeRelative} exceeds the ${MAX_ARTIFACT_BYTES}-byte limit.`);
|
|
197
|
+
}
|
|
192
198
|
const bytes = await managedGitBuffer(repoPath, ["cat-file", "blob", objectSpec], {
|
|
193
199
|
maxBuffer: MAX_ARTIFACT_BYTES + 4096
|
|
194
200
|
}).catch((error) => {
|
|
@@ -387,7 +387,14 @@ const taskChildren = [
|
|
|
387
387
|
usage: "yui task list [--all] [--status <status>] [--project <id>] [--search <text>] [--attention <category>] [--limit <1..100>] [--cursor <cursor>]",
|
|
388
388
|
options: ["--all", "--status", "--project", "--search", "--attention", "--limit", "--cursor"]
|
|
389
389
|
},
|
|
390
|
+
{
|
|
391
|
+
name: "search", discovery: { surface: "public", audiences: ["public", "operator", "leader"] },
|
|
392
|
+
summary: "Search authorized Task bodies and return exact, reference-only source citations.",
|
|
393
|
+
usage: "yui task search <query> [--task <id>] [--kind <brief|message|decision|result|completion>] [--limit <1..100>] [--cursor <cursor>]",
|
|
394
|
+
options: ["--task", "--kind", "--limit", "--cursor"]
|
|
395
|
+
},
|
|
390
396
|
{ name: "show", discovery: { surface: "public", audiences: ["public", "operator", "leader", "assignment"] }, summary: "Show Task metadata and counts; long details use contentPage.", usage: "yui task show <id> [--cursor <cursor>]", options: ["--cursor"] },
|
|
397
|
+
{ name: "usage", discovery: { surface: "public", audiences: ["public", "operator", "leader"] }, summary: "Read bounded Task usage and sourced costs; request detail pages explicitly.", usage: "yui task usage <task> [--limit <0..50>] [--offset <n>]", options: ["--limit", "--offset"] },
|
|
391
398
|
{
|
|
392
399
|
name: "artifact", discovery: { surface: "managed", audiences: ["operator", "leader"] },
|
|
393
400
|
summary: "Save Task files in local Git and read current or commit-pinned content.",
|
|
@@ -395,7 +402,7 @@ const taskChildren = [
|
|
|
395
402
|
children: [
|
|
396
403
|
{ name: "list", discovery: { surface: "public", audiences: ["public", "operator", "leader", "assignment"] }, summary: "List saved Task artifacts.", usage: "yui task artifact list <task>" },
|
|
397
404
|
{ name: "read", discovery: { surface: "public", audiences: ["public", "operator", "leader", "assignment"] }, summary: "Read a file at HEAD or an exact commit.", usage: "yui task artifact read <task> <relative-path> [<commit>]" },
|
|
398
|
-
{ name: "save", discovery: { surface: "managed", audiences: ["operator", "leader"] }, summary: "Save and locally commit one file.", usage: "yui task artifact save <task> <relative-path> <content> [--message <text>] [--expected-head <commit>]", options: ["--message", "--expected-head"] }
|
|
405
|
+
{ name: "save", discovery: { surface: "managed", audiences: ["operator", "leader"] }, summary: "Save UTF-8 text or a base64 static PNG/JPEG/WebP image and locally commit one file.", usage: "yui task artifact save <task> <relative-path> <content> [--encoding utf8|base64] [--message <text>] [--expected-head <commit>]", options: ["--encoding", "--message", "--expected-head"], optionValues: { "--encoding": ["utf8", "base64"] } }
|
|
399
406
|
]
|
|
400
407
|
},
|
|
401
408
|
{
|
|
@@ -422,9 +429,14 @@ const taskChildren = [
|
|
|
422
429
|
usage: "yui task archive-preflight <id> (--integrated|--abandon) [--force] [--json]",
|
|
423
430
|
options: ["--integrated", "--abandon", "--force", "--json"]
|
|
424
431
|
},
|
|
432
|
+
{
|
|
433
|
+
name: "archive-cleanup", discovery: { surface: "public", audiences: ["public", "operator", "leader"] },
|
|
434
|
+
summary: "Retry one foreground exact-owner cleanup for an archived Task; retain unsafe resources and all history.",
|
|
435
|
+
usage: "yui task archive-cleanup <id>"
|
|
436
|
+
},
|
|
425
437
|
{
|
|
426
438
|
name: "archive", discovery: { surface: "public", audiences: ["public", "operator", "leader"], optionAudiences: { "--force": ["public", "operator"] } },
|
|
427
|
-
summary: "Archive
|
|
439
|
+
summary: "Archive settled, delivered, stopped work, then attempt exact-owner cleanup. Retained physical resources do not require --force; force separately bypasses business admission.",
|
|
428
440
|
usage: "yui task archive <id> (--integrated|--abandon) [--force] [--source-message <id> --purpose <verbatim-authorization> --request-id <id>]",
|
|
429
441
|
options: ["--integrated", "--abandon", "--force", "--source-message", "--purpose", "--request-id"]
|
|
430
442
|
},
|
|
@@ -992,7 +1004,7 @@ const taskChildren = [
|
|
|
992
1004
|
},
|
|
993
1005
|
{ name: "list", discovery: { surface: "managed", audiences: ["operator", "leader"] }, summary: "List Integration Attempts.", usage: "yui task integration list <task>" },
|
|
994
1006
|
{ name: "show", discovery: { surface: "managed", audiences: ["operator", "leader"] }, summary: "Show one Integration Attempt.", usage: "yui task integration show <task>/<integration>" },
|
|
995
|
-
{ name: "cleanup", discovery: { surface: "managed", audiences: ["operator", "leader"] }, summary: "
|
|
1007
|
+
{ name: "cleanup", discovery: { surface: "managed", audiences: ["operator", "leader"] }, summary: "Release clean terminal Integration resources; preserve attempt history, check logs and dirty diagnostics.", usage: "yui task integration cleanup <task>/<integration>" }
|
|
996
1008
|
]
|
|
997
1009
|
},
|
|
998
1010
|
{
|
|
@@ -1509,13 +1521,14 @@ export const ROOT_COMMAND = buildNode({
|
|
|
1509
1521
|
{
|
|
1510
1522
|
name: "list", discovery: { surface: "public", audiences: ["public", "operator", "leader"] },
|
|
1511
1523
|
summary: "List Project knowledge.",
|
|
1512
|
-
usage: "yui project knowledge list <project> [--all]",
|
|
1513
|
-
options: ["--all"]
|
|
1524
|
+
usage: "yui project knowledge list <project> [--all] [--limit <1..100>] [--cursor <cursor>]",
|
|
1525
|
+
options: ["--all", "--limit", "--cursor"]
|
|
1514
1526
|
},
|
|
1515
1527
|
{
|
|
1516
1528
|
name: "show", discovery: { surface: "public", audiences: ["public", "operator", "leader"] },
|
|
1517
1529
|
summary: "Read one Project knowledge entry.",
|
|
1518
|
-
usage: "yui project knowledge show <project> <knowledge>"
|
|
1530
|
+
usage: "yui project knowledge show <project> <knowledge> [--cursor <cursor>]",
|
|
1531
|
+
options: ["--cursor"]
|
|
1519
1532
|
},
|
|
1520
1533
|
{
|
|
1521
1534
|
name: "propose", discovery: { surface: "public", audiences: ["public", "operator", "leader"] },
|
|
@@ -1533,13 +1546,14 @@ export const ROOT_COMMAND = buildNode({
|
|
|
1533
1546
|
{
|
|
1534
1547
|
name: "list", discovery: { surface: "public", audiences: ["public", "operator", "leader"] },
|
|
1535
1548
|
summary: "List Knowledge promotion proposals (pending by default).",
|
|
1536
|
-
usage: "yui project knowledge proposals list <project> [--status pending|accepted|rejected] [--all]",
|
|
1537
|
-
options: ["--status", "--all"]
|
|
1549
|
+
usage: "yui project knowledge proposals list <project> [--status pending|accepted|rejected] [--all] [--limit <1..100>] [--cursor <cursor>]",
|
|
1550
|
+
options: ["--status", "--all", "--limit", "--cursor"]
|
|
1538
1551
|
},
|
|
1539
1552
|
{
|
|
1540
1553
|
name: "show", discovery: { surface: "public", audiences: ["public", "operator", "leader"] },
|
|
1541
1554
|
summary: "Show one Knowledge promotion proposal.",
|
|
1542
|
-
usage: "yui project knowledge proposals show <project> <proposal>"
|
|
1555
|
+
usage: "yui project knowledge proposals show <project> <proposal> [--cursor <cursor>]",
|
|
1556
|
+
options: ["--cursor"]
|
|
1543
1557
|
}
|
|
1544
1558
|
]
|
|
1545
1559
|
},
|
|
@@ -1579,9 +1593,9 @@ export const ROOT_COMMAND = buildNode({
|
|
|
1579
1593
|
name: "task", discovery: { surface: "public", audiences: ["public", "operator"] },
|
|
1580
1594
|
summary: "Manage Tasks, WorkItems, AgentRuns, and integration.",
|
|
1581
1595
|
sections: [
|
|
1582
|
-
{ id: "lifecycle", title: "Lifecycle", entries: ["create", "project", "base", "update", "activate", "activation", "execution", "complete", "cancel", "reopen", "retire", "list", "show", "context", "next-action", "remote-delivery", "archive-preflight", "archive", "replace", "reconcile", "upstream", "artifact"] },
|
|
1596
|
+
{ id: "lifecycle", title: "Lifecycle", entries: ["create", "project", "base", "update", "activate", "activation", "execution", "complete", "cancel", "reopen", "retire", "list", "show", "context", "next-action", "remote-delivery", "archive-preflight", "archive", "archive-cleanup", "replace", "reconcile", "upstream", "artifact"] },
|
|
1583
1597
|
{ id: "collaboration", title: "Collaboration", entries: ["message", "input", "grant", "workflow", "publication", "work", "run", "review", "integration", "role", "overlap", "change-set"] },
|
|
1584
|
-
{ id: "knowledge", title: "Task Knowledge", entries: ["brief", "decision", "milestone", "event", "continuation", "wake"] }
|
|
1598
|
+
{ id: "knowledge", title: "Task Knowledge", entries: ["search", "brief", "decision", "milestone", "event", "continuation", "wake", "usage"] }
|
|
1585
1599
|
],
|
|
1586
1600
|
children: taskChildren
|
|
1587
1601
|
},
|
|
@@ -236,6 +236,10 @@ export const INTERACTION_POLICIES = Object.freeze([
|
|
|
236
236
|
...taskTarget("archive-preflight"),
|
|
237
237
|
trailingOptions: { "--integrated": "flag", "--abandon": "flag", "--force": "flag", "--json": "flag" }
|
|
238
238
|
},
|
|
239
|
+
{
|
|
240
|
+
...taskTarget("archive-cleanup", 2, ["archived"]),
|
|
241
|
+
confirmation: { action: "Clean archived Task resources", targetArgumentIndex: 2 }
|
|
242
|
+
},
|
|
239
243
|
{
|
|
240
244
|
...taskTarget("archive", 2, ["completed", "cancelled"]),
|
|
241
245
|
trailingOptions: { "--integrated": "flag", "--abandon": "flag", "--force": "flag" },
|
|
@@ -35,7 +35,7 @@ export function assertTaskInvocationScope(args, env) {
|
|
|
35
35
|
if (args[0] === "jobs" || (args[0] === "task" && ["create", "overlap"].includes(args[1] ?? ""))) {
|
|
36
36
|
throw usageError("This global operation is outside the caller's Task; use its scoped Context.");
|
|
37
37
|
}
|
|
38
|
-
if (args[0] !== "task" || args[1]
|
|
38
|
+
if (args[0] !== "task" || ["list", "search"].includes(args[1] ?? ""))
|
|
39
39
|
return;
|
|
40
40
|
const diagnostic = taskDiagnosticTarget(args);
|
|
41
41
|
if (diagnostic !== undefined) {
|
|
@@ -251,6 +251,11 @@ export function renderExecutionAudit(report, width = defaultTableWidth()) {
|
|
|
251
251
|
lines.push("", "Observed usage — Task lifetime, observed sources only (audit time window not applied):");
|
|
252
252
|
for (const usage of report.usage.data) {
|
|
253
253
|
lines.push(` ${usage.taskId}: tokens=${formatUsageMetric(usage.tokens)}; tools=${formatUsageMetric(usage.toolCalls)}; elapsed=${formatUsageMetric(usage.elapsedSeconds, "s")}; native execution sum=${formatUsageMetric(usage.executionSeconds, "s")}`);
|
|
254
|
+
for (const kind of ["actual", "estimated"]) {
|
|
255
|
+
const costs = usage.costs[kind];
|
|
256
|
+
lines.push(` ${kind} cost: ${costs.amounts.length === 0 ? `unknown (${costs.reasons.join(", ")})`
|
|
257
|
+
: costs.amounts.map(amount => `${formatUsageMetric(amount)} ${amount.currency}`).join("; ")} (observed sources only, not a bill)`);
|
|
258
|
+
}
|
|
254
259
|
}
|
|
255
260
|
}
|
|
256
261
|
else {
|
|
@@ -16,8 +16,38 @@ import { isSystemRoleName, SYSTEM_ROLE_NAMES, systemRoleDescription } from "../r
|
|
|
16
16
|
import { hasAgentConfigOptions, hasNoRoleMutation, parseRoleOptions, patchRoleAgentBinding, roleOptionSpecs, roleProfileFrom, roleProfilePatch } from "./roleConfiguration.js";
|
|
17
17
|
import { hasRoleLaunchContextOptions, validateConfiguredRoleSkills } from "./roleSkillValidation.js";
|
|
18
18
|
import { assertLiveRoleSessionAcknowledged, assertRoleRuntimeMutationAllowed, LIVE_SESSION_ACKNOWLEDGEMENT_OPTION } from "./roleRuntimeGuard.js";
|
|
19
|
+
/** Preview the complete binding, not just the fields supplied by the caller. */
|
|
20
|
+
export function previewGlobalRoleAgentConfigurationMutation(args, store, options = {}) {
|
|
21
|
+
const [command, rawName, ...tail] = args;
|
|
22
|
+
if (command !== "add" && command !== "update")
|
|
23
|
+
return undefined;
|
|
24
|
+
const name = roleName(rawName);
|
|
25
|
+
const parsed = parseRoleOptions(tail, roleOptionSpecs({
|
|
26
|
+
update: command === "update", includeAgent: true, includeWorkspace: true
|
|
27
|
+
}));
|
|
28
|
+
if (command === "update" && !hasAgentConfigOptions(parsed))
|
|
29
|
+
return undefined;
|
|
30
|
+
const role = command === "update" ? requireRole(name, store) : undefined;
|
|
31
|
+
const agentId = command === "add"
|
|
32
|
+
? required(parsed.one("--agent"), "--agent")
|
|
33
|
+
: parsed.one("--agent")?.trim() || role.activeAgentId;
|
|
34
|
+
const agent = requireAgent(agentId, store);
|
|
35
|
+
const binding = patchRoleAgentBinding(role?.agentBindings[agentId] ?? createRoleAgentBinding(definition(agent)), parsed);
|
|
36
|
+
return {
|
|
37
|
+
agentId, config: binding.config,
|
|
38
|
+
cwd: trimmed(parsed.one("--workspace")) ?? role?.workspace
|
|
39
|
+
?? (options.yuiHome !== undefined ? managedGlobalRoleWorkspace(options.yuiHome)
|
|
40
|
+
: store.getConfig().defaultWorkspace ?? process.cwd())
|
|
41
|
+
};
|
|
42
|
+
}
|
|
19
43
|
export function applyGlobalInputControl(name, input, store, options = {}) {
|
|
20
44
|
assertGlobalInputAuthority(store, options, name);
|
|
45
|
+
if (input.expectedSessionId !== undefined) {
|
|
46
|
+
const set = store.getGlobalRoleSessionSet(name);
|
|
47
|
+
if (set?.sessions[set.activeAgentId]?.nativeSessionId !== input.expectedSessionId) {
|
|
48
|
+
throw usageError("Selected native Session changed; refresh before sending.");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
21
51
|
return input.action === "interrupt"
|
|
22
52
|
? interruptGlobalInput(name, input, store)
|
|
23
53
|
: messageGlobalInput(name, input, store, options);
|
|
@@ -153,6 +183,7 @@ function roleContext(args, store, options) {
|
|
|
153
183
|
// Historical interrupt receipts are available through Session inspection;
|
|
154
184
|
// only this Turn's exact identity is required at entry.
|
|
155
185
|
sessionManifestPath: environment.YUI_SESSION_MANIFEST,
|
|
186
|
+
skillPackages: session?.effective.skillPackages ?? null,
|
|
156
187
|
cliCommand: "yui"
|
|
157
188
|
});
|
|
158
189
|
const data = boundedDocument(context, `global:${name}/context`, parsed.cursor);
|
|
@@ -206,6 +237,7 @@ function addRole(args, store, options) {
|
|
|
206
237
|
? managedGlobalRoleWorkspace(options.yuiHome)
|
|
207
238
|
: tx.getConfig().defaultWorkspace ?? process.cwd());
|
|
208
239
|
const binding = patchRoleAgentBinding(createRoleAgentBinding(definition(agent)), parsed);
|
|
240
|
+
options.validateAgentConfiguration?.({ agentId, config: binding.config, cwd: workspace });
|
|
209
241
|
const profile = roleProfileFrom(parsed);
|
|
210
242
|
validateConfiguredRoleSkills(options.yuiHome, profile.skills ?? []);
|
|
211
243
|
const role = createGlobalRole(name, [binding], agent.id, workspace, now, profile);
|
|
@@ -295,7 +327,11 @@ function updateRole(args, store, options) {
|
|
|
295
327
|
const agentId = parsed.one("--agent")?.trim() || role.activeAgentId;
|
|
296
328
|
const agent = requireAgent(agentId, tx);
|
|
297
329
|
const binding = role.agentBindings[agentId] ?? createRoleAgentBinding(definition(agent));
|
|
298
|
-
|
|
330
|
+
const patched = patchRoleAgentBinding(binding, parsed);
|
|
331
|
+
options.validateAgentConfiguration?.({
|
|
332
|
+
agentId, config: patched.config, cwd: workspace ?? role.workspace
|
|
333
|
+
});
|
|
334
|
+
bindings = { ...role.agentBindings, [agentId]: patched };
|
|
299
335
|
}
|
|
300
336
|
const updated = updateGlobalRole(role, {
|
|
301
337
|
...(workspace === undefined ? {} : { workspace }),
|
|
@@ -537,7 +573,8 @@ export function sendGlobalRoleMessageCommand(store, roleName, body, author, inpu
|
|
|
537
573
|
const original = existing.inputControl ?? existing.interruptThen?.reusedInput;
|
|
538
574
|
if (original?.action !== inputControl.action
|
|
539
575
|
|| existing.body !== body
|
|
540
|
-
|| original.expectedTarget !== inputControl.expectedTarget
|
|
576
|
+
|| original.expectedTarget !== inputControl.expectedTarget
|
|
577
|
+
|| original.expectedSessionId !== inputControl.expectedSessionId) {
|
|
541
578
|
throw usageError(`Input requestId ${inputControl.requestId} was already used with different content or target; use a new requestId for a new input.`);
|
|
542
579
|
}
|
|
543
580
|
return { message: existing, idempotentReplay: true };
|
|
@@ -630,7 +667,8 @@ function messageGlobalInput(name, input, store, options) {
|
|
|
630
667
|
? environment.YUI_ROLE === "operator" ? "operator" : "agent" : "user" };
|
|
631
668
|
const persisted = store.transaction((tx) => {
|
|
632
669
|
requireRole(name, tx);
|
|
633
|
-
return sendGlobalRoleMessageCommand(tx, name, body, author, { action, requestId, ...(expectedTarget === undefined ? {} : { expectedTarget })
|
|
670
|
+
return sendGlobalRoleMessageCommand(tx, name, body, author, { action, requestId, ...(expectedTarget === undefined ? {} : { expectedTarget }),
|
|
671
|
+
...(input.expectedSessionId === undefined ? {} : { expectedSessionId: input.expectedSessionId }) }, now);
|
|
634
672
|
});
|
|
635
673
|
if (action === "queue") {
|
|
636
674
|
const state = persisted.idempotentReplay ? "idempotent-replay" : "queued";
|