@zq-silk/yui 0.6.8 → 0.6.10

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.
Files changed (58) hide show
  1. package/README.md +10 -3
  2. package/dist/cli/commandCatalog.js +1 -1
  3. package/dist/commands/taskCommands.js +63 -28
  4. package/dist/commands/taskContextCommand.js +27 -1
  5. package/dist/commands/taskRoleRuntimeStatus.js +17 -6
  6. package/dist/controller/agentRuntimeObserver.js +6 -3
  7. package/dist/controller/controller.js +29 -47
  8. package/dist/controller/fileSchedulerStoreAdapter.js +572 -258
  9. package/dist/controller/runtime.js +8 -1
  10. package/dist/controller/runtimeEventInbox.js +16 -5
  11. package/dist/controller/runtimeHookRunFence.js +51 -5
  12. package/dist/controller/runtimeObservationHook.js +8 -2
  13. package/dist/coordination/workMailbox.js +408 -28
  14. package/dist/coordination/workMailboxQueue.js +12 -10
  15. package/dist/executor/agentExecutor.js +101 -94
  16. package/dist/executor/executorRegistry.js +47 -2
  17. package/dist/executor/fileRoleLaunchPlanner.js +4 -2
  18. package/dist/lifecycle/exactRunTerminalization.js +1 -7
  19. package/dist/repository/taskWorkspaceCoordinator.js +9 -4
  20. package/dist/runtime/agentDriver.js +83 -4
  21. package/dist/runtime/agentDriverObservation.js +25 -10
  22. package/dist/runtime/builtinAgentDrivers.js +168 -18
  23. package/dist/runtime/codexAppServerRuntime.js +355 -0
  24. package/dist/runtime/continuationManager.js +117 -0
  25. package/dist/runtime/index.js +2 -0
  26. package/dist/runtime/lifecycleReservation.js +4 -3
  27. package/dist/runtime/promptEnvelope.js +14 -3
  28. package/dist/runtime/providerContinuation.js +225 -0
  29. package/dist/runtime/providerContinuationReconciliationService.js +172 -0
  30. package/dist/runtime/providerRuntimeIdentity.js +232 -0
  31. package/dist/runtime/providerRuntimeReconciler.js +166 -0
  32. package/dist/runtime/runtimeContinuationProjection.js +34 -0
  33. package/dist/runtime/runtimeObservation.js +217 -6
  34. package/dist/runtime/runtimeProjection.js +172 -11
  35. package/dist/scheduler/activeRoleRunDelivery.js +314 -1
  36. package/dist/scheduler/leaderWakeupProcessor.js +2 -1
  37. package/dist/scheduler/operatorInputNotificationProcessor.js +3 -2
  38. package/dist/scheduler/roleRunLiveness.js +8 -7
  39. package/dist/scheduler/roleRunStall.js +4 -2
  40. package/dist/scheduler/taskExecutionProjection.js +2 -2
  41. package/dist/storage/migration/productionRegistry.js +474 -1
  42. package/dist/storage/sqliteSchema.js +102 -21
  43. package/dist/storage/sqliteStore.js +52 -110
  44. package/dist/storage/storageVersions.js +1 -1
  45. package/dist/storage/storeRpc.js +0 -1
  46. package/dist/storage/taskStore.js +40 -53
  47. package/dist/storage/upgrade/sqliteStateMigration.js +0 -21
  48. package/dist/task/nextAction.js +1 -1
  49. package/dist/web/assets/client/app.js +1 -1
  50. package/dist/web/assets/client/components.js +233 -4
  51. package/dist/web/assets/client/i18n.js +166 -2
  52. package/dist/web/assets/client/view.js +30 -13
  53. package/dist/web/assets/styles/cards.js +62 -0
  54. package/dist/web/assets/styles/widgets.js +1 -0
  55. package/dist/web/webSnapshot.js +11 -2
  56. package/package.json +1 -1
  57. package/skills/yui-leader/SKILL.md +33 -24
  58. package/skills/yui-operator/SKILL.md +7 -5
@@ -0,0 +1,232 @@
1
+ export function createProviderRuntimeBinding(input) {
2
+ const startedAt = timestamp(input.startedAt, "Provider Activation startedAt");
3
+ return validateProviderRuntimeBinding({
4
+ schemaVersion: 1,
5
+ providerNamespace: identity(input.providerNamespace, "Provider namespace"),
6
+ accountScope: identity(input.accountScope, "Provider account scope"),
7
+ runId: identity(input.runId, "Run id"),
8
+ currentConversationEpoch: 1,
9
+ conversations: [{
10
+ conversationId: identity(input.conversationId, "Provider Conversation id"),
11
+ epoch: 1,
12
+ status: "current",
13
+ recoverability: "unknown",
14
+ createdAt: startedAt
15
+ }],
16
+ activations: [{
17
+ activationId: identity(input.activationId, "Provider Activation id"),
18
+ conversationId: input.conversationId,
19
+ generation: 1,
20
+ status: "active",
21
+ writerLease: true,
22
+ startedAt
23
+ }]
24
+ });
25
+ }
26
+ export function currentProviderConversation(binding) {
27
+ validateProviderRuntimeBinding(binding);
28
+ return binding.conversations.find((entry) => (entry.epoch === binding.currentConversationEpoch && entry.status === "current"));
29
+ }
30
+ export function currentProviderActivation(binding) {
31
+ const conversation = currentProviderConversation(binding);
32
+ return [...binding.activations].reverse().find((entry) => (entry.conversationId === conversation.conversationId && entry.status === "active")) ?? null;
33
+ }
34
+ export function startProviderActivation(raw, input) {
35
+ const binding = validateProviderRuntimeBinding(raw);
36
+ if (currentProviderActivation(binding) !== null) {
37
+ throw new Error("Provider Conversation already has a live writer Activation.");
38
+ }
39
+ const conversation = currentProviderConversation(binding);
40
+ const generation = binding.activations
41
+ .filter((entry) => entry.conversationId === conversation.conversationId)
42
+ .reduce((maximum, entry) => Math.max(maximum, entry.generation), 0) + 1;
43
+ return validateProviderRuntimeBinding({
44
+ ...binding,
45
+ activations: [...binding.activations, {
46
+ activationId: identity(input.activationId, "Provider Activation id"),
47
+ conversationId: conversation.conversationId,
48
+ generation,
49
+ status: "active",
50
+ writerLease: true,
51
+ startedAt: timestamp(input.startedAt, "Provider Activation startedAt")
52
+ }]
53
+ });
54
+ }
55
+ export function endProviderActivation(raw, activationId, input) {
56
+ const binding = validateProviderRuntimeBinding(raw);
57
+ const id = identity(activationId, "Provider Activation id");
58
+ const target = binding.activations.find((entry) => entry.activationId === id);
59
+ if (target === undefined)
60
+ throw new Error(`Provider Activation is not recorded: ${id}.`);
61
+ if (target.status !== "active")
62
+ return binding;
63
+ const endedAt = timestamp(input.endedAt, "Provider Activation endedAt");
64
+ if (Date.parse(endedAt) < Date.parse(target.startedAt)) {
65
+ throw new Error("Provider Activation endedAt is earlier than startedAt.");
66
+ }
67
+ return validateProviderRuntimeBinding({
68
+ ...binding,
69
+ activations: binding.activations.map((entry) => entry.activationId === id
70
+ ? {
71
+ ...entry,
72
+ status: input.status,
73
+ writerLease: false,
74
+ endedAt,
75
+ ...(input.reason === undefined
76
+ ? {}
77
+ : { terminalReason: identity(input.reason, "Provider Activation terminal reason") })
78
+ }
79
+ : entry)
80
+ });
81
+ }
82
+ export function updateProviderConversationRecoverability(raw, recoverability) {
83
+ const binding = validateProviderRuntimeBinding(raw);
84
+ const current = currentProviderConversation(binding);
85
+ return validateProviderRuntimeBinding({
86
+ ...binding,
87
+ conversations: binding.conversations.map((entry) => entry.epoch === current.epoch
88
+ ? { ...entry, recoverability }
89
+ : entry)
90
+ });
91
+ }
92
+ export function supersedeProviderConversation(raw, input) {
93
+ const binding = validateProviderRuntimeBinding(raw);
94
+ const current = currentProviderConversation(binding);
95
+ if (current.recoverability !== "unrecoverable") {
96
+ throw new Error("Current Provider Conversation is not exactly unrecoverable.");
97
+ }
98
+ if (!input.noUnsettledInputDelivery) {
99
+ throw new Error("Cannot replace a Provider Conversation with unsettled input delivery.");
100
+ }
101
+ if (!input.writerUmbrellaClear || currentProviderActivation(binding) !== null) {
102
+ throw new Error("Cannot replace a Provider Conversation while its writer umbrella is owned.");
103
+ }
104
+ const switchedAt = timestamp(input.switchedAt, "Provider Conversation switch timestamp");
105
+ const epoch = current.epoch + 1;
106
+ return validateProviderRuntimeBinding({
107
+ ...binding,
108
+ currentConversationEpoch: epoch,
109
+ conversations: [
110
+ ...binding.conversations.map((entry) => entry.epoch === current.epoch
111
+ ? { ...entry, status: "superseded", supersededAt: switchedAt }
112
+ : entry),
113
+ {
114
+ conversationId: identity(input.conversationId, "Provider Conversation id"),
115
+ epoch,
116
+ status: "current",
117
+ recoverability: "unknown",
118
+ createdAt: switchedAt
119
+ }
120
+ ],
121
+ activations: [...binding.activations, {
122
+ activationId: identity(input.activationId, "Provider Activation id"),
123
+ conversationId: input.conversationId,
124
+ generation: 1,
125
+ status: "active",
126
+ writerLease: true,
127
+ startedAt: switchedAt
128
+ }]
129
+ });
130
+ }
131
+ export function validateProviderRuntimeBinding(value) {
132
+ if (value.schemaVersion !== 1)
133
+ throw new Error("Provider Runtime Binding schemaVersion must be 1.");
134
+ identity(value.providerNamespace, "Provider namespace");
135
+ identity(value.accountScope, "Provider account scope");
136
+ identity(value.runId, "Run id");
137
+ integer(value.currentConversationEpoch, 1, "Current Provider Conversation epoch");
138
+ if (!Array.isArray(value.conversations) || value.conversations.length === 0) {
139
+ throw new Error("Provider Runtime Binding requires a Conversation.");
140
+ }
141
+ const conversationIds = new Set();
142
+ const epochs = new Set();
143
+ let currentCount = 0;
144
+ for (const conversation of value.conversations) {
145
+ identity(conversation.conversationId, "Provider Conversation id");
146
+ integer(conversation.epoch, 1, "Provider Conversation epoch");
147
+ if (conversationIds.has(conversation.conversationId) || epochs.has(conversation.epoch)) {
148
+ throw new Error("Provider Runtime Binding contains duplicate Conversation identity.");
149
+ }
150
+ conversationIds.add(conversation.conversationId);
151
+ epochs.add(conversation.epoch);
152
+ if (conversation.status !== "current" && conversation.status !== "superseded") {
153
+ throw new Error("Provider Conversation status is invalid.");
154
+ }
155
+ if (!["unknown", "recoverable", "unrecoverable"].includes(conversation.recoverability)) {
156
+ throw new Error("Provider Conversation recoverability is invalid.");
157
+ }
158
+ timestamp(conversation.createdAt, "Provider Conversation createdAt");
159
+ if (conversation.status === "current") {
160
+ currentCount += 1;
161
+ if (conversation.epoch !== value.currentConversationEpoch) {
162
+ throw new Error("Current Provider Conversation epoch is inconsistent.");
163
+ }
164
+ if (conversation.supersededAt !== undefined) {
165
+ throw new Error("Current Provider Conversation cannot be superseded.");
166
+ }
167
+ }
168
+ else if (conversation.supersededAt === undefined) {
169
+ throw new Error("Superseded Provider Conversation requires supersededAt.");
170
+ }
171
+ else {
172
+ timestamp(conversation.supersededAt, "Provider Conversation supersededAt");
173
+ }
174
+ }
175
+ if (currentCount !== 1)
176
+ throw new Error("Provider Runtime Binding requires one current Conversation.");
177
+ const activationIds = new Set();
178
+ const activeByConversation = new Set();
179
+ const generations = new Set();
180
+ for (const activation of value.activations) {
181
+ identity(activation.activationId, "Provider Activation id");
182
+ if (activationIds.has(activation.activationId)) {
183
+ throw new Error("Provider Runtime Binding contains duplicate Activation identity.");
184
+ }
185
+ activationIds.add(activation.activationId);
186
+ if (!conversationIds.has(activation.conversationId)) {
187
+ throw new Error("Provider Activation references an unknown Conversation.");
188
+ }
189
+ integer(activation.generation, 1, "Provider Activation generation");
190
+ const generationKey = `${activation.conversationId}\u0000${activation.generation}`;
191
+ if (generations.has(generationKey)) {
192
+ throw new Error("Provider Runtime Binding contains duplicate Activation generation.");
193
+ }
194
+ generations.add(generationKey);
195
+ if (!["active", "ended", "failed"].includes(activation.status)) {
196
+ throw new Error("Provider Activation status is invalid.");
197
+ }
198
+ timestamp(activation.startedAt, "Provider Activation startedAt");
199
+ if (activation.status === "active") {
200
+ if (!activation.writerLease || activation.endedAt !== undefined) {
201
+ throw new Error("Active Provider Activation must hold its writer lease.");
202
+ }
203
+ if (activeByConversation.has(activation.conversationId)) {
204
+ throw new Error("Provider Conversation has multiple live writer Activations.");
205
+ }
206
+ activeByConversation.add(activation.conversationId);
207
+ }
208
+ else {
209
+ if (activation.writerLease || activation.endedAt === undefined) {
210
+ throw new Error("Terminal Provider Activation must release its writer lease.");
211
+ }
212
+ timestamp(activation.endedAt, "Provider Activation endedAt");
213
+ }
214
+ }
215
+ return value;
216
+ }
217
+ function identity(value, label) {
218
+ if (typeof value !== "string" || value.trim().length === 0)
219
+ throw new Error(`${label} is invalid.`);
220
+ return value.trim();
221
+ }
222
+ function timestamp(value, label) {
223
+ const normalized = identity(value, label);
224
+ if (!Number.isFinite(Date.parse(normalized)))
225
+ throw new Error(`${label} must be a timestamp.`);
226
+ return normalized;
227
+ }
228
+ function integer(value, minimum, label) {
229
+ if (!Number.isSafeInteger(value) || value < minimum)
230
+ throw new Error(`${label} is invalid.`);
231
+ return value;
232
+ }
@@ -0,0 +1,166 @@
1
+ import { continuationOwnsWriterUmbrella, observeProviderContinuation, providerContinuationKey } from "./providerContinuation.js";
2
+ const BASE_RECONCILE_MS = 2_000;
3
+ const MAX_RECONCILE_MS = 5 * 60_000;
4
+ const CIRCUIT_ERROR_LIMIT = 5;
5
+ const QUERY_TIMEOUT_MS = 5_000;
6
+ /**
7
+ * Reconciles only already-known children, grouped under one Conversation and
8
+ * Activation generation. Missing entries settle ownership only for an exact
9
+ * snapshot; partial/unavailable absence is never terminal evidence.
10
+ */
11
+ export async function reconcileKnownDetachedContinuations(input) {
12
+ const candidates = input.continuations.filter((entry) => (entry.attachment === "detached" && continuationOwnsWriterUmbrella(entry)));
13
+ if (candidates.length === 0) {
14
+ return { continuations: input.continuations, schedule: null, quality: "exact", changed: false };
15
+ }
16
+ const first = candidates[0];
17
+ const groupKey = [
18
+ first.identity.providerNamespace,
19
+ first.identity.accountScope,
20
+ first.identity.conversationId,
21
+ first.identity.activationId
22
+ ].join("\u0000");
23
+ if (candidates.some((entry) => [
24
+ entry.identity.providerNamespace,
25
+ entry.identity.accountScope,
26
+ entry.identity.conversationId,
27
+ entry.identity.activationId
28
+ ].join("\u0000") !== groupKey)) {
29
+ throw new Error("Provider reconcile input must contain one Conversation/Activation group.");
30
+ }
31
+ const nowMs = input.now.getTime();
32
+ if (input.previous?.circuitOpenUntil !== undefined
33
+ && Date.parse(input.previous.circuitOpenUntil) > nowMs) {
34
+ return {
35
+ continuations: input.continuations,
36
+ schedule: input.previous,
37
+ quality: "unavailable",
38
+ changed: false
39
+ };
40
+ }
41
+ let result;
42
+ try {
43
+ const raw = await withTimeout(input.port.queryKnownContinuations({
44
+ providerNamespace: first.identity.providerNamespace,
45
+ accountScope: first.identity.accountScope,
46
+ conversationId: first.identity.conversationId,
47
+ activationId: first.identity.activationId,
48
+ continuations: Object.freeze(candidates.map((entry) => Object.freeze({
49
+ continuationId: entry.identity.continuationId,
50
+ generation: entry.identity.generation
51
+ })))
52
+ }), QUERY_TIMEOUT_MS);
53
+ result = validateQueryResult(raw, new Set(candidates.map((entry) => (providerContinuationKey(entry.identity)))));
54
+ }
55
+ catch {
56
+ const errors = (input.previous?.consecutiveErrors ?? 0) + 1;
57
+ const delay = backoff(input.previous?.attempts ?? 0);
58
+ const circuitOpenUntil = errors >= CIRCUIT_ERROR_LIMIT
59
+ ? new Date(nowMs + MAX_RECONCILE_MS).toISOString()
60
+ : undefined;
61
+ return {
62
+ continuations: input.continuations,
63
+ schedule: {
64
+ key: groupKey,
65
+ attempts: (input.previous?.attempts ?? 0) + 1,
66
+ consecutiveErrors: errors,
67
+ nextReconcileAt: new Date(nowMs + delay).toISOString(),
68
+ ...(circuitOpenUntil === undefined ? {} : { circuitOpenUntil })
69
+ },
70
+ quality: "unavailable",
71
+ changed: false
72
+ };
73
+ }
74
+ const observations = new Map(result.continuations.map((entry) => [entry.key, entry]));
75
+ let changed = false;
76
+ const updated = input.continuations.map((continuation) => {
77
+ const observed = observations.get(providerContinuationKey(continuation.identity));
78
+ if (observed === undefined) {
79
+ // Exact absence closes a known child mechanically, without inventing a
80
+ // successful outcome or ResultRef. Partial absence preserves ownership.
81
+ if (result.quality !== "exact")
82
+ return continuation;
83
+ const next = observeProviderContinuation(continuation, {
84
+ execution: "quiescent",
85
+ outcome: "unknown",
86
+ attachment: "detached",
87
+ observation: "exact",
88
+ mayWriteWorkspace: false,
89
+ observedAt: input.now.toISOString()
90
+ });
91
+ changed ||= next !== continuation;
92
+ return next;
93
+ }
94
+ const next = observeProviderContinuation(continuation, {
95
+ execution: observed.execution,
96
+ outcome: observed.outcome,
97
+ attachment: "detached",
98
+ observation: result.quality,
99
+ mayWriteWorkspace: observed.mayWriteWorkspace,
100
+ observedAt: input.now.toISOString(),
101
+ ...(observed.resultRef === undefined ? {} : { resultRef: observed.resultRef }),
102
+ ...(observed.providerSequence === undefined
103
+ ? {}
104
+ : { providerSequence: observed.providerSequence })
105
+ });
106
+ changed ||= next !== continuation;
107
+ return next;
108
+ });
109
+ const unsettled = updated.some((entry) => (entry.attachment === "detached" && continuationOwnsWriterUmbrella(entry)));
110
+ return {
111
+ continuations: Object.freeze(updated),
112
+ schedule: unsettled
113
+ ? {
114
+ key: groupKey,
115
+ attempts: (input.previous?.attempts ?? 0) + 1,
116
+ consecutiveErrors: 0,
117
+ nextReconcileAt: new Date(nowMs + backoff(input.previous?.attempts ?? 0)).toISOString()
118
+ }
119
+ : null,
120
+ quality: result.quality,
121
+ changed
122
+ };
123
+ }
124
+ function validateQueryResult(input, expectedKeys) {
125
+ if (input === null || typeof input !== "object"
126
+ || !["exact", "partial", "unavailable"].includes(input.quality)
127
+ || !Array.isArray(input.continuations)) {
128
+ throw new Error("Provider continuation metadata response is invalid.");
129
+ }
130
+ const seen = new Set();
131
+ for (const entry of input.continuations) {
132
+ if (entry === null || typeof entry !== "object"
133
+ || typeof entry.key !== "string" || !expectedKeys.has(entry.key)
134
+ || seen.has(entry.key)
135
+ || !["active", "quiescent", "unknown"].includes(entry.execution)
136
+ || !["pending", "succeeded", "failed", "cancelled", "unknown"].includes(entry.outcome)
137
+ || typeof entry.mayWriteWorkspace !== "boolean"
138
+ || (entry.execution === "active" && entry.outcome !== "pending")
139
+ || (entry.execution === "quiescent" && entry.outcome === "pending")
140
+ || (entry.providerSequence !== undefined
141
+ && (!Number.isSafeInteger(entry.providerSequence) || entry.providerSequence < 0))) {
142
+ throw new Error("Provider continuation metadata entry is invalid or conflicting.");
143
+ }
144
+ seen.add(entry.key);
145
+ }
146
+ return input;
147
+ }
148
+ async function withTimeout(promise, timeoutMs) {
149
+ let timer;
150
+ try {
151
+ return await Promise.race([
152
+ promise,
153
+ new Promise((_resolve, reject) => {
154
+ timer = setTimeout(() => reject(new Error("Provider continuation metadata query timed out.")), timeoutMs);
155
+ timer.unref?.();
156
+ })
157
+ ]);
158
+ }
159
+ finally {
160
+ if (timer !== undefined)
161
+ clearTimeout(timer);
162
+ }
163
+ }
164
+ function backoff(attempts) {
165
+ return Math.min(MAX_RECONCILE_MS, BASE_RECONCILE_MS * (2 ** Math.min(attempts, 8)));
166
+ }
@@ -0,0 +1,34 @@
1
+ import { foldContinuationObservation } from "./continuationManager.js";
2
+ import { continuationOwnsWriterUmbrella, providerContinuationKey } from "./providerContinuation.js";
3
+ import { runtimeObservationFromTaskEvent } from "./runtimeObservation.js";
4
+ export function projectProviderContinuations(events) {
5
+ const projected = new Map();
6
+ const observations = events
7
+ .map(runtimeObservationFromTaskEvent)
8
+ .filter((entry) => (entry !== null && entry.kind.startsWith("continuation.")));
9
+ // Fold in durable Task-event order. Provider sequence is an identity-local
10
+ // monotonic fence applied by ProviderContinuation; it is not a global clock.
11
+ // In particular, Controller-derived exact snapshots intentionally have no
12
+ // Provider sequence and must not be moved before the Provider start fact.
13
+ for (const observation of observations) {
14
+ const fence = observation.fence;
15
+ if (fence.conversationId === undefined || fence.activationId === undefined
16
+ || fence.continuationId === undefined || fence.continuationGeneration === undefined) {
17
+ continue;
18
+ }
19
+ const key = [
20
+ fence.driverId,
21
+ fence.agentId,
22
+ fence.conversationId,
23
+ fence.activationId,
24
+ fence.continuationId,
25
+ fence.continuationGeneration
26
+ ].join("\u0000");
27
+ const result = foldContinuationObservation(projected.get(key) ?? null, observation);
28
+ projected.set(providerContinuationKey(result.continuation.identity), result.continuation);
29
+ }
30
+ return Object.freeze([...projected.values()].sort((left, right) => (providerContinuationKey(left.identity).localeCompare(providerContinuationKey(right.identity)))));
31
+ }
32
+ export function blockingProviderContinuations(events) {
33
+ return projectProviderContinuations(events).filter((entry) => (continuationOwnsWriterUmbrella(entry) || entry.identityConflict));
34
+ }