@zq-silk/yui 0.15.6 → 0.15.8
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/ARCHITECTURE.md +192 -399
- package/README.md +127 -1149
- package/dist/agent/adapterCatalog.js +15 -2
- package/dist/agent/agent.js +23 -3
- package/dist/agent/argumentPolicy.js +7 -1
- package/dist/agent/connectionPlan.js +62 -0
- package/dist/agent/executionComponents.js +158 -0
- package/dist/agent/launchEnvironment.js +31 -3
- package/dist/agent/managedRuntimeEnvironment.js +3 -5
- package/dist/{turn/turn.js → agentRun/agentRun.js} +166 -109
- package/dist/{turn/turnIdentity.js → agentRun/runIdentity.js} +4 -4
- package/dist/brief/taskBrief.js +12 -0
- package/dist/cli/agentConfigurationPicker.js +13 -0
- package/dist/cli/commandCatalog.js +167 -70
- package/dist/cli/interactionCandidates.js +5 -5
- package/dist/cli/interactionPolicy.js +38 -8
- package/dist/cli/invocationRouter.js +1 -1
- package/dist/cli/managedDiagnostics.js +28 -0
- package/dist/cli/operatorWizard.js +1 -7
- package/dist/cli/roleOptionOrder.js +27 -0
- package/dist/cli/roleWizard.js +50 -14
- package/dist/cli/updateOrchestrator.js +1 -1
- package/dist/cli/updatePorts.js +3 -4
- package/dist/cli.js +188 -95
- package/dist/commands/agentCommands.js +72 -14
- package/dist/commands/capabilityCommands.js +57 -0
- package/dist/commands/configCommands.js +20 -20
- package/dist/commands/deliveryGuardPreflight.js +2 -2
- package/dist/commands/executionAuditCommands.js +24 -24
- package/dist/commands/globalRoleCommands.js +1 -1
- package/dist/commands/grantCommands.js +4 -4
- package/dist/commands/operatorCommands.js +1 -7
- package/dist/commands/projectCommands.js +4 -4
- package/dist/commands/resourcesCommands.js +2 -2
- package/dist/commands/roleConfiguration.js +25 -5
- package/dist/commands/roleRuntimeGuard.js +4 -5
- package/dist/commands/sessionCommands.js +3 -7
- package/dist/commands/taskActivationCommands.js +259 -0
- package/dist/commands/taskActor.js +28 -49
- package/dist/commands/taskCommands.js +1216 -730
- package/dist/commands/taskContextCommand.js +39 -583
- package/dist/commands/taskExecutionCommands.js +32 -32
- package/dist/commands/taskInputCommands.js +40 -104
- package/dist/commands/taskIntegrationCommands.js +3 -2
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskNextActionCommand.js +8 -8
- package/dist/commands/taskOverviewCommand.js +33 -45
- package/dist/commands/taskRemoteDeliveryCommand.js +2 -2
- package/dist/commands/taskRoleRuntimeStatus.js +133 -102
- package/dist/commands/telemetryCommands.js +36 -38
- package/dist/config/configCatalog.js +4 -4
- package/dist/config/yuiConfig.js +8 -8
- package/dist/context/contextSnapshot.js +10 -10
- package/dist/context/dispatchContext.js +11 -11
- package/dist/context/roleSessionContext.js +6 -3
- package/dist/context/{turnContextPack.js → runContextPack.js} +146 -81
- package/dist/context/{turnInputContract.js → runInputContract.js} +73 -60
- package/dist/context/sessionBootstrapManifest.js +21 -2
- package/dist/context/sourceRunContext.js +30 -0
- package/dist/context/taskContext.js +458 -0
- package/dist/context/wakeNotification.js +27 -27
- package/dist/controller/agentRuntimeObserver.js +21 -24
- package/dist/controller/capabilityBridge.js +77 -0
- package/dist/controller/clientRuntime.js +65 -92
- package/dist/controller/controller.js +127 -188
- package/dist/controller/fileSchedulerStoreAdapter.js +787 -887
- package/dist/controller/jobControl.js +54 -85
- package/dist/controller/resourceInventory.js +8 -27
- package/dist/controller/resourceInventoryLinux.js +12 -13
- package/dist/controller/runtime.js +530 -476
- package/dist/controller/runtimeEventInbox.js +55 -25
- package/dist/controller/runtimeEventProcessor.js +22 -31
- package/dist/controller/{runtimeHookTurnFence.js → runtimeHookRunFence.js} +91 -115
- package/dist/controller/runtimeLaunchCoordinator.js +80 -426
- package/dist/controller/runtimeObservationHook.js +14 -18
- package/dist/controller/sessionNotify.js +16 -24
- package/dist/controller/sessionOwnerReconciliation.js +168 -50
- package/dist/controller/structuredProviderObservation.js +138 -99
- package/dist/coordination/workMailbox.js +3 -3
- package/dist/coordination/workMailboxQueue.js +36 -33
- package/dist/core/boundedRpc.js +8 -1
- package/dist/core/controllerClient.js +20 -1
- package/dist/core/controllerServer.js +4 -4
- package/dist/doctor/doctor.js +13 -2
- package/dist/domain/agentResultTransport.js +9 -9
- package/dist/execution/codexThreadNaming.js +2 -8
- package/dist/execution/executionHealth.js +51 -63
- package/dist/execution/reviewMainRun.js +137 -0
- package/dist/execution/workItemExecution.js +28 -29
- package/dist/execution/workItemExecutionProjection.js +99 -107
- package/dist/execution/workItemMainRun.js +141 -0
- package/dist/executor/agentAdapter.js +227 -20
- package/dist/executor/agentConfigurationCatalog.js +126 -4
- package/dist/executor/agentConfigurationProbe.js +162 -4
- package/dist/executor/agentExecutor.js +79 -78
- package/dist/executor/effectiveLaunch.js +105 -18
- package/dist/executor/executorRegistry.js +29 -44
- package/dist/executor/fileRoleLaunchPlanner.js +229 -154
- package/dist/executor/workspacePreflightClassification.js +16 -16
- package/dist/grant/capabilityGrant.js +6 -3
- package/dist/input/inputRequest.js +12 -10
- package/dist/integration/gitIntegrationService.js +4 -11
- package/dist/integration/integrationQueueService.js +4 -4
- package/dist/interaction/operatorPresentation.js +1 -1
- package/dist/kernel/builtinCapabilities.js +423 -0
- package/dist/kernel/capabilityRegistry.js +314 -0
- package/dist/kernel/capabilitySchema.js +91 -0
- package/dist/kernel/instanceHost.js +18 -1
- package/dist/kernel/kernelPorts.js +4 -1
- package/dist/lifecycle/canonicalLifecycleEvent.js +44 -49
- package/dist/lifecycle/exactRunTerminalization.js +449 -0
- package/dist/message/message.js +62 -6
- package/dist/message/messageContinuation.js +204 -0
- package/dist/observability/executionAudit.js +70 -72
- package/dist/observability/faultClassification.js +2 -2
- package/dist/observability/orchestrationMetrics.js +8 -8
- package/dist/operator/operatorSessionHistory.js +1 -7
- package/dist/output/agentConfigurationPresentation.js +8 -3
- package/dist/output/agentRunConfigurationPresentation.js +128 -0
- package/dist/output/rolePresentation.js +54 -3
- package/dist/plugins/pluginChild.js +104 -0
- package/dist/plugins/pluginIntent.js +26 -0
- package/dist/plugins/pluginInterpreter.js +43 -0
- package/dist/plugins/pluginPackage.js +101 -0
- package/dist/plugins/pluginProcess.js +112 -0
- package/dist/plugins/pluginService.js +380 -0
- package/dist/profile/agentProfile.js +1 -1
- package/dist/repository/gitWorkspace.js +26 -4
- package/dist/repository/project.js +19 -4
- package/dist/repository/taskBaseFreshness.js +13 -13
- package/dist/repository/taskWorkspaceCoordinator.js +20 -27
- package/dist/repository/taskWorkspacePreparer.js +344 -83
- package/dist/resources/autoResourceGc.js +3 -3
- package/dist/resources/liveReferences.js +3 -3
- package/dist/resources/projectResource.js +123 -0
- package/dist/resources/projectResourceService.js +421 -0
- package/dist/resources/resourceDiscovery.js +6 -6
- package/dist/resources/resourceGc.js +1 -1
- package/dist/resources/resourceRegistrar.js +1 -1
- package/dist/resources/resourceTypes.js +1 -1
- package/dist/review/deltaRecheck.js +3 -3
- package/dist/review/reviewAcceptance.js +16 -16
- package/dist/review/reviewDecision.js +7 -7
- package/dist/review/reviewRound.js +21 -20
- package/dist/review/reviewerAvailability.js +2 -2
- package/dist/role/role.js +51 -7
- package/dist/role/taskRoleUpdate.js +30 -0
- package/dist/runtime/acpProtocol.js +425 -0
- package/dist/runtime/acpSession.js +731 -0
- package/dist/runtime/acpSessionConfiguration.js +260 -0
- package/dist/runtime/agentDriver.js +30 -11
- package/dist/runtime/agentEndpoint.js +278 -0
- package/dist/runtime/agentEndpointIdentity.js +86 -0
- package/dist/runtime/agentEndpointOwnership.js +239 -0
- package/dist/runtime/agentError.js +2 -10
- package/dist/runtime/agentHost.js +565 -314
- package/dist/runtime/agentRunConfiguration.js +258 -0
- package/dist/runtime/builtinAgentDrivers.js +134 -18
- package/dist/runtime/builtinAgentErrorMappers.js +55 -3
- package/dist/runtime/builtinTranscriptUsage.js +1 -1
- package/dist/runtime/claude-process-owner +0 -0
- package/dist/runtime/codexAppServerRuntime.js +38 -30
- package/dist/runtime/codexInteractiveHost.js +41 -6
- package/dist/runtime/continuationManager.js +2 -6
- package/dist/runtime/executionEnvironment.js +30 -0
- package/dist/runtime/firstProgressAdvisory.js +11 -11
- package/dist/runtime/index.js +4 -3
- package/dist/runtime/jsonLineChannel.js +109 -0
- package/dist/runtime/launchBroker.js +91 -16
- package/dist/runtime/launchDiagnostics.js +2 -2
- package/dist/runtime/lifecycleReservation.js +10 -18
- package/dist/runtime/managedCaller.js +61 -17
- package/dist/runtime/nativeSessionControl.js +102 -0
- package/dist/runtime/ports.js +6 -21
- package/dist/runtime/processExitObservation.js +8 -7
- package/dist/runtime/promptEnvelope.js +17 -6
- package/dist/runtime/providerContinuation.js +3 -9
- package/dist/runtime/providerContinuationReconciliationService.js +4 -13
- package/dist/runtime/providerControl.js +2 -7
- package/dist/runtime/providerRuntimeIdentity.js +110 -222
- package/dist/runtime/providerRuntimeReconciler.js +5 -9
- package/dist/runtime/runtimeBinding.js +0 -1
- package/dist/runtime/runtimeContinuationProjection.js +4 -7
- package/dist/runtime/runtimeDeadlines.js +9 -0
- package/dist/runtime/runtimeHealthPolicy.js +1 -1
- package/dist/runtime/runtimeObservation.js +29 -65
- package/dist/runtime/runtimeProjection.js +43 -51
- package/dist/runtime/runtimeSessionCandidate.js +1 -3
- package/dist/runtime/sessionLaunchRequest.js +3 -7
- package/dist/runtime/sessionOwnerIdentity.js +7 -54
- package/dist/runtime/sessionOwnerRegistry.js +22 -17
- package/dist/runtime/sessionReconciliation.js +4 -8
- package/dist/runtime/sessionTerminationGuard.js +70 -259
- package/dist/runtime/sessionTokenMetrics.js +5 -16
- package/dist/runtime/structuredProviderHost.js +237 -117
- package/dist/runtime/taskRuntimeIsolation.js +39 -122
- package/dist/runtime/tmuxAdapters.js +39 -86
- package/dist/scheduler/activeRoleRunDelivery.js +354 -0
- package/dist/scheduler/leaderWakeupProcessor.js +75 -266
- package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
- package/dist/scheduler/ports.js +80 -9
- package/dist/scheduler/{roleTurnLiveness.js → roleRunLiveness.js} +26 -30
- package/dist/scheduler/{roleTurnStall.js → roleRunStall.js} +128 -139
- package/dist/scheduler/taskExecutionProjection.js +120 -124
- package/dist/scheduler/taskObservabilityProjection.js +29 -29
- package/dist/scheduler/taskWake.js +11 -4
- package/dist/scheduler/wakeReason.js +9 -1
- package/dist/setup/setupCommand.js +1 -0
- package/dist/storage/migrations/agentRunContract.js +159 -0
- package/dist/storage/migrations/removeRuntimeGeneration.js +207 -0
- package/dist/storage/sqliteSchema.js +431 -5
- package/dist/storage/sqliteStore.js +375 -220
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +10 -5
- package/dist/storage/taskStore.js +13 -11
- package/dist/storage/upgrade/upgradeOrchestrator.js +5 -7
- package/dist/surface/surfaceContributions.js +102 -0
- package/dist/task/completionReadiness.js +32 -6
- package/dist/task/deliveryGuard.js +16 -16
- package/dist/task/draftPlan.js +72 -12
- package/dist/task/nextAction.js +144 -128
- package/dist/task/remoteDelivery.js +6 -6
- package/dist/task/task.js +184 -18
- package/dist/task/taskActivation.js +301 -0
- package/dist/task/taskActivationService.js +392 -0
- package/dist/task/taskRecordReference.js +5 -4
- package/dist/task/taskRecordRetirement.js +1 -1
- package/dist/telemetry/sqliteTelemetryStore.js +55 -68
- package/dist/telemetry/telemetryConfig.js +14 -14
- package/dist/telemetry/telemetryWiring.js +2 -2
- package/dist/web/assets/assetManifest.js +2 -0
- package/dist/web/assets/client/app.js +120 -20
- package/dist/web/assets/client/components.js +87 -54
- package/dist/web/assets/client/i18n.js +83 -41
- package/dist/web/assets/client/markdown.js +1 -1
- package/dist/web/assets/client/taskSurface.js +353 -0
- package/dist/web/assets/client/view.js +49 -44
- package/dist/web/assets/shell.js +1 -1
- package/dist/web/assets/styles/cards.js +22 -4
- package/dist/web/controllerWeb.js +60 -0
- package/dist/web/webMutation.js +28 -0
- package/dist/web/webServer.js +118 -8
- package/dist/web/webSnapshot.js +81 -74
- package/dist/web/webTaskSurface.js +64 -0
- package/dist/workItem/dependencyGate.js +1 -1
- package/dist/workItem/workItem.js +80 -48
- package/dist/workspace/workItemChangeSetManager.js +16 -9
- package/docs/agent-result-consumption.md +94 -0
- package/docs/agent-runtime-drivers.md +91 -0
- package/docs/architecture/README.md +38 -0
- package/docs/architecture/capabilities-and-resources.md +79 -0
- package/docs/managed-turn-and-session-runtime.md +222 -0
- package/docs/observability/README.md +81 -0
- package/docs/plugin-sdk.md +290 -0
- package/docs/provider-runtime.md +163 -0
- package/docs/release-workflow.md +303 -0
- package/docs/roles-and-configuration.md +113 -0
- package/docs/sqlite-control-plane-design.md +76 -0
- package/docs/task-dag-semantics.md +57 -0
- package/docs/task-delivery.md +103 -0
- package/docs/task-local-identity.md +6 -6
- package/docs/testing/verification-levels.md +86 -0
- package/i18n/README.zh-CN.md +98 -739
- package/package.json +5 -5
- package/skills/yui-leader/SKILL.md +130 -103
- package/skills/yui-leader/references/integration.md +39 -0
- package/skills/yui-leader/references/replicated-execution.md +42 -0
- package/skills/yui-leader/references/task-plugins.md +33 -0
- package/skills/yui-operator/SKILL.md +30 -59
- package/skills/yui-reviewer/SKILL.md +35 -36
- package/skills/yui-runtime/SKILL.md +88 -24
- package/skills/yui-runtime/references/publication.md +22 -0
- package/skills/yui-runtime/references/recovery.md +64 -0
- package/skills/yui-worker/SKILL.md +37 -39
- package/dist/cli/roleOptionCatalog.js +0 -68
- package/dist/context/sourceTurnContext.js +0 -30
- package/dist/execution/reviewMainTurn.js +0 -161
- package/dist/execution/workItemMainTurn.js +0 -164
- package/dist/lifecycle/exactTurnTerminalization.js +0 -407
- package/dist/runtime/preallocatedNativeSession.js +0 -13
- package/dist/runtime/runtimeStopReceipt.js +0 -42
- package/dist/scheduler/activeRoleTurnDelivery.js +0 -315
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { capabilitySchemaError, checkCapabilitySchema } from "./capabilitySchema.js";
|
|
2
|
+
import { checkSurfaceDescriptors } from "../surface/surfaceContributions.js";
|
|
3
|
+
/** Trusted owners may accompany a failed operation with its current read model.
|
|
4
|
+
* This is diagnostic data, not an operation receipt or an authority token. */
|
|
5
|
+
export class CapabilityExecutionError extends Error {
|
|
6
|
+
value;
|
|
7
|
+
constructor(message, value) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.value = structuredClone(value);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const effectRank = { query: 0, "local-mutation": 1, "external-operation": 2 };
|
|
13
|
+
const reserved = new Set(["yui", "task", "context", "config", "job", "resource", "plugin", "runtime", "grant", "capability", "artifact", "environment", "project"]);
|
|
14
|
+
/** One rebuildable descriptor view over the composition root's existing Host.
|
|
15
|
+
* Only the trusted root owns this object. Extensions receive a bound invocation
|
|
16
|
+
* port, never the registry/Host or an actor-selecting call interface. */
|
|
17
|
+
export class CapabilityRegistry {
|
|
18
|
+
host;
|
|
19
|
+
authorize;
|
|
20
|
+
#descriptors = [];
|
|
21
|
+
#coreProviders = new Set();
|
|
22
|
+
constructor(host, authorize, builtins = []) {
|
|
23
|
+
this.host = host;
|
|
24
|
+
this.authorize = authorize;
|
|
25
|
+
this.#publish(builtins, true);
|
|
26
|
+
builtins.forEach((entry) => this.#coreProviders.add(entry.provider.id));
|
|
27
|
+
}
|
|
28
|
+
/** Publish a complete provider generation atomically after owner initialization.
|
|
29
|
+
* Failure leaves all existing descriptors intact. Host lifecycle remains owned
|
|
30
|
+
* by the root: publish replacement, then detach old generation when appropriate. */
|
|
31
|
+
register(descriptors) {
|
|
32
|
+
this.#publish(descriptors, false);
|
|
33
|
+
}
|
|
34
|
+
/** Data-only candidate check before author initialization; no acquisition or
|
|
35
|
+
* publication. Repeat immediately before publishing after any async work. */
|
|
36
|
+
checkRegistration(context, descriptors) {
|
|
37
|
+
const prepared = this.#prepare(descriptors, false);
|
|
38
|
+
if (!prepared.length)
|
|
39
|
+
throw new Error("Plugin must contribute capabilities.");
|
|
40
|
+
const available = [
|
|
41
|
+
...this.search(context).filter((entry) => entry.provider.id !== prepared[0].provider.id),
|
|
42
|
+
...prepared
|
|
43
|
+
];
|
|
44
|
+
const check = (entry, path) => {
|
|
45
|
+
this.authorize(context, entry);
|
|
46
|
+
if (path.has(entry))
|
|
47
|
+
throw new Error("Required capability dependency cycle.");
|
|
48
|
+
for (const dependency of entry.required ?? []) {
|
|
49
|
+
const matches = available.filter((candidate) => candidate.name === dependency.name
|
|
50
|
+
&& candidate.contractVersion === dependency.contractVersion && candidate.unavailable === undefined);
|
|
51
|
+
if (matches.length !== 1)
|
|
52
|
+
throw new Error(`Required capability is missing or ambiguous: ${dependency.name}.`);
|
|
53
|
+
check(matches[0], new Set([...path, entry]));
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
prepared.forEach((entry) => check(entry, new Set()));
|
|
57
|
+
}
|
|
58
|
+
disable(providerId) {
|
|
59
|
+
this.#descriptors = this.#descriptors.filter((entry) => entry.provider.id !== providerId);
|
|
60
|
+
}
|
|
61
|
+
search(context, query = "") {
|
|
62
|
+
const visibility = this.authorize(context);
|
|
63
|
+
const authorized = this.#descriptors.filter((entry) => visible(entry.scope, visibility))
|
|
64
|
+
.filter((entry) => {
|
|
65
|
+
try {
|
|
66
|
+
this.authorize(context, entry);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
return authorized.filter((entry) => `${entry.name} ${entry.summary}`.toLowerCase().includes(query.toLowerCase()))
|
|
74
|
+
.map((entry) => {
|
|
75
|
+
const unavailable = this.#unavailable(entry, authorized, new Set());
|
|
76
|
+
return unavailable === undefined ? entry : Object.freeze({ ...entry, unavailable });
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
describe(context, request) {
|
|
80
|
+
try {
|
|
81
|
+
const candidates = this.search(context).filter((entry) => entry.name === request.name
|
|
82
|
+
&& (request.contractVersion === undefined || request.contractVersion === entry.contractVersion)
|
|
83
|
+
&& (request.providerId === undefined || request.providerId === entry.provider.id));
|
|
84
|
+
if (!candidates.length)
|
|
85
|
+
return result("unavailable", "No authorized compatible Provider is available.");
|
|
86
|
+
const available = candidates.filter((entry) => entry.unavailable === undefined);
|
|
87
|
+
if (!available.length)
|
|
88
|
+
return {
|
|
89
|
+
...result("unavailable", candidates.map((entry) => `${entry.provider.id}: ${entry.unavailable}`).join("; ")),
|
|
90
|
+
candidates
|
|
91
|
+
};
|
|
92
|
+
if (available.length > 1)
|
|
93
|
+
return { ...result("ambiguous", "Select an explicit Provider and contract version."), candidates };
|
|
94
|
+
const selected = available[0];
|
|
95
|
+
return {
|
|
96
|
+
...result("value"), value: selected, candidates, provider: selected.provider,
|
|
97
|
+
selection: request.providerId === undefined ? "unique" : "explicit"
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return result("denied", "Current call authority is unavailable.");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
call(context, request) {
|
|
105
|
+
return this.#call(context, request, "external-operation");
|
|
106
|
+
}
|
|
107
|
+
async #call(context, request, ceiling, permissions) {
|
|
108
|
+
// Freeze one input snapshot across validation/acquisition and async nesting.
|
|
109
|
+
try {
|
|
110
|
+
request = deepFreeze(structuredClone(request));
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return result("invalid", "Capability request must be cloneable data.");
|
|
114
|
+
}
|
|
115
|
+
const resolved = this.describe(context, request);
|
|
116
|
+
if (resolved.kind !== "value")
|
|
117
|
+
return resolved;
|
|
118
|
+
const descriptor = resolved.value;
|
|
119
|
+
const origin = { provider: descriptor.provider, selection: resolved.selection };
|
|
120
|
+
if (effectRank[descriptor.effect] > effectRank[ceiling]) {
|
|
121
|
+
return { ...result("denied", "Nested call exceeds the parent's effect boundary."), ...origin };
|
|
122
|
+
}
|
|
123
|
+
if (permissions !== undefined && descriptor.requiredPermissions.some((permission) => !permissions.includes(permission))) {
|
|
124
|
+
return { ...result("denied", "Nested call exceeds the parent's declared permissions."), ...origin };
|
|
125
|
+
}
|
|
126
|
+
const inputError = capabilitySchemaError(descriptor.inputSchema, request.input);
|
|
127
|
+
if (inputError)
|
|
128
|
+
return { ...result("invalid", inputError), ...origin };
|
|
129
|
+
if (descriptor.effect !== "query" && (typeof request.requestId !== "string" || !request.requestId.trim())) {
|
|
130
|
+
return { ...result("invalid", "Effectful calls require requestId."), ...origin };
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
this.authorize(context, descriptor, request.input);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return { ...result("denied", "Current call authority was revoked."), ...origin };
|
|
137
|
+
}
|
|
138
|
+
const operations = [];
|
|
139
|
+
let effect = "none";
|
|
140
|
+
const observe = (operation) => {
|
|
141
|
+
operations.push(structuredClone(operation));
|
|
142
|
+
effect = accumulatedEffect(effect, operation.effect);
|
|
143
|
+
};
|
|
144
|
+
let entered = false;
|
|
145
|
+
try {
|
|
146
|
+
const value = await this.host.use(descriptor.provider, async (implementation) => {
|
|
147
|
+
entered = true;
|
|
148
|
+
let open = true;
|
|
149
|
+
const children = [];
|
|
150
|
+
try {
|
|
151
|
+
return await implementation.invoke(descriptor.name, request.input, Object.freeze({
|
|
152
|
+
context, requestId: request.requestId,
|
|
153
|
+
observe: (operation) => {
|
|
154
|
+
if (!open)
|
|
155
|
+
throw new Error("Capability invocation has ended.");
|
|
156
|
+
observe(operation);
|
|
157
|
+
},
|
|
158
|
+
call: (nested) => {
|
|
159
|
+
if (!open)
|
|
160
|
+
return Promise.resolve(result("denied", "Capability invocation has ended."));
|
|
161
|
+
const child = this.#call(context, nested, descriptor.effect, descriptor.requiredPermissions).then((outcome) => {
|
|
162
|
+
outcome.operations.forEach(observe);
|
|
163
|
+
effect = accumulatedEffect(effect, outcome.effect);
|
|
164
|
+
return outcome;
|
|
165
|
+
});
|
|
166
|
+
children.push(child);
|
|
167
|
+
return child;
|
|
168
|
+
}
|
|
169
|
+
}));
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
open = false;
|
|
173
|
+
// A wrapper throwing early must not detach its already-issued child
|
|
174
|
+
// operations from the returned evidence or release the parent handle.
|
|
175
|
+
await Promise.all(children);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
if (descriptor.effect === "local-mutation")
|
|
179
|
+
effect = accumulatedEffect(effect, "confirmed");
|
|
180
|
+
// An external implementation without an owner's operation reference is not
|
|
181
|
+
// proof of success. Preserve uncertainty rather than return a value success.
|
|
182
|
+
if (descriptor.effect === "external-operation" && !operations.length) {
|
|
183
|
+
return { kind: "failed", detail: "External implementation returned no operation evidence.", effect: "possible", operations, ...origin };
|
|
184
|
+
}
|
|
185
|
+
let output;
|
|
186
|
+
try {
|
|
187
|
+
output = JSON.parse(JSON.stringify(value, (_key, item) => {
|
|
188
|
+
if ((typeof item === "number" && !Number.isFinite(item))
|
|
189
|
+
|| typeof item === "bigint" || typeof item === "function" || typeof item === "symbol") {
|
|
190
|
+
throw new Error("Output contains a non-JSON value.");
|
|
191
|
+
}
|
|
192
|
+
return item;
|
|
193
|
+
}));
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
if (descriptor.effect !== "query")
|
|
197
|
+
effect = accumulatedEffect(effect, "possible");
|
|
198
|
+
return { kind: "invalid", detail: "Output is not JSON serializable.", effect, operations, ...origin };
|
|
199
|
+
}
|
|
200
|
+
const outputError = capabilitySchemaError(descriptor.outputSchema, output);
|
|
201
|
+
if (outputError) {
|
|
202
|
+
if (descriptor.effect !== "query")
|
|
203
|
+
effect = accumulatedEffect(effect, "possible");
|
|
204
|
+
return { kind: "invalid", detail: `Output ${outputError}`, effect, operations, ...origin };
|
|
205
|
+
}
|
|
206
|
+
return { kind: operations.length ? "operation" : "value", value: output, effect, operations, ...origin };
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
// An unrelated observation (including a historical queued Job with none)
|
|
210
|
+
// cannot prove that a failing effectful wrapper performed no other action.
|
|
211
|
+
// Preserve the owner's precise facts while reporting wrapper uncertainty.
|
|
212
|
+
if (entered && descriptor.effect !== "query")
|
|
213
|
+
effect = accumulatedEffect(effect, "possible");
|
|
214
|
+
return {
|
|
215
|
+
kind: entered ? "failed" : "unavailable",
|
|
216
|
+
detail: error instanceof Error ? error.message : "Capability invocation failed.",
|
|
217
|
+
...(error instanceof CapabilityExecutionError ? { value: error.value } : {}),
|
|
218
|
+
effect, operations, ...origin
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
#unavailable(entry, authorized, visiting) {
|
|
223
|
+
if (entry.unavailable !== undefined)
|
|
224
|
+
return entry.unavailable;
|
|
225
|
+
if (!this.host.isAvailable(entry.provider))
|
|
226
|
+
return "Provider implementation is not available.";
|
|
227
|
+
if (visiting.has(entry))
|
|
228
|
+
return "Required capability dependency cycle.";
|
|
229
|
+
const path = new Set([...visiting, entry]);
|
|
230
|
+
// Only declared exact name/version edges are checked. No installation,
|
|
231
|
+
// priority policy, version solver or persisted dependency state is involved.
|
|
232
|
+
const missing = entry.required?.filter((dependency) => !authorized.some((candidate) => (candidate.name === dependency.name && candidate.contractVersion === dependency.contractVersion
|
|
233
|
+
&& this.#unavailable(candidate, authorized, path) === undefined)));
|
|
234
|
+
if (missing?.length)
|
|
235
|
+
return `Missing available required capabilities: ${missing.map((dependency) => `${dependency.name}@${dependency.contractVersion}`).join(", ")}.`;
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
#publish(descriptors, core) {
|
|
239
|
+
if (!descriptors.length)
|
|
240
|
+
return;
|
|
241
|
+
const prepared = this.#prepare(descriptors, core);
|
|
242
|
+
const provider = prepared[0].provider;
|
|
243
|
+
// Validate acquisition before publishing; no implementation is invoked.
|
|
244
|
+
if (prepared.some((entry) => !entry.unavailable)) {
|
|
245
|
+
const handle = this.host.acquire(provider);
|
|
246
|
+
try {
|
|
247
|
+
if (typeof handle.value?.invoke !== "function")
|
|
248
|
+
throw new Error("Provider has no capability implementation.");
|
|
249
|
+
}
|
|
250
|
+
finally {
|
|
251
|
+
void handle.release();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
this.#descriptors = [
|
|
255
|
+
...this.#descriptors.filter((entry) => entry.provider.id !== provider.id),
|
|
256
|
+
...prepared.map((entry) => deepFreeze(entry))
|
|
257
|
+
];
|
|
258
|
+
}
|
|
259
|
+
#prepare(descriptors, core) {
|
|
260
|
+
if (!descriptors.length)
|
|
261
|
+
return [];
|
|
262
|
+
const prepared = structuredClone(descriptors);
|
|
263
|
+
const provider = prepared[0].provider;
|
|
264
|
+
if (!core && this.#coreProviders.has(provider.id))
|
|
265
|
+
throw new Error("Core Provider identity is reserved.");
|
|
266
|
+
const names = new Set();
|
|
267
|
+
for (const entry of prepared) {
|
|
268
|
+
if (!/^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)+$/u.test(entry.name)
|
|
269
|
+
|| !entry.contractVersion?.trim() || !entry.source?.trim()
|
|
270
|
+
|| !Object.hasOwn(effectRank, entry.effect)
|
|
271
|
+
|| !Array.isArray(entry.requiredPermissions)
|
|
272
|
+
|| entry.requiredPermissions.some((permission) => typeof permission !== "string" || !permission.trim())) {
|
|
273
|
+
throw new Error("Invalid capability descriptor.");
|
|
274
|
+
}
|
|
275
|
+
if (!core && reserved.has(entry.name.split(".")[0]))
|
|
276
|
+
throw new Error("Core capability namespace is reserved.");
|
|
277
|
+
if (!["global", "project", "task"].includes(entry.scope.kind)
|
|
278
|
+
|| (entry.scope.kind !== "global" && !entry.scope.id?.trim()))
|
|
279
|
+
throw new Error("Invalid capability scope.");
|
|
280
|
+
if (entry.provider.id !== provider.id || entry.provider.generation !== provider.generation) {
|
|
281
|
+
throw new Error("Publish one complete Provider generation at a time.");
|
|
282
|
+
}
|
|
283
|
+
const key = `${entry.name}@${entry.contractVersion}`;
|
|
284
|
+
if (names.has(key))
|
|
285
|
+
throw new Error(`Duplicate capability: ${key}.`);
|
|
286
|
+
names.add(key);
|
|
287
|
+
checkCapabilitySchema(entry.inputSchema);
|
|
288
|
+
checkCapabilitySchema(entry.outputSchema);
|
|
289
|
+
checkSurfaceDescriptors(entry);
|
|
290
|
+
for (const dependency of entry.required ?? []) {
|
|
291
|
+
if (!dependency.name?.trim() || !dependency.contractVersion?.trim())
|
|
292
|
+
throw new Error("Invalid required capability.");
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return [...prepared];
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function visible(scope, visibility) {
|
|
299
|
+
return scope.kind === "global" || (scope.kind === "task"
|
|
300
|
+
? visibility.taskIds.includes(scope.id) : visibility.projectIds.includes(scope.id));
|
|
301
|
+
}
|
|
302
|
+
function result(kind, detail) {
|
|
303
|
+
return { kind, effect: "none", operations: [], ...(detail === undefined ? {} : { detail }) };
|
|
304
|
+
}
|
|
305
|
+
function accumulatedEffect(a, b) {
|
|
306
|
+
return a === "confirmed" || b === "confirmed" ? "confirmed" : a === "possible" || b === "possible" ? "possible" : "none";
|
|
307
|
+
}
|
|
308
|
+
function deepFreeze(value) {
|
|
309
|
+
if (value !== null && typeof value === "object") {
|
|
310
|
+
Object.values(value).forEach(deepFreeze);
|
|
311
|
+
Object.freeze(value);
|
|
312
|
+
}
|
|
313
|
+
return value;
|
|
314
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
export function checkCapabilitySchema(schema) {
|
|
3
|
+
if (typeof schema !== "object" || schema === null || Array.isArray(schema)) {
|
|
4
|
+
throw new Error("Capability schema must be an object.");
|
|
5
|
+
}
|
|
6
|
+
const keywords = new Set([
|
|
7
|
+
"type", "properties", "required", "additionalProperties", "items",
|
|
8
|
+
"enum", "const", "anyOf", "minLength", "minItems"
|
|
9
|
+
]);
|
|
10
|
+
for (const key of Object.keys(schema)) {
|
|
11
|
+
if (!keywords.has(key))
|
|
12
|
+
throw new Error(`Unsupported schema keyword: ${key}.`);
|
|
13
|
+
}
|
|
14
|
+
if (schema.type !== undefined
|
|
15
|
+
&& !["object", "array", "string", "number", "integer", "boolean", "null"].includes(schema.type)) {
|
|
16
|
+
throw new Error("Unsupported schema type.");
|
|
17
|
+
}
|
|
18
|
+
if (schema.required !== undefined && (!Array.isArray(schema.required)
|
|
19
|
+
|| schema.required.some((key) => typeof key !== "string")))
|
|
20
|
+
throw new Error("Invalid schema required.");
|
|
21
|
+
for (const bound of [schema.minLength, schema.minItems]) {
|
|
22
|
+
if (bound !== undefined && (!Number.isSafeInteger(bound) || bound < 0))
|
|
23
|
+
throw new Error("Invalid schema bound.");
|
|
24
|
+
}
|
|
25
|
+
if (schema.enum !== undefined && (!Array.isArray(schema.enum) || schema.enum.length === 0)) {
|
|
26
|
+
throw new Error("Invalid schema enum.");
|
|
27
|
+
}
|
|
28
|
+
if (schema.anyOf !== undefined) {
|
|
29
|
+
if (!Array.isArray(schema.anyOf) || !schema.anyOf.length)
|
|
30
|
+
throw new Error("Invalid schema anyOf.");
|
|
31
|
+
schema.anyOf.forEach(checkCapabilitySchema);
|
|
32
|
+
}
|
|
33
|
+
if (schema.properties !== undefined) {
|
|
34
|
+
if (typeof schema.properties !== "object" || schema.properties === null || Array.isArray(schema.properties)) {
|
|
35
|
+
throw new Error("Invalid schema properties.");
|
|
36
|
+
}
|
|
37
|
+
Object.values(schema.properties).forEach(checkCapabilitySchema);
|
|
38
|
+
}
|
|
39
|
+
if (schema.items !== undefined)
|
|
40
|
+
checkCapabilitySchema(schema.items);
|
|
41
|
+
if (schema.additionalProperties !== undefined && typeof schema.additionalProperties !== "boolean") {
|
|
42
|
+
checkCapabilitySchema(schema.additionalProperties);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function capabilitySchemaError(schema, value, path = "$") {
|
|
46
|
+
if ("const" in schema && !isDeepStrictEqual(value, schema.const))
|
|
47
|
+
return `${path}: unexpected value.`;
|
|
48
|
+
if (schema.enum && !schema.enum.some((item) => isDeepStrictEqual(item, value)))
|
|
49
|
+
return `${path}: not in enum.`;
|
|
50
|
+
if (schema.anyOf && !schema.anyOf.some((branch) => capabilitySchemaError(branch, value) === undefined)) {
|
|
51
|
+
return `${path}: no matching schema branch.`;
|
|
52
|
+
}
|
|
53
|
+
const type = value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
|
|
54
|
+
if (schema.type !== undefined && (schema.type === "integer"
|
|
55
|
+
? typeof value !== "number" || !Number.isSafeInteger(value)
|
|
56
|
+
: type !== schema.type))
|
|
57
|
+
return `${path}: expected ${schema.type}.`;
|
|
58
|
+
if (typeof value === "number" && !Number.isFinite(value))
|
|
59
|
+
return `${path}: non-finite number.`;
|
|
60
|
+
if (typeof value === "string" && schema.minLength !== undefined && value.length < schema.minLength) {
|
|
61
|
+
return `${path}: string too short.`;
|
|
62
|
+
}
|
|
63
|
+
if (Array.isArray(value)) {
|
|
64
|
+
if (schema.minItems !== undefined && value.length < schema.minItems)
|
|
65
|
+
return `${path}: array too short.`;
|
|
66
|
+
if (schema.items) {
|
|
67
|
+
for (const [index, item] of value.entries()) {
|
|
68
|
+
const error = capabilitySchemaError(schema.items, item, `${path}[${index}]`);
|
|
69
|
+
if (error)
|
|
70
|
+
return error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else if (type === "object") {
|
|
75
|
+
const record = value;
|
|
76
|
+
for (const key of schema.required ?? []) {
|
|
77
|
+
if (!Object.hasOwn(record, key))
|
|
78
|
+
return `${path}.${key}: required.`;
|
|
79
|
+
}
|
|
80
|
+
for (const [key, item] of Object.entries(record)) {
|
|
81
|
+
const property = Object.hasOwn(schema.properties ?? {}, key) ? schema.properties[key] : undefined;
|
|
82
|
+
if (!property && schema.additionalProperties === false)
|
|
83
|
+
return `${path}.${key}: unexpected property.`;
|
|
84
|
+
const child = property ?? (typeof schema.additionalProperties === "object" ? schema.additionalProperties : undefined);
|
|
85
|
+
const error = child && capabilitySchemaError(child, item, `${path}.${key}`);
|
|
86
|
+
if (error)
|
|
87
|
+
return error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
@@ -14,11 +14,27 @@ export class InstanceHost {
|
|
|
14
14
|
void drained.catch(() => undefined);
|
|
15
15
|
const ref = Object.freeze({ ...implementation });
|
|
16
16
|
this.#instances.set(key, {
|
|
17
|
-
implementation: ref, value, references: 0, detached: false,
|
|
17
|
+
implementation: ref, value, references: 0, detached: false, disposed: false,
|
|
18
18
|
disposers: [...ownedDisposers], drained, resolve, reject
|
|
19
19
|
});
|
|
20
20
|
return ref;
|
|
21
21
|
}
|
|
22
|
+
/** Read-only availability for the rebuildable capability directory. This is
|
|
23
|
+
* not an acquisition or a grant; acquire still checks immediately before use. */
|
|
24
|
+
isAvailable(implementation) {
|
|
25
|
+
const instance = this.#instances.get(implementationKey(implementation));
|
|
26
|
+
return !this.#closed && instance !== undefined && !instance.detached;
|
|
27
|
+
}
|
|
28
|
+
/** Only the Host reports actual reference/disposal facts. Metadata consumers
|
|
29
|
+
* must not persist these observations as independently writable state. */
|
|
30
|
+
inspect(providerId) {
|
|
31
|
+
return [...this.#instances.values()].filter((instance) => instance.implementation.id === providerId && !instance.disposed)
|
|
32
|
+
.map((instance) => Object.freeze({
|
|
33
|
+
implementation: instance.implementation,
|
|
34
|
+
references: instance.references,
|
|
35
|
+
accepting: !this.#closed && !instance.detached
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
22
38
|
acquire(implementation) {
|
|
23
39
|
const instance = this.#instances.get(implementationKey(implementation));
|
|
24
40
|
if (this.#closed || instance === undefined || instance.detached) {
|
|
@@ -80,6 +96,7 @@ export class InstanceHost {
|
|
|
80
96
|
// Re-attaching a generation must not resurrect an old reference.
|
|
81
97
|
instance.value = undefined;
|
|
82
98
|
instance.disposers = [];
|
|
99
|
+
instance.disposed = true;
|
|
83
100
|
if (errors.length)
|
|
84
101
|
throw new AggregateError(errors, "Instance cleanup failed.");
|
|
85
102
|
})();
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import { createDurableJobControl } from "../controller/jobControl.js";
|
|
2
2
|
import { JOB_RUNNER_IMPLEMENTATION } from "../job/durableJob.js";
|
|
3
3
|
import { InstanceHost } from "./instanceHost.js";
|
|
4
|
+
import { createBuiltinCapabilities } from "./builtinCapabilities.js";
|
|
4
5
|
/** Called once by the existing Controller root. Does not open a Store, start
|
|
5
6
|
* another Controller, or provide arbitrary persistence to plugin code.
|
|
6
7
|
* T02 registers contributions on this Host and wraps this same Job control.
|
|
7
8
|
*/
|
|
8
|
-
export function createKernelPorts(store, runner) {
|
|
9
|
+
export function createKernelPorts(store, runner, signal = () => undefined, contextProviders = []) {
|
|
9
10
|
const host = new InstanceHost();
|
|
10
11
|
const runnerImplementation = host.attach(JOB_RUNNER_IMPLEMENTATION, runner);
|
|
11
12
|
const runnerHandle = host.acquire(runnerImplementation);
|
|
12
13
|
const jobImplementation = host.attach({ id: "yui:job-control", generation: "1" }, createDurableJobControl(store));
|
|
13
14
|
// The Controller is a long-lived consumer of this exact implementation.
|
|
14
15
|
const jobHandle = host.acquire(jobImplementation);
|
|
16
|
+
const capabilities = createBuiltinCapabilities(host, store, jobHandle.value, signal, contextProviders);
|
|
15
17
|
return {
|
|
16
18
|
host,
|
|
19
|
+
capabilities,
|
|
17
20
|
jobImplementation,
|
|
18
21
|
runnerImplementation,
|
|
19
22
|
runner: runnerHandle.value,
|