pikiloom 0.4.58 → 0.4.60

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 (23) hide show
  1. package/dashboard/dist/assets/{AgentTab-CLlOp3lJ.js → AgentTab-KSanVg_l.js} +1 -1
  2. package/dashboard/dist/assets/{ConnectionModal-BJhiouLS.js → ConnectionModal-BSoA2mZL.js} +1 -1
  3. package/dashboard/dist/assets/{DirBrowser-H0N2gMRB.js → DirBrowser-zKX7wmty.js} +1 -1
  4. package/dashboard/dist/assets/{ExtensionsTab-5uaiMRcm.js → ExtensionsTab-DKLjCr4u.js} +1 -1
  5. package/dashboard/dist/assets/{IMAccessTab-ClNbxnzS.js → IMAccessTab-BhvB7Hes.js} +1 -1
  6. package/dashboard/dist/assets/{Modal-Cy1zHcC7.js → Modal-Qwlap_Fl.js} +1 -1
  7. package/dashboard/dist/assets/{Modals-BrLADO-I.js → Modals-2Zw2FSLo.js} +1 -1
  8. package/dashboard/dist/assets/{Select-D4DbOixc.js → Select-dIxoi_TN.js} +1 -1
  9. package/dashboard/dist/assets/SessionPanel--D5NGYgd.js +1 -0
  10. package/dashboard/dist/assets/{SystemTab-CDrID55_.js → SystemTab-CtjiZa5s.js} +1 -1
  11. package/dashboard/dist/assets/index-BDSh0L9U.js +3 -0
  12. package/dashboard/dist/assets/index-DN0a6ZnO.js +23 -0
  13. package/dashboard/dist/assets/{shared-DQyw9U1Z.js → shared-D2sGZJ9f.js} +1 -1
  14. package/dashboard/dist/index.html +1 -1
  15. package/dist/agent/drivers/claude.js +34 -3
  16. package/dist/agent/drivers/codex.js +24 -4
  17. package/dist/agent/turn-snapshot.js +141 -0
  18. package/dist/bot/bot.js +11 -0
  19. package/dist/dashboard/routes/sessions.js +30 -9
  20. package/package.json +1 -1
  21. package/dashboard/dist/assets/SessionPanel-1hemRLg_.js +0 -1
  22. package/dashboard/dist/assets/index-Iot9Vn3K.js +0 -3
  23. package/dashboard/dist/assets/index-QFqtuRrZ.js +0 -23
@@ -0,0 +1,141 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { getUserConfigPath } from '../core/config/user-config.js';
4
+ // Durable per-turn snapshot of the content pikiloom actually delivered.
5
+ //
6
+ // The agent CLIs are the source of truth for transcript history, but their session jsonl is
7
+ // lossy under two upstream conditions we cannot prevent: (1) a turn's closing reply dies before
8
+ // the CLI flushes it to disk (kill/flush race), and (2) the CLI's resume rewrites the jsonl in
9
+ // place and drops a turn that never got a synthetic-free conclusion. In both cases pikiloom has
10
+ // already streamed the complete reply to the user (turn-audit confirms ok/end_turn), yet a later
11
+ // history render reads only a "No response requested." tombstone — the swallow.
12
+ //
13
+ // This sidecar is the missing durable copy: one JSON line per delivered turn, keyed by the user
14
+ // prompt, so the claude history renderer can restore the real reply at a tombstone instead of
15
+ // showing a bare "ended before a closing message" notice. It complements turn-audit.jsonl (which
16
+ // records how a turn ended, not what it said). See [[posttool-empty-result-no-response-requested]].
17
+ const MAX_SNAPSHOT_BYTES = 512 * 1024; // per-session cap, single-slot rotation
18
+ const MAX_TEXT_CHARS = 40_000; // don't archive megabyte replies verbatim
19
+ const STALE_MS = 30 * 24 * 60 * 60 * 1000;
20
+ // Match the raw prompt pikiloom ran against the user text reconstructed from the jsonl. Both
21
+ // sides normalize identically: collapse whitespace, trim, cap length. Case is preserved (matching
22
+ // is mostly CJK / mixed-script prose).
23
+ export function normalizeSnapshotPrompt(s) {
24
+ return (s || '').replace(/\s+/g, ' ').trim().slice(0, 240);
25
+ }
26
+ function snapshotDir() {
27
+ return path.join(path.dirname(getUserConfigPath()), 'turn-snapshots');
28
+ }
29
+ function sessionSnapshotPath(sessionId) {
30
+ // sessionId is a uuid / codex rollout id — safe as a filename, but strip separators defensively.
31
+ const safe = sessionId.replace(/[^A-Za-z0-9_-]/g, '_');
32
+ return path.join(snapshotDir(), `${safe}.jsonl`);
33
+ }
34
+ // A finished turn whose delivered message is only a placeholder or an appended incomplete-notice
35
+ // carries nothing worth restoring. Strip a trailing "⚠️ …" note (composeKernelFinalPresentation
36
+ // and the legacy driver append one to real prose) and drop the empty/placeholder shapes.
37
+ function bodyWorthSaving(message) {
38
+ let text = (message || '').replace(/\n\n⚠️[^\n]*$/u, '').trim();
39
+ if (!text)
40
+ return null;
41
+ if (text === '(no textual response)' || text === '(no output)')
42
+ return null;
43
+ if (text.length > MAX_TEXT_CHARS)
44
+ text = text.slice(0, MAX_TEXT_CHARS);
45
+ return text;
46
+ }
47
+ let cleanedThisProcess = false;
48
+ function cleanupStaleSnapshots() {
49
+ if (cleanedThisProcess)
50
+ return;
51
+ cleanedThisProcess = true;
52
+ try {
53
+ const dir = snapshotDir();
54
+ const now = Date.now();
55
+ for (const name of fs.readdirSync(dir)) {
56
+ const p = path.join(dir, name);
57
+ try {
58
+ if (now - fs.statSync(p).mtimeMs > STALE_MS)
59
+ fs.unlinkSync(p);
60
+ }
61
+ catch { /* skip */ }
62
+ }
63
+ }
64
+ catch { /* no dir yet */ }
65
+ }
66
+ export function recordDeliveredTurn(entry) {
67
+ try {
68
+ if (!entry.sessionId)
69
+ return;
70
+ const text = bodyWorthSaving(entry.message);
71
+ if (!text)
72
+ return;
73
+ const prompt = (entry.prompt || '').slice(0, 500);
74
+ const dir = snapshotDir();
75
+ fs.mkdirSync(dir, { recursive: true });
76
+ cleanupStaleSnapshots();
77
+ const file = sessionSnapshotPath(entry.sessionId);
78
+ try {
79
+ if (fs.statSync(file).size > MAX_SNAPSHOT_BYTES)
80
+ fs.renameSync(file, `${file}.1`);
81
+ }
82
+ catch { /* no file yet */ }
83
+ const line = JSON.stringify({
84
+ ts: new Date().toISOString(),
85
+ prompt,
86
+ promptNorm: normalizeSnapshotPrompt(prompt),
87
+ text,
88
+ model: entry.model ?? null,
89
+ ok: entry.ok,
90
+ stopReason: entry.stopReason ?? null,
91
+ });
92
+ fs.appendFileSync(file, line + '\n');
93
+ }
94
+ catch { /* snapshotting is best-effort by design */ }
95
+ }
96
+ // Ordered list (append order == turn order) of delivered turns for a session, for render merge.
97
+ export function loadDeliveredTurns(sessionId) {
98
+ if (!sessionId)
99
+ return [];
100
+ try {
101
+ const content = fs.readFileSync(sessionSnapshotPath(sessionId), 'utf-8');
102
+ const out = [];
103
+ for (const raw of content.split('\n')) {
104
+ if (!raw.trim() || raw[0] !== '{')
105
+ continue;
106
+ try {
107
+ const ev = JSON.parse(raw);
108
+ if (typeof ev.text !== 'string' || !ev.text)
109
+ continue;
110
+ out.push({
111
+ prompt: typeof ev.prompt === 'string' ? ev.prompt : '',
112
+ promptNorm: typeof ev.promptNorm === 'string' ? ev.promptNorm : normalizeSnapshotPrompt(ev.prompt || ''),
113
+ text: ev.text,
114
+ model: typeof ev.model === 'string' ? ev.model : null,
115
+ });
116
+ }
117
+ catch { /* skip bad line */ }
118
+ }
119
+ return out;
120
+ }
121
+ catch {
122
+ return [];
123
+ }
124
+ }
125
+ // A per-prompt FIFO of delivered replies. A tombstone consumes the next reply recorded for its
126
+ // preceding prompt. Front-pop is exact for the common case (a unique recent prompt was swallowed);
127
+ // for a prompt repeated across turns with mixed outcomes the match is by order and may attach a
128
+ // sibling occurrence's reply — still strictly better than a bare tombstone.
129
+ export function buildRecoveryQueue(turns) {
130
+ const q = new Map();
131
+ for (const t of turns) {
132
+ if (!t.promptNorm)
133
+ continue;
134
+ const list = q.get(t.promptNorm);
135
+ if (list)
136
+ list.push(t.text);
137
+ else
138
+ q.set(t.promptNorm, [t.text]);
139
+ }
140
+ return q;
141
+ }
package/dist/bot/bot.js CHANGED
@@ -11,6 +11,7 @@ import { resolveGuiIntegrationConfig } from '../agent/mcp/bridge.js';
11
11
  import { composeSessionToolPrompt } from '../agent/mcp/capabilities.js';
12
12
  import { terminateProcessTree } from '../core/process-control.js';
13
13
  import { appendTurnAudit } from '../core/turn-audit.js';
14
+ import { recordDeliveredTurn } from '../agent/turn-snapshot.js';
14
15
  import { expandTilde } from '../core/platform.js';
15
16
  import { VERSION } from '../core/version.js';
16
17
  import { buildHumanLoopResponse, createEmptyHumanLoopAnswer, currentHumanLoopQuestion, isHumanLoopAwaitingText, setHumanLoopOption, setHumanLoopText, skipHumanLoopQuestion, summarizeResolvedHumanLoopAnswers, } from './human-loop.js';
@@ -2132,6 +2133,16 @@ export class Bot {
2132
2133
  error: result.error ? String(result.error).slice(0, 500) : null,
2133
2134
  elapsedS: result.elapsedS, model: result.model ?? resolvedModel, promptPreview: prompt.slice(0, 120),
2134
2135
  });
2136
+ // Durable copy of what we just delivered, so the history render can restore it if the CLI's
2137
+ // jsonl later loses this turn to a flush race or an in-place resume rewrite (the swallow).
2138
+ // claude-only: it is the driver whose renderer knows how to merge these at a tombstone.
2139
+ if (cs.agent === 'claude') {
2140
+ recordDeliveredTurn({
2141
+ sessionId: result.sessionId || cs.sessionId || null,
2142
+ prompt, message: result.message, model: result.model ?? resolvedModel ?? null,
2143
+ ok: result.ok, stopReason: result.stopReason ?? null,
2144
+ });
2145
+ }
2135
2146
  if (cs.agent === 'claude' && workflowEnabled && result.thinkingEffort) {
2136
2147
  result.thinkingEffort = 'ultra';
2137
2148
  }
@@ -26,6 +26,31 @@ function parsePageSize(value, fallback = DEFAULT_SESSION_PAGE_SIZE) {
26
26
  return fallback;
27
27
  return Math.min(parsed, MAX_SESSION_PAGE_SIZE);
28
28
  }
29
+ function sameWorkspacePath(a, b) {
30
+ if (!a || !b)
31
+ return false;
32
+ return path.resolve(a) === path.resolve(b);
33
+ }
34
+ export function isRemovableWorkspacePath(workspacePath, runtimeWorkdir) {
35
+ return !!workspacePath && !sameWorkspacePath(workspacePath, runtimeWorkdir);
36
+ }
37
+ export function projectWorkspacesForDashboard(workspaces, runtimeWorkdir, nowIso = new Date().toISOString()) {
38
+ const runtimePath = runtimeWorkdir ? path.resolve(runtimeWorkdir) : '';
39
+ const projected = workspaces.map(workspace => sameWorkspacePath(workspace.path, runtimePath)
40
+ ? { ...workspace, isDefault: true, removable: false }
41
+ : { ...workspace, removable: true });
42
+ if (runtimePath && !projected.some(workspace => sameWorkspacePath(workspace.path, runtimePath))) {
43
+ projected.unshift({
44
+ path: runtimePath,
45
+ name: path.basename(runtimePath),
46
+ order: -1,
47
+ addedAt: nowIso,
48
+ isDefault: true,
49
+ removable: false,
50
+ });
51
+ }
52
+ return projected;
53
+ }
29
54
  function paginateSessionResult(items, page, limit) {
30
55
  const total = items.length;
31
56
  const totalPages = Math.max(1, Math.ceil(total / limit));
@@ -242,15 +267,7 @@ app.get('/api/workspaces', (c) => {
242
267
  const workspaces = loadWorkspaces();
243
268
  const config = loadUserConfig();
244
269
  const rwd = runtime.getRuntimeWorkdir(config);
245
- if (rwd && !workspaces.some(w => w.path === rwd)) {
246
- workspaces.unshift({
247
- path: rwd,
248
- name: path.basename(rwd),
249
- order: -1,
250
- addedAt: new Date().toISOString(),
251
- });
252
- }
253
- return c.json({ ok: true, workspaces });
270
+ return c.json({ ok: true, workspaces: projectWorkspacesForDashboard(workspaces, rwd) });
254
271
  });
255
272
  app.post('/api/workspaces', async (c) => {
256
273
  try {
@@ -271,6 +288,10 @@ app.delete('/api/workspaces', async (c) => {
271
288
  const wsPath = typeof body?.path === 'string' ? body.path.trim() : '';
272
289
  if (!wsPath)
273
290
  return c.json({ ok: false, error: 'path is required' }, 400);
291
+ const runtimeWorkdir = runtime.getRuntimeWorkdir(loadUserConfig());
292
+ if (!isRemovableWorkspacePath(wsPath, runtimeWorkdir)) {
293
+ return c.json({ ok: false, error: 'default workspace cannot be removed' }, 400);
294
+ }
274
295
  const removed = removeWorkspace(wsPath);
275
296
  return c.json({ ok: true, removed });
276
297
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pikiloom",
3
- "version": "0.4.58",
3
+ "version": "0.4.60",
4
4
  "description": "Put the world's smartest AI agents in your pocket. Command local Claude & Gemini via IM. | 让最好用的 IM 变成你电脑上的顶级 Agent 控制台",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1 +0,0 @@
1
- import{r as n,j as s}from"./react-vendor-C7Sl8SE7.js";import{c as on,I as an,S as ze,m as We,a as T,u as pt,d as un,g as cn,l as dn,e as fn,j as mn,f as gn,s as pn,Y as hn}from"./index-Iot9Vn3K.js";import{s as xn,l as kn,n as Et,m as Sn,a as bn,o as vn,d as In,b as Tn,p as yn,h as Mt,c as Qe,e as Rn,f as Pt,g as $e,i as Ke,j as jn,k as wn,T as Nn,R as An,U as Ct,q as Ln,r as Ut,L as qt,t as On,I as En}from"./index-QFqtuRrZ.js";import{M as Ht,a as Ft}from"./Modal-Cy1zHcC7.js";import"./router-DHISdpPk.js";import"./Select-D4DbOixc.js";import"./DirBrowser-H0N2gMRB.js";import"./markdown-DxQYQFeH.js";import"./ExtensionsTab-5uaiMRcm.js";function Mn({snapshot:o}){const[t,f]=n.useState(o.currentIndex??0),[N,y]=n.useState(""),[x,p]=n.useState(!1),[C,S]=n.useState(null);n.useEffect(()=>{f(o.currentIndex??0),y(""),S(null)},[o.promptId,o.currentIndex]);const O=o.questions||[],R=O[t]||null,Z=O.length,U=!!(R?.options&&R.options.length),E=U?!!R?.allowFreeform:!0,de=c=>{c&&(f(i=>i+1),y(""))},fe=async c=>{if(!x){p(!0),S(null);try{const i=await T.interactionSelectOption(o.promptId,c);if(!i.ok){S(i.error||"Failed to submit selection.");return}de(i.advanced)}catch(i){S(i?.message||"Network error.")}finally{p(!1)}}},me=async()=>{if(x)return;const c=N.trim();if(!c&&!R?.allowEmpty){S("Please enter a response.");return}p(!0),S(null);try{const i=await T.interactionSubmitText(o.promptId,c);if(!i.ok){S(i.error||"Failed to submit answer.");return}de(i.advanced)}catch(i){S(i?.message||"Network error.")}finally{p(!1)}},m=async()=>{if(!x){p(!0),S(null);try{const c=await T.interactionSkip(o.promptId);if(!c.ok){S(c.error||"Failed to skip.");return}de(c.advanced)}catch(c){S(c?.message||"Network error.")}finally{p(!1)}}},ee=async()=>{if(!x){p(!0);try{await T.interactionCancel(o.promptId)}catch{}}},te=n.useMemo(()=>{const c=[];return o.hint&&c.push(o.hint),Z>1&&c.push(`Question ${t+1} of ${Z}`),c.join(" · ")||void 0},[o.hint,t,Z]);return s.jsxs(Ht,{open:!0,onClose:ee,wide:U&&(R?.options?.length||0)>3,children:[s.jsx(Ft,{title:o.title||"Pikiloom needs your input",description:te,onClose:ee}),R?s.jsxs("div",{className:"space-y-4",children:[s.jsxs("div",{children:[s.jsx("div",{className:"text-xs font-medium uppercase tracking-wide text-fg-5",children:R.header||"Question"}),s.jsx("div",{className:"mt-1 whitespace-pre-wrap text-sm leading-relaxed text-fg",children:R.prompt})]}),U&&s.jsx("div",{className:"grid grid-cols-1 gap-2 sm:grid-cols-2",children:(R.options||[]).map(c=>s.jsxs("button",{type:"button",disabled:x,onClick:()=>fe(c.value||c.label),className:on("group rounded-lg border border-edge bg-panel-alt px-3 py-2 text-left text-sm transition","hover:border-control-border-h hover:bg-control-h hover:shadow-sm","focus:outline-none focus:ring-2 focus:ring-[var(--th-glow-a)]","disabled:cursor-not-allowed disabled:opacity-50"),children:[s.jsx("div",{className:"font-medium text-fg group-hover:text-fg",children:c.label}),c.description&&s.jsx("div",{className:"mt-0.5 text-xs leading-snug text-fg-4",children:c.description})]},c.value||c.label))}),E&&s.jsx("div",{children:s.jsx(an,{value:N,onChange:c=>y(c.target.value),onKeyDown:c=>{c.key==="Enter"&&!c.shiftKey&&!x&&(c.preventDefault(),me())},placeholder:U?"Or type a custom answer…":"Type your answer…",disabled:x,autoFocus:!U})}),C&&s.jsx("div",{className:"rounded-md border border-red-300/40 bg-red-500/10 px-3 py-2 text-xs text-red-600",children:C}),s.jsxs("div",{className:"flex items-center justify-between gap-3",children:[s.jsx("div",{className:"text-xs text-fg-5",children:x?s.jsxs("span",{className:"inline-flex items-center gap-2",children:[s.jsx(ze,{})," Submitting…"]}):s.jsxs("span",{children:["Press ",s.jsx("kbd",{className:"rounded border border-edge bg-panel-alt px-1.5 py-0.5 text-[10px] uppercase",children:"Enter"})," to send"]})}),s.jsxs("div",{className:"flex items-center gap-2",children:[s.jsx(We,{variant:"ghost",size:"sm",onClick:m,disabled:x,children:"Skip"}),E&&s.jsx(We,{variant:"primary",size:"sm",onClick:me,disabled:x||!N.trim()&&!R.allowEmpty,children:"Submit"})]})]})]}):s.jsxs("div",{className:"py-6 text-center text-sm text-fg-5",children:[s.jsx(ze,{className:"mr-2 inline-block"})," Waiting for the agent…"]})]})}function Pn(o){return o.isNull?(o.localStreamPending||o.holdsActiveState)&&!o.holdExpired?"reject-null":"apply":(typeof o.updatedAt=="number"?o.updatedAt:0)<o.lastAppliedUpdatedAt?"reject-stale":"apply"}function Cn(o,t){return typeof t!="number"?o:t>o?t:o}function Un(o,t){return!o||!o.length?[]:t.size?o.filter(f=>!t.has(f)):o.slice()}function qn(o,t,f,N){if(!o.size)return o;const y=new Set(t);let x=!1;const p=new Map;for(const[C,S]of o){if(f-S>N||!y.has(C)){x=!0;continue}p.set(C,S)}return x?p:o}const ht=12,Dt=160,Dn=96,Hn=[],Fn=[],Bn=[],_n={phase:"streaming",text:"",thinking:""},Qn=20,$n=6e4,Kn=15e3,zn=7e3,ie=new Map;function Wn(o,t){return`${o}:${t}`}function Yn(o,t){for(ie.delete(o),ie.set(o,t);ie.size>Qn;)ie.delete(ie.keys().next().value)}const lr=n.memo(function({session:t,workdir:f,active:N=!0,onSessionChange:y,initialPendingPrompt:x,initialPendingImageUrls:p,onPendingPromptConsumed:C}){const S=pt(e=>e.locale),O=pt(e=>e.agentStatus?.agents?.find(r=>r.agent===t.agent)??null),R=O?.selectedEffort??null,Z=O?.selectedModel??null,U=pt(e=>e.modelLayer),E=t.profileId?U?.profiles.find(e=>e.id===t.profileId)??null:null,de=E?U?.providers.find(e=>e.id===E.providerId)??null:null,fe=t.profileId===void 0?O?.byokProviderName??null:de?.name??null,me=t.profileId===void 0?O?.byokProfileName??null:E&&E.name.trim().toLowerCase()!==E.modelId.trim().toLowerCase()?E.name:null,m=n.useMemo(()=>un(S),[S]),ee=cn(t.agent||""),te=dn(t),c=!!x||!!(p&&p.length),[i,Ye]=n.useState(null),[Ge,ge]=n.useState(!c),[pe,xt]=n.useState(!1),[u,B]=n.useState(null),[_,he]=n.useState(!1),[q,Ve]=n.useState(null),[Bt,kt]=n.useState(0),[_t,Xe]=n.useState(null),[D,Re]=n.useState([]),[Qt,je]=n.useState([]),[xe,Je]=n.useState([]),[h,ne]=n.useState(x||null),[M,re]=n.useState(p||[]),[$t,se]=n.useState(null),Q=n.useRef(null);Q.current=$t;const St=n.useRef(h);St.current=h;const[we,H]=n.useState([]),Ne=n.useRef([]);Ne.current=we;const ke=n.useRef(null),[Kt,bt]=n.useState(null),[Ae,Se]=n.useState(null),[be,Ze]=n.useState(""),[$,et]=n.useState(!1),zt=!!O?.capabilities?.fork,tt=n.useRef(null),L=n.useRef(p||[]),K=n.useRef(u),nt=n.useRef(_);K.current=u,nt.current=_;const rt=n.useRef(D);rt.current=D;const vt=n.useRef(q);vt.current=q;const z=n.useRef(null),Le=n.useRef(null),W=n.useRef(!0),le=n.useRef(!1),Oe=n.useRef(null),st=n.useRef(!1),P=n.useRef(c),oe=n.useRef(!1),Y=n.useRef(!1),lt=n.useRef(!1),ot=n.useRef(!1),G=n.useRef(null),Ee=n.useRef(0),ve=n.useRef(Date.now()),Ie=n.useRef(new Map),It=n.useRef({model:null,effort:null}),Wt=n.useCallback(e=>{It.current=e},[]);n.useEffect(()=>{lt.current||!c||(lt.current=!0,x&&!h&&ne(x),p&&p.length&&!M.length&&(re(p),L.current=p),ge(!1),kt(e=>e+1),C?.())},[c,C]);const A=n.useCallback(()=>{ne(null),re(e=>{for(const r of e)URL.revokeObjectURL(r);return[]}),L.current=[],se(null)},[]),V=n.useCallback(()=>{H(e=>{if(!e.length)return e;for(const r of e)for(const l of r.imageUrls)URL.revokeObjectURL(l);return[]}),ke.current=null},[]),Tt=n.useCallback((e,r)=>{ve.current=Date.now();const l=xn({streaming:nt.current,liveStreamPhase:K.current?.phase??null,streamPhase:vt.current,queuedTaskCount:rt.current.length,pendingQueuedCount:Ne.current.length}),a=r||[];if(l){const d=`local-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,8)}`;ke.current=d,H(k=>[...k,{localId:d,taskId:null,prompt:e||"",imageUrls:a}]);return}K.current?.phase==="done"&&B(null);for(const d of L.current)URL.revokeObjectURL(d);ke.current=null,ne(e||null),re(a),L.current=a,se(null)},[]),Yt=n.useCallback(e=>{const r=ke.current;if(r){ke.current=null,H(l=>{const a=l.findIndex(k=>k.localId===r);if(a<0)return l;const d=l.slice();return d[a]={...d[a],taskId:e},d});return}se(e)},[]),Gt=n.useCallback(async()=>{if(!Ae)return;const e=be.trim();if(e){et(!0);try{const r=await T.forkSession(f,t.agent||"",t.sessionId,Ae.atTurn,e,{});if(!r.ok||!r.sessionKey){et(!1);return}const[l,a]=r.sessionKey.split(":");Se(null),Ze(""),y?.({agent:l,sessionId:a,workdir:f})}finally{et(!1)}}},[Ae,be,f,t.agent,t.sessionId,y]);tt.current=Gt;const Me=n.useCallback(async(e,r={})=>{try{const l=await kn({workdir:f,agent:t.agent||"",sessionId:t.sessionId,rich:!0,turnOffset:e.turnOffset,turnLimit:e.turnLimit,lastNTurns:e.lastNTurns},{force:r.force});return l.ok?Et(l):null}catch{return null}},[f,t.agent,t.sessionId]),j=n.useCallback(async({keepOlder:e,force:r=!1,scrollToBottom:l=!1})=>{const a=t.sessionId;if(Oe.current===a)return!1;Oe.current=a;try{const d=await Me({turnOffset:0,turnLimit:ht},{force:r});if(!d||t.sessionId!==a)return!1;if(l&&(le.current=!0),Ye(k=>!k||!e?d:Sn(k,d)),ge(!1),oe.current&&(oe.current=!1,A()),Y.current){const k=Y.current;Y.current=!1;const X=k!==!0?k.taskId:null;K.current&&(k===!0||K.current.taskId===X)&&B(null)}return!0}finally{Oe.current===a&&(Oe.current=null)}},[Me,A,t.sessionId]),Pe=n.useCallback(async()=>{if(!i?.hasOlder||st.current)return;const e=z.current;e&&(Le.current={scrollHeight:e.scrollHeight,scrollTop:e.scrollTop}),st.current=!0,xt(!0);try{const r=await Me({turnOffset:Math.max(0,i.totalTurns-i.startTurn),turnLimit:ht});r?Ye(l=>l?bn(l,r):r):Le.current=null}finally{st.current=!1,xt(!1)}},[Me,i]),Ce=n.useRef(null),ae=n.useCallback((e,r="ws")=>{const l=nt.current||rt.current.length>0,a=Date.now()-ve.current>Kn;if(Pn({updatedAt:e?.updatedAt,isNull:!e,lastAppliedUpdatedAt:Ee.current,localStreamPending:P.current,holdsActiveState:l,holdExpired:a})!=="apply")return;const k=!e&&a&&(P.current||l);if(e&&(ve.current=Date.now(),Ee.current=Cn(Ee.current,e.updatedAt)),e?.sessionId&&e.sessionId!==t.sessionId&&(ot.current=!0,Te.current=`${t.agent}:${e.sessionId}`,y?.({agent:t.agent||"",sessionId:e.sessionId,workdir:f})),!e){const g=Ce.current;he(!1),k?(P.current=!1,oe.current=!0,Y.current=!0,V(),j({keepOlder:!0,force:!0,scrollToBottom:W.current})):g==="streaming"?(oe.current=!0,Y.current=!0,j({keepOlder:!0,force:!0,scrollToBottom:W.current})):B(null),k||(P.current&&g!=="streaming"?j({keepOlder:!0,force:!0}):(g==="done"&&(A(),V()),g!==null&&(P.current=!1))),Xe(null),Ve(null),Re([]),je([]),Je([]),Ce.current=null;return}Ve(e.phase),Xe(e.taskId||null);const X=[];e.taskId&&X.push(e.taskId),Array.isArray(e.queuedTaskIds)&&X.push(...e.queuedTaskIds),Ie.current=qn(Ie.current,X,Date.now(),$n);const ue=Ie.current,mt=Un(e.queuedTaskIds,ue),Ot=(Array.isArray(e.queuedTasks)?e.queuedTasks:[]).filter(g=>!ue.has(g.taskId));if(Re(mt.length?mt:Hn),je(Ot.length?Ot:Fn),Je(Array.isArray(e.interactions)&&e.interactions.length?e.interactions:Bn),vn({pendingTaskId:Q.current,streamTaskId:e.taskId||null,queuedTaskIds:e.queuedTaskIds})){const g=Q.current,w=St.current||"",I=L.current;H(v=>v.some(ce=>ce.taskId===g)?v:[...v,{localId:`demote-${g}`,taskId:g,prompt:w,imageUrls:I}]),ne(null),re([]),L.current=[],se(null),Q.current=null}if(e.phase==="streaming"){if(B({taskId:e.taskId||null,phase:"streaming",text:e.text||"",thinking:e.thinking||"",activity:e.activity,plan:e.plan??null,model:e.model??null,effort:e.effort??null,previewMeta:e.previewMeta??null,subAgents:e.previewMeta?.subAgents??null,generatingImages:e.previewMeta?.generatingImages??0,artifacts:e.artifacts??null,startedAt:typeof e.startedAt=="number"?e.startedAt:null,error:null,question:e.question??null,questionBlocks:e.questionBlocks??null}),he(!0),e.taskId&&e.taskId!==Q.current){const g=Ne.current,w=g.findIndex(I=>I.taskId===e.taskId);if(w>=0){const I=g[w];for(const v of L.current)URL.revokeObjectURL(v);ne(I.prompt||null),re(I.imageUrls),L.current=I.imageUrls,se(e.taskId),H(v=>v.filter((ce,J)=>J!==w))}}W.current&&(le.current=!0)}else if(e.phase==="queued")B(null),he(!1);else if(e.phase==="done"){const g=In(K.current?.taskId??null,e.taskId||null),w=mt.length>0;if(g){he(!1),B(J=>J?{...J,phase:"done",error:e.error??null}:e.error?{taskId:e.taskId||null,phase:"done",text:"",thinking:"",activity:"",plan:null,model:e.model??null,effort:e.effort??null,previewMeta:e.previewMeta??null,subAgents:e.previewMeta?.subAgents??null,generatingImages:e.previewMeta?.generatingImages??0,artifacts:e.artifacts??null,error:e.error,question:e.question??null,questionBlocks:e.questionBlocks??null}:J);const I=K.current,v=!!I&&Tn(I),ce=!!e.incomplete&&v&&!w;if(Ce.current!=="done"){w||(oe.current=!0),Y.current=ce?!1:{taskId:e.taskId||null},j({keepOlder:!0,force:!0,scrollToBottom:W.current});const J=t.agent||"",rn=t.sessionId,sn=Te.current;G.current&&clearTimeout(G.current),G.current=setTimeout(()=>{G.current=null,T.getSessionStreamState(J,rn).then(ln=>{Te.current===sn&&Ue.current(ln.state,"seed")}).catch(()=>{})},900)}}w||(P.current=!1)}const gt=new Set;if(e.taskId&&gt.add(e.taskId),Array.isArray(e.queuedTaskIds))for(const g of e.queuedTaskIds)gt.add(g);H(g=>{let w=!1;const I=[];for(const v of g)if(!v.taskId||gt.has(v.taskId))I.push(v);else{for(const ce of v.imageUrls)URL.revokeObjectURL(ce);w=!0}return w?I:g}),Ce.current=e.phase},[A,V,j,t.sessionId,t.agent,y,f]),Ue=n.useRef(ae);Ue.current=ae;const yt=n.useCallback(()=>{P.current=!0,ve.current=Date.now(),kt(e=>e+1)},[]);n.useEffect(()=>()=>{G.current&&(clearTimeout(G.current),G.current=null)},[]);const Vt=n.useCallback(async e=>{try{Ie.current.set(e,Date.now()),await T.recallSessionMessage(e),Q.current===e&&A(),H(r=>{let l=!1;const a=[];for(const d of r)if(d.taskId===e){for(const k of d.imageUrls)URL.revokeObjectURL(k);l=!0}else a.push(d);return l?a:r}),Re(r=>r.filter(l=>l!==e)),je(r=>r.filter(l=>l.taskId!==e)),Xe(r=>r===e?null:r)}catch{}},[A]),Xt=n.useCallback(async e=>{const r=Ne.current.find(l=>l.taskId===e)||null;if(r&&r.imageUrls.length>0){for(const l of L.current)URL.revokeObjectURL(l);ne(r.prompt||null),re(r.imageUrls),L.current=r.imageUrls,se(e),Q.current=e,H(l=>l.filter(a=>a.taskId!==e))}try{await T.steerSession(e)}catch{}},[]),Jt=n.useCallback(async()=>{try{await T.stopSession(t.agent||"",t.sessionId)}catch{}},[t.agent,t.sessionId]),qe=Wn(t.agent||"",t.sessionId);n.useEffect(()=>{if(ot.current){ot.current=!1;let d=!1;return j({keepOlder:!0,force:!0}).finally(()=>{d||ge(!1)}),()=>{d=!0}}let e=!1;const r=yn({workdir:f,agent:t.agent||"",sessionId:t.sessionId,rich:!0,turnOffset:0,turnLimit:ht},{allowStale:!0}),l=c&&!lt.current,a=r?.ok?Et(r):ie.get(qe)||null;return ge(l?!1:!a),Ye(a),B(null),he(!1),Ve(null),Re([]),je([]),Je([]),Ee.current=0,ve.current=Date.now(),Ie.current=new Map,l||(A(),V(),P.current=!1,oe.current=!1,Y.current=!1),W.current=!0,le.current=!0,l||j({keepOlder:!1,force:!0}).finally(()=>{e||ge(!1)}),()=>{e=!0}},[j,t.agent,t.sessionId,f,qe,A,V]),n.useEffect(()=>{i&&i.turns.length>0&&Yn(qe,i)},[qe,i]),n.useEffect(()=>{N&&j({keepOlder:!0,force:!0})},[N,j]);const Te=n.useRef(`${t.agent}:${t.sessionId}`);Te.current=`${t.agent}:${t.sessionId}`,fn("stream-update",n.useCallback(e=>{e.key===Te.current&&ae(e.snapshot??null)},[ae])),n.useEffect(()=>{let e=!0;return T.getSessionStreamState(t.agent||"",t.sessionId).then(r=>{e&&Ue.current(r.state,"seed")}).catch(()=>{}),()=>{e=!1}},[t.agent,t.sessionId,Bt]),mn(n.useCallback(()=>{T.getSessionStreamState(t.agent||"",t.sessionId).then(e=>{ae(e.state,"seed")}).catch(()=>{}),j({keepOlder:!0,force:!0})},[ae,t.agent,t.sessionId,j]));const Rt=_||!!q||D.length>0||!!h||we.length>0;n.useEffect(()=>{if(!N||!Rt)return;const e=setInterval(()=>{typeof document<"u"&&document.visibilityState!=="visible"||T.getSessionStreamState(t.agent||"",t.sessionId).then(r=>Ue.current(r.state,"seed")).catch(()=>{})},zn);return()=>clearInterval(e)},[N,Rt,t.agent,t.sessionId]),n.useEffect(()=>{!P.current&&te!=="running"&&!_&&!u&&!q&&D.length===0&&(A(),V())},[te,_,u,q,D.length,A,V]),n.useLayoutEffect(()=>{const e=Le.current,r=z.current;!e||!r||(Le.current=null,r.scrollTop=e.scrollTop+(r.scrollHeight-e.scrollHeight))},[i?.turns.length]),n.useLayoutEffect(()=>{if(!le.current)return;const e=z.current;e&&(le.current=!1,e.scrollTop=e.scrollHeight,requestAnimationFrame(()=>{W.current&&(e.scrollTop=e.scrollHeight)}))},[i,u]),n.useLayoutEffect(()=>{if(!h)return;const e=z.current;e&&(e.scrollTop=e.scrollHeight)},[h]),n.useEffect(()=>{if(!i?.hasOlder||Ge||pe)return;const e=z.current;e&&e.scrollHeight<=e.clientHeight+Dt&&Pe()},[i?.hasOlder,i?.turns.length,Pe,Ge,pe]);const Zt=n.useCallback(()=>{const e=z.current;if(!e)return;const r=e.scrollHeight-e.scrollTop-e.clientHeight;W.current=r<=Dn,e.scrollTop<=Dt&&Pe()},[Pe]),ye=u?.model||t.model||Z||null,De=gn(t.agent||"",u?.effort||t.thinkingEffort||R||null,t.workflowEnabled??O?.workflowEnabled)||null,at=me&&(!ye||ye===Z)?me:ye?pn(ye):null,ut=hn(t,{streaming:_,hasLiveStream:!!u,streamPhase:q,queuedTaskCount:D.length}),b=i?.turns||[],jt=n.useMemo(()=>{for(let e=b.length-1;e>=0;e--){const r=b[e].assistant?.blocks;if(r)for(let l=r.length-1;l>=0;l--){const a=r[l];if(a.type==="plan"&&Mt(a.plan))return a.plan}}return null},[b]),He=n.useMemo(()=>{if(!M.length||!b.length)return!1;const e=b[b.length-1];return!e.user||!Qe(e.user.text,h)?!1:e.user.blocks.filter(l=>l.type==="image").length<M.length},[b,h,M.length]),ct=n.useMemo(()=>M.length?M.map(e=>({type:"image",content:e})):!h||!u?.questionBlocks?.length?[]:Qe(h,u.question)?u.questionBlocks:[],[M,h,u]),wt=u?.question||null,Fe=Rn(h,wt),it=Pt(wt,h),Nt=b.length>0?b[b.length-1]?.user?.text:null,At=!!Fe&&b.length>0&&$e(Nt,Fe),F=n.useMemo(()=>{let e=b;if(He){const ue=e[e.length-1];e=[...e.slice(0,-1),{...ue,user:null}]}if(!u||!e.length)return e;const r=e[e.length-1],l=Fe;if(!r.assistant)return!!r.user&&!!l&&!Qe(r.user.text,l)&&($e(r.user.text,l)||Pt(l,r.user.text))?[...e.slice(0,-1),{...r,user:{...r.user,text:l}}]:e;const a=(u.text||"").trim(),d=r.assistant.text?.trim()||"";if(!(l!=null?$e(r.user?.text,l):!!d&&!!a&&(a.startsWith(d)||d.startsWith(a))))return e;const X=r.user&&l&&!Qe(r.user.text,l)?{...r.user,text:l}:r.user;return[...e.slice(0,-1),{...r,user:X,assistant:null}]},[b,u,Fe,He]),en=(!!h||ct.length>0)&&!it&&(He||!At)&&!u,tn=!!u&&Ke(u)&&u.phase==="streaming",dt=jn({sessionRunning:te==="running",streaming:_,streamPhase:q,queuedTaskCount:D.length,pendingQueuedCount:we.length,liveTurnStreaming:tn,pendingBubbleDots:en}),Be=dt&&F.length>0?F[F.length-1]:null,_e=!!Be?.assistant&&wn(Be.assistant),ft=_e?Be.assistant.usage??null:null,nn=_e?Be.assistant.blocks.filter(e=>e.type==="tool_use").length:0,Lt=(()=>{if(!dt||te!=="running")return null;const e=t.runUpdatedAt?Date.parse(t.runUpdatedAt):NaN;return Number.isFinite(e)?e:null})();return s.jsxs("div",{className:"flex flex-col h-full overflow-hidden",children:[s.jsx("div",{ref:z,onScroll:Zt,className:"flex-1 overflow-y-auto overscroll-contain",children:Ge&&!h&&!M.length&&!u?s.jsx("div",{className:"flex items-center justify-center py-20",children:s.jsx(ze,{className:"h-5 w-5 text-fg-4"})}):F.length===0&&!h&&!M.length&&!u&&!ut?s.jsx("div",{className:"py-20 text-center text-[13px] text-fg-5",children:m("hub.noMessages")}):s.jsxs("div",{className:"max-w-[900px] mx-auto px-6 py-6 space-y-0",children:[(i?.hasOlder||pe)&&s.jsxs("div",{className:"mb-4 flex items-center justify-center gap-2 text-[11px] text-fg-5",children:[pe?s.jsx(ze,{className:"h-3 w-3 text-fg-5"}):s.jsx("span",{className:"h-1.5 w-1.5 rounded-full bg-fg-5/35"}),s.jsx("span",{children:m(pe?"hub.loadingOlderTurns":"hub.loadOlderTurnsHint")})]}),t.migratedFrom?.kind==="fork"&&t.migratedFrom.sessionId&&s.jsxs("button",{type:"button",onClick:()=>y?.({agent:t.migratedFrom.agent||t.agent||"",sessionId:t.migratedFrom.sessionId,workdir:f}),className:"mb-4 inline-flex items-center gap-1.5 rounded-md border border-edge bg-panel-alt px-2.5 py-1 text-[11px] text-fg-5 transition hover:border-edge-h hover:text-fg-2",title:`#${t.migratedFrom.sessionId.slice(0,8)}`,children:[s.jsxs("svg",{width:"10",height:"10",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",children:[s.jsx("circle",{cx:"6",cy:"6",r:"2"}),s.jsx("circle",{cx:"18",cy:"6",r:"2"}),s.jsx("circle",{cx:"12",cy:"20",r:"2"}),s.jsx("path",{d:"M6 8v3a3 3 0 0 0 3 3h6a3 3 0 0 0 3-3V8"}),s.jsx("path",{d:"M12 14v4"})]}),s.jsx("span",{children:m("hub.forkBadge")}),s.jsxs("span",{className:"font-mono",children:["#",t.migratedFrom.sessionId.slice(0,8)]}),typeof t.migratedFrom.forkedAtTurn=="number"&&s.jsxs("span",{className:"text-fg-5/70",children:["· ",m("hub.forkBadgeAt").replace("{turn}",String(t.migratedFrom.forkedAtTurn+1))]})]}),F.map((e,r)=>{const l=(i?.startTurn||0)+r;return s.jsx(Nn,{turn:e,turnIndex:l,hideHeaderUsage:_e&&r===F.length-1,isTail:r===F.length-1,fallbackPlan:r===F.length-1&&!(u&&Ke(u))?jt:void 0,agent:t.agent||"",meta:ee,model:at,effort:De,providerName:fe,t:m,workdir:f,onResend:a=>{le.current=!0,Tt(a);const d=It.current;T.sendSessionMessage(f,t.agent||"",t.sessionId,a,{model:d.model||ye||void 0,effort:d.effort||De||void 0}).then(k=>{k.ok&&yt()}).catch(()=>{A()})},onEdit:a=>bt(a),onFork:zt?a=>{Ze(""),Se({atTurn:a})}:void 0},`${i?.startTurn||0}:${r}`)}),ut&&s.jsx("div",{className:"mb-5 animate-in",children:s.jsx(An,{detail:ut,t:m})}),(h||ct.length>0)&&!it&&(He||!At)&&s.jsxs("div",{className:"session-turn session-turn--live",children:[s.jsx(Ct,{text:h||"",blocks:ct,t:m}),!u&&s.jsx("div",{className:"mt-3 mb-5 animate-in",children:s.jsx(Ln,{className:"text-fg-5"})})]}),u&&Ke(u)&&u.question&&(!h||it)&&!(b.length>0&&$e(Nt,u.question))&&s.jsx("div",{className:"session-turn session-turn--live",children:s.jsx(Ct,{text:u.question,blocks:u.questionBlocks||void 0,t:m})}),u&&Ke(u)&&s.jsxs("div",{className:"mb-6",children:[s.jsx(Ut,{agent:t.agent||"",meta:ee,model:at,effort:De,providerName:fe,previewMeta:u.previewMeta,hideContextUsage:!0}),s.jsx(qt,{stream:Mt(u.plan)?u:{...u,plan:jt},t:m,workdir:f})]}),dt&&(_e?s.jsx("div",{className:"-mt-3 mb-6 animate-in",children:s.jsx(On,{toolCount:nn,ctxPct:ft?.contextPercent??null,ctxTokens:ft?.contextUsedTokens??0,turnOutTokens:ft?.turnOutputTokens??0,startedAt:Lt})}):s.jsxs("div",{className:"mb-6",children:[s.jsx(Ut,{agent:t.agent||"",meta:ee,model:at,effort:De,providerName:fe,hideContextUsage:!0}),s.jsx(qt,{stream:{..._n,startedAt:Lt},t:m,workdir:f})]})),s.jsx("div",{className:"h-4"})]})}),s.jsx(En,{session:t,workdir:f,onStreamQueued:yt,onSendStart:Tt,onSendTaskAssigned:Yt,onSessionChange:y,t:m,streamPhase:q,streamTaskId:_t,queuedTaskIds:D,queuedTasks:Qt,pendingQueuedSends:we,onRecall:Vt,onSteer:Xt,onStopAll:Jt,editDraft:Kt,onEditDraftConsumed:()=>bt(null),onSelectionChange:Wt}),Ae&&s.jsxs(Ht,{open:!0,onClose:()=>{$||Se(null)},children:[s.jsx(Ft,{title:m("hub.forkPromptTitle"),description:m("hub.forkPromptHint"),onClose:()=>{$||Se(null)}}),s.jsx("textarea",{autoFocus:!0,value:be,disabled:$,onChange:e=>Ze(e.target.value),onKeyDown:e=>{e.key==="Enter"&&(e.metaKey||e.ctrlKey)&&be.trim()&&!$&&(e.preventDefault(),tt.current?.())},placeholder:m("hub.forkPromptPlaceholder"),className:"w-full min-h-[120px] resize-y rounded-md border border-edge bg-panel-alt px-3 py-2 text-[13px] leading-relaxed text-fg outline-none focus:border-edge-h"}),s.jsxs("div",{className:"mt-4 flex items-center justify-end gap-2",children:[s.jsx(We,{variant:"ghost",disabled:$,onClick:()=>Se(null),children:m("modal.cancel")}),s.jsx(We,{variant:"primary",disabled:$||!be.trim(),onClick:()=>{tt.current?.()},children:m($?"hub.forkSubmitting":"hub.forkSubmit")})]})]}),N&&xe.length>0&&s.jsx(Mn,{snapshot:xe[xe.length-1]},xe[xe.length-1].promptId)]})});export{lr as SessionPanel};