pikiloom 0.4.53 → 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-DSpqOkYp.js → AgentTab-CvghrxSK.js} +1 -1
- package/dashboard/dist/assets/{ConnectionModal-BFcN8nJr.js → ConnectionModal-DHw2M8z4.js} +1 -1
- package/dashboard/dist/assets/{DirBrowser-Dk-TadnX.js → DirBrowser-hui6YZ3V.js} +1 -1
- package/dashboard/dist/assets/{ExtensionsTab-wiaVcnfS.js → ExtensionsTab-DAqHco47.js} +1 -1
- package/dashboard/dist/assets/{IMAccessTab-C4ltstAX.js → IMAccessTab-BFKhRG8w.js} +1 -1
- package/dashboard/dist/assets/{Modal-BeyxM41U.js → Modal-oPg6drTY.js} +1 -1
- package/dashboard/dist/assets/{Modals-B2a8IIvO.js → Modals-DwMaDCJc.js} +1 -1
- package/dashboard/dist/assets/{Select-DxYPQ-41.js → Select-Dlz8-JdF.js} +1 -1
- package/dashboard/dist/assets/SessionPanel-DPb40DDF.js +1 -0
- package/dashboard/dist/assets/{SystemTab-1frRjK4R.js → SystemTab-CzexSRMC.js} +1 -1
- package/dashboard/dist/assets/{index-dR0w8M7K.js → index-BIX62DbC.js} +2 -2
- package/dashboard/dist/assets/{index-C-9wwAiw.js → index-CRE3Clam.js} +15 -15
- package/dashboard/dist/assets/{shared-BbgVZfU_.js → shared-BATHuwRO.js} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dist/agent/drivers/claude.js +37 -45
- 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-CYO6NGSt.js +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-
|
|
1
|
+
import{j as s}from"./react-vendor-C7Sl8SE7.js";import{O as t,m as i,S as a}from"./index-BIX62DbC.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,7 +6,7 @@
|
|
|
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-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-BIX62DbC.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
12
|
<link rel="stylesheet" crossorigin href="/assets/index-BQ_R1F_G.css">
|
|
@@ -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, 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, 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';
|
|
@@ -1343,12 +1343,16 @@ function getClaudeSessionTail(opts) {
|
|
|
1343
1343
|
try {
|
|
1344
1344
|
const ev = JSON.parse(raw);
|
|
1345
1345
|
if (ev.type === 'user') {
|
|
1346
|
+
if (ev.isMeta === true)
|
|
1347
|
+
continue;
|
|
1346
1348
|
const text = stripInjectedPrompts(extractClaudeText(ev.message?.content, true));
|
|
1347
|
-
if (text)
|
|
1349
|
+
if (text && !isSystemInjectedUserText(text))
|
|
1348
1350
|
allMsgs.push({ role: 'user', text });
|
|
1349
1351
|
}
|
|
1350
1352
|
else if (ev.type === 'assistant') {
|
|
1351
1353
|
const text = extractClaudeText(ev.message?.content, true);
|
|
1354
|
+
if (ev.message?.model === '<synthetic>' && isClaudeSyntheticResumeNoise(text))
|
|
1355
|
+
continue;
|
|
1352
1356
|
if (text)
|
|
1353
1357
|
allMsgs.push({ role: 'assistant', text });
|
|
1354
1358
|
}
|
|
@@ -1425,26 +1429,6 @@ function extractClaudeBlocks(content, skipSystemBlocks = false, todoWriteToolIds
|
|
|
1425
1429
|
}
|
|
1426
1430
|
return blocks;
|
|
1427
1431
|
}
|
|
1428
|
-
const SYSTEM_INJECTED_USER_TAGS = new Set([
|
|
1429
|
-
'task-notification',
|
|
1430
|
-
'system-reminder',
|
|
1431
|
-
'persisted-output',
|
|
1432
|
-
'local-command-stdout',
|
|
1433
|
-
'local-command-caveat',
|
|
1434
|
-
'local-command-stderr',
|
|
1435
|
-
'ide_opened_file',
|
|
1436
|
-
'ide_diagnostics',
|
|
1437
|
-
'ide_selection',
|
|
1438
|
-
'event',
|
|
1439
|
-
'analysis',
|
|
1440
|
-
'case_id',
|
|
1441
|
-
'tool-use-id',
|
|
1442
|
-
'output-file',
|
|
1443
|
-
// Kernel claude driver's in-process truncated-turn recovery prompt (see packages/kernel
|
|
1444
|
-
// drivers/claude.ts): injected on the CLI's stdin, so it lands in the jsonl as a real user
|
|
1445
|
-
// message — hide it from the transcript like other system-injected turns.
|
|
1446
|
-
'pikiloom-recover',
|
|
1447
|
-
]);
|
|
1448
1432
|
// The CLI's resume-time repair placeholder for a turn that never concluded (paired with an
|
|
1449
1433
|
// isMeta "Continue from where you left off." user record). It is not model output — but it IS
|
|
1450
1434
|
// the only durable marker that the previous reply was cut off before its closing message.
|
|
@@ -1455,19 +1439,6 @@ function isClaudeSyntheticResumeNoise(text) {
|
|
|
1455
1439
|
return t === 'no response requested.' || t === 'no response requested';
|
|
1456
1440
|
}
|
|
1457
1441
|
const CLAUDE_INCOMPLETE_TURN_NOTICE = '⚠️ This reply ended before a closing message was delivered (interrupted, or the model returned an empty final response).';
|
|
1458
|
-
function isSystemInjectedUserEvent(text) {
|
|
1459
|
-
const trimmed = (text || '').trim();
|
|
1460
|
-
if (!trimmed)
|
|
1461
|
-
return true;
|
|
1462
|
-
if (/^\[Request interrupted by user(?: for tool use)?\]$/i.test(trimmed))
|
|
1463
|
-
return true;
|
|
1464
|
-
const leading = trimmed.match(/^<([a-z][a-z0-9_-]*)\b/i);
|
|
1465
|
-
if (leading && SYSTEM_INJECTED_USER_TAGS.has(leading[1].toLowerCase()))
|
|
1466
|
-
return true;
|
|
1467
|
-
const lower = trimmed.toLowerCase();
|
|
1468
|
-
const markers = ['continued from a previous', 'summary below covers', 'earlier portion of the conversation', 'here is a summary of', 'conversation summary'];
|
|
1469
|
-
return markers.some(m => lower.includes(m));
|
|
1470
|
-
}
|
|
1471
1442
|
function getClaudeSessionMessages(opts) {
|
|
1472
1443
|
const projectDir = path.join(getHome(), '.claude', 'projects', claudeProjectDirName(opts.workdir));
|
|
1473
1444
|
const filePath = path.join(projectDir, `${opts.sessionId}.jsonl`);
|
|
@@ -1600,11 +1571,14 @@ function getClaudeSessionMessages(opts) {
|
|
|
1600
1571
|
}
|
|
1601
1572
|
continue;
|
|
1602
1573
|
}
|
|
1574
|
+
const probe = extractClaudeText(ev.message?.content, true);
|
|
1603
1575
|
if (pendingRole === 'assistant') {
|
|
1604
|
-
|
|
1605
|
-
if (isSystemInjectedUserEvent(probe))
|
|
1576
|
+
if (isSystemInjectedUserText(probe))
|
|
1606
1577
|
continue;
|
|
1607
1578
|
}
|
|
1579
|
+
else if (probe.trim() && isSystemInjectedUserText(probe)) {
|
|
1580
|
+
continue;
|
|
1581
|
+
}
|
|
1608
1582
|
flush();
|
|
1609
1583
|
const rawText = stripInjectedPrompts(extractClaudeText(ev.message?.content, true));
|
|
1610
1584
|
const userBlocks = extractClaudeBlocks(ev.message?.content, true);
|
|
@@ -2005,6 +1979,13 @@ async function fetchClaudeUsageFromOAuth(tokenOverride) {
|
|
|
2005
1979
|
// one request. `force` is for explicit user-clicked refreshes only: it bypasses both the TTL and
|
|
2006
1980
|
// the failure backoff (human-paced clicks cannot stampede the endpoint), with a short floor to
|
|
2007
1981
|
// absorb double-clicks.
|
|
1982
|
+
//
|
|
1983
|
+
// The 429 budget is PER ACCOUNT and shared with every other Claude Code process on the machine,
|
|
1984
|
+
// so the endpoint can stay rate-limited for long stretches through no fault of our cadence. When
|
|
1985
|
+
// it fails and there is no last-good to serve, degrade to the inference-header probe (the same
|
|
1986
|
+
// channel setup-token accounts use): coarser — 5h/7d only, no Extra spend or per-model weekly —
|
|
1987
|
+
// but reliably available, so the native row never goes blank. A later successful oauth read
|
|
1988
|
+
// replaces it with the full-fidelity snapshot.
|
|
2008
1989
|
const NATIVE_USAGE_FRESH_TTL_MS = 15_000;
|
|
2009
1990
|
const NATIVE_USAGE_RETRY_TTL_MS = 60_000;
|
|
2010
1991
|
const NATIVE_USAGE_FORCE_FLOOR_MS = 3_000;
|
|
@@ -2022,20 +2003,31 @@ export function claudeNativeUsage(opts) {
|
|
|
2022
2003
|
}
|
|
2023
2004
|
if (claudeNativeUsageLive.inflight)
|
|
2024
2005
|
return claudeNativeUsageLive.inflight;
|
|
2025
|
-
const p =
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2006
|
+
const p = (async () => {
|
|
2007
|
+
let result = await fetchClaudeUsageFromOAuth();
|
|
2008
|
+
const oauthOk = !!result;
|
|
2009
|
+
// Degrade to the header probe when oauth fails and there is no full-fidelity snapshot to
|
|
2010
|
+
// serve — including when last-good is itself a header-probe result (keep it refreshing
|
|
2011
|
+
// through a long 429 stretch instead of freezing on the first degraded read).
|
|
2012
|
+
if (!result && (!claudeUsageCache.lastGood || claudeUsageCache.lastGood.source === 'ratelimit-headers')) {
|
|
2013
|
+
const token = getClaudeOAuthToken();
|
|
2014
|
+
if (token)
|
|
2015
|
+
result = await claudeUsageForToken(token, opts);
|
|
2016
|
+
agentLog(`[usage] native oauth usage read failed; header-probe fallback -> ${result ? result.source : 'null'}`);
|
|
2017
|
+
}
|
|
2018
|
+
if (result) {
|
|
2019
|
+
claudeUsageCache.lastGood = result;
|
|
2029
2020
|
claudeUsageCache.lastAttemptAt = Date.now();
|
|
2030
2021
|
claudeNativeUsageLive.at = Date.now();
|
|
2031
|
-
|
|
2022
|
+
// A header-probe fallback is a degraded read: keep failedAt so the oauth endpoint is
|
|
2023
|
+
// retried on the normal backoff cadence instead of idling out the full ok-TTL.
|
|
2024
|
+
claudeNativeUsageLive.failedAt = oauthOk ? 0 : Date.now();
|
|
2032
2025
|
}
|
|
2033
2026
|
else {
|
|
2034
2027
|
claudeNativeUsageLive.failedAt = Date.now();
|
|
2035
2028
|
}
|
|
2036
|
-
return
|
|
2037
|
-
})
|
|
2038
|
-
.finally(() => { claudeNativeUsageLive.inflight = null; });
|
|
2029
|
+
return result ?? claudeUsageCache.lastGood;
|
|
2030
|
+
})().finally(() => { claudeNativeUsageLive.inflight = null; });
|
|
2039
2031
|
claudeNativeUsageLive.inflight = p;
|
|
2040
2032
|
return p;
|
|
2041
2033
|
}
|
package/dist/agent/index.js
CHANGED
|
@@ -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, 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, 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';
|
|
@@ -150,7 +150,12 @@ export function composeKernelFinalPresentation(input) {
|
|
|
150
150
|
? 'The turn stopped responding after tool use without producing a reply (the model or provider may have stalled). Send any message to continue.'
|
|
151
151
|
: input.stopReason === 'truncated'
|
|
152
152
|
? 'The reply ended after the last tool call without a closing message (the model returned an empty final response). Send any message to continue.'
|
|
153
|
-
|
|
153
|
+
// 'background' with narration = the hold cap closed the turn while background work was
|
|
154
|
+
// still running (a silent stop from the user's view). With NO narration it is the
|
|
155
|
+
// intentional detached-launch shape, which keeps the friendlier substitution below.
|
|
156
|
+
: (input.stopReason === 'background' && input.bodyText)
|
|
157
|
+
? 'Background work was still running when this turn closed (hold limit reached). Send any message to check on it or continue.'
|
|
158
|
+
: null;
|
|
154
159
|
// Empty-text fallback (mirrors the legacy driver): a clean turn with no prose reads
|
|
155
160
|
// "(no textual response)", not "(no output)" (which the kernel path used to show for every
|
|
156
161
|
// textless turn — including a background-launch turn that intentionally ends without prose).
|
package/dist/agent/utils.js
CHANGED
|
@@ -522,6 +522,39 @@ export function stripInjectedPrompts(text) {
|
|
|
522
522
|
export const SESSION_PREVIEW_IGNORED_USER_PATTERNS = [
|
|
523
523
|
/^\[Request interrupted by user(?: for tool use)?\]$/i,
|
|
524
524
|
];
|
|
525
|
+
const SYSTEM_INJECTED_USER_TAGS = new Set([
|
|
526
|
+
'task-notification',
|
|
527
|
+
'system-reminder',
|
|
528
|
+
'persisted-output',
|
|
529
|
+
'local-command-stdout',
|
|
530
|
+
'local-command-caveat',
|
|
531
|
+
'local-command-stderr',
|
|
532
|
+
'ide_opened_file',
|
|
533
|
+
'ide_diagnostics',
|
|
534
|
+
'ide_selection',
|
|
535
|
+
'event',
|
|
536
|
+
'analysis',
|
|
537
|
+
'case_id',
|
|
538
|
+
'tool-use-id',
|
|
539
|
+
'output-file',
|
|
540
|
+
// Kernel claude driver's in-process truncated-turn recovery prompt (see packages/kernel
|
|
541
|
+
// drivers/claude.ts): injected on the CLI's stdin, so it lands in the jsonl as a real user
|
|
542
|
+
// message — hide it from the transcript like other system-injected turns.
|
|
543
|
+
'pikiloom-recover',
|
|
544
|
+
]);
|
|
545
|
+
export function isSystemInjectedUserText(text) {
|
|
546
|
+
const trimmed = (text || '').trim();
|
|
547
|
+
if (!trimmed)
|
|
548
|
+
return true;
|
|
549
|
+
if (/^\[Request interrupted by user(?: for tool use)?\]$/i.test(trimmed))
|
|
550
|
+
return true;
|
|
551
|
+
const leading = trimmed.match(/^<([a-z][a-z0-9_-]*)\b/i);
|
|
552
|
+
if (leading && SYSTEM_INJECTED_USER_TAGS.has(leading[1].toLowerCase()))
|
|
553
|
+
return true;
|
|
554
|
+
const lower = trimmed.toLowerCase();
|
|
555
|
+
const markers = ['continued from a previous', 'summary below covers', 'earlier portion of the conversation', 'here is a summary of', 'conversation summary'];
|
|
556
|
+
return markers.some(m => lower.includes(m));
|
|
557
|
+
}
|
|
525
558
|
export const SESSION_PREVIEW_IMAGE_PLACEHOLDER_RE = /\[Image:[^\]]+\]/gi;
|
|
526
559
|
export const SESSION_PREVIEW_FILE_PLACEHOLDER_RE = /\[Attached file:[^\]]+\]/gi;
|
|
527
560
|
export const CLAUDE_AT_MENTION_IMAGE_RE = /(^|\s)@(\/[^\s@\n]+\.(?:png|jpe?g|gif|webp|svg))(?=\s|$)/gi;
|
package/dist/bot/commands.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import { fmtTokens, fmtUptime, fmtBytes } from './bot.js';
|
|
4
|
-
import { getProjectSkillPaths, normalizeClaudeModelId, sessionListDisplayTitle, listAllMcpExtensions, listSkills as listAllSkills, } from '../agent/index.js';
|
|
4
|
+
import { getProjectSkillPaths, normalizeClaudeModelId, sessionListDisplayTitle, listAllMcpExtensions, listSkills as listAllSkills, isSystemInjectedUserText, } from '../agent/index.js';
|
|
5
5
|
import { getDriver } from '../agent/driver.js';
|
|
6
6
|
import { accountAgentSupported, listAccounts, getActiveAccountId, getCachedAccountUsage, getAccountsUsageSnapshot, } from '../agent/accounts.js';
|
|
7
7
|
import { withTimeoutFallback } from '../core/utils.js';
|
|
@@ -272,9 +272,11 @@ export function formatSessionsDigestText(data) {
|
|
|
272
272
|
export function extractLastSessionTurn(messages) {
|
|
273
273
|
if (!messages.length)
|
|
274
274
|
return null;
|
|
275
|
+
// Harness-injected user records (<task-notification>, <system-reminder>, …) can trail the
|
|
276
|
+
// real question in the session file — the preview must anchor on what the user actually asked.
|
|
275
277
|
let lastUserIndex = -1;
|
|
276
278
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
277
|
-
if (messages[i].role === 'user') {
|
|
279
|
+
if (messages[i].role === 'user' && !isSystemInjectedUserText(messages[i].text)) {
|
|
278
280
|
lastUserIndex = i;
|
|
279
281
|
break;
|
|
280
282
|
}
|
package/package.json
CHANGED
|
@@ -33,6 +33,9 @@ export declare function claudeContextWindowFromModel(model: unknown): number | n
|
|
|
33
33
|
export declare function claudeEffectiveContextWindow(advertised: number | null): number | null;
|
|
34
34
|
export declare function handleClaudeEvent(ev: any, s: any, emit: (e: DriverEvent) => void): void;
|
|
35
35
|
export declare function claudeBgHoldCapMs(): number;
|
|
36
|
+
export declare function claudeBgAgentHoldCapMs(): number;
|
|
37
|
+
export declare function claudeTurnHasAgentBackground(s: any): boolean;
|
|
38
|
+
export declare function claudeBgHoldRecheckMs(): number;
|
|
36
39
|
export declare function claudeBgSettleQuietMs(): number;
|
|
37
40
|
export declare function claudeModelStallMs(): number;
|
|
38
41
|
export declare const CLAUDE_TRUNCATED_RECOVERY_PROMPT: string;
|
|
@@ -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 as t,j as s}from"./react-vendor-C7Sl8SE7.js";import{c as _t,I as $t,S as Ue,m as Fe,a as R,u as rt,d as Kt,g as zt,l as Wt,e as Yt,j as Xt,f as Gt,s as Vt,Y as Jt}from"./index-dR0w8M7K.js";import{s as Zt,l as er,n as St,m as tr,a as rr,o as nr,d as sr,b as lr,p as or,c as Ce,e as ar,f as bt,g as qe,T as ur,R as cr,U as vt,h as ir,i as It,j as dr,L as fr,I as mr}from"./index-C-9wwAiw.js";import{M as Tt,a as Rt}from"./Modal-BeyxM41U.js";import"./router-DHISdpPk.js";import"./Select-DxYPQ-41.js";import"./DirBrowser-Dk-TadnX.js";import"./markdown-DxQYQFeH.js";import"./ExtensionsTab-wiaVcnfS.js";function gr({snapshot:o}){const[r,f]=t.useState(o.currentIndex??0),[L,I]=t.useState(""),[x,g]=t.useState(!1),[E,k]=t.useState(null);t.useEffect(()=>{f(o.currentIndex??0),I(""),k(null)},[o.promptId,o.currentIndex]);const O=o.questions||[],y=O[r]||null,z=O.length,C=!!(y?.options&&y.options.length),M=C?!!y?.allowFreeform:!0,oe=u=>{u&&(f(c=>c+1),I(""))},ve=async u=>{if(!x){g(!0),k(null);try{const c=await R.interactionSelectOption(o.promptId,u);if(!c.ok){k(c.error||"Failed to submit selection.");return}oe(c.advanced)}catch(c){k(c?.message||"Network error.")}finally{g(!1)}}},ae=async()=>{if(x)return;const u=L.trim();if(!u&&!y?.allowEmpty){k("Please enter a response.");return}g(!0),k(null);try{const c=await R.interactionSubmitText(o.promptId,u);if(!c.ok){k(c.error||"Failed to submit answer.");return}oe(c.advanced)}catch(c){k(c?.message||"Network error.")}finally{g(!1)}},p=async()=>{if(!x){g(!0),k(null);try{const u=await R.interactionSkip(o.promptId);if(!u.ok){k(u.error||"Failed to skip.");return}oe(u.advanced)}catch(u){k(u?.message||"Network error.")}finally{g(!1)}}},ue=async()=>{if(!x){g(!0);try{await R.interactionCancel(o.promptId)}catch{}}},Ie=t.useMemo(()=>{const u=[];return o.hint&&u.push(o.hint),z>1&&u.push(`Question ${r+1} of ${z}`),u.join(" · ")||void 0},[o.hint,r,z]);return s.jsxs(Tt,{open:!0,onClose:ue,wide:C&&(y?.options?.length||0)>3,children:[s.jsx(Rt,{title:o.title||"Pikiloom needs your input",description:Ie,onClose:ue}),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})]}),C&&s.jsx("div",{className:"grid grid-cols-1 gap-2 sm:grid-cols-2",children:(y.options||[]).map(u=>s.jsxs("button",{type:"button",disabled:x,onClick:()=>ve(u.value||u.label),className:_t("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:u.label}),u.description&&s.jsx("div",{className:"mt-0.5 text-xs leading-snug text-fg-4",children:u.description})]},u.value||u.label))}),M&&s.jsx("div",{children:s.jsx($t,{value:L,onChange:u=>I(u.target.value),onKeyDown:u=>{u.key==="Enter"&&!u.shiftKey&&!x&&(u.preventDefault(),ae())},placeholder:C?"Or type a custom answer…":"Type your answer…",disabled:x,autoFocus:!C})}),E&&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:E}),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(Ue,{})," 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(Fe,{variant:"ghost",size:"sm",onClick:p,disabled:x,children:"Skip"}),M&&s.jsx(Fe,{variant:"primary",size:"sm",onClick:ae,disabled:x||!L.trim()&&!y.allowEmpty,children:"Submit"})]})]})]}):s.jsxs("div",{className:"py-6 text-center text-sm text-fg-5",children:[s.jsx(Ue,{className:"mr-2 inline-block"})," Waiting for the agent…"]})]})}function pr(o){return o.isNull?o.localStreamPending||o.holdsActiveState?"reject-null":"apply":(typeof o.updatedAt=="number"?o.updatedAt:0)<o.lastAppliedUpdatedAt?"reject-stale":"apply"}function hr(o,r){return typeof r!="number"?o:r>o?r:o}function xr(o,r){return!o||!o.length?[]:r.size?o.filter(f=>!r.has(f)):o.slice()}function kr(o,r,f,L){if(!o.size)return o;const I=new Set(r);let x=!1;const g=new Map;for(const[E,k]of o){if(f-k>L||!I.has(E)){x=!0;continue}g.set(E,k)}return x?g:o}const nt=12,yt=160,Sr=96,br=[],vr=[],Ir=[],yr=20,Tr=6e4,le=new Map;function Rr(o,r){return`${o}:${r}`}function jr(o,r){for(le.delete(o),le.set(o,r);le.size>yr;)le.delete(le.keys().next().value)}const qr=t.memo(function({session:r,workdir:f,active:L=!0,onSessionChange:I,initialPendingPrompt:x,initialPendingImageUrls:g,onPendingPromptConsumed:E}){const k=rt(e=>e.locale),O=rt(e=>e.agentStatus?.agents?.find(n=>n.agent===r.agent)??null),y=O?.selectedEffort??null,z=O?.selectedModel??null,C=rt(e=>e.modelLayer),M=r.profileId?C?.profiles.find(e=>e.id===r.profileId)??null:null,oe=M?C?.providers.find(e=>e.id===M.providerId)??null:null,ve=r.profileId===void 0?O?.byokProviderName??null:oe?.name??null,ae=r.profileId===void 0?O?.byokProfileName??null:M&&M.name.trim().toLowerCase()!==M.modelId.trim().toLowerCase()?M.name:null,p=t.useMemo(()=>Kt(k),[k]),ue=zt(r.agent||""),Ie=Wt(r),u=!!x||!!(g&&g.length),[c,He]=t.useState(null),[Be,ce]=t.useState(!u),[ie,st]=t.useState(!1),[i,U]=t.useState(null),[de,fe]=t.useState(!1),[W,De]=t.useState(null),[jt,lt]=t.useState(0),[wt,Qe]=t.useState(null),[Y,ye]=t.useState([]),[Nt,Te]=t.useState([]),[me,_e]=t.useState([]),[S,X]=t.useState(x||null),[P,G]=t.useState(g||[]),[At,V]=t.useState(null),F=t.useRef(null);F.current=At;const ot=t.useRef(S);ot.current=S;const[at,q]=t.useState([]),Re=t.useRef([]);Re.current=at;const ge=t.useRef(null),[Lt,ut]=t.useState(null),[je,pe]=t.useState(null),[he,$e]=t.useState(""),[H,Ke]=t.useState(!1),Ot=!!O?.capabilities?.fork,ze=t.useRef(null),A=t.useRef(g||[]),B=t.useRef(i),We=t.useRef(de);B.current=i,We.current=de;const Ye=t.useRef(Y);Ye.current=Y;const ct=t.useRef(W);ct.current=W;const D=t.useRef(null),we=t.useRef(null),J=t.useRef(!0),Z=t.useRef(!1),Ne=t.useRef(null),Xe=t.useRef(!1),Q=t.useRef(u),xe=t.useRef(!1),ee=t.useRef(!1),Ge=t.useRef(!1),Ve=t.useRef(!1),_=t.useRef(null),Ae=t.useRef(0),ke=t.useRef(new Map),it=t.useRef({model:null,effort:null}),Mt=t.useCallback(e=>{it.current=e},[]);t.useEffect(()=>{Ge.current||!u||(Ge.current=!0,x&&!S&&X(x),g&&g.length&&!P.length&&(G(g),A.current=g),ce(!1),lt(e=>e+1),E?.())},[u,E]);const j=t.useCallback(()=>{X(null),G(e=>{for(const n of e)URL.revokeObjectURL(n);return[]}),A.current=[],V(null)},[]),te=t.useCallback(()=>{q(e=>{if(!e.length)return e;for(const n of e)for(const l of n.imageUrls)URL.revokeObjectURL(l);return[]}),ge.current=null},[]),dt=t.useCallback((e,n)=>{const l=Zt({streaming:We.current,liveStreamPhase:B.current?.phase??null,streamPhase:ct.current,queuedTaskCount:Ye.current.length,pendingQueuedCount:Re.current.length}),a=n||[];if(l){const d=`local-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,8)}`;ge.current=d,q(h=>[...h,{localId:d,taskId:null,prompt:e||"",imageUrls:a}]);return}B.current?.phase==="done"&&U(null);for(const d of A.current)URL.revokeObjectURL(d);ge.current=null,X(e||null),G(a),A.current=a,V(null)},[]),Pt=t.useCallback(e=>{const n=ge.current;if(n){ge.current=null,q(l=>{const a=l.findIndex(h=>h.localId===n);if(a<0)return l;const d=l.slice();return d[a]={...d[a],taskId:e},d});return}V(e)},[]),Et=t.useCallback(async()=>{if(!je)return;const e=he.trim();if(e){Ke(!0);try{const n=await R.forkSession(f,r.agent||"",r.sessionId,je.atTurn,e,{});if(!n.ok||!n.sessionKey){Ke(!1);return}const[l,a]=n.sessionKey.split(":");pe(null),$e(""),I?.({agent:l,sessionId:a,workdir:f})}finally{Ke(!1)}}},[je,he,f,r.agent,r.sessionId,I]);ze.current=Et;const Le=t.useCallback(async(e,n={})=>{try{const l=await er({workdir:f,agent:r.agent||"",sessionId:r.sessionId,rich:!0,turnOffset:e.turnOffset,turnLimit:e.turnLimit,lastNTurns:e.lastNTurns},{force:n.force});return l.ok?St(l):null}catch{return null}},[f,r.agent,r.sessionId]),w=t.useCallback(async({keepOlder:e,force:n=!1,scrollToBottom:l=!1})=>{const a=r.sessionId;if(Ne.current===a)return!1;Ne.current=a;try{const d=await Le({turnOffset:0,turnLimit:nt},{force:n});if(!d||r.sessionId!==a)return!1;if(l&&(Z.current=!0),He(h=>!h||!e?d:tr(h,d)),ce(!1),xe.current&&(xe.current=!1,j()),ee.current){const h=ee.current;ee.current=!1;const ne=h!==!0?h.taskId:null;B.current&&(h===!0||B.current.taskId===ne)&&U(null)}return!0}finally{Ne.current===a&&(Ne.current=null)}},[Le,j,r.sessionId]),Oe=t.useCallback(async()=>{if(!c?.hasOlder||Xe.current)return;const e=D.current;e&&(we.current={scrollHeight:e.scrollHeight,scrollTop:e.scrollTop}),Xe.current=!0,st(!0);try{const n=await Le({turnOffset:Math.max(0,c.totalTurns-c.startTurn),turnLimit:nt});n?He(l=>l?rr(l,n):n):we.current=null}finally{Xe.current=!1,st(!1)}},[Le,c]),Me=t.useRef(null),re=t.useCallback((e,n="ws")=>{if(pr({updatedAt:e?.updatedAt,isNull:!e,lastAppliedUpdatedAt:Ae.current,localStreamPending:Q.current,holdsActiveState:We.current||Ye.current.length>0})!=="apply")return;if(e&&(Ae.current=hr(Ae.current,e.updatedAt)),e?.sessionId&&e.sessionId!==r.sessionId&&(Ve.current=!0,Se.current=`${r.agent}:${e.sessionId}`,I?.({agent:r.agent||"",sessionId:e.sessionId,workdir:f})),!e){const m=Me.current;fe(!1),m==="streaming"?(xe.current=!0,ee.current=!0,w({keepOlder:!0,force:!0,scrollToBottom:J.current})):U(null),Q.current&&m!=="streaming"?w({keepOlder:!0,force:!0}):(m==="done"&&(j(),te()),m!==null&&(Q.current=!1)),Qe(null),De(null),ye([]),Te([]),_e([]),Me.current=null;return}De(e.phase),Qe(e.taskId||null);const a=[];e.taskId&&a.push(e.taskId),Array.isArray(e.queuedTaskIds)&&a.push(...e.queuedTaskIds),ke.current=kr(ke.current,a,Date.now(),Tr);const d=ke.current,h=xr(e.queuedTaskIds,d),ne=(Array.isArray(e.queuedTasks)?e.queuedTasks:[]).filter(m=>!d.has(m.taskId));if(ye(h.length?h:br),Te(ne.length?ne:vr),_e(Array.isArray(e.interactions)&&e.interactions.length?e.interactions:Ir),nr({pendingTaskId:F.current,streamTaskId:e.taskId||null,queuedTaskIds:e.queuedTaskIds})){const m=F.current,T=ot.current||"",v=A.current;q(b=>b.some(se=>se.taskId===m)?b:[...b,{localId:`demote-${m}`,taskId:m,prompt:T,imageUrls:v}]),X(null),G([]),A.current=[],V(null),F.current=null}if(e.phase==="streaming"){if(U({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}),fe(!0),e.taskId&&e.taskId!==F.current){const m=Re.current,T=m.findIndex(v=>v.taskId===e.taskId);if(T>=0){const v=m[T];for(const b of A.current)URL.revokeObjectURL(b);X(v.prompt||null),G(v.imageUrls),A.current=v.imageUrls,V(e.taskId),q(b=>b.filter((se,K)=>K!==T))}}J.current&&(Z.current=!0)}else if(e.phase==="queued")U(null),fe(!1);else if(e.phase==="done"){const m=sr(B.current?.taskId??null,e.taskId||null),T=h.length>0;if(m){fe(!1),U(K=>K?{...K,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}:K);const v=B.current,b=!!v&&lr(v),se=!!e.incomplete&&b&&!T;if(Me.current!=="done"){T||(xe.current=!0),ee.current=se?!1:{taskId:e.taskId||null},w({keepOlder:!0,force:!0,scrollToBottom:J.current});const K=r.agent||"",Bt=r.sessionId,Dt=Se.current;_.current&&clearTimeout(_.current),_.current=setTimeout(()=>{_.current=null,R.getSessionStreamState(K,Bt).then(Qt=>{Se.current===Dt&&Je.current(Qt.state,"seed")}).catch(()=>{})},900)}}T||(Q.current=!1)}const $=new Set;if(e.taskId&&$.add(e.taskId),Array.isArray(e.queuedTaskIds))for(const m of e.queuedTaskIds)$.add(m);q(m=>{let T=!1;const v=[];for(const b of m)if(!b.taskId||$.has(b.taskId))v.push(b);else{for(const se of b.imageUrls)URL.revokeObjectURL(se);T=!0}return T?v:m}),Me.current=e.phase},[j,te,w,r.sessionId,r.agent,I,f]),Je=t.useRef(re);Je.current=re;const ft=t.useCallback(()=>{Q.current=!0,lt(e=>e+1)},[]);t.useEffect(()=>()=>{_.current&&(clearTimeout(_.current),_.current=null)},[]);const Ct=t.useCallback(async e=>{try{ke.current.set(e,Date.now()),await R.recallSessionMessage(e),F.current===e&&j(),q(n=>{let l=!1;const a=[];for(const d of n)if(d.taskId===e){for(const h of d.imageUrls)URL.revokeObjectURL(h);l=!0}else a.push(d);return l?a:n}),ye(n=>n.filter(l=>l!==e)),Te(n=>n.filter(l=>l.taskId!==e)),Qe(n=>n===e?null:n)}catch{}},[j]),qt=t.useCallback(async e=>{const n=Re.current.find(l=>l.taskId===e)||null;if(n&&n.imageUrls.length>0){for(const l of A.current)URL.revokeObjectURL(l);X(n.prompt||null),G(n.imageUrls),A.current=n.imageUrls,V(e),F.current=e,q(l=>l.filter(a=>a.taskId!==e))}try{await R.steerSession(e)}catch{}},[]),Ut=t.useCallback(async()=>{try{await R.stopSession(r.agent||"",r.sessionId)}catch{}},[r.agent,r.sessionId]),Pe=Rr(r.agent||"",r.sessionId);t.useEffect(()=>{if(Ve.current){Ve.current=!1;let d=!1;return w({keepOlder:!0,force:!0}).finally(()=>{d||ce(!1)}),()=>{d=!0}}let e=!1;const n=or({workdir:f,agent:r.agent||"",sessionId:r.sessionId,rich:!0,turnOffset:0,turnLimit:nt},{allowStale:!0}),l=u&&!Ge.current,a=n?.ok?St(n):le.get(Pe)||null;return ce(l?!1:!a),He(a),U(null),fe(!1),De(null),ye([]),Te([]),_e([]),Ae.current=0,ke.current=new Map,l||(j(),te(),Q.current=!1,xe.current=!1,ee.current=!1),J.current=!0,Z.current=!0,l||w({keepOlder:!1,force:!0}).finally(()=>{e||ce(!1)}),()=>{e=!0}},[w,r.agent,r.sessionId,f,Pe,j,te]),t.useEffect(()=>{c&&c.turns.length>0&&jr(Pe,c)},[Pe,c]),t.useEffect(()=>{L&&w({keepOlder:!0,force:!0})},[L,w]);const Se=t.useRef(`${r.agent}:${r.sessionId}`);Se.current=`${r.agent}:${r.sessionId}`,Yt("stream-update",t.useCallback(e=>{e.key===Se.current&&re(e.snapshot??null)},[re])),t.useEffect(()=>{let e=!0;return R.getSessionStreamState(r.agent||"",r.sessionId).then(n=>{e&&Je.current(n.state,"seed")}).catch(()=>{}),()=>{e=!1}},[r.agent,r.sessionId,jt]),Xt(t.useCallback(()=>{R.getSessionStreamState(r.agent||"",r.sessionId).then(e=>{re(e.state,"seed")}).catch(()=>{}),w({keepOlder:!0,force:!0})},[re,r.agent,r.sessionId,w])),t.useEffect(()=>{!Q.current&&Ie!=="running"&&!de&&!i&&!W&&Y.length===0&&(j(),te())},[Ie,de,i,W,Y.length,j,te]),t.useLayoutEffect(()=>{const e=we.current,n=D.current;!e||!n||(we.current=null,n.scrollTop=e.scrollTop+(n.scrollHeight-e.scrollHeight))},[c?.turns.length]),t.useLayoutEffect(()=>{if(!Z.current)return;const e=D.current;e&&(Z.current=!1,e.scrollTop=e.scrollHeight,requestAnimationFrame(()=>{J.current&&(e.scrollTop=e.scrollHeight)}))},[c,i]),t.useLayoutEffect(()=>{if(!S)return;const e=D.current;e&&(e.scrollTop=e.scrollHeight)},[S]),t.useEffect(()=>{if(!c?.hasOlder||Be||ie)return;const e=D.current;e&&e.scrollHeight<=e.clientHeight+yt&&Oe()},[c?.hasOlder,c?.turns.length,Oe,Be,ie]);const Ft=t.useCallback(()=>{const e=D.current;if(!e)return;const n=e.scrollHeight-e.scrollTop-e.clientHeight;J.current=n<=Sr,e.scrollTop<=yt&&Oe()},[Oe]),be=i?.model||r.model||z||null,Ze=Gt(r.agent||"",i?.effort||r.thinkingEffort||y||null,r.workflowEnabled??O?.workflowEnabled)||null,mt=ae&&(!be||be===z)?ae:be?Vt(be):null,et=Jt(r,{streaming:de,hasLiveStream:!!i,streamPhase:W,queuedTaskCount:Y.length}),N=c?.turns||[],tt=t.useMemo(()=>{if(!P.length||!N.length)return!1;const e=N[N.length-1];return!e.user||!Ce(e.user.text,S)?!1:e.user.blocks.filter(l=>l.type==="image").length<P.length},[N,S,P.length]),gt=t.useMemo(()=>P.length?P.map(e=>({type:"image",content:e})):!S||!i?.questionBlocks?.length?[]:Ce(S,i.question)?i.questionBlocks:[],[P,S,i]),pt=i?.question||null,Ee=ar(S,pt),ht=bt(pt,S),xt=N.length>0?N[N.length-1]?.user?.text:null,Ht=!!Ee&&N.length>0&&qe(xt,Ee),kt=t.useMemo(()=>{let e=N;if(tt){const $=e[e.length-1];e=[...e.slice(0,-1),{...$,user:null}]}if(!i||!e.length)return e;const n=e[e.length-1],l=Ee;if(!n.assistant)return!!n.user&&!!l&&!Ce(n.user.text,l)&&(qe(n.user.text,l)||bt(l,n.user.text))?[...e.slice(0,-1),{...n,user:{...n.user,text:l}}]:e;const a=(i.text||"").trim(),d=n.assistant.text?.trim()||"";if(!(l!=null?qe(n.user?.text,l):!!d&&!!a&&(a.startsWith(d)||d.startsWith(a))))return e;const ne=n.user&&l&&!Ce(n.user.text,l)?{...n.user,text:l}:n.user;return[...e.slice(0,-1),{...n,user:ne,assistant:null}]},[N,i,Ee,tt]);return s.jsxs("div",{className:"flex flex-col h-full overflow-hidden",children:[s.jsx("div",{ref:D,onScroll:Ft,className:"flex-1 overflow-y-auto overscroll-contain",children:Be&&!S&&!P.length&&!i?s.jsx("div",{className:"flex items-center justify-center py-20",children:s.jsx(Ue,{className:"h-5 w-5 text-fg-4"})}):kt.length===0&&!S&&!P.length&&!i&&!et?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:[(c?.hasOlder||ie)&&s.jsxs("div",{className:"mb-4 flex items-center justify-center gap-2 text-[11px] text-fg-5",children:[ie?s.jsx(Ue,{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(ie?"hub.loadingOlderTurns":"hub.loadOlderTurnsHint")})]}),r.migratedFrom?.kind==="fork"&&r.migratedFrom.sessionId&&s.jsxs("button",{type:"button",onClick:()=>I?.({agent:r.migratedFrom.agent||r.agent||"",sessionId:r.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:`#${r.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:["#",r.migratedFrom.sessionId.slice(0,8)]}),typeof r.migratedFrom.forkedAtTurn=="number"&&s.jsxs("span",{className:"text-fg-5/70",children:["· ",p("hub.forkBadgeAt").replace("{turn}",String(r.migratedFrom.forkedAtTurn+1))]})]}),kt.map((e,n)=>{const l=(c?.startTurn||0)+n;return s.jsx(ur,{turn:e,turnIndex:l,agent:r.agent||"",meta:ue,model:mt,effort:Ze,providerName:ve,t:p,workdir:f,onResend:a=>{Z.current=!0,dt(a);const d=it.current;R.sendSessionMessage(f,r.agent||"",r.sessionId,a,{model:d.model||be||void 0,effort:d.effort||Ze||void 0}).then(h=>{h.ok&&ft()}).catch(()=>{j()})},onEdit:a=>ut(a),onFork:Ot?a=>{$e(""),pe({atTurn:a})}:void 0},`${c?.startTurn||0}:${n}`)}),et&&s.jsx("div",{className:"mb-5 animate-in",children:s.jsx(cr,{detail:et,t:p})}),(S||gt.length>0)&&!ht&&(tt||!Ht)&&s.jsxs("div",{className:"session-turn",children:[s.jsx(vt,{text:S||"",blocks:gt,t:p}),!i&&s.jsx("div",{className:"mt-3 mb-5 animate-in",children:s.jsx(ir,{className:"text-fg-5"})})]}),i&&It(i)&&i.question&&(!S||ht)&&!(N.length>0&&qe(xt,i.question))&&s.jsx("div",{className:"session-turn",children:s.jsx(vt,{text:i.question,blocks:i.questionBlocks||void 0,t:p})}),i&&It(i)&&s.jsxs("div",{className:"mb-6",children:[s.jsx(dr,{agent:r.agent||"",meta:ue,model:mt,effort:Ze,providerName:ve,previewMeta:i.previewMeta,hideContextUsage:!0}),s.jsx(fr,{stream:i,t:p,workdir:f})]}),s.jsx("div",{className:"h-4"})]})}),s.jsx(mr,{session:r,workdir:f,onStreamQueued:ft,onSendStart:dt,onSendTaskAssigned:Pt,onSessionChange:I,t:p,streamPhase:W,streamTaskId:wt,queuedTaskIds:Y,queuedTasks:Nt,pendingQueuedSends:at,onRecall:Ct,onSteer:qt,onStopAll:Ut,editDraft:Lt,onEditDraftConsumed:()=>ut(null),onSelectionChange:Mt}),je&&s.jsxs(Tt,{open:!0,onClose:()=>{H||pe(null)},children:[s.jsx(Rt,{title:p("hub.forkPromptTitle"),description:p("hub.forkPromptHint"),onClose:()=>{H||pe(null)}}),s.jsx("textarea",{autoFocus:!0,value:he,disabled:H,onChange:e=>$e(e.target.value),onKeyDown:e=>{e.key==="Enter"&&(e.metaKey||e.ctrlKey)&&he.trim()&&!H&&(e.preventDefault(),ze.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(Fe,{variant:"ghost",disabled:H,onClick:()=>pe(null),children:p("modal.cancel")}),s.jsx(Fe,{variant:"primary",disabled:H||!he.trim(),onClick:()=>{ze.current?.()},children:p(H?"hub.forkSubmitting":"hub.forkSubmit")})]})]}),L&&me.length>0&&s.jsx(gr,{snapshot:me[me.length-1]},me[me.length-1].promptId)]})});export{qr as SessionPanel};
|