@tomflow/chatloop-extension 0.3.46 → 0.3.47

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
@@ -49,3 +49,5 @@ Clean 只清空输入框,不发送,不更改 Owner;必须指定精确标
49
49
  **新页准备超时策略(0.3.45)**:Start 新 Chat 编辑器只读等待约 218 秒,Handoff 新 Chat 只读等待约 234 秒,均保持一次新建/一次发送且不跳过 ChatGPT 网页验证。Start CLI 最多等待 310 秒,MCP Tools 转发最多 330 秒;Worker 对 Start/Handoff 的未确认资源等待 330 秒,其它 Action 维持 180 秒。窗口到期仍无确定回执时保留 BLOCKED,禁止自动重发。
50
50
 
51
51
  **0.3.46 Chrome pendingUrl 修复**:Worker 创建正式 Action 时,以自己刚发起的受控 Extension URL 与 Chrome 返回的数值 tabId 登记资源,不能依赖 `tabs.create` 立即返回最终 `tab.url`(它可能仅有 `pendingUrl`)。Action 自身仍需验证真实 URL、Owner、token,任何未确认发送都不得重试;代码回归覆盖过渡 tab.url 缺失但单次 Action 正常创建。
52
+
53
+ **0.3.47 严格受限的 E2E BLOCKED 标签回收**:只通过正式内置 `popup.html?cleanupBlockedE2E=<tabId>`,要求当前唯一 BLOCKED/CONTINUE 测试 Session、精确 tabId/URL/userKey/assistantKey、对应测试用户消息标识、助手完整 CONTINUE 尾标记、空草稿、无上传和非生成状态两次稳定检查;最后重核 Worker 身份及未发出新 Action,才可关闭此测试标签,不删除 ChatGPT 聊天记录。普通用户 Chat、未知副作用、活动 Session 均禁止清理。
@@ -1,7 +1,7 @@
1
1
  // Read-only acceptance inventory and strictly scoped test-tab lifecycle.
2
2
  (() => {
3
3
  const q = new URLSearchParams(location.search);
4
- const modes = ["audit", "newTest", "cleanup", "cleanupVerified", "cleanupStaleMachine", "cleanupTestHome", "cleanupFailedStartHome"].filter(k => q.has(k));
4
+ const modes = ["audit", "newTest", "cleanup", "cleanupVerified", "cleanupStaleMachine", "cleanupTestHome", "cleanupFailedStartHome", "cleanupBlockedE2E"].filter(k => q.has(k));
5
5
  if (!modes.length) return; // Ordinary popup remains unchanged.
6
6
  const runKey = "chat-loop.action.run.v1";
7
7
  const ownedKey = "chat-loop.acceptance.owned.v1";
@@ -317,6 +317,70 @@
317
317
  chatTabs: left.filter(tab => isChat(tab.url)).length,
318
318
  extensionTabs: left.filter(tab => tab.url?.startsWith(extensionOrigin)
319
319
  && tab.id !== own.id).length};
320
+ } else if (mode === "cleanupBlockedE2E") {
321
+ // One explicitly scoped test Chat only. Closing a tab preserves its
322
+ // canonical ChatGPT history; never close a real user or unknown Turn.
323
+ const tabId=Number(q.get('cleanupBlockedE2E'));
324
+ if(!Number.isSafeInteger(tabId)||tabId<=0 || run?.phase!=='BLOCKED'
325
+ ||run.action!=='CONTINUE' ||run.tabId!==tabId
326
+ ||typeof run.page!=='string'
327
+ ||!/^\/c\/[0-9a-f-]{12,}$/i.test(run.page)
328
+ ||!/^cl-intent-e2e-[a-z0-9-]+$/i.test(run.projectId||'')
329
+ ||!/^user:[^\s]{8,}$/.test(run.userKey||'')
330
+ ||!/^assistant:[^\s]{8,}$/.test(run.assistantKey||''))
331
+ throw Error('BLOCKED_E2E_IDENTITY_UNVERIFIED');
332
+ const expectedUrl='https://chatgpt.com'+run.page;
333
+ const source=await chrome.tabs.get(tabId).catch(()=>null);
334
+ if(!source||source.url!==expectedUrl||source.active||source.pinned)
335
+ throw Error('BLOCKED_E2E_TAB_NOT_SAFE');
336
+ const expectedCode=run.projectId.toUpperCase().replace(/^CL-INTENT-E2E-/, 'CL-INTENT-E2E-');
337
+ async function observe(){
338
+ const [check]=await chrome.scripting.executeScript({
339
+ target:{tabId},func:(code,userKey,assistantKey,expectedPage)=>{
340
+ if(location.origin!=='https://chatgpt.com'||location.pathname!==expectedPage)
341
+ return false;
342
+ const visible=n=>!!n?.getClientRects?.().length;
343
+ const mains=[...document.querySelectorAll('main')].filter(visible);
344
+ if(mains.length!==1)return false;
345
+ const modern=[...mains[0].querySelectorAll('[data-chatgpt-search-unit-key][data-chatgpt-search-message-ids]')];
346
+ const rows=modern.length?modern:[...mains[0].querySelectorAll('[data-message-author-role]')];
347
+ const role=n=>n.getAttribute('data-message-author-role')
348
+ ||n.getAttribute('data-chatgpt-search-unit-key')?.split(':').at(-1);
349
+ const id=n=>n.getAttribute('data-message-id')
350
+ ||n.getAttribute('data-chatgpt-search-message-ids')?.trim().split(/\s+/)[0];
351
+ const users=rows.filter(n=>role(n)==='user');
352
+ const assistants=rows.filter(n=>role(n)==='assistant');
353
+ const user=users.at(-1),assistant=assistants.at(-1);
354
+ const content=assistant?.querySelector('.markdown')?.innerText
355
+ ||assistant?.innerText||assistant?.textContent||'';
356
+ const editor=document.querySelector('[contenteditable="true"][data-composer-markdown]')
357
+ ||document.querySelector('#prompt-textarea[contenteditable="true"]');
358
+ const hasUploads=!!document.querySelector('[data-testid*="attachment-preview"],[data-testid*="image-preview"],[data-testid="composer-attachment"]');
359
+ const stillRunning=[...document.querySelectorAll('button[data-testid="stop-button"],button[aria-label="Stop generating"],button[aria-label="停止响应"]')].some(visible);
360
+ return !!(user&&assistant&&'user:'+id(user)===userKey
361
+ &&'assistant:'+id(assistant)===assistantKey
362
+ &&(user.innerText||user.textContent||'').includes(code)
363
+ &&content.trim().endsWith('[CHAT_LOOP_STATUS:CONTINUE]')
364
+ &&visible(editor)&&!(editor.innerText||editor.textContent||'').trim()
365
+ &&!hasUploads&&!stillRunning);
366
+ },args:[expectedCode,run.userKey,run.assistantKey,run.page]});
367
+ return check?.result===true;
368
+ }
369
+ if(!await observe())throw Error('BLOCKED_E2E_FINAL_TURN_NOT_VERIFIED');
370
+ await sleep(1250);
371
+ if(!await observe())throw Error('BLOCKED_E2E_FINAL_TURN_UNSTABLE');
372
+ const latest=(await chrome.storage.local.get(runKey))[runKey];
373
+ const same=latest?.sessionId===run.sessionId&&latest?.phase==='BLOCKED'
374
+ &&latest.action===run.action&&latest.tabId===tabId
375
+ &&latest.userKey===run.userKey&&latest.assistantKey===run.assistantKey;
376
+ const live=await chrome.tabs.get(tabId).catch(()=>null);
377
+ const actionPages=await chrome.tabs.query({});
378
+ if(!same||live?.url!==source.url||live.active||live.pinned
379
+ ||actionPages.some(t=>t.url?.startsWith(chrome.runtime.getURL('continue.html?tabId='+tabId))
380
+ ||t.url?.startsWith(chrome.runtime.getURL('handoff.html?tabId='+tabId))))
381
+ throw Error('BLOCKED_E2E_SOURCE_CHANGED');
382
+ await chrome.tabs.remove(tabId);
383
+ audit.cleanedBlockedE2E=tabId;
320
384
  } else if (mode === "cleanupFailedStartHome") {
321
385
  // Only a known failed pre-send Start home, never a Chat conversation.
322
386
  const id=Number(q.get('cleanupFailedStartHome'));
@@ -24,7 +24,7 @@
24
24
  if (u.pathname === '/popup.html' && u.searchParams.size === 1) {
25
25
  const name=[...u.searchParams.keys()][0],value=u.searchParams.get(name);
26
26
  const noId=new Set(['newTest','cleanup','cleanupVerified','cleanupStaleMachine']);
27
- const tabId=new Set(['cleanupTestHome','cleanupFailedStartHome']);
27
+ const tabId=new Set(['cleanupTestHome','cleanupFailedStartHome','cleanupBlockedE2E']);
28
28
  if ((noId.has(name)&&value==='1') || (tabId.has(name)&&validTabId(value)))
29
29
  return {kind:'manual',name:'acceptance'};
30
30
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Chat Loop Extension",
4
- "version": "0.3.46",
4
+ "version": "0.3.47",
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.46",
3
+ "version": "0.3.47",
4
4
  "type": "module",
5
5
  "description": "Chat Loop Extension package: nine formal Action pages and its own native CLI bridge",
6
6
  "bin": {