pikiloom 0.4.56 → 0.4.58

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 (24) hide show
  1. package/dashboard/dist/assets/{AgentTab-BBrcBA3Q.js → AgentTab-CLlOp3lJ.js} +1 -1
  2. package/dashboard/dist/assets/{ConnectionModal-B8p6NzY7.js → ConnectionModal-BJhiouLS.js} +1 -1
  3. package/dashboard/dist/assets/{DirBrowser-BW7l1k3h.js → DirBrowser-H0N2gMRB.js} +1 -1
  4. package/dashboard/dist/assets/{ExtensionsTab-CTXhgsep.js → ExtensionsTab-5uaiMRcm.js} +1 -1
  5. package/dashboard/dist/assets/{IMAccessTab-BAymEU1l.js → IMAccessTab-ClNbxnzS.js} +1 -1
  6. package/dashboard/dist/assets/{Modal-CaunzS8l.js → Modal-Cy1zHcC7.js} +1 -1
  7. package/dashboard/dist/assets/{Modals-Co5yJFIK.js → Modals-BrLADO-I.js} +1 -1
  8. package/dashboard/dist/assets/{Select-Bur7mVMe.js → Select-D4DbOixc.js} +1 -1
  9. package/dashboard/dist/assets/SessionPanel-1hemRLg_.js +1 -0
  10. package/dashboard/dist/assets/{SystemTab-D1haU2ST.js → SystemTab-CDrID55_.js} +1 -1
  11. package/dashboard/dist/assets/index-BO-FYAny.css +1 -0
  12. package/dashboard/dist/assets/{index-BVBpUn_6.js → index-Iot9Vn3K.js} +3 -3
  13. package/dashboard/dist/assets/{index-C3fUMz3j.js → index-QFqtuRrZ.js} +17 -17
  14. package/dashboard/dist/assets/{shared-DwuSoSl7.js → shared-DQyw9U1Z.js} +1 -1
  15. package/dashboard/dist/index.html +2 -2
  16. package/dist/agent/drivers/claude.js +110 -17
  17. package/dist/agent/index.js +1 -1
  18. package/dist/agent/utils.js +33 -0
  19. package/dist/dashboard/routes/sessions.js +16 -1
  20. package/package.json +1 -1
  21. package/packages/kernel/dist/drivers/claude.d.ts +1 -0
  22. package/packages/kernel/dist/drivers/claude.js +56 -7
  23. package/dashboard/dist/assets/SessionPanel-lPdXcjdM.js +0 -1
  24. package/dashboard/dist/assets/index-N_mAxxEJ.css +0 -1
@@ -1 +1 @@
1
- import{j as s}from"./react-vendor-C7Sl8SE7.js";import{O as t,m as i,S as a}from"./index-BVBpUn_6.js";function o({primary:e,secondary:l,tertiary:n}){return s.jsxs("div",{className:"flex flex-col gap-2 pt-1 sm:flex-row sm:flex-wrap sm:items-center sm:justify-between",children:[s.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e&&s.jsxs(i,{tone:"primary",onClick:e.onClick,disabled:e.disabled,children:[e.loading&&s.jsx(a,{}),e.label]}),l&&s.jsx(i,{tone:"secondary",onClick:l.onClick,disabled:l.disabled,children:l.label})]}),n&&s.jsx("div",{className:"text-xs leading-relaxed text-fg-4",children:n})]})}function c({children:e,className:l}){return s.jsx(t,{padding:"md",elevation:"flat",className:l,children:e})}export{o as A,c as S};
1
+ import{j as s}from"./react-vendor-C7Sl8SE7.js";import{O as t,m as i,S as a}from"./index-Iot9Vn3K.js";function o({primary:e,secondary:l,tertiary:n}){return s.jsxs("div",{className:"flex flex-col gap-2 pt-1 sm:flex-row sm:flex-wrap sm:items-center sm:justify-between",children:[s.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e&&s.jsxs(i,{tone:"primary",onClick:e.onClick,disabled:e.disabled,children:[e.loading&&s.jsx(a,{}),e.label]}),l&&s.jsx(i,{tone:"secondary",onClick:l.onClick,disabled:l.disabled,children:l.label})]}),n&&s.jsx("div",{className:"text-xs leading-relaxed text-fg-4",children:n})]})}function c({children:e,className:l}){return s.jsx(t,{padding:"md",elevation:"flat",className:l,children:e})}export{o as A,c as S};
@@ -6,10 +6,10 @@
6
6
  <link rel="icon" type="image/png" href="/logo.png">
7
7
  <title>Pikiloom</title>
8
8
  <link href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500&display=swap" rel="stylesheet">
9
- <script type="module" crossorigin src="/assets/index-BVBpUn_6.js"></script>
9
+ <script type="module" crossorigin src="/assets/index-Iot9Vn3K.js"></script>
10
10
  <link rel="modulepreload" crossorigin href="/assets/react-vendor-C7Sl8SE7.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/router-DHISdpPk.js">
12
- <link rel="stylesheet" crossorigin href="/assets/index-N_mAxxEJ.css">
12
+ <link rel="stylesheet" crossorigin href="/assets/index-BO-FYAny.css">
13
13
  </head>
14
14
  <body>
15
15
  <div id="root"></div>
@@ -3,7 +3,7 @@ import path from 'node:path';
3
3
  import { execSync, spawn } from 'node:child_process';
4
4
  import { createInterface } from 'node:readline';
5
5
  import { registerDriver } from '../driver.js';
6
- import { Q, run, agentError, agentLog, agentWarn, buildStreamPreviewMeta, computeContext, pushRecentActivity, summarizeClaudeToolUse, summarizeClaudeToolResult, joinErrorMessages, parseTodoWriteAsPlan, previewToolCallInput, previewToolCallResult, detectClaudeApiError, isRetryableClaudeApiError, detectClaudeModelError, claudeModelErrorMessage, emitSessionIdUpdate, IMAGE_EXTS, mimeForExt, listPikiloomSessions, mergeManagedAndNativeSessions, managedRecordToSessionInfo, readTailLines, stripInjectedPrompts, sanitizeSessionUserPreviewText, isSystemInjectedUserText, SESSION_PREVIEW_IMAGE_PLACEHOLDER_RE, CLAUDE_AT_MENTION_IMAGE_RE, extractClaudeAtMentionImagePaths, attachAgentImage, applyTurnWindow, shortValue, roundPercent, toIsoFromEpochSeconds, modelFamily, normalizeClaudeModelId, emptyUsage, normalizeUsageStatus, collapseSkillPrompt, } from '../index.js';
6
+ import { Q, run, agentError, agentLog, agentWarn, buildStreamPreviewMeta, computeContext, pushRecentActivity, summarizeClaudeToolUse, summarizeClaudeToolResult, joinErrorMessages, parseTodoWriteAsPlan, applyClaudeTaskUpdateToPlan, previewToolCallInput, previewToolCallResult, detectClaudeApiError, isRetryableClaudeApiError, detectClaudeModelError, claudeModelErrorMessage, emitSessionIdUpdate, IMAGE_EXTS, mimeForExt, listPikiloomSessions, mergeManagedAndNativeSessions, managedRecordToSessionInfo, readTailLines, stripInjectedPrompts, sanitizeSessionUserPreviewText, isSystemInjectedUserText, SESSION_PREVIEW_IMAGE_PLACEHOLDER_RE, CLAUDE_AT_MENTION_IMAGE_RE, extractClaudeAtMentionImagePaths, attachAgentImage, applyTurnWindow, shortValue, roundPercent, toIsoFromEpochSeconds, modelFamily, normalizeClaudeModelId, emptyUsage, normalizeUsageStatus, collapseSkillPrompt, } from '../index.js';
7
7
  import { AGENT_STREAM_HARD_KILL_GRACE_MS, AGENT_GRACEFUL_ABORT_GRACE_MS, SESSION_RUNNING_THRESHOLD_MS } from '../../core/constants.js';
8
8
  import { terminateProcessTree } from '../../core/process-control.js';
9
9
  import { getHome, IS_MAC, encodePathAsDirName } from '../../core/platform.js';
@@ -517,10 +517,13 @@ export function claudeParse(ev, s) {
517
517
  const toolName = String(block?.name || 'Tool').trim() || 'Tool';
518
518
  if (toolName === 'TodoWrite') {
519
519
  const plan = parseTodoWriteAsPlan(block?.input);
520
- if (plan)
520
+ if (plan) {
521
521
  s.plan = plan;
522
+ s.claudeTodoPlan = plan;
523
+ }
522
524
  s.seenClaudeToolIds.add(toolId);
523
525
  s.claudeToolsById.set(toolId, { name: toolName, summary: 'Update plan' });
526
+ pushRecentActivity(s.recentActivity, 'Update plan');
524
527
  continue;
525
528
  }
526
529
  if (toolName === 'TaskCreate') {
@@ -528,13 +531,18 @@ export function claudeParse(ev, s) {
528
531
  if (subject)
529
532
  s.pendingClaudeTaskCreates.set(toolId, { subject });
530
533
  s.seenClaudeToolIds.add(toolId);
531
- s.claudeToolsById.set(toolId, { name: toolName, summary: subject ? `Create task: ${subject}` : 'Create task' });
534
+ const createSummary = subject ? `Create task: ${subject}` : 'Create task';
535
+ s.claudeToolsById.set(toolId, { name: toolName, summary: createSummary });
536
+ pushRecentActivity(s.recentActivity, createSummary);
532
537
  continue;
533
538
  }
534
539
  if (toolName === 'TaskUpdate') {
535
540
  const taskId = String(block?.input?.taskId ?? '').trim();
536
541
  const rawStatus = String(block?.input?.status ?? '').trim().toLowerCase();
537
- if (taskId) {
542
+ // Chronological: the plan reflects the state AFTER this update. An id known to the
543
+ // TaskCreate store lands there; otherwise it lands positionally on the latest
544
+ // TodoWrite list (ids are 1-based positions when the todo panel owns the list).
545
+ if (taskId && s.claudeTaskList.has(taskId)) {
538
546
  if (rawStatus === 'deleted') {
539
547
  s.claudeTaskList.delete(taskId);
540
548
  s.claudeTaskOrder = s.claudeTaskOrder.filter((id) => id !== taskId);
@@ -546,8 +554,17 @@ export function claudeParse(ev, s) {
546
554
  }
547
555
  rebuildClaudePlanFromTasks(s);
548
556
  }
557
+ else if (taskId) {
558
+ const updated = applyClaudeTaskUpdateToPlan(s.claudeTodoPlan, taskId, rawStatus);
559
+ if (updated) {
560
+ s.plan = updated;
561
+ s.claudeTodoPlan = updated;
562
+ }
563
+ }
549
564
  s.seenClaudeToolIds.add(toolId);
550
- s.claudeToolsById.set(toolId, { name: toolName, summary: `Update task ${taskId || '?'} → ${rawStatus || 'unknown'}` });
565
+ const updateSummary = `Update task ${taskId || '?'} → ${rawStatus || 'unknown'}`;
566
+ s.claudeToolsById.set(toolId, { name: toolName, summary: updateSummary });
567
+ pushRecentActivity(s.recentActivity, updateSummary);
551
568
  continue;
552
569
  }
553
570
  if (toolName === 'Task' || toolName === 'Agent') {
@@ -722,6 +739,7 @@ export function createClaudeStreamState(opts) {
722
739
  activity: '',
723
740
  recentActivity: [],
724
741
  plan: null,
742
+ claudeTodoPlan: null,
725
743
  claudeTaskList: new Map(),
726
744
  claudeTaskOrder: [],
727
745
  pendingClaudeTaskCreates: new Map(),
@@ -1376,7 +1394,26 @@ function claudeImageBlockFromEntry(entry) {
1376
1394
  const mime = (source.media_type || 'image/png').toLowerCase();
1377
1395
  return { type: 'image', content: `data:${mime};base64,${source.data}`, imageMime: mime };
1378
1396
  }
1379
- function extractClaudeBlocks(content, skipSystemBlocks = false, todoWriteToolIds, toolNamesByUseId) {
1397
+ function createClaudeHistoryTaskCtx() {
1398
+ return { planToolIds: new Set(), pendingTaskCreates: new Map(), taskList: new Map(), taskOrder: [], todoPlan: null };
1399
+ }
1400
+ function claudeTaskPlanFromCtx(ctx) {
1401
+ if (!ctx.taskOrder.length)
1402
+ return null;
1403
+ const steps = [];
1404
+ for (const id of ctx.taskOrder) {
1405
+ const task = ctx.taskList.get(id);
1406
+ if (!task)
1407
+ continue;
1408
+ const lowered = String(task.status || '').toLowerCase();
1409
+ const status = lowered === 'completed' ? 'completed'
1410
+ : lowered === 'in_progress' || lowered === 'inprogress' ? 'inProgress'
1411
+ : 'pending';
1412
+ steps.push({ step: task.subject, status });
1413
+ }
1414
+ return steps.length ? { explanation: null, steps } : null;
1415
+ }
1416
+ function extractClaudeBlocks(content, skipSystemBlocks = false, taskCtx, toolNamesByUseId) {
1380
1417
  if (typeof content === 'string')
1381
1418
  return [{ type: 'text', content }];
1382
1419
  if (!Array.isArray(content))
@@ -1394,15 +1431,55 @@ function extractClaudeBlocks(content, skipSystemBlocks = false, todoWriteToolIds
1394
1431
  blocks.push({ type: 'thinking', content: block.thinking });
1395
1432
  }
1396
1433
  else if (block.type === 'tool_use') {
1397
- if (block.name === 'TodoWrite' && todoWriteToolIds) {
1398
- const plan = parseTodoWriteAsPlan(block.input);
1399
- if (plan) {
1400
- todoWriteToolIds.add(block.id);
1401
- blocks.push({ type: 'plan', content: '', plan, toolId: block.id });
1402
- continue;
1434
+ const inputStr = block.input ? JSON.stringify(block.input, null, 2) : '';
1435
+ if (taskCtx && (block.name === 'TodoWrite' || block.name === 'TaskCreate' || block.name === 'TaskUpdate')) {
1436
+ // The command itself stays visible as an Activity row (matching the CLI's own
1437
+ // transcript); the structured task state additionally surfaces as a plan block.
1438
+ taskCtx.planToolIds.add(block.id);
1439
+ blocks.push({ type: 'tool_use', content: inputStr, toolName: block.name, toolId: block.id });
1440
+ if (block.name === 'TodoWrite') {
1441
+ const plan = parseTodoWriteAsPlan(block.input);
1442
+ if (plan) {
1443
+ taskCtx.todoPlan = plan;
1444
+ blocks.push({ type: 'plan', content: '', plan, toolId: block.id });
1445
+ }
1446
+ }
1447
+ else if (block.name === 'TaskCreate') {
1448
+ const subject = typeof block.input?.subject === 'string' ? block.input.subject.trim() : '';
1449
+ if (subject)
1450
+ taskCtx.pendingTaskCreates.set(block.id, { subject });
1451
+ }
1452
+ else {
1453
+ const taskId = String(block.input?.taskId ?? '').trim();
1454
+ const rawStatus = String(block.input?.status ?? '').trim().toLowerCase();
1455
+ if (taskId && taskCtx.taskList.has(taskId)) {
1456
+ if (rawStatus === 'deleted') {
1457
+ taskCtx.taskList.delete(taskId);
1458
+ const idx = taskCtx.taskOrder.indexOf(taskId);
1459
+ if (idx >= 0)
1460
+ taskCtx.taskOrder.splice(idx, 1);
1461
+ }
1462
+ else if (rawStatus) {
1463
+ const existing = taskCtx.taskList.get(taskId);
1464
+ if (existing)
1465
+ existing.status = rawStatus;
1466
+ }
1467
+ const plan = claudeTaskPlanFromCtx(taskCtx);
1468
+ if (plan)
1469
+ blocks.push({ type: 'plan', content: '', plan, toolId: block.id });
1470
+ }
1471
+ else if (taskId) {
1472
+ // Unknown id in the TaskCreate store: land the update positionally on the latest
1473
+ // TodoWrite list so the plan card reflects the state AFTER this update.
1474
+ const updated = applyClaudeTaskUpdateToPlan(taskCtx.todoPlan, taskId, rawStatus);
1475
+ if (updated) {
1476
+ taskCtx.todoPlan = updated;
1477
+ blocks.push({ type: 'plan', content: '', plan: updated, toolId: block.id });
1478
+ }
1479
+ }
1403
1480
  }
1481
+ continue;
1404
1482
  }
1405
- const inputStr = block.input ? JSON.stringify(block.input, null, 2) : '';
1406
1483
  blocks.push({ type: 'tool_use', content: inputStr, toolName: block.name || 'unknown', toolId: block.id });
1407
1484
  }
1408
1485
  else if (block.type === 'tool_result') {
@@ -1455,7 +1532,7 @@ function getClaudeSessionMessages(opts) {
1455
1532
  let pendingBlocks = [];
1456
1533
  let pendingUsage = null;
1457
1534
  let pendingCallOutputs = new Map();
1458
- const todoWriteToolIds = new Set();
1535
+ const taskCtx = createClaudeHistoryTaskCtx();
1459
1536
  const subAgentBlocksById = new Map();
1460
1537
  const subAgentToolIds = new Set();
1461
1538
  const toolNamesByUseId = new Map();
@@ -1518,7 +1595,7 @@ function getClaudeSessionMessages(opts) {
1518
1595
  if (ev.isMeta === true) {
1519
1596
  if (pendingRole === 'assistant') {
1520
1597
  const toolUseId = typeof ev.sourceToolUseID === 'string' ? ev.sourceToolUseID : '';
1521
- if (toolUseId && !todoWriteToolIds.has(toolUseId) && !subAgentToolIds.has(toolUseId)) {
1598
+ if (toolUseId && !taskCtx.planToolIds.has(toolUseId) && !subAgentToolIds.has(toolUseId)) {
1522
1599
  const text = extractClaudeText(ev.message?.content, false);
1523
1600
  if (text) {
1524
1601
  pendingBlocks.push({ type: 'tool_result', content: text, toolId: toolUseId });
@@ -1535,7 +1612,23 @@ function getClaudeSessionMessages(opts) {
1535
1612
  if (pendingRole === 'assistant') {
1536
1613
  for (const block of contentArr) {
1537
1614
  const toolUseId = block.tool_use_id;
1538
- if (todoWriteToolIds.has(toolUseId))
1615
+ // TaskCreate result: the assigned task id arrives here — register the task
1616
+ // and surface the refreshed list as a plan block on this turn.
1617
+ const pendingCreate = toolUseId ? taskCtx.pendingTaskCreates.get(toolUseId) : undefined;
1618
+ if (pendingCreate) {
1619
+ taskCtx.pendingTaskCreates.delete(toolUseId);
1620
+ const assignedId = readClaudeTaskCreateId(ev, block);
1621
+ if (assignedId) {
1622
+ if (!taskCtx.taskList.has(assignedId))
1623
+ taskCtx.taskOrder.push(assignedId);
1624
+ taskCtx.taskList.set(assignedId, { subject: pendingCreate.subject, status: 'pending' });
1625
+ const plan = claudeTaskPlanFromCtx(taskCtx);
1626
+ if (plan)
1627
+ pendingBlocks.push({ type: 'plan', content: '', plan, toolId: toolUseId });
1628
+ }
1629
+ continue;
1630
+ }
1631
+ if (taskCtx.planToolIds.has(toolUseId))
1539
1632
  continue;
1540
1633
  if (subAgentToolIds.has(toolUseId))
1541
1634
  continue;
@@ -1654,7 +1747,7 @@ function getClaudeSessionMessages(opts) {
1654
1747
  toolNamesByUseId.set(inner.id, inner.name);
1655
1748
  }
1656
1749
  }
1657
- const blocks = extractClaudeBlocks(ev.message?.content, true, todoWriteToolIds, toolNamesByUseId);
1750
+ const blocks = extractClaudeBlocks(ev.message?.content, true, taskCtx, toolNamesByUseId);
1658
1751
  const contents = Array.isArray(ev.message?.content) ? ev.message.content : [];
1659
1752
  for (let i = 0; i < blocks.length; i++) {
1660
1753
  const block = blocks[i];
@@ -5,7 +5,7 @@ import './drivers/hermes.js';
5
5
  export { IMAGE_EXTS } from './types.js';
6
6
  export { attachAgentImage, attachInlineImage, materializeImage, rewriteAttachmentBlocksForTransport, attachmentUrl, resolveAllowedAttachmentPath, allowedAttachmentRoots, decodeAttachmentPathParam, sessionAttachmentsDir, codexHome, } from './images.js';
7
7
  export { deliverArtifact, readDeliveredArtifacts, deliveredArtifactBlocks, tailDeliveredBlocks, latestDeliveredTaskId, mimeForArtifact, } from './artifacts.js';
8
- export { Q, agentLog, agentWarn, agentError, dedupeStrings, numberOrNull, normalizeStreamPreviewPlan, parseTodoWriteAsPlan, normalizeActivityLine, pushRecentActivity, detectClaudeApiError, isRetryableClaudeApiError, detectClaudeModelError, claudeModelErrorMessage, firstNonEmptyLine, shortValue, normalizeErrorMessage, joinErrorMessages, appendSystemPrompt, mimeForExt, computeContext, buildStreamPreviewMeta, summarizeClaudeToolUse, summarizeClaudeToolResult, previewToolCallInput, previewToolCallResult, roundPercent, toIsoFromEpochSeconds, normalizeUsageStatus, labelFromWindowMinutes, usageWindowFromRateLimit, parseJsonTail, modelFamily, normalizeClaudeModelId, emptyUsage, readTailLines, stripInjectedPrompts, sanitizeSessionUserPreviewText, isSystemInjectedUserText, SESSION_PREVIEW_IMAGE_PLACEHOLDER_RE, CLAUDE_AT_MENTION_IMAGE_RE, extractClaudeAtMentionImagePaths, stripClaudeAtMentionImages, isPendingSessionId, emitSessionIdUpdate, sessionListDisplayTitle, } from './utils.js';
8
+ export { Q, agentLog, agentWarn, agentError, dedupeStrings, numberOrNull, normalizeStreamPreviewPlan, parseTodoWriteAsPlan, applyClaudeTaskUpdateToPlan, normalizeActivityLine, pushRecentActivity, detectClaudeApiError, isRetryableClaudeApiError, detectClaudeModelError, claudeModelErrorMessage, firstNonEmptyLine, shortValue, normalizeErrorMessage, joinErrorMessages, appendSystemPrompt, mimeForExt, computeContext, buildStreamPreviewMeta, summarizeClaudeToolUse, summarizeClaudeToolResult, previewToolCallInput, previewToolCallResult, roundPercent, toIsoFromEpochSeconds, normalizeUsageStatus, labelFromWindowMinutes, usageWindowFromRateLimit, parseJsonTail, modelFamily, normalizeClaudeModelId, emptyUsage, readTailLines, stripInjectedPrompts, sanitizeSessionUserPreviewText, isSystemInjectedUserText, SESSION_PREVIEW_IMAGE_PLACEHOLDER_RE, CLAUDE_AT_MENTION_IMAGE_RE, extractClaudeAtMentionImagePaths, stripClaudeAtMentionImages, isPendingSessionId, emitSessionIdUpdate, sessionListDisplayTitle, } from './utils.js';
9
9
  export { updateSessionMeta, promoteSessionId, recordFork, resolveCanonicalSessionId, getSessionPromotions, listPikiloomSessions, findPikiloomSession, getSessionStoredConfig, ensureManagedSession, findManagedThreadSession, stageSessionFiles, mergeManagedAndNativeSessions, managedRecordToSessionInfo, getSessions, getSessionTail, getSessionMessages, applyTurnWindow, applyTurnFilter, classifySession, deriveUserStatus, exportSession, importSession, deleteAgentSession, isProcessAlive, isRunningSessionStale, reconcileOrphanedRunningSessions, } from './session.js';
10
10
  export { detectAgentBin, listAgents, resolveDefaultAgent, run, doStream, recoverProfileIdForModel, listModels, resolveAgentModels, dropNativeShadowedByProfiles, getUsage, getAgentBoundModelId, setAgentBoundModelId, } from './stream.js';
11
11
  export { registerDriver, getDriver, getDriverCapabilities, allDrivers, allDriverIds, hasDriver, shutdownAllDrivers, } from './driver.js';
@@ -89,6 +89,30 @@ export function parseTodoWriteAsPlan(input) {
89
89
  return null;
90
90
  return { explanation: null, steps };
91
91
  }
92
+ // Apply a TaskUpdate to a TodoWrite-produced plan positionally (taskId = 1-based item index).
93
+ // Used when the id isn't in the TaskCreate store, so an update issued AFTER a TodoWrite still
94
+ // lands on the displayed list — the plan card always reflects the state after the LAST change,
95
+ // whichever mechanism wrote it. Returns a fresh plan (never mutates) or null when inapplicable.
96
+ export function applyClaudeTaskUpdateToPlan(plan, taskId, rawStatus) {
97
+ if (!plan || !Array.isArray(plan.steps) || !plan.steps.length)
98
+ return null;
99
+ if (!/^\d+$/.test(taskId))
100
+ return null;
101
+ const idx = Number(taskId) - 1;
102
+ if (idx < 0 || idx >= plan.steps.length)
103
+ return null;
104
+ if (rawStatus === 'deleted') {
105
+ const steps = plan.steps.filter((_, i) => i !== idx);
106
+ return steps.length ? { explanation: plan.explanation ?? null, steps } : null;
107
+ }
108
+ const status = rawStatus === 'completed' ? 'completed'
109
+ : rawStatus === 'in_progress' || rawStatus === 'inprogress' ? 'inProgress'
110
+ : rawStatus === 'pending' ? 'pending' : null;
111
+ if (!status)
112
+ return null;
113
+ const steps = plan.steps.map((step, i) => i === idx ? { ...step, status } : step);
114
+ return { explanation: plan.explanation ?? null, steps };
115
+ }
92
116
  export function normalizeActivityLine(text) { return text.replace(/\s+/g, ' ').trim(); }
93
117
  export function pushRecentActivity(lines, line, maxLines = 80) {
94
118
  const cleaned = normalizeActivityLine(line);
@@ -339,6 +363,15 @@ export function summarizeClaudeToolUse(name, input) {
339
363
  return q ? `Search web: ${q}` : 'Search web';
340
364
  }
341
365
  case 'TodoWrite': return 'Update plan';
366
+ case 'TaskCreate': {
367
+ const subject = shortValue(input?.subject, 120);
368
+ return subject ? `Create task: ${subject}` : 'Create task';
369
+ }
370
+ case 'TaskUpdate': {
371
+ const taskId = shortValue(input?.taskId, 24);
372
+ const status = shortValue(input?.status, 24);
373
+ return taskId ? `Update task ${taskId}${status ? ` → ${status}` : ''}` : 'Update task';
374
+ }
342
375
  case 'AskUserQuestion': {
343
376
  const qs = Array.isArray(input?.questions) ? input.questions : [];
344
377
  const first = qs[0];
@@ -99,6 +99,20 @@ function sanitizeUploadFileName(rawName, mimeType, index) {
99
99
  const ext = parsed.ext || extensionForMimeType(mimeType) || '.bin';
100
100
  return `${safeStem}${ext.toLowerCase()}`;
101
101
  }
102
+ export function reserveUploadFileName(rawName, mimeType, index, usedNames) {
103
+ const desired = sanitizeUploadFileName(rawName, mimeType, index);
104
+ const parsed = path.parse(desired);
105
+ const stem = parsed.name || `attachment-${index + 1}`;
106
+ const ext = parsed.ext;
107
+ let candidate = desired;
108
+ let suffix = 2;
109
+ while (usedNames.has(candidate.toLowerCase())) {
110
+ candidate = `${stem}-${suffix}${ext}`;
111
+ suffix++;
112
+ }
113
+ usedNames.add(candidate.toLowerCase());
114
+ return candidate;
115
+ }
102
116
  async function materializeUploadedFiles(entries) {
103
117
  const files = entries.filter(isUploadFile);
104
118
  if (!files.length) {
@@ -107,8 +121,9 @@ async function materializeUploadedFiles(entries) {
107
121
  const tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'pikiloom-dashboard-upload-'));
108
122
  try {
109
123
  const attachments = [];
124
+ const usedNames = new Set();
110
125
  for (const [index, file] of files.entries()) {
111
- const filename = sanitizeUploadFileName(String(file.name || ''), String(file.type || ''), index);
126
+ const filename = reserveUploadFileName(String(file.name || ''), String(file.type || ''), index, usedNames);
112
127
  const filePath = path.join(tempDir, filename);
113
128
  await fs.promises.writeFile(filePath, Buffer.from(await file.arrayBuffer()));
114
129
  attachments.push(filePath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pikiloom",
3
- "version": "0.4.56",
3
+ "version": "0.4.58",
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": {
@@ -57,6 +57,7 @@ export declare function emitClaudeImages(blocks: any[], s: any, emit: (e: Driver
57
57
  export declare function todoWriteToPlan(input: any): UniversalPlan | null;
58
58
  export declare function readClaudeTaskCreateId(ev: any, block: any): string | null;
59
59
  export declare function rebuildClaudeTaskPlan(s: any): UniversalPlan | null;
60
+ export declare function applyTaskUpdateToTodoPlan(plan: UniversalPlan | null | undefined, taskId: string, rawStatus: string): UniversalPlan | null;
60
61
  export declare function shortToolValue(value: unknown, max?: number): string;
61
62
  export declare function summarizeToolUse(name: string, input: any): string;
62
63
  export declare function firstResultLine(content: any): string;
@@ -531,29 +531,41 @@ export function handleClaudeEvent(ev, s, emit) {
531
531
  const name = String(b.name || 'Tool');
532
532
  if (name === 'TodoWrite') {
533
533
  const plan = todoWriteToPlan(b.input);
534
- if (plan)
534
+ if (plan) {
535
+ s.todoPlan = plan;
535
536
  emit({ type: 'plan', plan });
537
+ }
538
+ const summary = 'Update plan';
539
+ (s.tools ||= new Map()).set(id, { name, summary });
540
+ emit({ type: 'tool', call: { id, name, summary, input: null, status: 'running' } });
536
541
  continue;
537
542
  }
538
543
  // Task list (current Claude mechanism): stash the subject; the tool_result assigns the id.
539
- // Plan-only like the legacy driver these never surface as Activity rows.
544
+ // The command surfaces as an Activity row (matching the CLI's own transcript); the
545
+ // structured task state still flows through plan events.
540
546
  if (name === 'TaskCreate') {
541
547
  const subject = typeof b.input?.subject === 'string' ? b.input.subject.trim() : '';
542
548
  if (subject)
543
549
  (s.pendingTaskCreates ||= new Map()).set(id, { subject });
550
+ const summary = subject ? `Create task: ${subject}` : 'Create task';
551
+ (s.tools ||= new Map()).set(id, { name, summary });
552
+ emit({ type: 'tool', call: { id, name, summary, input: null, status: 'running' } });
544
553
  continue;
545
554
  }
546
555
  if (name === 'TaskUpdate') {
547
556
  const taskId = String(b.input?.taskId ?? '').trim();
548
557
  const rawStatus = String(b.input?.status ?? '').trim().toLowerCase();
549
- if (taskId) {
558
+ // Chronological: the plan reflects the state AFTER this update. An id known to the
559
+ // TaskCreate store lands there; otherwise it lands positionally on the latest
560
+ // TodoWrite list (ids are 1-based positions when the todo panel owns the list).
561
+ if (taskId && s.taskList?.has(taskId)) {
550
562
  if (rawStatus === 'deleted') {
551
- s.taskList?.delete(taskId);
563
+ s.taskList.delete(taskId);
552
564
  if (Array.isArray(s.taskOrder))
553
565
  s.taskOrder = s.taskOrder.filter((x) => x !== taskId);
554
566
  }
555
567
  else if (rawStatus) {
556
- const existing = s.taskList?.get(taskId);
568
+ const existing = s.taskList.get(taskId);
557
569
  if (existing)
558
570
  existing.status = rawStatus;
559
571
  }
@@ -561,6 +573,16 @@ export function handleClaudeEvent(ev, s, emit) {
561
573
  if (plan)
562
574
  emit({ type: 'plan', plan });
563
575
  }
576
+ else if (taskId) {
577
+ const updated = applyTaskUpdateToTodoPlan(s.todoPlan, taskId, rawStatus);
578
+ if (updated) {
579
+ s.todoPlan = updated;
580
+ emit({ type: 'plan', plan: updated });
581
+ }
582
+ }
583
+ const summary = `Update task ${taskId || '?'} → ${rawStatus || 'unknown'}`;
584
+ (s.tools ||= new Map()).set(id, { name, summary });
585
+ emit({ type: 'tool', call: { id, name, summary, input: null, status: 'running' } });
564
586
  continue;
565
587
  }
566
588
  if (name === 'Task' || name === 'Agent') {
@@ -641,13 +663,16 @@ export function handleClaudeEvent(ev, s, emit) {
641
663
  if (plan)
642
664
  emit({ type: 'plan', plan });
643
665
  }
666
+ if (tool)
667
+ emit({ type: 'tool', call: { id, name: tool.name, summary: tool.summary, status: b.is_error ? 'failed' : 'done', result: null } });
644
668
  continue;
645
669
  }
646
670
  if (!tool)
647
671
  continue;
648
672
  const isError = !!b.is_error;
649
- // File-shaped tools have no useful result detail (mirrors pikiloom): just mark done.
650
- const fileTool = tool.name === 'Read' || tool.name === 'Edit' || tool.name === 'Write' || tool.name === 'TodoWrite';
673
+ // File-shaped and task-list tools have no useful result detail (mirrors pikiloom): just mark done.
674
+ const fileTool = tool.name === 'Read' || tool.name === 'Edit' || tool.name === 'Write'
675
+ || tool.name === 'TodoWrite' || tool.name === 'TaskCreate' || tool.name === 'TaskUpdate';
651
676
  const detail = (isError || !fileTool) ? firstResultLine(b.content) : null;
652
677
  emit({ type: 'tool', call: { id, name: tool.name, summary: tool.summary, status: isError ? 'failed' : 'done', result: detail || null } });
653
678
  }
@@ -960,6 +985,30 @@ export function rebuildClaudeTaskPlan(s) {
960
985
  }
961
986
  return steps.length ? { explanation: null, steps } : null;
962
987
  }
988
+ // Apply a TaskUpdate to a TodoWrite-produced plan positionally (taskId = 1-based item index).
989
+ // Used when the id isn't in the TaskCreate store, so an update issued AFTER a TodoWrite still
990
+ // lands on the displayed list — the plan reflects the state after the LAST change, whichever
991
+ // mechanism wrote it. Returns a fresh plan (never mutates) or null when inapplicable.
992
+ export function applyTaskUpdateToTodoPlan(plan, taskId, rawStatus) {
993
+ if (!plan || !Array.isArray(plan.steps) || !plan.steps.length)
994
+ return null;
995
+ if (!/^\d+$/.test(taskId))
996
+ return null;
997
+ const idx = Number(taskId) - 1;
998
+ if (idx < 0 || idx >= plan.steps.length)
999
+ return null;
1000
+ if (rawStatus === 'deleted') {
1001
+ const steps = plan.steps.filter((_, i) => i !== idx);
1002
+ return steps.length ? { explanation: plan.explanation ?? null, steps } : null;
1003
+ }
1004
+ const status = rawStatus === 'completed' ? 'completed'
1005
+ : (rawStatus === 'in_progress' || rawStatus === 'inprogress') ? 'inProgress'
1006
+ : rawStatus === 'pending' ? 'pending' : null;
1007
+ if (!status)
1008
+ return null;
1009
+ const steps = plan.steps.map((step, i) => i === idx ? { ...step, status } : step);
1010
+ return { explanation: plan.explanation ?? null, steps };
1011
+ }
963
1012
  // ── Tool-call summarization (ported from pikiloom's summarizeClaudeToolUse) ──────────
964
1013
  // Turns a Claude tool_use {name,input} into a one-line human summary. The runtime's
965
1014
  // activity projector joins these into snapshot.activity; the structured form lives in
@@ -1 +0,0 @@
1
- import{r as n,j as s}from"./react-vendor-C7Sl8SE7.js";import{c as sn,I as ln,S as Ke,m as ze,a as I,u as gt,d as on,g as an,l as un,e as cn,j as dn,f as fn,s as mn,Y as gn}from"./index-BVBpUn_6.js";import{s as pn,l as hn,n as Ot,m as xn,a as kn,o as Sn,d as bn,b as vn,p as In,c as Qe,e as Tn,f as Et,g as $e,h as pt,i as yn,j as Rn,T as jn,R as wn,U as Mt,k as Nn,q as Ct,L as Pt,r as An,I as Ln}from"./index-C3fUMz3j.js";import{M as qt,a as Dt}from"./Modal-CaunzS8l.js";import"./router-DHISdpPk.js";import"./Select-Bur7mVMe.js";import"./DirBrowser-BW7l1k3h.js";import"./markdown-DxQYQFeH.js";import"./ExtensionsTab-CTXhgsep.js";function On({snapshot:o}){const[t,f]=n.useState(o.currentIndex??0),[w,T]=n.useState(""),[x,p]=n.useState(!1),[P,S]=n.useState(null);n.useEffect(()=>{f(o.currentIndex??0),T(""),S(null)},[o.promptId,o.currentIndex]);const O=o.questions||[],y=O[t]||null,J=O.length,U=!!(y?.options&&y.options.length),E=U?!!y?.allowFreeform:!0,de=a=>{a&&(f(i=>i+1),T(""))},fe=async a=>{if(!x){p(!0),S(null);try{const i=await I.interactionSelectOption(o.promptId,a);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 a=w.trim();if(!a&&!y?.allowEmpty){S("Please enter a response.");return}p(!0),S(null);try{const i=await I.interactionSubmitText(o.promptId,a);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 a=await I.interactionSkip(o.promptId);if(!a.ok){S(a.error||"Failed to skip.");return}de(a.advanced)}catch(a){S(a?.message||"Network error.")}finally{p(!1)}}},Z=async()=>{if(!x){p(!0);try{await I.interactionCancel(o.promptId)}catch{}}},ee=n.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(qt,{open:!0,onClose:Z,wide:U&&(y?.options?.length||0)>3,children:[s.jsx(Dt,{title:o.title||"Pikiloom needs your input",description:ee,onClose:Z}),y?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:y.header||"Question"}),s.jsx("div",{className:"mt-1 whitespace-pre-wrap text-sm leading-relaxed text-fg",children:y.prompt})]}),U&&s.jsx("div",{className:"grid grid-cols-1 gap-2 sm:grid-cols-2",children:(y.options||[]).map(a=>s.jsxs("button",{type:"button",disabled:x,onClick:()=>fe(a.value||a.label),className:sn("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(ln,{value:w,onChange:a=>T(a.target.value),onKeyDown:a=>{a.key==="Enter"&&!a.shiftKey&&!x&&(a.preventDefault(),me())},placeholder:U?"Or type a custom answer…":"Type your answer…",disabled:x,autoFocus:!U})}),P&&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:P}),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(Ke,{})," 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(ze,{variant:"ghost",size:"sm",onClick:m,disabled:x,children:"Skip"}),E&&s.jsx(ze,{variant:"primary",size:"sm",onClick:me,disabled:x||!w.trim()&&!y.allowEmpty,children:"Submit"})]})]})]}):s.jsxs("div",{className:"py-6 text-center text-sm text-fg-5",children:[s.jsx(Ke,{className:"mr-2 inline-block"})," Waiting for the agent…"]})]})}function En(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 Mn(o,t){return typeof t!="number"?o:t>o?t:o}function Cn(o,t){return!o||!o.length?[]:t.size?o.filter(f=>!t.has(f)):o.slice()}function Pn(o,t,f,w){if(!o.size)return o;const T=new Set(t);let x=!1;const p=new Map;for(const[P,S]of o){if(f-S>w||!T.has(P)){x=!0;continue}p.set(P,S)}return x?p:o}const ht=12,Ut=160,Un=96,qn=[],Dn=[],Hn=[],Fn={phase:"streaming",text:"",thinking:""},Bn=20,_n=6e4,Qn=15e3,$n=7e3,ie=new Map;function Kn(o,t){return`${o}:${t}`}function zn(o,t){for(ie.delete(o),ie.set(o,t);ie.size>Bn;)ie.delete(ie.keys().next().value)}const rr=n.memo(function({session:t,workdir:f,active:w=!0,onSessionChange:T,initialPendingPrompt:x,initialPendingImageUrls:p,onPendingPromptConsumed:P}){const S=gt(e=>e.locale),O=gt(e=>e.agentStatus?.agents?.find(r=>r.agent===t.agent)??null),y=O?.selectedEffort??null,J=O?.selectedModel??null,U=gt(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(()=>on(S),[S]),Z=an(t.agent||""),ee=un(t),a=!!x||!!(p&&p.length),[i,We]=n.useState(null),[Ye,ge]=n.useState(!a),[pe,xt]=n.useState(!1),[c,F]=n.useState(null),[B,he]=n.useState(!1),[q,Ge]=n.useState(null),[Ht,kt]=n.useState(0),[Ft,Ve]=n.useState(null),[D,Re]=n.useState([]),[Bt,je]=n.useState([]),[xe,Xe]=n.useState([]),[h,te]=n.useState(x||null),[M,ne]=n.useState(p||[]),[_t,re]=n.useState(null),_=n.useRef(null);_.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),[Qt,bt]=n.useState(null),[Ae,Se]=n.useState(null),[be,Je]=n.useState(""),[Q,Ze]=n.useState(!1),$t=!!O?.capabilities?.fork,et=n.useRef(null),L=n.useRef(p||[]),$=n.useRef(c),tt=n.useRef(B);$.current=c,tt.current=B;const nt=n.useRef(D);nt.current=D;const vt=n.useRef(q);vt.current=q;const K=n.useRef(null),Le=n.useRef(null),z=n.useRef(!0),se=n.useRef(!1),Oe=n.useRef(null),rt=n.useRef(!1),C=n.useRef(a),le=n.useRef(!1),W=n.useRef(!1),st=n.useRef(!1),lt=n.useRef(!1),Y=n.useRef(null),Ee=n.useRef(0),ve=n.useRef(Date.now()),Ie=n.useRef(new Map),It=n.useRef({model:null,effort:null}),Kt=n.useCallback(e=>{It.current=e},[]);n.useEffect(()=>{st.current||!a||(st.current=!0,x&&!h&&te(x),p&&p.length&&!M.length&&(ne(p),L.current=p),ge(!1),kt(e=>e+1),P?.())},[a,P]);const N=n.useCallback(()=>{te(null),ne(e=>{for(const r of e)URL.revokeObjectURL(r);return[]}),L.current=[],re(null)},[]),G=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=pn({streaming:tt.current,liveStreamPhase:$.current?.phase??null,streamPhase:vt.current,queuedTaskCount:nt.current.length,pendingQueuedCount:Ne.current.length}),u=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:u}]);return}$.current?.phase==="done"&&F(null);for(const d of L.current)URL.revokeObjectURL(d);ke.current=null,te(e||null),ne(u),L.current=u,re(null)},[]),zt=n.useCallback(e=>{const r=ke.current;if(r){ke.current=null,H(l=>{const u=l.findIndex(k=>k.localId===r);if(u<0)return l;const d=l.slice();return d[u]={...d[u],taskId:e},d});return}re(e)},[]),Wt=n.useCallback(async()=>{if(!Ae)return;const e=be.trim();if(e){Ze(!0);try{const r=await I.forkSession(f,t.agent||"",t.sessionId,Ae.atTurn,e,{});if(!r.ok||!r.sessionKey){Ze(!1);return}const[l,u]=r.sessionKey.split(":");Se(null),Je(""),T?.({agent:l,sessionId:u,workdir:f})}finally{Ze(!1)}}},[Ae,be,f,t.agent,t.sessionId,T]);et.current=Wt;const Me=n.useCallback(async(e,r={})=>{try{const l=await hn({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?Ot(l):null}catch{return null}},[f,t.agent,t.sessionId]),R=n.useCallback(async({keepOlder:e,force:r=!1,scrollToBottom:l=!1})=>{const u=t.sessionId;if(Oe.current===u)return!1;Oe.current=u;try{const d=await Me({turnOffset:0,turnLimit:ht},{force:r});if(!d||t.sessionId!==u)return!1;if(l&&(se.current=!0),We(k=>!k||!e?d:xn(k,d)),ge(!1),le.current&&(le.current=!1,N()),W.current){const k=W.current;W.current=!1;const V=k!==!0?k.taskId:null;$.current&&(k===!0||$.current.taskId===V)&&F(null)}return!0}finally{Oe.current===u&&(Oe.current=null)}},[Me,N,t.sessionId]),Ce=n.useCallback(async()=>{if(!i?.hasOlder||rt.current)return;const e=K.current;e&&(Le.current={scrollHeight:e.scrollHeight,scrollTop:e.scrollTop}),rt.current=!0,xt(!0);try{const r=await Me({turnOffset:Math.max(0,i.totalTurns-i.startTurn),turnLimit:ht});r?We(l=>l?kn(l,r):r):Le.current=null}finally{rt.current=!1,xt(!1)}},[Me,i]),Pe=n.useRef(null),oe=n.useCallback((e,r="ws")=>{const l=tt.current||nt.current.length>0,u=Date.now()-ve.current>Qn;if(En({updatedAt:e?.updatedAt,isNull:!e,lastAppliedUpdatedAt:Ee.current,localStreamPending:C.current,holdsActiveState:l,holdExpired:u})!=="apply")return;const k=!e&&u&&(C.current||l);if(e&&(ve.current=Date.now(),Ee.current=Mn(Ee.current,e.updatedAt)),e?.sessionId&&e.sessionId!==t.sessionId&&(lt.current=!0,Te.current=`${t.agent}:${e.sessionId}`,T?.({agent:t.agent||"",sessionId:e.sessionId,workdir:f})),!e){const g=Pe.current;he(!1),k?(C.current=!1,le.current=!0,W.current=!0,G(),R({keepOlder:!0,force:!0,scrollToBottom:z.current})):g==="streaming"?(le.current=!0,W.current=!0,R({keepOlder:!0,force:!0,scrollToBottom:z.current})):F(null),k||(C.current&&g!=="streaming"?R({keepOlder:!0,force:!0}):(g==="done"&&(N(),G()),g!==null&&(C.current=!1))),Ve(null),Ge(null),Re([]),je([]),Xe([]),Pe.current=null;return}Ge(e.phase),Ve(e.taskId||null);const V=[];e.taskId&&V.push(e.taskId),Array.isArray(e.queuedTaskIds)&&V.push(...e.queuedTaskIds),Ie.current=Pn(Ie.current,V,Date.now(),_n);const ue=Ie.current,ft=Cn(e.queuedTaskIds,ue),Lt=(Array.isArray(e.queuedTasks)?e.queuedTasks:[]).filter(g=>!ue.has(g.taskId));if(Re(ft.length?ft:qn),je(Lt.length?Lt:Dn),Xe(Array.isArray(e.interactions)&&e.interactions.length?e.interactions:Hn),Sn({pendingTaskId:_.current,streamTaskId:e.taskId||null,queuedTaskIds:e.queuedTaskIds})){const g=_.current,j=St.current||"",v=L.current;H(b=>b.some(ce=>ce.taskId===g)?b:[...b,{localId:`demote-${g}`,taskId:g,prompt:j,imageUrls:v}]),te(null),ne([]),L.current=[],re(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}),he(!0),e.taskId&&e.taskId!==_.current){const g=Ne.current,j=g.findIndex(v=>v.taskId===e.taskId);if(j>=0){const v=g[j];for(const b of L.current)URL.revokeObjectURL(b);te(v.prompt||null),ne(v.imageUrls),L.current=v.imageUrls,re(e.taskId),H(b=>b.filter((ce,X)=>X!==j))}}z.current&&(se.current=!0)}else if(e.phase==="queued")F(null),he(!1);else if(e.phase==="done"){const g=bn($.current?.taskId??null,e.taskId||null),j=ft.length>0;if(g){he(!1),F(X=>X?{...X,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}:X);const v=$.current,b=!!v&&vn(v),ce=!!e.incomplete&&b&&!j;if(Pe.current!=="done"){j||(le.current=!0),W.current=ce?!1:{taskId:e.taskId||null},R({keepOlder:!0,force:!0,scrollToBottom:z.current});const X=t.agent||"",tn=t.sessionId,nn=Te.current;Y.current&&clearTimeout(Y.current),Y.current=setTimeout(()=>{Y.current=null,I.getSessionStreamState(X,tn).then(rn=>{Te.current===nn&&Ue.current(rn.state,"seed")}).catch(()=>{})},900)}}j||(C.current=!1)}const mt=new Set;if(e.taskId&&mt.add(e.taskId),Array.isArray(e.queuedTaskIds))for(const g of e.queuedTaskIds)mt.add(g);H(g=>{let j=!1;const v=[];for(const b of g)if(!b.taskId||mt.has(b.taskId))v.push(b);else{for(const ce of b.imageUrls)URL.revokeObjectURL(ce);j=!0}return j?v:g}),Pe.current=e.phase},[N,G,R,t.sessionId,t.agent,T,f]),Ue=n.useRef(oe);Ue.current=oe;const yt=n.useCallback(()=>{C.current=!0,ve.current=Date.now(),kt(e=>e+1)},[]);n.useEffect(()=>()=>{Y.current&&(clearTimeout(Y.current),Y.current=null)},[]);const Yt=n.useCallback(async e=>{try{Ie.current.set(e,Date.now()),await I.recallSessionMessage(e),_.current===e&&N(),H(r=>{let l=!1;const u=[];for(const d of r)if(d.taskId===e){for(const k of d.imageUrls)URL.revokeObjectURL(k);l=!0}else u.push(d);return l?u:r}),Re(r=>r.filter(l=>l!==e)),je(r=>r.filter(l=>l.taskId!==e)),Ve(r=>r===e?null:r)}catch{}},[N]),Gt=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);te(r.prompt||null),ne(r.imageUrls),L.current=r.imageUrls,re(e),_.current=e,H(l=>l.filter(u=>u.taskId!==e))}try{await I.steerSession(e)}catch{}},[]),Vt=n.useCallback(async()=>{try{await I.stopSession(t.agent||"",t.sessionId)}catch{}},[t.agent,t.sessionId]),qe=Kn(t.agent||"",t.sessionId);n.useEffect(()=>{if(lt.current){lt.current=!1;let d=!1;return R({keepOlder:!0,force:!0}).finally(()=>{d||ge(!1)}),()=>{d=!0}}let e=!1;const r=In({workdir:f,agent:t.agent||"",sessionId:t.sessionId,rich:!0,turnOffset:0,turnLimit:ht},{allowStale:!0}),l=a&&!st.current,u=r?.ok?Ot(r):ie.get(qe)||null;return ge(l?!1:!u),We(u),F(null),he(!1),Ge(null),Re([]),je([]),Xe([]),Ee.current=0,ve.current=Date.now(),Ie.current=new Map,l||(N(),G(),C.current=!1,le.current=!1,W.current=!1),z.current=!0,se.current=!0,l||R({keepOlder:!1,force:!0}).finally(()=>{e||ge(!1)}),()=>{e=!0}},[R,t.agent,t.sessionId,f,qe,N,G]),n.useEffect(()=>{i&&i.turns.length>0&&zn(qe,i)},[qe,i]),n.useEffect(()=>{w&&R({keepOlder:!0,force:!0})},[w,R]);const Te=n.useRef(`${t.agent}:${t.sessionId}`);Te.current=`${t.agent}:${t.sessionId}`,cn("stream-update",n.useCallback(e=>{e.key===Te.current&&oe(e.snapshot??null)},[oe])),n.useEffect(()=>{let e=!0;return I.getSessionStreamState(t.agent||"",t.sessionId).then(r=>{e&&Ue.current(r.state,"seed")}).catch(()=>{}),()=>{e=!1}},[t.agent,t.sessionId,Ht]),dn(n.useCallback(()=>{I.getSessionStreamState(t.agent||"",t.sessionId).then(e=>{oe(e.state,"seed")}).catch(()=>{}),R({keepOlder:!0,force:!0})},[oe,t.agent,t.sessionId,R]));const Rt=B||!!q||D.length>0||!!h||we.length>0;n.useEffect(()=>{if(!w||!Rt)return;const e=setInterval(()=>{typeof document<"u"&&document.visibilityState!=="visible"||I.getSessionStreamState(t.agent||"",t.sessionId).then(r=>Ue.current(r.state,"seed")).catch(()=>{})},$n);return()=>clearInterval(e)},[w,Rt,t.agent,t.sessionId]),n.useEffect(()=>{!C.current&&ee!=="running"&&!B&&!c&&!q&&D.length===0&&(N(),G())},[ee,B,c,q,D.length,N,G]),n.useLayoutEffect(()=>{const e=Le.current,r=K.current;!e||!r||(Le.current=null,r.scrollTop=e.scrollTop+(r.scrollHeight-e.scrollHeight))},[i?.turns.length]),n.useLayoutEffect(()=>{if(!se.current)return;const e=K.current;e&&(se.current=!1,e.scrollTop=e.scrollHeight,requestAnimationFrame(()=>{z.current&&(e.scrollTop=e.scrollHeight)}))},[i,c]),n.useLayoutEffect(()=>{if(!h)return;const e=K.current;e&&(e.scrollTop=e.scrollHeight)},[h]),n.useEffect(()=>{if(!i?.hasOlder||Ye||pe)return;const e=K.current;e&&e.scrollHeight<=e.clientHeight+Ut&&Ce()},[i?.hasOlder,i?.turns.length,Ce,Ye,pe]);const Xt=n.useCallback(()=>{const e=K.current;if(!e)return;const r=e.scrollHeight-e.scrollTop-e.clientHeight;z.current=r<=Un,e.scrollTop<=Ut&&Ce()},[Ce]),ye=c?.model||t.model||J||null,De=fn(t.agent||"",c?.effort||t.thinkingEffort||y||null,t.workflowEnabled??O?.workflowEnabled)||null,ot=me&&(!ye||ye===J)?me:ye?mn(ye):null,at=gn(t,{streaming:B,hasLiveStream:!!c,streamPhase:q,queuedTaskCount:D.length}),A=i?.turns||[],He=n.useMemo(()=>{if(!M.length||!A.length)return!1;const e=A[A.length-1];return!e.user||!Qe(e.user.text,h)?!1:e.user.blocks.filter(l=>l.type==="image").length<M.length},[A,h,M.length]),ut=n.useMemo(()=>M.length?M.map(e=>({type:"image",content:e})):!h||!c?.questionBlocks?.length?[]:Qe(h,c.question)?c.questionBlocks:[],[M,h,c]),jt=c?.question||null,Fe=Tn(h,jt),ct=Et(jt,h),wt=A.length>0?A[A.length-1]?.user?.text:null,Nt=!!Fe&&A.length>0&&$e(wt,Fe),ae=n.useMemo(()=>{let e=A;if(He){const ue=e[e.length-1];e=[...e.slice(0,-1),{...ue,user:null}]}if(!c||!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)||Et(l,r.user.text))?[...e.slice(0,-1),{...r,user:{...r.user,text:l}}]:e;const u=(c.text||"").trim(),d=r.assistant.text?.trim()||"";if(!(l!=null?$e(r.user?.text,l):!!d&&!!u&&(u.startsWith(d)||d.startsWith(u))))return e;const V=r.user&&l&&!Qe(r.user.text,l)?{...r.user,text:l}:r.user;return[...e.slice(0,-1),{...r,user:V,assistant:null}]},[A,c,Fe,He]),Jt=(!!h||ut.length>0)&&!ct&&(He||!Nt)&&!c,Zt=!!c&&pt(c)&&c.phase==="streaming",it=yn({sessionRunning:ee==="running",streaming:B,streamPhase:q,queuedTaskCount:D.length,pendingQueuedCount:we.length,liveTurnStreaming:Zt,pendingBubbleDots:Jt}),Be=it&&ae.length>0?ae[ae.length-1]:null,_e=!!Be?.assistant&&Rn(Be.assistant),dt=_e?Be.assistant.usage??null:null,en=_e?Be.assistant.blocks.filter(e=>e.type==="tool_use").length:0,At=(()=>{if(!it||ee!=="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:K,onScroll:Xt,className:"flex-1 overflow-y-auto overscroll-contain",children:Ye&&!h&&!M.length&&!c?s.jsx("div",{className:"flex items-center justify-center py-20",children:s.jsx(Ke,{className:"h-5 w-5 text-fg-4"})}):ae.length===0&&!h&&!M.length&&!c&&!at?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(Ke,{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:()=>T?.({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))]})]}),ae.map((e,r)=>{const l=(i?.startTurn||0)+r;return s.jsx(jn,{turn:e,turnIndex:l,hideHeaderUsage:_e&&r===ae.length-1,agent:t.agent||"",meta:Z,model:ot,effort:De,providerName:fe,t:m,workdir:f,onResend:u=>{se.current=!0,Tt(u);const d=It.current;I.sendSessionMessage(f,t.agent||"",t.sessionId,u,{model:d.model||ye||void 0,effort:d.effort||De||void 0}).then(k=>{k.ok&&yt()}).catch(()=>{N()})},onEdit:u=>bt(u),onFork:$t?u=>{Je(""),Se({atTurn:u})}:void 0},`${i?.startTurn||0}:${r}`)}),at&&s.jsx("div",{className:"mb-5 animate-in",children:s.jsx(wn,{detail:at,t:m})}),(h||ut.length>0)&&!ct&&(He||!Nt)&&s.jsxs("div",{className:"session-turn",children:[s.jsx(Mt,{text:h||"",blocks:ut,t:m}),!c&&s.jsx("div",{className:"mt-3 mb-5 animate-in",children:s.jsx(Nn,{className:"text-fg-5"})})]}),c&&pt(c)&&c.question&&(!h||ct)&&!(A.length>0&&$e(wt,c.question))&&s.jsx("div",{className:"session-turn",children:s.jsx(Mt,{text:c.question,blocks:c.questionBlocks||void 0,t:m})}),c&&pt(c)&&s.jsxs("div",{className:"mb-6",children:[s.jsx(Ct,{agent:t.agent||"",meta:Z,model:ot,effort:De,providerName:fe,previewMeta:c.previewMeta,hideContextUsage:!0}),s.jsx(Pt,{stream:c,t:m,workdir:f})]}),it&&(_e?s.jsx("div",{className:"-mt-3 mb-6 animate-in",children:s.jsx(An,{toolCount:en,ctxPct:dt?.contextPercent??null,ctxTokens:dt?.contextUsedTokens??0,turnOutTokens:dt?.turnOutputTokens??0,startedAt:At})}):s.jsxs("div",{className:"mb-6",children:[s.jsx(Ct,{agent:t.agent||"",meta:Z,model:ot,effort:De,providerName:fe,hideContextUsage:!0}),s.jsx(Pt,{stream:{...Fn,startedAt:At},t:m,workdir:f})]})),s.jsx("div",{className:"h-4"})]})}),s.jsx(Ln,{session:t,workdir:f,onStreamQueued:yt,onSendStart:Tt,onSendTaskAssigned:zt,onSessionChange:T,t:m,streamPhase:q,streamTaskId:Ft,queuedTaskIds:D,queuedTasks:Bt,pendingQueuedSends:we,onRecall:Yt,onSteer:Gt,onStopAll:Vt,editDraft:Qt,onEditDraftConsumed:()=>bt(null),onSelectionChange:Kt}),Ae&&s.jsxs(qt,{open:!0,onClose:()=>{Q||Se(null)},children:[s.jsx(Dt,{title:m("hub.forkPromptTitle"),description:m("hub.forkPromptHint"),onClose:()=>{Q||Se(null)}}),s.jsx("textarea",{autoFocus:!0,value:be,disabled:Q,onChange:e=>Je(e.target.value),onKeyDown:e=>{e.key==="Enter"&&(e.metaKey||e.ctrlKey)&&be.trim()&&!Q&&(e.preventDefault(),et.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(ze,{variant:"ghost",disabled:Q,onClick:()=>Se(null),children:m("modal.cancel")}),s.jsx(ze,{variant:"primary",disabled:Q||!be.trim(),onClick:()=>{et.current?.()},children:m(Q?"hub.forkSubmitting":"hub.forkSubmit")})]})]}),w&&xe.length>0&&s.jsx(On,{snapshot:xe[xe.length-1]},xe[xe.length-1].promptId)]})});export{rr as SessionPanel};