newmark-agent 0.6.3 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/conversation-utility-host.bundle.cjs +206 -9
- package/dist/core/agent.d.ts +23 -0
- package/dist/core/agent.js +60 -4
- package/dist/core/agentKernelRunner.js +15 -2
- package/dist/core/branchIdentity.d.ts +29 -0
- package/dist/core/branchIdentity.js +54 -0
- package/dist/core/continuation/contracts.d.ts +1 -1
- package/dist/core/continuation/store.d.ts +28 -0
- package/dist/core/continuation/store.js +89 -1
- package/dist/core/conversationKernel.d.ts +13 -1
- package/dist/core/conversationKernel.js +62 -0
- package/dist/main.js +1 -1
- package/dist/ui/index.html +343 -44
- package/dist/wsl-agent-host.bundle.cjs +206 -9
- package/package.json +4 -2
|
@@ -271,6 +271,87 @@ class GuardedContinuationStore {
|
|
|
271
271
|
return { ...build, input: cloneInput(build.input) };
|
|
272
272
|
});
|
|
273
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* dev-0.6.4 hotfix: 当某个被受理的 Build 失败/取消后,它的后继会永远无法被
|
|
276
|
+
* claim(`claimBuild` 只认「父 = 已提交 frontier」),整条队列就此卡死,新提交
|
|
277
|
+
* 也只是接在失败链后面。
|
|
278
|
+
*
|
|
279
|
+
* 这里提供一条**显式**修复通道:把被阻断的排队 Build 重新挂到当前已提交的
|
|
280
|
+
* frontier 上,保持原有队列顺序,并为每一次改挂写入 `BuildQueueRepaired`
|
|
281
|
+
* 审计事件(记录旧 parent / 新 parent / 原因)。它不是静默 rebase:调用方是
|
|
282
|
+
* 用户的显式动作(恢复队列)或一次新的入队命令。
|
|
283
|
+
*/
|
|
284
|
+
repairBlockedQueue(input) {
|
|
285
|
+
return this.mutate(ledger => {
|
|
286
|
+
const branch = this.requireBranch(ledger, input);
|
|
287
|
+
const frontier = branch.headBuildId || null;
|
|
288
|
+
const queued = Object.values(ledger.builds)
|
|
289
|
+
.filter(build => build.branchId === branch.branchId && build.status === 'QUEUED')
|
|
290
|
+
.sort((a, b) => a.queueSequence - b.queueSequence);
|
|
291
|
+
const repaired = [];
|
|
292
|
+
let chainParent = frontier;
|
|
293
|
+
let mutated = false;
|
|
294
|
+
for (const build of queued) {
|
|
295
|
+
if ((build.parentBuildId || null) !== (chainParent || null)) {
|
|
296
|
+
const previousParentBuildId = build.parentBuildId;
|
|
297
|
+
build.parentBuildId = chainParent;
|
|
298
|
+
build.waitingReason = undefined;
|
|
299
|
+
build.updatedAt = new Date().toISOString();
|
|
300
|
+
repaired.push(build.buildId);
|
|
301
|
+
mutated = true;
|
|
302
|
+
this.appendEvent(ledger, {
|
|
303
|
+
type: 'BuildQueueRepaired',
|
|
304
|
+
workspaceId: this.workspaceId,
|
|
305
|
+
rootId: branch.rootId,
|
|
306
|
+
branchId: branch.branchId,
|
|
307
|
+
buildId: build.buildId,
|
|
308
|
+
attemptId: null,
|
|
309
|
+
fence: null,
|
|
310
|
+
payload: {
|
|
311
|
+
previousParentBuildId,
|
|
312
|
+
newParentBuildId: chainParent,
|
|
313
|
+
reason: String(input.reason || 'committed-frontier-repair'),
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
chainParent = build.buildId;
|
|
318
|
+
}
|
|
319
|
+
if (mutated) {
|
|
320
|
+
branch.queueRevision += 1;
|
|
321
|
+
branch.lifecycleRevision += 1;
|
|
322
|
+
branch.updatedAt = new Date().toISOString();
|
|
323
|
+
}
|
|
324
|
+
return { repaired, frontier };
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* dev-0.6.4 hotfix: 读取视角的受阻推导。
|
|
329
|
+
*
|
|
330
|
+
* 一个 QUEUED Build 只有在它的父正好是「已提交 frontier」或队列里位于它之前
|
|
331
|
+
* 的可执行 Build 时才可能被 claim(见 `claimBuild`)。这里按队列顺序推导哪些行
|
|
332
|
+
* 目前不可执行:父不是链上前驱、或父已 FAILED/CANCELLED 的行即为受阻,且受阻点
|
|
333
|
+
* 之后的所有行同样受阻。
|
|
334
|
+
*/
|
|
335
|
+
blockedQueuedBuildIds(ledger, branch) {
|
|
336
|
+
const blocked = new Set();
|
|
337
|
+
const queued = Object.values(ledger.builds)
|
|
338
|
+
.filter(build => build.branchId === branch.branchId && build.status === 'QUEUED')
|
|
339
|
+
.sort((a, b) => a.queueSequence - b.queueSequence);
|
|
340
|
+
let chainParent = branch.headBuildId || null;
|
|
341
|
+
for (const build of queued) {
|
|
342
|
+
const parent = build.parentBuildId ? ledger.builds[build.parentBuildId] : null;
|
|
343
|
+
// 链上的前驱(frontier 或队列里位于它之前的行)在执行时已经提交,因此只有
|
|
344
|
+
// 「父不是链上前驱」或「父已 FAILED/CANCELLED」的行才是真正受阻的。
|
|
345
|
+
const parentIsChainPredecessor = (build.parentBuildId || null) === (chainParent || null);
|
|
346
|
+
const parentFailed = !!parent && (parent.status === 'FAILED' || parent.status === 'CANCELLED');
|
|
347
|
+
if (!parentIsChainPredecessor || parentFailed) {
|
|
348
|
+
blocked.add(build.buildId);
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
chainParent = build.buildId;
|
|
352
|
+
}
|
|
353
|
+
return blocked;
|
|
354
|
+
}
|
|
274
355
|
deleteQueuedBuild(input) {
|
|
275
356
|
this.mutate(ledger => {
|
|
276
357
|
const build = ledger.builds[input.buildId];
|
|
@@ -702,10 +783,17 @@ class GuardedContinuationStore {
|
|
|
702
783
|
}
|
|
703
784
|
snapshotUnlocked(ledger, branch) {
|
|
704
785
|
const guard = ledger.guards[branch.branchId] || (0, contracts_1.defaultGuard)(branch.branchId);
|
|
786
|
+
// dev-0.6.4 hotfix: 受阻状态由父链推导,而不是只在失败瞬间给直系后继打标记。
|
|
787
|
+
// 这样“失败之后才入队”的行同样会显示为受阻,界面才能给出显式修复入口。
|
|
788
|
+
const blockedBuildIds = this.blockedQueuedBuildIds(ledger, branch);
|
|
705
789
|
const builds = Object.values(ledger.builds)
|
|
706
790
|
.filter(build => build.branchId === branch.branchId)
|
|
707
791
|
.sort((a, b) => a.queueSequence - b.queueSequence)
|
|
708
|
-
.map(build => ({
|
|
792
|
+
.map(build => ({
|
|
793
|
+
...build,
|
|
794
|
+
input: cloneInput(build.input),
|
|
795
|
+
waitingReason: build.waitingReason || (blockedBuildIds.has(build.buildId) ? 'DEPENDENCY_FAILED' : undefined),
|
|
796
|
+
}));
|
|
709
797
|
return {
|
|
710
798
|
workspaceId: this.workspaceId,
|
|
711
799
|
rootId: branch.rootId,
|
|
@@ -19,8 +19,12 @@ export interface ConversationQueueItemSnapshot {
|
|
|
19
19
|
/** Exact workspace ledger branch id used when this row was admitted. */
|
|
20
20
|
ledgerBranchId?: string;
|
|
21
21
|
modelSelection?: string;
|
|
22
|
+
/** Ledger reason shown when a queued row cannot run (for example DEPENDENCY_FAILED). */
|
|
23
|
+
waitingReason?: string;
|
|
24
|
+
/** True when the ledger currently blocks this row from being claimed. */
|
|
25
|
+
blocked?: boolean;
|
|
22
26
|
}
|
|
23
|
-
export type ConversationQueueAction = 'enqueue' | 'update' | 'delete' | 'reorder' | 'toggle_pause' | 'set_pause' | 'guide';
|
|
27
|
+
export type ConversationQueueAction = 'enqueue' | 'update' | 'delete' | 'reorder' | 'toggle_pause' | 'set_pause' | 'repair_blocked' | 'guide';
|
|
24
28
|
export interface ConversationQueueActionInput {
|
|
25
29
|
id?: string;
|
|
26
30
|
text?: string;
|
|
@@ -303,6 +307,14 @@ export declare class ConversationKernel {
|
|
|
303
307
|
deleteQueueItem(target: ConversationTargetInput, idInput: string): boolean;
|
|
304
308
|
reorderQueueItems(target: ConversationTargetInput, orderedIdsInput: string[]): ConversationQueueItemSnapshot[];
|
|
305
309
|
setQueuePaused(target: ConversationTargetInput, paused: boolean): boolean;
|
|
310
|
+
/**
|
|
311
|
+
* dev-0.6.4 hotfix: 显式修复被失败前置阻断的排队行。
|
|
312
|
+
*
|
|
313
|
+
* 0.6.3 的合约是「失败/取消阻断后继,且不静默跳过」,所以这里不挂在恢复动作上
|
|
314
|
+
* 自动执行;它必须由用户的显式动作触发(界面上的“修复受阻队列”按钮)。每一次
|
|
315
|
+
* 改挂都写入 `BuildQueueRepaired` 事件并发布可见状态,触发者与原因可审计。
|
|
316
|
+
*/
|
|
317
|
+
private repairBlockedQueueNow;
|
|
306
318
|
queueAction(target: ConversationTargetInput, action: ConversationQueueAction, input?: ConversationQueueActionInput): {
|
|
307
319
|
ok: boolean;
|
|
308
320
|
queueItems: ConversationQueueItemSnapshot[];
|
|
@@ -388,6 +388,23 @@ class ConversationKernel {
|
|
|
388
388
|
const runtime = this.findRuntime(target);
|
|
389
389
|
if (!runtime)
|
|
390
390
|
return [];
|
|
391
|
+
// dev-0.6.4 hotfix: 把账本里的受阻原因带进队列投影,界面才能显示
|
|
392
|
+
// “被失败前置阻断”而不是无声卡住。
|
|
393
|
+
let waitingByBuildId = new Map();
|
|
394
|
+
const continuation = this.continuationStore(runtime);
|
|
395
|
+
if (continuation) {
|
|
396
|
+
try {
|
|
397
|
+
const snapshot = continuation.store.snapshot({
|
|
398
|
+
workspaceId: continuation.workspaceId,
|
|
399
|
+
rootId: continuation.rootId,
|
|
400
|
+
branchId: continuation.branchId,
|
|
401
|
+
});
|
|
402
|
+
waitingByBuildId = new Map(snapshot.builds
|
|
403
|
+
.filter(build => !!build.waitingReason)
|
|
404
|
+
.map(build => [build.buildId, String(build.waitingReason)]));
|
|
405
|
+
}
|
|
406
|
+
catch { /* the projection stays usable without ledger diagnostics */ }
|
|
407
|
+
}
|
|
391
408
|
return runtime.pendingNextTurn.flatMap(item => {
|
|
392
409
|
if (item.queueMode !== 'followUp')
|
|
393
410
|
return [];
|
|
@@ -396,6 +413,7 @@ class ConversationKernel {
|
|
|
396
413
|
const id = String(item.message.clientMessageId || '');
|
|
397
414
|
if (!id)
|
|
398
415
|
return [];
|
|
416
|
+
const buildId = String(item.message.buildId || '');
|
|
399
417
|
return [{
|
|
400
418
|
id,
|
|
401
419
|
buildId: item.message.buildId,
|
|
@@ -409,6 +427,8 @@ class ConversationKernel {
|
|
|
409
427
|
branchNodeId: item.message.branchNodeId,
|
|
410
428
|
branchPath: item.message.branchPath?.slice(),
|
|
411
429
|
modelSelection: item.message.modelSelection,
|
|
430
|
+
waitingReason: waitingByBuildId.get(buildId),
|
|
431
|
+
blocked: waitingByBuildId.has(buildId),
|
|
412
432
|
}];
|
|
413
433
|
});
|
|
414
434
|
}
|
|
@@ -484,6 +504,8 @@ class ConversationKernel {
|
|
|
484
504
|
const ledgerBranchId = continuation?.branchId;
|
|
485
505
|
let buildId = '';
|
|
486
506
|
if (continuation) {
|
|
507
|
+
// 入队严格保持 0.6.3 语义:新命令接在当前队尾之后。被失败前置阻断的行不会
|
|
508
|
+
// 被静默跳过;用户可通过界面上的“修复受阻队列”显式动作重建可执行链。
|
|
487
509
|
const authoritative = continuation.store.snapshot({
|
|
488
510
|
workspaceId: continuation.workspaceId,
|
|
489
511
|
rootId: continuation.rootId,
|
|
@@ -708,6 +730,10 @@ class ConversationKernel {
|
|
|
708
730
|
return this.queueItems(runtime.target);
|
|
709
731
|
}
|
|
710
732
|
setQueuePaused(target, paused) {
|
|
733
|
+
// 队列策略只作用于已经存在的 runtime:在这里按需重建 runtime 会让空闲会话
|
|
734
|
+
// 重新变成“持有排队工作”的所有者,破坏工作区清理契约
|
|
735
|
+
// (`Cannot release a conversation with running or queued work`)。界面路径统一
|
|
736
|
+
// 走 queueAction,它已经负责按需创建 runtime。
|
|
711
737
|
const runtime = this.findRuntime(target);
|
|
712
738
|
if (!runtime)
|
|
713
739
|
throw new Error('Target conversation runtime is unavailable');
|
|
@@ -720,6 +746,35 @@ class ConversationKernel {
|
|
|
720
746
|
}
|
|
721
747
|
return runtime.queuePaused;
|
|
722
748
|
}
|
|
749
|
+
/**
|
|
750
|
+
* dev-0.6.4 hotfix: 显式修复被失败前置阻断的排队行。
|
|
751
|
+
*
|
|
752
|
+
* 0.6.3 的合约是「失败/取消阻断后继,且不静默跳过」,所以这里不挂在恢复动作上
|
|
753
|
+
* 自动执行;它必须由用户的显式动作触发(界面上的“修复受阻队列”按钮)。每一次
|
|
754
|
+
* 改挂都写入 `BuildQueueRepaired` 事件并发布可见状态,触发者与原因可审计。
|
|
755
|
+
*/
|
|
756
|
+
repairBlockedQueueNow(runtime) {
|
|
757
|
+
const continuation = this.continuationStore(runtime);
|
|
758
|
+
if (!continuation)
|
|
759
|
+
return 0;
|
|
760
|
+
try {
|
|
761
|
+
const result = continuation.store.repairBlockedQueue({
|
|
762
|
+
workspaceId: continuation.workspaceId,
|
|
763
|
+
rootId: continuation.rootId,
|
|
764
|
+
branchId: continuation.branchId,
|
|
765
|
+
reason: 'explicit-user-repair',
|
|
766
|
+
});
|
|
767
|
+
if (result.repaired.length) {
|
|
768
|
+
runtime.runner.recordWorkStatus(`${result.repaired.length} queued row(s) blocked by a failed predecessor were re-anchored to the last committed build; the queue resumes in order.`);
|
|
769
|
+
this.schedulePendingRuntimeContinuation(runtime, runtime.runId);
|
|
770
|
+
}
|
|
771
|
+
return result.repaired.length;
|
|
772
|
+
}
|
|
773
|
+
catch (error) {
|
|
774
|
+
runtime.runner.recordWorkStatus(`Queue repair failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
775
|
+
return 0;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
723
778
|
queueAction(target, action, input = {}) {
|
|
724
779
|
const normalized = this.normalizeTarget(target);
|
|
725
780
|
const runtime = this.findRuntime(normalized) || this.runtime(normalized, {
|
|
@@ -758,6 +813,10 @@ class ConversationKernel {
|
|
|
758
813
|
else if (action === 'set_pause') {
|
|
759
814
|
this.setQueuePaused(runtime.target, input.paused === true);
|
|
760
815
|
}
|
|
816
|
+
else if (action === 'repair_blocked') {
|
|
817
|
+
// 显式修复:把被失败前置阻断的排队行重新挂到最后一个已提交 Build。
|
|
818
|
+
this.repairBlockedQueueNow(runtime);
|
|
819
|
+
}
|
|
761
820
|
else if (action === 'guide') {
|
|
762
821
|
if (input.text !== undefined)
|
|
763
822
|
this.updateQueueItem(runtime.target, String(input.id || ''), input.text, input);
|
|
@@ -1675,6 +1734,9 @@ class ConversationKernel {
|
|
|
1675
1734
|
&& runtime.queued.followUp.length === 0
|
|
1676
1735
|
&& runner.subagents.readRootInbox().length === 0;
|
|
1677
1736
|
});
|
|
1737
|
+
// 执行投影严格跟随当前激活分支节点:新分支绝不接管另一条分页的排队输入
|
|
1738
|
+
// (dev-0.6.3 的既有契约)。界面上跨分页的排队行可见性由渲染层负责,
|
|
1739
|
+
// 执行始终属于受理时捕获的那条分支。
|
|
1678
1740
|
for (const continuation of runner.conversationContinuations()) {
|
|
1679
1741
|
runtime.pendingNextTurn.push({
|
|
1680
1742
|
message: continuation.clientMessageId || continuation.images?.length || continuation.hiddenUserInput
|
package/dist/main.js
CHANGED
|
@@ -3123,7 +3123,7 @@ else {
|
|
|
3123
3123
|
};
|
|
3124
3124
|
const mutateConversationQueue = async (target, rawAction, input = {}) => {
|
|
3125
3125
|
const action = rawAction.replace(/^queue_/, '');
|
|
3126
|
-
if (!['enqueue', 'update', 'delete', 'reorder', 'toggle_pause', 'set_pause', 'guide'].includes(action))
|
|
3126
|
+
if (!['enqueue', 'update', 'delete', 'reorder', 'toggle_pause', 'set_pause', 'repair_blocked', 'guide'].includes(action))
|
|
3127
3127
|
throw new Error('Unknown queue action');
|
|
3128
3128
|
const key = activeFlowStateKey(target);
|
|
3129
3129
|
const flow = activeFlowStateFor(target);
|