@tomflow/chatloop-extension 0.3.34 → 0.3.36

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 CHANGED
@@ -30,7 +30,7 @@ Run: chatloop-extension extension <command> --help
30
30
  {"projectId":"new-distinct-test","prompt":"新的独立任务","confirmed":true,"replaceBlockedSessionId":"11111111-1111-4111-a111-111111111111"}
31
31
  ```
32
32
 
33
- `PAGE_ERROR` 且无原生 Retry 时,Worker 仅观察最多四次;只有原用户消息 ID 未变且连续两次证明错误消失才继续观察,其余情况依旧 BLOCKED。**不能把当前网页错误消失当成原消息未发送的证据**。
33
+ `PAGE_ERROR` 且无原生 Retry 时,Worker 仅观察最多四次;只有原用户消息 ID 未变且连续两次证明错误消失才继续观察;若回查时已经识别到同一 Turn 的可靠完成事件或已发出完成事件,则让已排队的完成回执自然结项,避免 4 秒回查窗口错误覆盖 DONE。其余情况依旧 BLOCKED。正式 `popup.html?audit=1` Audit 包含最近一条真实已核验故障的 `run.lastFaultProof`(不含正文);若仅有页面告警而无此字段,不能宣称 Worker 实际进入了恢复分支。**不能把当前网页错误消失当成原消息未发送的证据**。
34
34
 
35
35
  Clean 只清空输入框,不发送,不更改 Owner;必须指定精确标签页 ID 且验证目标。Chrome 已安装扩展不随 npm 包自动安装。本地源码与已发布 npm 版本可能不同,真实结果请看 [CURRENT](../../context/CURRENT.md)。
36
36
 
@@ -55,7 +55,8 @@
55
55
  "clockAdvancedAt", "clockAdvanceMinutes", "clockRunningProof",
56
56
  "wallTestHours", "wallHoldAt", "wallReleaseAt", "wallReleasedAt",
57
57
  "wallTestMinutes", "wallAdjustedAt", "wallOriginalReleaseAt",
58
- "countMode", "countValue", "countHandoffs", "countLastSendAt", "countReleaseAt"]
58
+ "countMode", "countValue", "countHandoffs", "countLastSendAt", "countReleaseAt",
59
+ "lastFaultProof"]
59
60
  .filter(k => run[k] !== undefined).map(k => [k, run[k]])) : null
60
61
  };
61
62
  if (run?.parseEvidence && owned.some(item => item.id === run.tabId || item.id === run.sourceTabId)) {
@@ -319,6 +319,14 @@ async function onTurnBlocked(message, sender) {
319
319
  const second = await verify();
320
320
  if (!second || first.retryAvailable !== second.retryAvailable)
321
321
  return {status: "IGNORED", code: "ERROR_NOT_STABLE"};
322
+ // A bounded non-content receipt lets the existing Audit prove which
323
+ // decision the ONLY Worker actually took. Never log raw Chat text.
324
+ const faultProof = code => ({code, reason, userKey: message.userKey, at: Date.now()});
325
+ const recordFault = async code => {
326
+ await saveRun({...previous, lastFaultProof: faultProof(code)});
327
+ return {status: "IGNORED", code};
328
+ };
329
+ await saveRun({...previous, lastFaultProof: faultProof("ERROR_CONFIRMED")});
322
330
  // A network interruption may show a transient page error without a native
323
331
  // Retry button. Only *observe* for a bounded grace period: never resend an
324
332
  // uncertain user Turn. Resume normal observation only if the EXACT submitted
@@ -333,9 +341,17 @@ async function onTurnBlocked(message, sender) {
333
341
  const sameTurn = proof?.contract === "chat-loop.turn-observer.v1"
334
342
  && proof.page === previous.page
335
343
  && proof.userKey === previous.newUserKey;
336
- if (sameTurn && ["RUNNING", "UNKNOWN", "IDLE"].includes(proof.status)) {
344
+ // The Observer may emit its verified completion while this error handler
345
+ // owns the serialized Worker queue. That completion message is waiting
346
+ // behind us: fail-closing here would discard a real, finished Turn.
347
+ // TURN_FINISHED or the exact already-emitted state is sufficient proof
348
+ // that the SAME user Turn recovered; do not invent/send another message.
349
+ if (sameTurn && (proof.status === "TURN_FINISHED"
350
+ || proof.status === "IDLE" && proof.reason === "TURN_ALREADY_EMITTED"))
351
+ return recordFault("PAGE_ERROR_COMPLETION_QUEUED");
352
+ if (sameTurn && ["RUNNING", "UNKNOWN"].includes(proof.status)) {
337
353
  recovered++;
338
- if (recovered === 2) return {status: "IGNORED", code: "PAGE_ERROR_CLEARED_SAME_TURN"};
354
+ if (recovered === 2) return recordFault("PAGE_ERROR_CLEARED_SAME_TURN");
339
355
  } else recovered = 0;
340
356
  }
341
357
  }
@@ -343,6 +359,7 @@ async function onTurnBlocked(message, sender) {
343
359
  // or evade site access controls. Keep the original Chat and user Turn intact.
344
360
  if (reason === "SECURITY_CHALLENGE") {
345
361
  await saveRun({...previous, phase:"BLOCKED", reason,
362
+ lastFaultProof:faultProof("SECURITY_CHALLENGE"),
346
363
  errorUserKey:message.userKey, errorAt:Date.now()});
347
364
  return {status:"BLOCKED", code:reason};
348
365
  }
@@ -354,7 +371,8 @@ async function onTurnBlocked(message, sender) {
354
371
  const retry = reason === "PAGE_ERROR";
355
372
  const token = crypto.randomUUID();
356
373
  let next = {...previous, phase: "RESERVED", action: retry ? "CONTINUE" : "HANDOFF",
357
- token, reason: "", ...(retry ? {retryMode: "NATIVE", retryCount: 1}
374
+ token, reason: "", lastFaultProof:faultProof(retry ? "NATIVE_RETRY_ONCE" : "CONTEXT_HANDOFF"),
375
+ ...(retry ? {retryMode: "NATIVE", retryCount: 1}
358
376
  : {handoffMode: "ERROR", errorReason: reason})};
359
377
  if (previous.modelIntent) {
360
378
  next=updateModelReceipt(next,previous.modelIntent.id,{
@@ -372,9 +390,12 @@ async function onTurnBlocked(message, sender) {
372
390
  return {status: "BLOCKED", code: "ERROR_ACTION_CREATE_FAILED"};
373
391
  }
374
392
  }
375
- await saveRun({...previous, phase: "BLOCKED", reason: reason === "PAGE_ERROR"
393
+ const finalReason = reason === "PAGE_ERROR"
376
394
  ? previous.retryCount === 1 ? "PAGE_ERROR_AFTER_RETRY" : "PAGE_ERROR_NO_RETRY_BUTTON"
377
- : "CONTEXT_HANDOFF_REPEATED", errorUserKey: message.userKey, errorAt: Date.now()});
395
+ : "CONTEXT_HANDOFF_REPEATED";
396
+ await saveRun({...previous, phase:"BLOCKED", reason:finalReason,
397
+ lastFaultProof:faultProof(finalReason),
398
+ errorUserKey:message.userKey, errorAt:Date.now()});
378
399
  return {status: "BLOCKED", code: reason === "PAGE_ERROR" ? "PAGE_ERROR" : "CONTEXT_HANDOFF_REPEATED"};
379
400
  }
380
401
  // A model-driven request can only be queued against a currently RUNNING,
@@ -563,6 +584,7 @@ async function onTurnFinished(message, sender) {
563
584
  tabId, page: message.page, userKey: message.userKey,
564
585
  assistantKey: message.assistantKey, phase: "RESERVED", token,
565
586
  action: nextAction, startedAt, modelReceipts:receiptHistory(previous),
587
+ ...(previous?.lastFaultProof ? {lastFaultProof:previous.lastFaultProof} : {}),
566
588
  ...(countMode ? {countMode:true, countValue:verifiedCount,
567
589
  countLastSendAt:previous.countLastSendAt,
568
590
  countHandoffs:previous.countHandoffs || 0} : {}),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Chat Loop Extension",
4
- "version": "0.3.34",
4
+ "version": "0.3.36",
5
5
  "description": "Chat Loop browser companion: Eye actions, Turn observer, Continue and Handoff Actions",
6
6
  "permissions": [
7
7
  "activeTab",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomflow/chatloop-extension",
3
- "version": "0.3.34",
3
+ "version": "0.3.36",
4
4
  "type": "module",
5
5
  "description": "Chat Loop Extension package: nine formal Action pages and its own native CLI bridge",
6
6
  "bin": {