pikiloom 0.4.54 → 0.4.55
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/dashboard/dist/assets/{AgentTab-DJfd-aVj.js → AgentTab-CvghrxSK.js} +1 -1
- package/dashboard/dist/assets/{ConnectionModal-DyybJ5vd.js → ConnectionModal-DHw2M8z4.js} +1 -1
- package/dashboard/dist/assets/{DirBrowser-BeFl4pxe.js → DirBrowser-hui6YZ3V.js} +1 -1
- package/dashboard/dist/assets/{ExtensionsTab-VUqUXTUU.js → ExtensionsTab-DAqHco47.js} +1 -1
- package/dashboard/dist/assets/{IMAccessTab-x5c4G5ck.js → IMAccessTab-BFKhRG8w.js} +1 -1
- package/dashboard/dist/assets/{Modal-CVov70Z-.js → Modal-oPg6drTY.js} +1 -1
- package/dashboard/dist/assets/{Modals-Cru4ScU4.js → Modals-DwMaDCJc.js} +1 -1
- package/dashboard/dist/assets/{Select-D39-GxSd.js → Select-Dlz8-JdF.js} +1 -1
- package/dashboard/dist/assets/SessionPanel-DPb40DDF.js +1 -0
- package/dashboard/dist/assets/{SystemTab-C1SNkUop.js → SystemTab-CzexSRMC.js} +1 -1
- package/dashboard/dist/assets/{index-89WIHUvA.js → index-BIX62DbC.js} +2 -2
- package/dashboard/dist/assets/{index-BTlkZmgz.js → index-CRE3Clam.js} +3 -3
- package/dashboard/dist/assets/{shared-CpIdR9QI.js → shared-BATHuwRO.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dist/agent/drivers/claude.js +11 -37
- package/dist/agent/index.js +1 -1
- package/dist/agent/kernel-bridge.js +6 -1
- package/dist/agent/utils.js +33 -0
- package/dist/bot/commands.js +4 -2
- package/package.json +1 -1
- package/packages/kernel/dist/drivers/claude.d.ts +3 -0
- package/packages/kernel/dist/drivers/claude.js +53 -4
- package/packages/kernel/dist/index.d.ts +1 -1
- package/packages/kernel/dist/index.js +1 -1
- package/dashboard/dist/assets/SessionPanel-CryozDR-.js +0 -1
|
@@ -59,6 +59,8 @@ export class ClaudeDriver {
|
|
|
59
59
|
// Dangling-tool-loop tracking: sawToolResult flips on the first tool_result; textSinceToolResult
|
|
60
60
|
// goes false at every tool_result and true again once the model streams visible text.
|
|
61
61
|
sawToolResult: false, textSinceToolResult: false,
|
|
62
|
+
// Wall-clock of the last parsed stream event — the hold cap defers while this is fresh.
|
|
63
|
+
lastEventAt: Date.now(),
|
|
62
64
|
sessionId: null, model: null,
|
|
63
65
|
stopReason: null, error: null,
|
|
64
66
|
input: null, output: null, cached: null,
|
|
@@ -138,13 +140,29 @@ export class ClaudeDriver {
|
|
|
138
140
|
ok: opts.ok ?? !state.error, text: state.text, reasoning: state.reasoning || undefined,
|
|
139
141
|
error: state.error, stopReason: opts.stopReason ?? state.stopReason, sessionId: state.sessionId, usage: usageOf(),
|
|
140
142
|
}, opts.kill ?? true);
|
|
141
|
-
//
|
|
142
|
-
// "still running in the background" so the
|
|
143
|
+
// Cap while holding for a still-running background task (stopReason marks it as
|
|
144
|
+
// "still running in the background" so the terminal presentation reads right). Idempotent —
|
|
145
|
+
// the countdown is absolute from the first arm. Sub-agent-backed holds use the longer
|
|
146
|
+
// agent cap, and a cap that fires while events are still flowing defers instead of
|
|
147
|
+
// cutting an actively-working turn mid-generation (the 2026-07-06 "停止不再继续生成":
|
|
148
|
+
// the 10-min cap yanked a live research turn 22s after its last tool_result and the
|
|
149
|
+
// graceful-close leak guard then killed the 4 still-running Explore agents).
|
|
143
150
|
const armHoldCap = () => {
|
|
144
151
|
if (holdCapTimer)
|
|
145
152
|
return;
|
|
146
|
-
|
|
147
|
-
|
|
153
|
+
const capMs = claudeTurnHasAgentBackground(state) ? claudeBgAgentHoldCapMs() : claudeBgHoldCapMs();
|
|
154
|
+
const fire = () => {
|
|
155
|
+
holdCapTimer = null;
|
|
156
|
+
if (settled)
|
|
157
|
+
return;
|
|
158
|
+
if (Date.now() - (state.lastEventAt ?? 0) < claudeBgSettleQuietMs()) {
|
|
159
|
+
holdCapTimer = setTimeout(fire, claudeBgHoldRecheckMs());
|
|
160
|
+
unref(holdCapTimer);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
settleResult({ stopReason: 'background', kill: false });
|
|
164
|
+
};
|
|
165
|
+
holdCapTimer = setTimeout(fire, capMs);
|
|
148
166
|
unref(holdCapTimer);
|
|
149
167
|
};
|
|
150
168
|
// Grace close once all background work is done: settle gracefully (no kill) if Claude stays
|
|
@@ -225,6 +243,7 @@ export class ClaudeDriver {
|
|
|
225
243
|
catch {
|
|
226
244
|
continue;
|
|
227
245
|
}
|
|
246
|
+
state.lastEventAt = Date.now();
|
|
228
247
|
handleClaudeEvent(ev, state, ctx.emit);
|
|
229
248
|
const pending = pendingClaudeBackgroundTasks(state);
|
|
230
249
|
// Any model output means the model is alive and streaming — cancel the post-tool stall watchdog.
|
|
@@ -689,6 +708,27 @@ export function claudeBgHoldCapMs() {
|
|
|
689
708
|
const raw = Number(process.env.PIKILOOM_CLAUDE_BG_HOLD_MS);
|
|
690
709
|
return Number.isFinite(raw) && raw > 0 ? raw : CLAUDE_BG_HOLD_CAP_DEFAULT_MS;
|
|
691
710
|
}
|
|
711
|
+
// Sub-agent-backed background work (Task/Agent tool launches) gets a much longer hold: these
|
|
712
|
+
// are finite model-driven jobs whose results the turn is genuinely waiting on — a research
|
|
713
|
+
// fleet mapping two repos legitimately runs past the 10-minute daemon cap, and capping it
|
|
714
|
+
// there discarded the agents' work and cut the turn mid-flight ("停止不再继续生成").
|
|
715
|
+
// Override with PIKILOOM_CLAUDE_BG_AGENT_HOLD_MS.
|
|
716
|
+
const CLAUDE_BG_AGENT_HOLD_CAP_DEFAULT_MS = 45 * 60_000;
|
|
717
|
+
export function claudeBgAgentHoldCapMs() {
|
|
718
|
+
const raw = Number(process.env.PIKILOOM_CLAUDE_BG_AGENT_HOLD_MS);
|
|
719
|
+
return Number.isFinite(raw) && raw > 0 ? raw : CLAUDE_BG_AGENT_HOLD_CAP_DEFAULT_MS;
|
|
720
|
+
}
|
|
721
|
+
export function claudeTurnHasAgentBackground(s) {
|
|
722
|
+
return !!s?.bgAgentTasks?.size;
|
|
723
|
+
}
|
|
724
|
+
// When the hold cap fires while events are still flowing, defer and re-check on this cadence
|
|
725
|
+
// instead of yanking an actively-working turn (the cap bounds SILENT stuck holds, nothing else).
|
|
726
|
+
// Override with PIKILOOM_CLAUDE_BG_HOLD_RECHECK_MS (tests need sub-second rechecks).
|
|
727
|
+
const CLAUDE_BG_HOLD_RECHECK_DEFAULT_MS = 30_000;
|
|
728
|
+
export function claudeBgHoldRecheckMs() {
|
|
729
|
+
const raw = Number(process.env.PIKILOOM_CLAUDE_BG_HOLD_RECHECK_MS);
|
|
730
|
+
return Number.isFinite(raw) && raw > 0 ? raw : CLAUDE_BG_HOLD_RECHECK_DEFAULT_MS;
|
|
731
|
+
}
|
|
692
732
|
// Once every KNOWN background task has finished, how long Claude must stay quiet (no further
|
|
693
733
|
// output at all) before we close a background turn. A completed task's status races AHEAD of the
|
|
694
734
|
// wake-up turn that reports it — with N parallel agents finishing together, the last agent's
|
|
@@ -743,6 +783,15 @@ export function trackClaudeBackgroundTask(ev, s) {
|
|
|
743
783
|
const subtype = ev?.subtype;
|
|
744
784
|
if (subtype !== 'task_started' && subtype !== 'task_updated' && subtype !== 'task_notification')
|
|
745
785
|
return;
|
|
786
|
+
// Sub-agent-backed background tasks (Task/Agent tool launches) are FINITE model-driven jobs,
|
|
787
|
+
// unlike a detached shell that may daemonize forever — they earn a much longer hold cap
|
|
788
|
+
// (see armHoldCap). The task_started's tool_use_id points at the launching tool call, which
|
|
789
|
+
// for sub-agents lives in s.subAgents.
|
|
790
|
+
if (subtype === 'task_started') {
|
|
791
|
+
const tui = String(ev?.tool_use_id ?? '').trim();
|
|
792
|
+
if (tui && s?.subAgents?.has?.(tui))
|
|
793
|
+
(s.bgAgentTasks ||= new Set()).add(String(ev?.task_id ?? ev?.id ?? tui));
|
|
794
|
+
}
|
|
746
795
|
const id = String(ev?.task_id ?? ev?.tool_use_id ?? '').trim();
|
|
747
796
|
if (!id)
|
|
748
797
|
return;
|
|
@@ -9,7 +9,7 @@ export type { SessionStore, CoreSessionRecord, ModelResolver, ModelInjection, To
|
|
|
9
9
|
export type { LoomIO, PromptInput, Surface, SurfaceCapabilities, Plugin, SpawnContribution, } from './contracts/surface.js';
|
|
10
10
|
export { FsSessionStore, NullModelResolver, NoopToolProvider, PassthroughSystemPromptBuilder, AutoCancelInteractionHandler, DeferToTerminalInteractionHandler, NoopCatalog, defaultBaseDir, } from './ports/defaults.js';
|
|
11
11
|
export { EchoDriver } from './drivers/echo.js';
|
|
12
|
-
export { ClaudeDriver, isTerminalTaskStatus, trackClaudeBackgroundTask, pendingClaudeBackgroundTasks, markClaudeTaskNotificationTerminal, decideClaudeResultSettle, claudeBgHoldCapMs, claudeBgSettleQuietMs, type ClaudeResultSettleDecision, claudeModelStallMs, claudeUserEventHasToolResult, handleClaudeEvent, claudeTurnEndedDangling, claudeTruncatedRecoveryEnabled, CLAUDE_TRUNCATED_RECOVERY_PROMPT, } from './drivers/claude.js';
|
|
12
|
+
export { ClaudeDriver, isTerminalTaskStatus, trackClaudeBackgroundTask, pendingClaudeBackgroundTasks, markClaudeTaskNotificationTerminal, decideClaudeResultSettle, claudeBgHoldCapMs, claudeBgAgentHoldCapMs, claudeTurnHasAgentBackground, claudeBgHoldRecheckMs, claudeBgSettleQuietMs, type ClaudeResultSettleDecision, claudeModelStallMs, claudeUserEventHasToolResult, handleClaudeEvent, claudeTurnEndedDangling, claudeTruncatedRecoveryEnabled, CLAUDE_TRUNCATED_RECOVERY_PROMPT, } from './drivers/claude.js';
|
|
13
13
|
export { CodexDriver } from './drivers/codex.js';
|
|
14
14
|
export { GeminiDriver } from './drivers/gemini.js';
|
|
15
15
|
export { AcpDriver, type AcpDriverConfig } from './drivers/acp.js';
|
|
@@ -19,7 +19,7 @@ export { attachTui } from './runtime/tui.js';
|
|
|
19
19
|
export { FsSessionStore, NullModelResolver, NoopToolProvider, PassthroughSystemPromptBuilder, AutoCancelInteractionHandler, DeferToTerminalInteractionHandler, NoopCatalog, defaultBaseDir, } from './ports/defaults.js';
|
|
20
20
|
// Drivers & surfaces (also available via subpath exports)
|
|
21
21
|
export { EchoDriver } from './drivers/echo.js';
|
|
22
|
-
export { ClaudeDriver, isTerminalTaskStatus, trackClaudeBackgroundTask, pendingClaudeBackgroundTasks, markClaudeTaskNotificationTerminal, decideClaudeResultSettle, claudeBgHoldCapMs, claudeBgSettleQuietMs, claudeModelStallMs, claudeUserEventHasToolResult, handleClaudeEvent, claudeTurnEndedDangling, claudeTruncatedRecoveryEnabled, CLAUDE_TRUNCATED_RECOVERY_PROMPT, } from './drivers/claude.js';
|
|
22
|
+
export { ClaudeDriver, isTerminalTaskStatus, trackClaudeBackgroundTask, pendingClaudeBackgroundTasks, markClaudeTaskNotificationTerminal, decideClaudeResultSettle, claudeBgHoldCapMs, claudeBgAgentHoldCapMs, claudeTurnHasAgentBackground, claudeBgHoldRecheckMs, claudeBgSettleQuietMs, claudeModelStallMs, claudeUserEventHasToolResult, handleClaudeEvent, claudeTurnEndedDangling, claudeTruncatedRecoveryEnabled, CLAUDE_TRUNCATED_RECOVERY_PROMPT, } from './drivers/claude.js';
|
|
23
23
|
export { CodexDriver } from './drivers/codex.js';
|
|
24
24
|
export { GeminiDriver } from './drivers/gemini.js';
|
|
25
25
|
export { AcpDriver } from './drivers/acp.js';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r,j as s}from"./react-vendor-C7Sl8SE7.js";import{c as Jt,I as Zt,S as Be,m as _e,a as I,u as ut,d as er,g as tr,l as rr,e as nr,j as sr,f as lr,s as or,Y as ar}from"./index-89WIHUvA.js";import{s as ur,l as cr,n as jt,m as ir,a as dr,o as fr,d as mr,b as gr,p as pr,c as De,e as hr,f as wt,g as Fe,h as ct,i as xr,T as kr,R as Sr,U as Nt,j as At,k as br,L as vr,I as Ir}from"./index-BTlkZmgz.js";import{M as Ot,a as Et}from"./Modal-CVov70Z-.js";import"./router-DHISdpPk.js";import"./Select-D39-GxSd.js";import"./DirBrowser-BeFl4pxe.js";import"./markdown-DxQYQFeH.js";import"./ExtensionsTab-VUqUXTUU.js";function yr({snapshot:o}){const[t,f]=r.useState(o.currentIndex??0),[w,y]=r.useState(""),[x,g]=r.useState(!1),[C,S]=r.useState(null);r.useEffect(()=>{f(o.currentIndex??0),y(""),S(null)},[o.promptId,o.currentIndex]);const O=o.questions||[],T=O[t]||null,J=O.length,q=!!(T?.options&&T.options.length),E=q?!!T?.allowFreeform:!0,ue=a=>{a&&(f(i=>i+1),y(""))},ye=async a=>{if(!x){g(!0),S(null);try{const i=await I.interactionSelectOption(o.promptId,a);if(!i.ok){S(i.error||"Failed to submit selection.");return}ue(i.advanced)}catch(i){S(i?.message||"Network error.")}finally{g(!1)}}},ce=async()=>{if(x)return;const a=w.trim();if(!a&&!T?.allowEmpty){S("Please enter a response.");return}g(!0),S(null);try{const i=await I.interactionSubmitText(o.promptId,a);if(!i.ok){S(i.error||"Failed to submit answer.");return}ue(i.advanced)}catch(i){S(i?.message||"Network error.")}finally{g(!1)}},p=async()=>{if(!x){g(!0),S(null);try{const a=await I.interactionSkip(o.promptId);if(!a.ok){S(a.error||"Failed to skip.");return}ue(a.advanced)}catch(a){S(a?.message||"Network error.")}finally{g(!1)}}},ie=async()=>{if(!x){g(!0);try{await I.interactionCancel(o.promptId)}catch{}}},de=r.useMemo(()=>{const a=[];return o.hint&&a.push(o.hint),J>1&&a.push(`Question ${t+1} of ${J}`),a.join(" · ")||void 0},[o.hint,t,J]);return s.jsxs(Ot,{open:!0,onClose:ie,wide:q&&(T?.options?.length||0)>3,children:[s.jsx(Et,{title:o.title||"Pikiloom needs your input",description:de,onClose:ie}),T?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:T.header||"Question"}),s.jsx("div",{className:"mt-1 whitespace-pre-wrap text-sm leading-relaxed text-fg",children:T.prompt})]}),q&&s.jsx("div",{className:"grid grid-cols-1 gap-2 sm:grid-cols-2",children:(T.options||[]).map(a=>s.jsxs("button",{type:"button",disabled:x,onClick:()=>ye(a.value||a.label),className:Jt("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:a.label}),a.description&&s.jsx("div",{className:"mt-0.5 text-xs leading-snug text-fg-4",children:a.description})]},a.value||a.label))}),E&&s.jsx("div",{children:s.jsx(Zt,{value:w,onChange:a=>y(a.target.value),onKeyDown:a=>{a.key==="Enter"&&!a.shiftKey&&!x&&(a.preventDefault(),ce())},placeholder:q?"Or type a custom answer…":"Type your answer…",disabled:x,autoFocus:!q})}),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(Be,{})," 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(_e,{variant:"ghost",size:"sm",onClick:p,disabled:x,children:"Skip"}),E&&s.jsx(_e,{variant:"primary",size:"sm",onClick:ce,disabled:x||!w.trim()&&!T.allowEmpty,children:"Submit"})]})]})]}):s.jsxs("div",{className:"py-6 text-center text-sm text-fg-5",children:[s.jsx(Be,{className:"mr-2 inline-block"})," Waiting for the agent…"]})]})}function Tr(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 Rr(o,t){return typeof t!="number"?o:t>o?t:o}function jr(o,t){return!o||!o.length?[]:t.size?o.filter(f=>!t.has(f)):o.slice()}function wr(o,t,f,w){if(!o.size)return o;const y=new Set(t);let x=!1;const g=new Map;for(const[C,S]of o){if(f-S>w||!y.has(C)){x=!0;continue}g.set(C,S)}return x?g:o}const it=12,Lt=160,Nr=96,Ar=[],Lr=[],Or=[],Er=20,Mr=6e4,Pr=15e3,Cr=7e3,ae=new Map;function qr(o,t){return`${o}:${t}`}function Ur(o,t){for(ae.delete(o),ae.set(o,t);ae.size>Er;)ae.delete(ae.keys().next().value)}const Yr=r.memo(function({session:t,workdir:f,active:w=!0,onSessionChange:y,initialPendingPrompt:x,initialPendingImageUrls:g,onPendingPromptConsumed:C}){const S=ut(e=>e.locale),O=ut(e=>e.agentStatus?.agents?.find(n=>n.agent===t.agent)??null),T=O?.selectedEffort??null,J=O?.selectedModel??null,q=ut(e=>e.modelLayer),E=t.profileId?q?.profiles.find(e=>e.id===t.profileId)??null:null,ue=E?q?.providers.find(e=>e.id===E.providerId)??null:null,ye=t.profileId===void 0?O?.byokProviderName??null:ue?.name??null,ce=t.profileId===void 0?O?.byokProfileName??null:E&&E.name.trim().toLowerCase()!==E.modelId.trim().toLowerCase()?E.name:null,p=r.useMemo(()=>er(S),[S]),ie=tr(t.agent||""),de=rr(t),a=!!x||!!(g&&g.length),[i,Qe]=r.useState(null),[$e,fe]=r.useState(!a),[me,dt]=r.useState(!1),[c,F]=r.useState(null),[B,ge]=r.useState(!1),[U,Ke]=r.useState(null),[Mt,ft]=r.useState(0),[Pt,ze]=r.useState(null),[H,Te]=r.useState([]),[Ct,Re]=r.useState([]),[pe,We]=r.useState([]),[h,Z]=r.useState(x||null),[M,ee]=r.useState(g||[]),[qt,te]=r.useState(null),_=r.useRef(null);_.current=qt;const mt=r.useRef(h);mt.current=h;const[je,D]=r.useState([]),we=r.useRef([]);we.current=je;const he=r.useRef(null),[Ut,gt]=r.useState(null),[Ne,xe]=r.useState(null),[ke,Ye]=r.useState(""),[Q,Ve]=r.useState(!1),Ht=!!O?.capabilities?.fork,Xe=r.useRef(null),L=r.useRef(g||[]),$=r.useRef(c),Ge=r.useRef(B);$.current=c,Ge.current=B;const Je=r.useRef(H);Je.current=H;const pt=r.useRef(U);pt.current=U;const K=r.useRef(null),Ae=r.useRef(null),z=r.useRef(!0),re=r.useRef(!1),Le=r.useRef(null),Ze=r.useRef(!1),P=r.useRef(a),ne=r.useRef(!1),W=r.useRef(!1),et=r.useRef(!1),tt=r.useRef(!1),Y=r.useRef(null),Oe=r.useRef(0),Se=r.useRef(Date.now()),be=r.useRef(new Map),ht=r.useRef({model:null,effort:null}),Dt=r.useCallback(e=>{ht.current=e},[]);r.useEffect(()=>{et.current||!a||(et.current=!0,x&&!h&&Z(x),g&&g.length&&!M.length&&(ee(g),L.current=g),fe(!1),ft(e=>e+1),C?.())},[a,C]);const N=r.useCallback(()=>{Z(null),ee(e=>{for(const n of e)URL.revokeObjectURL(n);return[]}),L.current=[],te(null)},[]),V=r.useCallback(()=>{D(e=>{if(!e.length)return e;for(const n of e)for(const l of n.imageUrls)URL.revokeObjectURL(l);return[]}),he.current=null},[]),xt=r.useCallback((e,n)=>{Se.current=Date.now();const l=ur({streaming:Ge.current,liveStreamPhase:$.current?.phase??null,streamPhase:pt.current,queuedTaskCount:Je.current.length,pendingQueuedCount:we.current.length}),u=n||[];if(l){const d=`local-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,8)}`;he.current=d,D(k=>[...k,{localId:d,taskId:null,prompt:e||"",imageUrls:u}]);return}$.current?.phase==="done"&&F(null);for(const d of L.current)URL.revokeObjectURL(d);he.current=null,Z(e||null),ee(u),L.current=u,te(null)},[]),Ft=r.useCallback(e=>{const n=he.current;if(n){he.current=null,D(l=>{const u=l.findIndex(k=>k.localId===n);if(u<0)return l;const d=l.slice();return d[u]={...d[u],taskId:e},d});return}te(e)},[]),Bt=r.useCallback(async()=>{if(!Ne)return;const e=ke.trim();if(e){Ve(!0);try{const n=await I.forkSession(f,t.agent||"",t.sessionId,Ne.atTurn,e,{});if(!n.ok||!n.sessionKey){Ve(!1);return}const[l,u]=n.sessionKey.split(":");xe(null),Ye(""),y?.({agent:l,sessionId:u,workdir:f})}finally{Ve(!1)}}},[Ne,ke,f,t.agent,t.sessionId,y]);Xe.current=Bt;const Ee=r.useCallback(async(e,n={})=>{try{const l=await cr({workdir:f,agent:t.agent||"",sessionId:t.sessionId,rich:!0,turnOffset:e.turnOffset,turnLimit:e.turnLimit,lastNTurns:e.lastNTurns},{force:n.force});return l.ok?jt(l):null}catch{return null}},[f,t.agent,t.sessionId]),R=r.useCallback(async({keepOlder:e,force:n=!1,scrollToBottom:l=!1})=>{const u=t.sessionId;if(Le.current===u)return!1;Le.current=u;try{const d=await Ee({turnOffset:0,turnLimit:it},{force:n});if(!d||t.sessionId!==u)return!1;if(l&&(re.current=!0),Qe(k=>!k||!e?d:ir(k,d)),fe(!1),ne.current&&(ne.current=!1,N()),W.current){const k=W.current;W.current=!1;const X=k!==!0?k.taskId:null;$.current&&(k===!0||$.current.taskId===X)&&F(null)}return!0}finally{Le.current===u&&(Le.current=null)}},[Ee,N,t.sessionId]),Me=r.useCallback(async()=>{if(!i?.hasOlder||Ze.current)return;const e=K.current;e&&(Ae.current={scrollHeight:e.scrollHeight,scrollTop:e.scrollTop}),Ze.current=!0,dt(!0);try{const n=await Ee({turnOffset:Math.max(0,i.totalTurns-i.startTurn),turnLimit:it});n?Qe(l=>l?dr(l,n):n):Ae.current=null}finally{Ze.current=!1,dt(!1)}},[Ee,i]),Pe=r.useRef(null),se=r.useCallback((e,n="ws")=>{const l=Ge.current||Je.current.length>0,u=Date.now()-Se.current>Pr;if(Tr({updatedAt:e?.updatedAt,isNull:!e,lastAppliedUpdatedAt:Oe.current,localStreamPending:P.current,holdsActiveState:l,holdExpired:u})!=="apply")return;const k=!e&&u&&(P.current||l);if(e&&(Se.current=Date.now(),Oe.current=Rr(Oe.current,e.updatedAt)),e?.sessionId&&e.sessionId!==t.sessionId&&(tt.current=!0,ve.current=`${t.agent}:${e.sessionId}`,y?.({agent:t.agent||"",sessionId:e.sessionId,workdir:f})),!e){const m=Pe.current;ge(!1),k?(P.current=!1,ne.current=!0,W.current=!0,V(),R({keepOlder:!0,force:!0,scrollToBottom:z.current})):m==="streaming"?(ne.current=!0,W.current=!0,R({keepOlder:!0,force:!0,scrollToBottom:z.current})):F(null),k||(P.current&&m!=="streaming"?R({keepOlder:!0,force:!0}):(m==="done"&&(N(),V()),m!==null&&(P.current=!1))),ze(null),Ke(null),Te([]),Re([]),We([]),Pe.current=null;return}Ke(e.phase),ze(e.taskId||null);const X=[];e.taskId&&X.push(e.taskId),Array.isArray(e.queuedTaskIds)&&X.push(...e.queuedTaskIds),be.current=wr(be.current,X,Date.now(),Mr);const le=be.current,ot=jr(e.queuedTaskIds,le),Rt=(Array.isArray(e.queuedTasks)?e.queuedTasks:[]).filter(m=>!le.has(m.taskId));if(Te(ot.length?ot:Ar),Re(Rt.length?Rt:Lr),We(Array.isArray(e.interactions)&&e.interactions.length?e.interactions:Or),fr({pendingTaskId:_.current,streamTaskId:e.taskId||null,queuedTaskIds:e.queuedTaskIds})){const m=_.current,j=mt.current||"",v=L.current;D(b=>b.some(oe=>oe.taskId===m)?b:[...b,{localId:`demote-${m}`,taskId:m,prompt:j,imageUrls:v}]),Z(null),ee([]),L.current=[],te(null),_.current=null}if(e.phase==="streaming"){if(F({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}),ge(!0),e.taskId&&e.taskId!==_.current){const m=we.current,j=m.findIndex(v=>v.taskId===e.taskId);if(j>=0){const v=m[j];for(const b of L.current)URL.revokeObjectURL(b);Z(v.prompt||null),ee(v.imageUrls),L.current=v.imageUrls,te(e.taskId),D(b=>b.filter((oe,G)=>G!==j))}}z.current&&(re.current=!0)}else if(e.phase==="queued")F(null),ge(!1);else if(e.phase==="done"){const m=mr($.current?.taskId??null,e.taskId||null),j=ot.length>0;if(m){ge(!1),F(G=>G?{...G,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}:G);const v=$.current,b=!!v&&gr(v),oe=!!e.incomplete&&b&&!j;if(Pe.current!=="done"){j||(ne.current=!0),W.current=oe?!1:{taskId:e.taskId||null},R({keepOlder:!0,force:!0,scrollToBottom:z.current});const G=t.agent||"",Vt=t.sessionId,Xt=ve.current;Y.current&&clearTimeout(Y.current),Y.current=setTimeout(()=>{Y.current=null,I.getSessionStreamState(G,Vt).then(Gt=>{ve.current===Xt&&Ce.current(Gt.state,"seed")}).catch(()=>{})},900)}}j||(P.current=!1)}const at=new Set;if(e.taskId&&at.add(e.taskId),Array.isArray(e.queuedTaskIds))for(const m of e.queuedTaskIds)at.add(m);D(m=>{let j=!1;const v=[];for(const b of m)if(!b.taskId||at.has(b.taskId))v.push(b);else{for(const oe of b.imageUrls)URL.revokeObjectURL(oe);j=!0}return j?v:m}),Pe.current=e.phase},[N,V,R,t.sessionId,t.agent,y,f]),Ce=r.useRef(se);Ce.current=se;const kt=r.useCallback(()=>{P.current=!0,Se.current=Date.now(),ft(e=>e+1)},[]);r.useEffect(()=>()=>{Y.current&&(clearTimeout(Y.current),Y.current=null)},[]);const _t=r.useCallback(async e=>{try{be.current.set(e,Date.now()),await I.recallSessionMessage(e),_.current===e&&N(),D(n=>{let l=!1;const u=[];for(const d of n)if(d.taskId===e){for(const k of d.imageUrls)URL.revokeObjectURL(k);l=!0}else u.push(d);return l?u:n}),Te(n=>n.filter(l=>l!==e)),Re(n=>n.filter(l=>l.taskId!==e)),ze(n=>n===e?null:n)}catch{}},[N]),Qt=r.useCallback(async e=>{const n=we.current.find(l=>l.taskId===e)||null;if(n&&n.imageUrls.length>0){for(const l of L.current)URL.revokeObjectURL(l);Z(n.prompt||null),ee(n.imageUrls),L.current=n.imageUrls,te(e),_.current=e,D(l=>l.filter(u=>u.taskId!==e))}try{await I.steerSession(e)}catch{}},[]),$t=r.useCallback(async()=>{try{await I.stopSession(t.agent||"",t.sessionId)}catch{}},[t.agent,t.sessionId]),qe=qr(t.agent||"",t.sessionId);r.useEffect(()=>{if(tt.current){tt.current=!1;let d=!1;return R({keepOlder:!0,force:!0}).finally(()=>{d||fe(!1)}),()=>{d=!0}}let e=!1;const n=pr({workdir:f,agent:t.agent||"",sessionId:t.sessionId,rich:!0,turnOffset:0,turnLimit:it},{allowStale:!0}),l=a&&!et.current,u=n?.ok?jt(n):ae.get(qe)||null;return fe(l?!1:!u),Qe(u),F(null),ge(!1),Ke(null),Te([]),Re([]),We([]),Oe.current=0,Se.current=Date.now(),be.current=new Map,l||(N(),V(),P.current=!1,ne.current=!1,W.current=!1),z.current=!0,re.current=!0,l||R({keepOlder:!1,force:!0}).finally(()=>{e||fe(!1)}),()=>{e=!0}},[R,t.agent,t.sessionId,f,qe,N,V]),r.useEffect(()=>{i&&i.turns.length>0&&Ur(qe,i)},[qe,i]),r.useEffect(()=>{w&&R({keepOlder:!0,force:!0})},[w,R]);const ve=r.useRef(`${t.agent}:${t.sessionId}`);ve.current=`${t.agent}:${t.sessionId}`,nr("stream-update",r.useCallback(e=>{e.key===ve.current&&se(e.snapshot??null)},[se])),r.useEffect(()=>{let e=!0;return I.getSessionStreamState(t.agent||"",t.sessionId).then(n=>{e&&Ce.current(n.state,"seed")}).catch(()=>{}),()=>{e=!1}},[t.agent,t.sessionId,Mt]),sr(r.useCallback(()=>{I.getSessionStreamState(t.agent||"",t.sessionId).then(e=>{se(e.state,"seed")}).catch(()=>{}),R({keepOlder:!0,force:!0})},[se,t.agent,t.sessionId,R]));const St=B||!!U||H.length>0||!!h||je.length>0;r.useEffect(()=>{if(!w||!St)return;const e=setInterval(()=>{typeof document<"u"&&document.visibilityState!=="visible"||I.getSessionStreamState(t.agent||"",t.sessionId).then(n=>Ce.current(n.state,"seed")).catch(()=>{})},Cr);return()=>clearInterval(e)},[w,St,t.agent,t.sessionId]),r.useEffect(()=>{!P.current&&de!=="running"&&!B&&!c&&!U&&H.length===0&&(N(),V())},[de,B,c,U,H.length,N,V]),r.useLayoutEffect(()=>{const e=Ae.current,n=K.current;!e||!n||(Ae.current=null,n.scrollTop=e.scrollTop+(n.scrollHeight-e.scrollHeight))},[i?.turns.length]),r.useLayoutEffect(()=>{if(!re.current)return;const e=K.current;e&&(re.current=!1,e.scrollTop=e.scrollHeight,requestAnimationFrame(()=>{z.current&&(e.scrollTop=e.scrollHeight)}))},[i,c]),r.useLayoutEffect(()=>{if(!h)return;const e=K.current;e&&(e.scrollTop=e.scrollHeight)},[h]),r.useEffect(()=>{if(!i?.hasOlder||$e||me)return;const e=K.current;e&&e.scrollHeight<=e.clientHeight+Lt&&Me()},[i?.hasOlder,i?.turns.length,Me,$e,me]);const Kt=r.useCallback(()=>{const e=K.current;if(!e)return;const n=e.scrollHeight-e.scrollTop-e.clientHeight;z.current=n<=Nr,e.scrollTop<=Lt&&Me()},[Me]),Ie=c?.model||t.model||J||null,rt=lr(t.agent||"",c?.effort||t.thinkingEffort||T||null,t.workflowEnabled??O?.workflowEnabled)||null,bt=ce&&(!Ie||Ie===J)?ce:Ie?or(Ie):null,nt=ar(t,{streaming:B,hasLiveStream:!!c,streamPhase:U,queuedTaskCount:H.length}),A=i?.turns||[],Ue=r.useMemo(()=>{if(!M.length||!A.length)return!1;const e=A[A.length-1];return!e.user||!De(e.user.text,h)?!1:e.user.blocks.filter(l=>l.type==="image").length<M.length},[A,h,M.length]),st=r.useMemo(()=>M.length?M.map(e=>({type:"image",content:e})):!h||!c?.questionBlocks?.length?[]:De(h,c.question)?c.questionBlocks:[],[M,h,c]),vt=c?.question||null,He=hr(h,vt),lt=wt(vt,h),It=A.length>0?A[A.length-1]?.user?.text:null,yt=!!He&&A.length>0&&Fe(It,He),Tt=r.useMemo(()=>{let e=A;if(Ue){const le=e[e.length-1];e=[...e.slice(0,-1),{...le,user:null}]}if(!c||!e.length)return e;const n=e[e.length-1],l=He;if(!n.assistant)return!!n.user&&!!l&&!De(n.user.text,l)&&(Fe(n.user.text,l)||wt(l,n.user.text))?[...e.slice(0,-1),{...n,user:{...n.user,text:l}}]:e;const u=(c.text||"").trim(),d=n.assistant.text?.trim()||"";if(!(l!=null?Fe(n.user?.text,l):!!d&&!!u&&(u.startsWith(d)||d.startsWith(u))))return e;const X=n.user&&l&&!De(n.user.text,l)?{...n.user,text:l}:n.user;return[...e.slice(0,-1),{...n,user:X,assistant:null}]},[A,c,He,Ue]),zt=(!!h||st.length>0)&&!lt&&(Ue||!yt)&&!c,Wt=!!c&&ct(c)&&c.phase==="streaming",Yt=xr({sessionRunning:de==="running",streaming:B,streamPhase:U,queuedTaskCount:H.length,pendingQueuedCount:je.length,liveTurnStreaming:Wt,pendingBubbleDots:zt});return s.jsxs("div",{className:"flex flex-col h-full overflow-hidden",children:[s.jsx("div",{ref:K,onScroll:Kt,className:"flex-1 overflow-y-auto overscroll-contain",children:$e&&!h&&!M.length&&!c?s.jsx("div",{className:"flex items-center justify-center py-20",children:s.jsx(Be,{className:"h-5 w-5 text-fg-4"})}):Tt.length===0&&!h&&!M.length&&!c&&!nt?s.jsx("div",{className:"py-20 text-center text-[13px] text-fg-5",children:p("hub.noMessages")}):s.jsxs("div",{className:"max-w-[900px] mx-auto px-6 py-6 space-y-0",children:[(i?.hasOlder||me)&&s.jsxs("div",{className:"mb-4 flex items-center justify-center gap-2 text-[11px] text-fg-5",children:[me?s.jsx(Be,{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:p(me?"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:p("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:["· ",p("hub.forkBadgeAt").replace("{turn}",String(t.migratedFrom.forkedAtTurn+1))]})]}),Tt.map((e,n)=>{const l=(i?.startTurn||0)+n;return s.jsx(kr,{turn:e,turnIndex:l,agent:t.agent||"",meta:ie,model:bt,effort:rt,providerName:ye,t:p,workdir:f,onResend:u=>{re.current=!0,xt(u);const d=ht.current;I.sendSessionMessage(f,t.agent||"",t.sessionId,u,{model:d.model||Ie||void 0,effort:d.effort||rt||void 0}).then(k=>{k.ok&&kt()}).catch(()=>{N()})},onEdit:u=>gt(u),onFork:Ht?u=>{Ye(""),xe({atTurn:u})}:void 0},`${i?.startTurn||0}:${n}`)}),nt&&s.jsx("div",{className:"mb-5 animate-in",children:s.jsx(Sr,{detail:nt,t:p})}),(h||st.length>0)&&!lt&&(Ue||!yt)&&s.jsxs("div",{className:"session-turn",children:[s.jsx(Nt,{text:h||"",blocks:st,t:p}),!c&&s.jsx("div",{className:"mt-3 mb-5 animate-in",children:s.jsx(At,{className:"text-fg-5"})})]}),c&&ct(c)&&c.question&&(!h||lt)&&!(A.length>0&&Fe(It,c.question))&&s.jsx("div",{className:"session-turn",children:s.jsx(Nt,{text:c.question,blocks:c.questionBlocks||void 0,t:p})}),c&&ct(c)&&s.jsxs("div",{className:"mb-6",children:[s.jsx(br,{agent:t.agent||"",meta:ie,model:bt,effort:rt,providerName:ye,previewMeta:c.previewMeta,hideContextUsage:!0}),s.jsx(vr,{stream:c,t:p,workdir:f})]}),Yt&&s.jsx("div",{className:"mt-3 mb-5 animate-in",children:s.jsx(At,{className:"text-fg-5"})}),s.jsx("div",{className:"h-4"})]})}),s.jsx(Ir,{session:t,workdir:f,onStreamQueued:kt,onSendStart:xt,onSendTaskAssigned:Ft,onSessionChange:y,t:p,streamPhase:U,streamTaskId:Pt,queuedTaskIds:H,queuedTasks:Ct,pendingQueuedSends:je,onRecall:_t,onSteer:Qt,onStopAll:$t,editDraft:Ut,onEditDraftConsumed:()=>gt(null),onSelectionChange:Dt}),Ne&&s.jsxs(Ot,{open:!0,onClose:()=>{Q||xe(null)},children:[s.jsx(Et,{title:p("hub.forkPromptTitle"),description:p("hub.forkPromptHint"),onClose:()=>{Q||xe(null)}}),s.jsx("textarea",{autoFocus:!0,value:ke,disabled:Q,onChange:e=>Ye(e.target.value),onKeyDown:e=>{e.key==="Enter"&&(e.metaKey||e.ctrlKey)&&ke.trim()&&!Q&&(e.preventDefault(),Xe.current?.())},placeholder:p("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(_e,{variant:"ghost",disabled:Q,onClick:()=>xe(null),children:p("modal.cancel")}),s.jsx(_e,{variant:"primary",disabled:Q||!ke.trim(),onClick:()=>{Xe.current?.()},children:p(Q?"hub.forkSubmitting":"hub.forkSubmit")})]})]}),w&&pe.length>0&&s.jsx(yr,{snapshot:pe[pe.length-1]},pe[pe.length-1].promptId)]})});export{Yr as SessionPanel};
|