@songsid/agend 2.2.0-beta.1 → 2.2.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/README.zh-TW.md +1 -1
- package/dist/agent-cli-instructions.md +16 -9
- package/dist/agent-cli.js +15 -7
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.js +24 -9
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.d.ts +47 -0
- package/dist/agent-whats-new.js +147 -0
- package/dist/agent-whats-new.js.map +1 -0
- package/dist/auth-api.d.ts +5 -3
- package/dist/auth-api.js +19 -3
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/antigravity.d.ts +8 -0
- package/dist/backend/antigravity.js +32 -9
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-carry.d.ts +22 -0
- package/dist/backend/claude-carry.js +170 -0
- package/dist/backend/claude-carry.js.map +1 -0
- package/dist/backend/claude-code.d.ts +65 -2
- package/dist/backend/claude-code.js +226 -33
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex-metadata.d.ts +33 -0
- package/dist/backend/codex-metadata.js +122 -18
- package/dist/backend/codex-metadata.js.map +1 -1
- package/dist/backend/codex.d.ts +3 -0
- package/dist/backend/codex.js +17 -1
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/credential-profile.d.ts +40 -3
- package/dist/backend/credential-profile.js +113 -5
- package/dist/backend/credential-profile.js.map +1 -1
- package/dist/backend/effort-metadata.d.ts +37 -1
- package/dist/backend/effort-metadata.js +86 -15
- package/dist/backend/effort-metadata.js.map +1 -1
- package/dist/backend/grok.d.ts +4 -0
- package/dist/backend/grok.js +14 -0
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro-engine-ledger.d.ts +5 -0
- package/dist/backend/kiro-engine-ledger.js +20 -0
- package/dist/backend/kiro-engine-ledger.js.map +1 -1
- package/dist/backend/kiro-identity.d.ts +6 -0
- package/dist/backend/kiro-identity.js +19 -5
- package/dist/backend/kiro-identity.js.map +1 -1
- package/dist/backend/kiro.d.ts +39 -3
- package/dist/backend/kiro.js +143 -19
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/launch-effort.d.ts +61 -0
- package/dist/backend/launch-effort.js +170 -0
- package/dist/backend/launch-effort.js.map +1 -0
- package/dist/backend/muse.d.ts +84 -0
- package/dist/backend/muse.js +449 -53
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +47 -4
- package/dist/backend/types.js +12 -1
- package/dist/backend/types.js.map +1 -1
- package/dist/backend-command.d.ts +91 -0
- package/dist/backend-command.js +122 -0
- package/dist/backend-command.js.map +1 -0
- package/dist/background-work-ended.d.ts +18 -0
- package/dist/background-work-ended.js +30 -0
- package/dist/background-work-ended.js.map +1 -0
- package/dist/cache-service.d.ts +2 -0
- package/dist/cache-service.js +2 -1
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +36 -1
- package/dist/channel/adapters/discord.js +126 -4
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +42 -2
- package/dist/channel/adapters/telegram.js +142 -23
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-server.js +9 -1
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.d.ts +11 -0
- package/dist/channel/mcp-tools.js +26 -13
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/message-edit-errors.d.ts +5 -0
- package/dist/channel/message-edit-errors.js +24 -0
- package/dist/channel/message-edit-errors.js.map +1 -0
- package/dist/channel/telegram-markdown.d.ts +6 -0
- package/dist/channel/telegram-markdown.js +104 -0
- package/dist/channel/telegram-markdown.js.map +1 -0
- package/dist/channel/tool-router.d.ts +2 -0
- package/dist/channel/tool-router.js +10 -4
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/channel/types.d.ts +55 -3
- package/dist/cli.js +167 -39
- package/dist/cli.js.map +1 -1
- package/dist/command-table.d.ts +2 -2
- package/dist/command-table.js +6 -3
- package/dist/command-table.js.map +1 -1
- package/dist/config-validator.js +59 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/cross-instance-envelope.d.ts +15 -2
- package/dist/cross-instance-envelope.js +29 -5
- package/dist/cross-instance-envelope.js.map +1 -1
- package/dist/daemon.d.ts +156 -1
- package/dist/daemon.js +773 -89
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.d.ts +70 -8
- package/dist/delivery-outbox.js +315 -24
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/delivery-reconciliation.d.ts +18 -1
- package/dist/delivery-reconciliation.js +67 -2
- package/dist/delivery-reconciliation.js.map +1 -1
- package/dist/fleet-context.d.ts +10 -1
- package/dist/fleet-manager.d.ts +404 -13
- package/dist/fleet-manager.js +2039 -210
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/backend-providers/SKILL.md +1 -1
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +92 -21
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +27 -3
- package/dist/general-knowledge/skills/delegation-playbook/SKILL.md +8 -8
- package/dist/general-knowledge/skills/fleet-config/SKILL.md +5 -5
- package/dist/general-knowledge/skills/fleet-health/SKILL.md +4 -4
- package/dist/general-knowledge/skills/fleet-restart/SKILL.md +13 -13
- package/dist/general-knowledge/skills/instance-lifecycle/SKILL.md +3 -2
- package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +6 -0
- package/dist/general-knowledge/skills/model-discovery/SKILL.md +12 -9
- package/dist/general-knowledge/skills/multi-channel/SKILL.md +8 -2
- package/dist/general-knowledge/skills/scheduling/SKILL.md +5 -3
- package/dist/general-knowledge/skills/session-management/SKILL.md +6 -5
- package/dist/general-knowledge/skills/tui-effort/SKILL.md +2 -2
- package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +8 -3
- package/dist/general-knowledge/steering/core-rules.md +8 -7
- package/dist/host-memory.d.ts +10 -0
- package/dist/host-memory.js +33 -1
- package/dist/host-memory.js.map +1 -1
- package/dist/human-delivery-receipts.d.ts +83 -0
- package/dist/human-delivery-receipts.js +194 -0
- package/dist/human-delivery-receipts.js.map +1 -0
- package/dist/instance-avatar.d.ts +36 -0
- package/dist/instance-avatar.js +57 -0
- package/dist/instance-avatar.js.map +1 -0
- package/dist/instance-config-impact.js +6 -0
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +29 -6
- package/dist/instance-lifecycle.js +79 -28
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.d.ts +3 -1
- package/dist/instructions.js +11 -8
- package/dist/instructions.js.map +1 -1
- package/dist/kiro-log-gate.d.ts +42 -0
- package/dist/kiro-log-gate.js +39 -0
- package/dist/kiro-log-gate.js.map +1 -0
- package/dist/locale.js +226 -44
- package/dist/locale.js.map +1 -1
- package/dist/log-service.d.ts +99 -0
- package/dist/log-service.js +243 -0
- package/dist/log-service.js.map +1 -0
- package/dist/log-steps.d.ts +169 -0
- package/dist/log-steps.js +858 -0
- package/dist/log-steps.js.map +1 -0
- package/dist/login-controller.d.ts +20 -3
- package/dist/login-controller.js +71 -19
- package/dist/login-controller.js.map +1 -1
- package/dist/login-flows.d.ts +5 -2
- package/dist/login-flows.js +5 -4
- package/dist/login-flows.js.map +1 -1
- package/dist/memory-pressure.d.ts +13 -1
- package/dist/memory-pressure.js +50 -10
- package/dist/memory-pressure.js.map +1 -1
- package/dist/needs-you-hub.js +2 -0
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you-live.d.ts +6 -0
- package/dist/needs-you-live.js +23 -3
- package/dist/needs-you-live.js.map +1 -1
- package/dist/next-steps.js +1 -1
- package/dist/next-steps.js.map +1 -1
- package/dist/outbound-handlers.d.ts +100 -0
- package/dist/outbound-handlers.js +284 -97
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +38 -15
- package/dist/outbound-schemas.js +36 -8
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-link-progress.d.ts +1 -1
- package/dist/public-link-progress.js +8 -2
- package/dist/public-link-progress.js.map +1 -1
- package/dist/public-preview.d.ts +101 -0
- package/dist/public-preview.js +236 -0
- package/dist/public-preview.js.map +1 -0
- package/dist/public-web-gateway.js +14 -4
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +24 -1
- package/dist/public-web-link.js +63 -9
- package/dist/public-web-link.js.map +1 -1
- package/dist/reply-buttons-controller.d.ts +89 -5
- package/dist/reply-buttons-controller.js +273 -8
- package/dist/reply-buttons-controller.js.map +1 -1
- package/dist/reply-buttons.d.ts +48 -3
- package/dist/reply-buttons.js +105 -10
- package/dist/reply-buttons.js.map +1 -1
- package/dist/reply-polls.d.ts +85 -0
- package/dist/reply-polls.js +154 -0
- package/dist/reply-polls.js.map +1 -0
- package/dist/restart-guard.d.ts +6 -3
- package/dist/restart-guard.js +6 -3
- package/dist/restart-guard.js.map +1 -1
- package/dist/restart-progress.d.ts +33 -0
- package/dist/restart-progress.js +30 -0
- package/dist/restart-progress.js.map +1 -1
- package/dist/service-activation.d.ts +15 -7
- package/dist/service-activation.js +46 -29
- package/dist/service-activation.js.map +1 -1
- package/dist/service-installer.d.ts +31 -0
- package/dist/service-installer.js +41 -2
- package/dist/service-installer.js.map +1 -1
- package/dist/settings-api.js +85 -25
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +72 -1
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +28 -4
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.js +7 -0
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/setup-form.d.ts +1 -1
- package/dist/setup-form.js +1 -1
- package/dist/spawn-gate.js +5 -1
- package/dist/spawn-gate.js.map +1 -1
- package/dist/spawn-hold-reply.d.ts +12 -0
- package/dist/spawn-hold-reply.js +39 -0
- package/dist/spawn-hold-reply.js.map +1 -0
- package/dist/status-emojis.d.ts +10 -3
- package/dist/status-emojis.js +11 -2
- package/dist/status-emojis.js.map +1 -1
- package/dist/systemd-peer-bus.d.ts +95 -0
- package/dist/systemd-peer-bus.js +430 -0
- package/dist/systemd-peer-bus.js.map +1 -0
- package/dist/systemd-runtime.d.ts +27 -1
- package/dist/systemd-runtime.js +69 -7
- package/dist/systemd-runtime.js.map +1 -1
- package/dist/tips.js +4 -4
- package/dist/tips.js.map +1 -1
- package/dist/tool-permissions.js +3 -3
- package/dist/tool-permissions.js.map +1 -1
- package/dist/tool-progress.js +3 -2
- package/dist/tool-progress.js.map +1 -1
- package/dist/topic-commands.d.ts +42 -2
- package/dist/topic-commands.js +101 -11
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-delta-reader.d.ts +3 -3
- package/dist/transcript-delta-reader.js +8 -8
- package/dist/transcript-delta-reader.js.map +1 -1
- package/dist/transcript-jsonl.d.ts +80 -3
- package/dist/transcript-jsonl.js +147 -10
- package/dist/transcript-jsonl.js.map +1 -1
- package/dist/transcript-monitor.d.ts +19 -0
- package/dist/transcript-monitor.js +87 -15
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.d.ts +78 -0
- package/dist/transcript-sources.js +375 -64
- package/dist/transcript-sources.js.map +1 -1
- package/dist/transcript-turns.d.ts +15 -2
- package/dist/transcript-turns.js +15 -2
- package/dist/transcript-turns.js.map +1 -1
- package/dist/tunnel/cloudflared.d.ts +7 -15
- package/dist/tunnel/cloudflared.js +36 -1
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/purpose-lane.d.ts +5 -1
- package/dist/tunnel/purpose-lane.js +14 -7
- package/dist/tunnel/purpose-lane.js.map +1 -1
- package/dist/tunnel/types.d.ts +5 -1
- package/dist/tunnel/types.js.map +1 -1
- package/dist/turn-resume.d.ts +2 -0
- package/dist/turn-resume.js +1 -1
- package/dist/turn-resume.js.map +1 -1
- package/dist/types.d.ts +25 -0
- package/dist/ui/chat-commands.js +1 -0
- package/dist/ui/chat-emojis.js +33 -0
- package/dist/ui/chat-mentions.js +63 -0
- package/dist/ui/chat-render.js +61 -4
- package/dist/ui/chat-store.js +37 -3
- package/dist/ui/chat-strings.js +40 -20
- package/dist/ui/chat-thread.js +111 -12
- package/dist/ui/chat-time.js +80 -0
- package/dist/ui/panel-chat.js +41 -10
- package/dist/ui/panel-details.js +2 -0
- package/dist/ui/panel-fleet.js +199 -57
- package/dist/ui/panel-log.js +428 -0
- package/dist/ui/panel-needs.js +6 -2
- package/dist/ui/panel-settings.js +184 -32
- package/dist/ui/preview-panel.js +55 -15
- package/dist/ui/preview-status.js +28 -0
- package/dist/ui/preview.js +44 -3
- package/dist/ui/settings-dialogs.js +7 -2
- package/dist/ui/settings-model.js +2 -1
- package/dist/ui/settings-strings.js +56 -4
- package/dist/ui/settings-token.js +1 -1
- package/dist/ui/shared/app-i18n.js +43 -17
- package/dist/ui/shared/app-prefs.js +250 -0
- package/dist/ui/shared/app-route.js +7 -2
- package/dist/ui/shared/app-shell.js +165 -8
- package/dist/ui/shared/app-store.js +5 -1
- package/dist/ui/shared/app-stream.js +26 -5
- package/dist/ui/shared/app.css +163 -22
- package/dist/ui/shared/app.js +11 -1
- package/dist/ui/shared/fonts/README.md +1 -1
- package/dist/ui/shared/header-tools.js +55 -5
- package/dist/ui/shared/instance-nav.js +241 -45
- package/dist/ui/shared/instance-switch.js +4 -4
- package/dist/ui/shared/panel-view.js +6 -0
- package/dist/ui/shared/signin.js +7 -7
- package/dist/ui/shared/theme.js +39 -7
- package/dist/ui/shared/tokens.css +1 -1
- package/dist/ui/shared/ui-icons.js +1 -0
- package/dist/ui/shared/usage-dialog.js +23 -5
- package/dist/ui/shared/view-strings.js +32 -4
- package/dist/ui/signin.html +1 -1
- package/dist/unrecognised-prompt.d.ts +23 -0
- package/dist/unrecognised-prompt.js +74 -0
- package/dist/unrecognised-prompt.js.map +1 -0
- package/dist/update-check.d.ts +31 -0
- package/dist/update-check.js +47 -0
- package/dist/update-check.js.map +1 -1
- package/dist/update-install.d.ts +28 -2
- package/dist/update-install.js +22 -4
- package/dist/update-install.js.map +1 -1
- package/dist/update-launch.d.ts +46 -1
- package/dist/update-launch.js +146 -3
- package/dist/update-launch.js.map +1 -1
- package/dist/update-marker.d.ts +51 -0
- package/dist/update-marker.js +61 -0
- package/dist/update-marker.js.map +1 -1
- package/dist/update-preflight.d.ts +100 -0
- package/dist/update-preflight.js +264 -0
- package/dist/update-preflight.js.map +1 -0
- package/dist/updater-caller.d.ts +31 -0
- package/dist/updater-caller.js +109 -0
- package/dist/updater-caller.js.map +1 -0
- package/dist/usage/i18n-keys.d.ts +1 -1
- package/dist/usage/i18n-keys.js +1 -1
- package/dist/usage/i18n-keys.js.map +1 -1
- package/dist/usage/providers.d.ts +34 -5
- package/dist/usage/providers.js +160 -27
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/statusline-usage.d.ts +6 -2
- package/dist/usage/statusline-usage.js +10 -4
- package/dist/usage/statusline-usage.js.map +1 -1
- package/dist/view-api.d.ts +1 -1
- package/dist/view-api.js +38 -8
- package/dist/view-api.js.map +1 -1
- package/dist/wake-coordinator.d.ts +11 -0
- package/dist/wake-coordinator.js +38 -4
- package/dist/wake-coordinator.js.map +1 -1
- package/dist/web-api.d.ts +89 -4
- package/dist/web-api.js +551 -45
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.d.ts +2 -2
- package/dist/web-auth.js +9 -4
- package/dist/web-auth.js.map +1 -1
- package/dist/web-chat-history.d.ts +12 -1
- package/dist/web-chat-history.js +29 -7
- package/dist/web-chat-history.js.map +1 -1
- package/dist/web-commands.d.ts +27 -4
- package/dist/web-commands.js +19 -4
- package/dist/web-commands.js.map +1 -1
- package/dist/web-host-guard.d.ts +3 -11
- package/dist/web-host-guard.js +3 -12
- package/dist/web-host-guard.js.map +1 -1
- package/dist/web-images.d.ts +47 -0
- package/dist/web-images.js +169 -0
- package/dist/web-images.js.map +1 -0
- package/dist/web-login.d.ts +14 -0
- package/dist/web-login.js +4 -2
- package/dist/web-login.js.map +1 -1
- package/dist/web-mentions.d.ts +44 -0
- package/dist/web-mentions.js +75 -0
- package/dist/web-mentions.js.map +1 -0
- package/dist/web-prefs.d.ts +81 -0
- package/dist/web-prefs.js +297 -0
- package/dist/web-prefs.js.map +1 -0
- package/dist/web-preview.d.ts +14 -1
- package/dist/web-preview.js +35 -13
- package/dist/web-preview.js.map +1 -1
- package/dist/web-profile.d.ts +32 -0
- package/dist/web-profile.js +132 -0
- package/dist/web-profile.js.map +1 -0
- package/dist/web-reply-files.d.ts +45 -0
- package/dist/web-reply-files.js +223 -0
- package/dist/web-reply-files.js.map +1 -0
- package/dist/web-schedules.d.ts +12 -0
- package/dist/web-schedules.js +34 -0
- package/dist/web-schedules.js.map +1 -0
- package/dist/web-server-drain.d.ts +10 -0
- package/dist/web-server-drain.js +50 -0
- package/dist/web-server-drain.js.map +1 -0
- package/dist/web-session.d.ts +50 -1
- package/dist/web-session.js +63 -6
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +23 -0
- package/dist/web-settings-patch.js +77 -0
- package/dist/web-settings-patch.js.map +1 -0
- package/dist/web-shell-routes.d.ts +5 -1
- package/dist/web-shell-routes.js +10 -1
- package/dist/web-shell-routes.js.map +1 -1
- package/dist/web-upload.d.ts +17 -4
- package/dist/web-upload.js +64 -13
- package/dist/web-upload.js.map +1 -1
- package/dist/workflow-templates/default.md +1 -1
- package/package.json +1 -1
- package/templates/launchd.plist.ejs +3 -1
- package/templates/systemd.service.ejs +2 -1
|
@@ -0,0 +1,858 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #1218: an instance's work, step by step, for the web Log tab (紀錄) — the step model and the per-backend parsers.
|
|
3
|
+
* Design: issue #1218 comment 6142912335 and the user's decisions after it; docs/design/1218-live-activity.md.
|
|
4
|
+
*
|
|
5
|
+
* A parser turns one backend's transcript records, in file order, into steps: what a person asked (user), what the
|
|
6
|
+
* agent said (assistant — never its thinking), each tool call with its arguments and, once seen, its result (an
|
|
7
|
+
* update of the same step, by id), errors, and where a turn ended. Every string a step carries is masked and
|
|
8
|
+
* bounded HERE, before it leaves this module: known secret shapes and long mixed-case opaque tokens are masked (the
|
|
9
|
+
* user's decision: only actual secret values are hidden, nothing by category), and text is cut with the count of what
|
|
10
|
+
* was cut, so the page can say so. The page renders words from the structured fields (kind, cat, subject), so the
|
|
11
|
+
* labels follow its language.
|
|
12
|
+
*
|
|
13
|
+
* Nothing here reads a file except `readTailRecords` (bounded, async, for an on-demand backfill).
|
|
14
|
+
*/
|
|
15
|
+
import { open } from "node:fs/promises";
|
|
16
|
+
import { LINE_MAX_BYTES, LONG_HEAD_BYTES, lastLineBoundary } from "./transcript-jsonl.js";
|
|
17
|
+
import { redactSecrets } from "./tool-progress.js";
|
|
18
|
+
export const SUBJECT_MAX = 300;
|
|
19
|
+
export const INPUT_MAX = 2000;
|
|
20
|
+
export const TEXT_MAX = 4000;
|
|
21
|
+
export const RESULT_MAX = 4000;
|
|
22
|
+
/* ------------------------------------------------------------------ masking and bounds */
|
|
23
|
+
/**
|
|
24
|
+
* A run of 32+ token characters that mixes upper case, lower case and digits — the shape of a base64/random secret.
|
|
25
|
+
* Git SHAs and UUIDs (lower-case hex), long instance names and paths (which have "/") are not masked.
|
|
26
|
+
*/
|
|
27
|
+
const OPAQUE = /(?<![A-Za-z0-9_+=-])[A-Za-z0-9_+=-]{32,}(?![A-Za-z0-9_+=-])/g;
|
|
28
|
+
function looksOpaque(s) { return /[a-z]/.test(s) && /[A-Z]/.test(s) && /[0-9]/.test(s); }
|
|
29
|
+
/** A name that holds a secret: any identifier containing one of these words (DB_PASSWORD, apiKey, "client_secret"…). */
|
|
30
|
+
const SECRET_WORD = "(?:password|passwd|secret|token|api[_-]?key|apikey|access[_-]?key|private[_-]?key|credential)";
|
|
31
|
+
// Bounded on both sides: an unbounded name would backtrack over a long run of word characters (quadratic).
|
|
32
|
+
const NAME = `[A-Za-z0-9_.-]{0,40}${SECRET_WORD}[A-Za-z0-9_.-]{0,40}`;
|
|
33
|
+
/** A whole private key block (also one cut off before its END line). */
|
|
34
|
+
const PEM = /-----BEGIN ([A-Z0-9 ]*)PRIVATE KEY-----[\s\S]*?(?:-----END \1PRIVATE KEY-----|$)/g;
|
|
35
|
+
/** JSON: "…secret name…": "value" (as a tool's arguments are shown). */
|
|
36
|
+
const JSON_SECRET = new RegExp(`("${NAME}"\\s*:\\s*")((?:[^"\\\\]|\\\\.)+)(")`, "gi");
|
|
37
|
+
/**
|
|
38
|
+
* A secret's value: in double or single quotes up to the same quote (spaces included — a passphrase,
|
|
39
|
+
* DB_PASSWORD="correct horse battery staple"); in escaped quotes as JSON text writes them (DB_PASSWORD=\"…\", a
|
|
40
|
+
* tool's result that is JSON); else up to whitespace, a quote or one of `stop`.
|
|
41
|
+
*/
|
|
42
|
+
const VALUE = (stop) => `(?:"([^"\\n]*)"|'([^'\\n]*)'|\\\\"((?:[^"\\\\\\n]|\\\\[^"\\n])*)\\\\"|([^\\s${stop}]+))`;
|
|
43
|
+
/** `head` and the value masked, quotes kept — unless the value is plain (a number, a boolean, null) or empty. */
|
|
44
|
+
function masked(m, head, d, sq, esc, bare) {
|
|
45
|
+
const v = d ?? sq ?? esc ?? bare ?? "";
|
|
46
|
+
if (!v.trim() || PLAIN.test(v.trim()))
|
|
47
|
+
return m;
|
|
48
|
+
return d !== undefined ? `${head}"[REDACTED]"` : sq !== undefined ? `${head}'[REDACTED]'` : esc !== undefined ? `${head}\\"[REDACTED]\\"` : `${head}[REDACTED]`;
|
|
49
|
+
}
|
|
50
|
+
/** A flag: --password value, --api-key=value. */
|
|
51
|
+
const FLAG_SECRET = new RegExp(`(--?${NAME})(\\s+|=)${VALUE("\"'")}`, "gi");
|
|
52
|
+
/** An assignment: DB_PASSWORD=value, export TOKEN="value", password: value — the value may hold "/" (AWS keys do). */
|
|
53
|
+
const ASSIGN_SECRET = new RegExp(`(^|[^A-Za-z0-9_.-])(${NAME})(\\s*[=:]\\s*)${VALUE("\"'\`,;")}`, "gim");
|
|
54
|
+
/**
|
|
55
|
+
* A key given as an argument after its name, with no = or -- (aws configure set aws_secret_access_key …): masked only
|
|
56
|
+
* when it looks like a key — 16+ key characters with a letter and a digit — so prose after the word stays.
|
|
57
|
+
*/
|
|
58
|
+
const POSITIONAL_SECRET = new RegExp(`(^|[^A-Za-z0-9_.-])(${NAME})([ \\t]+)([A-Za-z0-9/+=_.-]{16,})(?![A-Za-z0-9/+=_.-])`, "gim");
|
|
59
|
+
/** A value that is not a secret whatever its name says: a number (max_tokens: 100), a boolean, null. */
|
|
60
|
+
const PLAIN = /^(?:-?\d+(?:\.\d+)?|true|false|null|none|\[REDACTED\])$/i;
|
|
61
|
+
/**
|
|
62
|
+
* Mask secret values in `text` (#1788 review P1: every common way a secret is written, since arguments and results are
|
|
63
|
+
* shown): a private key block; a secret-named JSON field, flag or assignment (any identifier containing password,
|
|
64
|
+
* secret, token, api/access/private key, credential — DB_PASSWORD, "api_key", --password); then the bubble's redactor
|
|
65
|
+
* (known key shapes, Bearer, URL userinfo); then long mixed-case opaque tokens. Numbers, booleans and null are kept.
|
|
66
|
+
*/
|
|
67
|
+
export function maskSecrets(text) {
|
|
68
|
+
return redactSecrets(text
|
|
69
|
+
.replace(PEM, (_m, kind) => `-----BEGIN ${kind}PRIVATE KEY----- [REDACTED] -----END ${kind}PRIVATE KEY-----`)
|
|
70
|
+
.replace(JSON_SECRET, (m, a, v, b) => (PLAIN.test(v) ? m : `${a}[REDACTED]${b}`))
|
|
71
|
+
.replace(FLAG_SECRET, (m, k, sep, d, sq, esc, bare) => masked(m, `${k}${sep}`, d, sq, esc, bare))
|
|
72
|
+
.replace(ASSIGN_SECRET, (m, pre, k, sep, d, sq, esc, bare) => masked(m, `${pre}${k}${sep}`, d, sq, esc, bare))
|
|
73
|
+
.replace(POSITIONAL_SECRET, (m, pre, k, sep, v) => (/[A-Za-z]/.test(v) && /\d/.test(v) ? `${pre}${k}${sep}[REDACTED]` : m)))
|
|
74
|
+
.replace(OPAQUE, (m) => (looksOpaque(m) ? "[REDACTED]" : m));
|
|
75
|
+
}
|
|
76
|
+
/** Characters past the limit that are masked with the rest: a secret that crosses the limit is masked whole. */
|
|
77
|
+
const MASK_MARGIN = 512;
|
|
78
|
+
/**
|
|
79
|
+
* `text` masked and cut to `max` characters (never inside a surrogate pair), with how many were cut. Only the part that
|
|
80
|
+
* can be shown (plus MASK_MARGIN) is masked: a tool result can be megabytes.
|
|
81
|
+
*/
|
|
82
|
+
export function bounded(text, max) {
|
|
83
|
+
const head = text.length > max + MASK_MARGIN ? text.slice(0, max + MASK_MARGIN) : text;
|
|
84
|
+
return cutTo(maskSecrets(head), max, text.length - head.length);
|
|
85
|
+
}
|
|
86
|
+
/** `text` (already masked) cut to `max` characters, never inside a surrogate pair; `more` characters were dropped before. */
|
|
87
|
+
function cutTo(text, max, more = 0) {
|
|
88
|
+
if (text.length + more <= max)
|
|
89
|
+
return { text, cut: 0 };
|
|
90
|
+
let end = Math.min(max, text.length);
|
|
91
|
+
if (/[\uD800-\uDBFF]/.test(text[end - 1] ?? ""))
|
|
92
|
+
end--;
|
|
93
|
+
return { text: text.slice(0, end), cut: text.length + more - end };
|
|
94
|
+
}
|
|
95
|
+
const SECRET_KEY = new RegExp(SECRET_WORD, "i");
|
|
96
|
+
/**
|
|
97
|
+
* A tool's arguments masked BEFORE they become JSON text (#1788 review r3): each string is masked as written — in JSON
|
|
98
|
+
* a command's quotes would be escaped (\"…\"), which no text rule should have to undo — and a string under a
|
|
99
|
+
* secret-named key ("password", "api_key") is masked whole unless it is plain. A string longer than can be shown is
|
|
100
|
+
* cut first (`dropped` counts what was cut), so a megabyte file body is not masked in full.
|
|
101
|
+
*/
|
|
102
|
+
function maskArgs(v, max, dropped, depth = 0) {
|
|
103
|
+
if (typeof v === "string") {
|
|
104
|
+
if (v.length <= max + MASK_MARGIN)
|
|
105
|
+
return maskSecrets(v);
|
|
106
|
+
dropped.n += v.length - (max + MASK_MARGIN);
|
|
107
|
+
return maskSecrets(v.slice(0, max + MASK_MARGIN));
|
|
108
|
+
}
|
|
109
|
+
if (v === null || typeof v !== "object")
|
|
110
|
+
return v;
|
|
111
|
+
if (depth >= 32)
|
|
112
|
+
return "[…]";
|
|
113
|
+
if (Array.isArray(v))
|
|
114
|
+
return v.map(x => maskArgs(x, max, dropped, depth + 1));
|
|
115
|
+
const out = {};
|
|
116
|
+
for (const [k, x] of Object.entries(v)) {
|
|
117
|
+
out[k] = typeof x === "string" && k.length <= 200 && SECRET_KEY.test(k) && x.trim() && !PLAIN.test(x.trim())
|
|
118
|
+
? "[REDACTED]" : maskArgs(x, max, dropped, depth + 1);
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
/** Arguments as JSON text: a string that is JSON (codex's `arguments`) is parsed first, so it is masked the same way. */
|
|
123
|
+
function argsText(input, max) {
|
|
124
|
+
let value = input;
|
|
125
|
+
if (typeof value === "string" && /^\s*[[{]/.test(value)) {
|
|
126
|
+
try {
|
|
127
|
+
value = JSON.parse(value);
|
|
128
|
+
}
|
|
129
|
+
catch { /* text */ }
|
|
130
|
+
}
|
|
131
|
+
if (typeof value === "string")
|
|
132
|
+
return bounded(value, max);
|
|
133
|
+
const dropped = { n: 0 };
|
|
134
|
+
return cutTo(JSON.stringify(maskArgs(value, max, dropped), null, 1) ?? "", max, dropped.n);
|
|
135
|
+
}
|
|
136
|
+
const oneLine = (s) => s.replace(/\s+/g, " ").trim();
|
|
137
|
+
function subjectOf(s) {
|
|
138
|
+
if (s == null || s === "")
|
|
139
|
+
return undefined;
|
|
140
|
+
if (typeof s !== "string") {
|
|
141
|
+
const t = oneLine(argsText(s, SUBJECT_MAX).text);
|
|
142
|
+
return t || undefined;
|
|
143
|
+
}
|
|
144
|
+
const t = oneLine(s);
|
|
145
|
+
return t ? bounded(t, SUBJECT_MAX).text : undefined;
|
|
146
|
+
}
|
|
147
|
+
function inputOf(step, input) {
|
|
148
|
+
if (input == null || input === "")
|
|
149
|
+
return;
|
|
150
|
+
const b = argsText(input, INPUT_MAX);
|
|
151
|
+
step.input = b.text;
|
|
152
|
+
if (b.cut)
|
|
153
|
+
step.inputCut = b.cut;
|
|
154
|
+
}
|
|
155
|
+
function bodyOf(step, text, max) {
|
|
156
|
+
const b = bounded(text, max);
|
|
157
|
+
step.body = b.text;
|
|
158
|
+
if (b.cut)
|
|
159
|
+
step.bodyCut = b.cut;
|
|
160
|
+
else
|
|
161
|
+
delete step.bodyCut;
|
|
162
|
+
}
|
|
163
|
+
const tsOf = (v) => { const t = typeof v === "string" ? Date.parse(v) : NaN; return Number.isFinite(t) ? t : 0; };
|
|
164
|
+
/* ------------------------------------------------------------------ tool calls: what they do */
|
|
165
|
+
const MCP = /^mcp__(.+?)__(.+)$/;
|
|
166
|
+
/** AgEnD's fleet tools as kiro lists them (bare names). */
|
|
167
|
+
const AGEND_BARE = new Set(["reply", "react", "edit_message", "send_to_instance", "report_result", "delegate_task",
|
|
168
|
+
"request_information", "list_instances", "describe_instance", "delivery_status", "get_instance_logs", "start_instance", "wake_instance",
|
|
169
|
+
"list_decisions", "post_decision", "create_schedule", "delete_schedule", "list_schedules", "checkout_repo", "release_repo"]);
|
|
170
|
+
/** A "tool" that is the model's thinking (kiro's `thinking`): never a step (the user's decision: thinking is never shown). */
|
|
171
|
+
export const THINKING_TOOLS = new Set(["thinking"]);
|
|
172
|
+
/** The category and subject of a tool call, from its name and input. An unknown tool: "other", its name. */
|
|
173
|
+
export function describeTool(name, input) {
|
|
174
|
+
const inp = (input && typeof input === "object" ? input : {});
|
|
175
|
+
const mcp = MCP.exec(name);
|
|
176
|
+
if (mcp) {
|
|
177
|
+
const agend = /^agend/.test(mcp[1]);
|
|
178
|
+
return { cat: agend ? "message" : "mcp", subject: `${mcp[1]}:${mcp[2]}` };
|
|
179
|
+
}
|
|
180
|
+
switch (name) {
|
|
181
|
+
// claude-code
|
|
182
|
+
case "Read":
|
|
183
|
+
case "NotebookRead": return { cat: "read", subject: subjectOf(inp.file_path ?? inp.notebook_path ?? inp.path) };
|
|
184
|
+
case "Edit":
|
|
185
|
+
case "MultiEdit":
|
|
186
|
+
case "Write":
|
|
187
|
+
case "NotebookEdit": return { cat: "edit", subject: subjectOf(inp.file_path ?? inp.notebook_path ?? inp.path) };
|
|
188
|
+
case "Glob": return { cat: "search", subject: subjectOf(inp.pattern) };
|
|
189
|
+
case "Grep": return { cat: "search", subject: subjectOf([inp.pattern, inp.path].filter(Boolean).join(" in ")) };
|
|
190
|
+
case "Bash":
|
|
191
|
+
case "bash":
|
|
192
|
+
case "shell":
|
|
193
|
+
case "BashOutput": return { cat: "run", subject: subjectOf(inp.command ?? inp.cmd) };
|
|
194
|
+
case "WebFetch": return { cat: "web", subject: subjectOf(inp.url) };
|
|
195
|
+
case "WebSearch": return { cat: "web", subject: subjectOf(inp.query) };
|
|
196
|
+
case "Task":
|
|
197
|
+
case "Agent": return { cat: "agent", subject: subjectOf(inp.description ?? inp.subagent_type) };
|
|
198
|
+
case "Skill": return { cat: "other", subject: subjectOf(inp.skill) };
|
|
199
|
+
case "TodoWrite": {
|
|
200
|
+
const n = Array.isArray(inp.todos) ? inp.todos.length : 0;
|
|
201
|
+
return { cat: "plan", subject: n ? `${n}` : undefined };
|
|
202
|
+
}
|
|
203
|
+
// codex
|
|
204
|
+
case "exec_command": return { cat: "run", subject: subjectOf(inp.cmd ?? inp.command) };
|
|
205
|
+
case "update_plan": return { cat: "plan" };
|
|
206
|
+
case "apply_patch": {
|
|
207
|
+
const text = typeof input === "string" ? input : String(inp.input ?? inp.patch ?? "");
|
|
208
|
+
const files = [...text.matchAll(/^\*\*\* (?:Add|Update|Delete) File: (.+)$/gm)].map((m) => m[1].trim());
|
|
209
|
+
return { cat: "edit", subject: subjectOf(files.join(", ")) };
|
|
210
|
+
}
|
|
211
|
+
case "exec": {
|
|
212
|
+
// codex's custom tool: a script driving tools.* — name the tools it calls, else its first line.
|
|
213
|
+
const text = typeof input === "string" ? input : "";
|
|
214
|
+
const calls = [...new Set([...text.matchAll(/\btools\.(\w+)\s*\(/g)].map((m) => m[1]))];
|
|
215
|
+
return { cat: "run", subject: subjectOf(calls.length ? calls.join(", ") : text.split("\n").find((l) => l.trim()) ?? "") };
|
|
216
|
+
}
|
|
217
|
+
// kiro (classic and TUI names)
|
|
218
|
+
case "fs_read":
|
|
219
|
+
case "read": {
|
|
220
|
+
const ops = inp.operations;
|
|
221
|
+
return { cat: "read", subject: subjectOf(ops?.[0]?.path ?? inp.path) };
|
|
222
|
+
}
|
|
223
|
+
case "fs_write":
|
|
224
|
+
case "write": return { cat: "edit", subject: subjectOf(inp.path) };
|
|
225
|
+
case "execute_bash":
|
|
226
|
+
case "execute_cmd": return { cat: "run", subject: subjectOf(inp.command) };
|
|
227
|
+
case "grep": return { cat: "search", subject: subjectOf([inp.pattern, inp.path].filter(Boolean).join(" in ")) };
|
|
228
|
+
case "glob": return { cat: "search", subject: subjectOf(inp.pattern) };
|
|
229
|
+
case "web_fetch":
|
|
230
|
+
case "web_search": return { cat: "web", subject: subjectOf(inp.url ?? inp.query) };
|
|
231
|
+
case "todo_list": return { cat: "plan", subject: subjectOf(inp.task_list_description) };
|
|
232
|
+
case "task":
|
|
233
|
+
case "subagent":
|
|
234
|
+
case "use_subagent": return { cat: "agent", subject: subjectOf(inp.task ?? inp.content) };
|
|
235
|
+
}
|
|
236
|
+
// AgEnD's own tools, as kiro names them (no mcp__ prefix): messaging, not work.
|
|
237
|
+
if (AGEND_BARE.has(name))
|
|
238
|
+
return { cat: "message", subject: subjectOf(inp.target_instance ?? inp.instance_name ?? inp.name) };
|
|
239
|
+
return { cat: "other" };
|
|
240
|
+
}
|
|
241
|
+
class StepParser {
|
|
242
|
+
turn = "";
|
|
243
|
+
open = new Map();
|
|
244
|
+
n = 0;
|
|
245
|
+
/** The byte offset of the record being parsed (-1: unknown), for ids that do not depend on where parsing began. */
|
|
246
|
+
at = -1;
|
|
247
|
+
k = 0;
|
|
248
|
+
/** Steps in the order they were first seen (updated in place). */
|
|
249
|
+
steps = [];
|
|
250
|
+
/** A record too long to parse: what its head still says (by default nothing). */
|
|
251
|
+
long(_head, _bytes) { return []; }
|
|
252
|
+
/** One transcript line (JSON text) or a long line's head: the steps it created or changed. Malformed: none. */
|
|
253
|
+
push(item, at = -1) {
|
|
254
|
+
this.at = at;
|
|
255
|
+
this.k = 0;
|
|
256
|
+
if (typeof item !== "string")
|
|
257
|
+
return this.long(item.head, item.bytes);
|
|
258
|
+
let entry;
|
|
259
|
+
try {
|
|
260
|
+
entry = JSON.parse(item);
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
return [];
|
|
264
|
+
}
|
|
265
|
+
return entry && typeof entry === "object" ? this.record(entry) : [];
|
|
266
|
+
}
|
|
267
|
+
add(step) {
|
|
268
|
+
const id = step.id ?? (this.at >= 0 ? `o${this.at}.${this.k++}` : `s${++this.n}`);
|
|
269
|
+
const s = { ...step, id, turn: this.turn };
|
|
270
|
+
this.steps.push(s);
|
|
271
|
+
if (s.kind === "tool" && s.status === "running")
|
|
272
|
+
this.open.set(s.id, s);
|
|
273
|
+
return s;
|
|
274
|
+
}
|
|
275
|
+
openTool(id) { return this.open.get(id); }
|
|
276
|
+
close(id) { const s = this.open.get(id); this.open.delete(id); return s; }
|
|
277
|
+
/** A new turn (`key`: the transcript's id for it): every tool still running in the last one is interrupted. */
|
|
278
|
+
newTurn(key) {
|
|
279
|
+
const out = this.endOpen("interrupted");
|
|
280
|
+
this.turn = key;
|
|
281
|
+
return out;
|
|
282
|
+
}
|
|
283
|
+
endOpen(status) {
|
|
284
|
+
const out = [];
|
|
285
|
+
for (const s of this.open.values()) {
|
|
286
|
+
s.status = status;
|
|
287
|
+
out.push({ ...s });
|
|
288
|
+
}
|
|
289
|
+
this.open.clear();
|
|
290
|
+
return out;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/** A tool result's text: a string, or text blocks joined (an image block says so). */
|
|
294
|
+
function resultText(content) {
|
|
295
|
+
if (typeof content === "string")
|
|
296
|
+
return content;
|
|
297
|
+
// An object result is masked before it is JSON text, as arguments are (#1788 review P3).
|
|
298
|
+
if (!Array.isArray(content))
|
|
299
|
+
return content == null ? "" : JSON.stringify(maskArgs(content, RESULT_MAX, { n: 0 }));
|
|
300
|
+
return content.map((b) => {
|
|
301
|
+
const blk = b;
|
|
302
|
+
if (typeof blk.text === "string")
|
|
303
|
+
return blk.text;
|
|
304
|
+
if (blk.type === "image" || blk.type === "input_image")
|
|
305
|
+
return "[image]";
|
|
306
|
+
return "";
|
|
307
|
+
}).filter(Boolean).join("\n");
|
|
308
|
+
}
|
|
309
|
+
/** The start of a JSON string value `"key":"…` in a long line's head, unescaped as far as the head goes. */
|
|
310
|
+
function headString(head, key) {
|
|
311
|
+
const at = head.indexOf(`"${key}":"`);
|
|
312
|
+
if (at < 0)
|
|
313
|
+
return null;
|
|
314
|
+
let raw = head.slice(at + key.length + 4);
|
|
315
|
+
const end = /(?<!\\)(?:\\\\)*"/.exec(raw);
|
|
316
|
+
if (end)
|
|
317
|
+
raw = raw.slice(0, end.index + end[0].length - 1);
|
|
318
|
+
raw = raw.replace(/\\u[0-9a-fA-F]{0,3}$|\\$/, ""); // never end inside an escape
|
|
319
|
+
try {
|
|
320
|
+
return JSON.parse(`"${raw}"`);
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* claude-code: the JSONL at statusline.json's transcript_path. user: a prompt (string content; `isMeta` records and
|
|
328
|
+
* tool results are not prompts); assistant: `text` (thinking never), `tool_use` (running); a `tool_result` (by
|
|
329
|
+
* `tool_use_id`, `is_error`) completes its call; an API error is an assistant record with `isApiErrorMessage`;
|
|
330
|
+
* `system` `turn_duration` ends a turn, `compact_boundary` is a state.
|
|
331
|
+
*/
|
|
332
|
+
export class ClaudeStepParser extends StepParser {
|
|
333
|
+
started = new Map();
|
|
334
|
+
record(e) {
|
|
335
|
+
const ts = tsOf(e.timestamp);
|
|
336
|
+
const msg = e.message;
|
|
337
|
+
if (e.type === "system") {
|
|
338
|
+
if (e.subtype === "turn_duration") {
|
|
339
|
+
const out = this.endOpen("interrupted");
|
|
340
|
+
out.push(this.add({ kind: "turn_end", ts, ...(typeof e.durationMs === "number" ? { durationMs: e.durationMs } : {}) }));
|
|
341
|
+
return out;
|
|
342
|
+
}
|
|
343
|
+
if (e.subtype === "compact_boundary")
|
|
344
|
+
return [this.add({ kind: "state", ts, note: "compacted" })];
|
|
345
|
+
return [];
|
|
346
|
+
}
|
|
347
|
+
if (e.type === "assistant" && e.isApiErrorMessage === true) {
|
|
348
|
+
const s = this.add({ kind: "error", ts, ...(typeof e.apiErrorStatus === "number" ? { note: String(e.apiErrorStatus) } : {}) });
|
|
349
|
+
bodyOf(s, resultText(msg?.content) || String(e.error ?? ""), TEXT_MAX);
|
|
350
|
+
return [s];
|
|
351
|
+
}
|
|
352
|
+
if (!msg || (e.type !== "user" && e.type !== "assistant") || e.isSidechain === true)
|
|
353
|
+
return [];
|
|
354
|
+
const content = msg.content;
|
|
355
|
+
if (e.type === "user") {
|
|
356
|
+
if (e.isMeta === true)
|
|
357
|
+
return [];
|
|
358
|
+
if (typeof content === "string") {
|
|
359
|
+
if (!content.trim())
|
|
360
|
+
return [];
|
|
361
|
+
const out = this.newTurn(String(e.promptId ?? e.uuid ?? (this.at >= 0 ? `o${this.at}` : "")));
|
|
362
|
+
const cmd = /<command-name>([^<]{1,100})<\/command-name>/.exec(content);
|
|
363
|
+
const s = this.add({ kind: "user", ts });
|
|
364
|
+
bodyOf(s, cmd ? cmd[1].trim() : content, TEXT_MAX);
|
|
365
|
+
out.push(s);
|
|
366
|
+
return out;
|
|
367
|
+
}
|
|
368
|
+
const out = [];
|
|
369
|
+
for (const b of Array.isArray(content) ? content : []) {
|
|
370
|
+
const blk = b;
|
|
371
|
+
if (blk.type === "tool_result" && typeof blk.tool_use_id === "string")
|
|
372
|
+
out.push(this.result(blk.tool_use_id, resultText(blk.content), blk.is_error === true, ts));
|
|
373
|
+
else if (blk.type === "text" && typeof blk.text === "string" && blk.text.trim()) {
|
|
374
|
+
const s = this.add({ kind: "user", ts });
|
|
375
|
+
bodyOf(s, blk.text, TEXT_MAX);
|
|
376
|
+
out.push(s);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return out;
|
|
380
|
+
}
|
|
381
|
+
const out = [];
|
|
382
|
+
for (const b of Array.isArray(content) ? content : []) {
|
|
383
|
+
const blk = b;
|
|
384
|
+
if (blk.type === "text" && typeof blk.text === "string" && blk.text.trim()) {
|
|
385
|
+
const s = this.add({ kind: "assistant", ts });
|
|
386
|
+
bodyOf(s, blk.text, TEXT_MAX);
|
|
387
|
+
out.push(s);
|
|
388
|
+
}
|
|
389
|
+
else if (blk.type === "tool_use" && typeof blk.id === "string") {
|
|
390
|
+
const name = String(blk.name ?? "tool");
|
|
391
|
+
const s = this.add({ id: blk.id, kind: "tool", ts, tool: name, ...describeTool(name, blk.input), status: "running" });
|
|
392
|
+
inputOf(s, blk.input);
|
|
393
|
+
if (ts)
|
|
394
|
+
this.started.set(blk.id, ts);
|
|
395
|
+
out.push(s);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return out;
|
|
399
|
+
}
|
|
400
|
+
result(id, text, isError, ts, bytes) {
|
|
401
|
+
const s = this.close(id) ?? this.add({ id, kind: "tool", ts, tool: "", cat: "other", status: "running" });
|
|
402
|
+
s.status = isError ? "failed" : "done";
|
|
403
|
+
const t0 = this.started.get(id);
|
|
404
|
+
this.started.delete(id);
|
|
405
|
+
if (t0 && ts >= t0)
|
|
406
|
+
s.durationMs = ts - t0;
|
|
407
|
+
if (bytes !== undefined) {
|
|
408
|
+
s.bodyBytes = bytes;
|
|
409
|
+
}
|
|
410
|
+
if (text)
|
|
411
|
+
bodyOf(s, text, RESULT_MAX);
|
|
412
|
+
return { ...s };
|
|
413
|
+
}
|
|
414
|
+
/** A tool result too long to parse (most long claude lines are): its call completes, with the start of its text. */
|
|
415
|
+
long(head, bytes) {
|
|
416
|
+
if (!head.includes('"type":"tool_result"'))
|
|
417
|
+
return [];
|
|
418
|
+
const id = /"tool_use_id":"([^"]{1,200})"/.exec(head)?.[1];
|
|
419
|
+
if (!id)
|
|
420
|
+
return [];
|
|
421
|
+
const ts = tsOf(/"timestamp":"([^"]{10,40})"/.exec(head)?.[1]);
|
|
422
|
+
return [this.result(id, headString(head, "content") ?? "", false, ts, bytes)];
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
const CODEX_EXIT = /Process exited with code (-?\d+)/;
|
|
426
|
+
const CODEX_WALL = /Wall time: ([\d.]+) seconds/;
|
|
427
|
+
/**
|
|
428
|
+
* codex: the rollout JSONL. `response_item`: `message` (user / assistant), `function_call` / `custom_tool_call` /
|
|
429
|
+
* `local_shell_call` (running, by `call_id`) and their `*_output` (exec_command's output says its exit code and wall
|
|
430
|
+
* time); `event_msg`: `task_started` (a turn), `task_complete` / `turn_aborted` (its end), and — on versions that
|
|
431
|
+
* write them — `item_completed` items: CommandExecution (a command, with exit code and duration), McpToolCall,
|
|
432
|
+
* FileChange. Their AgentMessage / UserMessage duplicate the response items and are not shown twice; reasoning never.
|
|
433
|
+
*/
|
|
434
|
+
export class CodexStepParser extends StepParser {
|
|
435
|
+
started = new Map();
|
|
436
|
+
record(e) {
|
|
437
|
+
const ts = tsOf(e.timestamp);
|
|
438
|
+
const p = e.payload;
|
|
439
|
+
if (!p)
|
|
440
|
+
return [];
|
|
441
|
+
if (e.type === "compacted")
|
|
442
|
+
return [this.add({ kind: "state", ts, note: "compacted" })];
|
|
443
|
+
if (e.type === "event_msg") {
|
|
444
|
+
if (p.type === "task_started")
|
|
445
|
+
return this.newTurn(String(p.turn_id ?? ""));
|
|
446
|
+
if (p.type === "task_complete" || p.type === "turn_aborted") {
|
|
447
|
+
const out = this.endOpen("interrupted");
|
|
448
|
+
const s = this.add({ kind: "turn_end", ts, ...(typeof p.duration_ms === "number" ? { durationMs: p.duration_ms } : {}) });
|
|
449
|
+
if (p.type === "turn_aborted") {
|
|
450
|
+
s.status = "interrupted";
|
|
451
|
+
if (typeof p.reason === "string")
|
|
452
|
+
s.note = p.reason;
|
|
453
|
+
}
|
|
454
|
+
else if (p.last_agent_message == null)
|
|
455
|
+
s.status = "failed";
|
|
456
|
+
out.push(s);
|
|
457
|
+
return out;
|
|
458
|
+
}
|
|
459
|
+
if (p.type === "error" && typeof p.message === "string") {
|
|
460
|
+
const s = this.add({ kind: "error", ts });
|
|
461
|
+
bodyOf(s, p.message, TEXT_MAX);
|
|
462
|
+
return [s];
|
|
463
|
+
}
|
|
464
|
+
if (p.type === "item_completed")
|
|
465
|
+
return this.item(p.item, ts);
|
|
466
|
+
return [];
|
|
467
|
+
}
|
|
468
|
+
if (e.type !== "response_item")
|
|
469
|
+
return [];
|
|
470
|
+
switch (p.type) {
|
|
471
|
+
case "message": {
|
|
472
|
+
const role = p.role;
|
|
473
|
+
if (role !== "user" && role !== "assistant")
|
|
474
|
+
return [];
|
|
475
|
+
const text = resultText(p.content);
|
|
476
|
+
if (!text.trim() || (role === "user" && /^<(environment_context|user_instructions|permissions)/.test(text.trim())))
|
|
477
|
+
return [];
|
|
478
|
+
const out = [];
|
|
479
|
+
const s = this.add({ kind: role, ts });
|
|
480
|
+
bodyOf(s, text, TEXT_MAX);
|
|
481
|
+
out.push(s);
|
|
482
|
+
return out;
|
|
483
|
+
}
|
|
484
|
+
case "function_call":
|
|
485
|
+
case "custom_tool_call":
|
|
486
|
+
case "local_shell_call": {
|
|
487
|
+
const id = String(p.call_id ?? p.id ?? "");
|
|
488
|
+
let input = p.type === "local_shell_call" ? { command: p.action?.command } : p.type === "custom_tool_call" ? p.input : p.arguments;
|
|
489
|
+
if (typeof input === "string" && p.type === "function_call") {
|
|
490
|
+
try {
|
|
491
|
+
input = JSON.parse(input);
|
|
492
|
+
}
|
|
493
|
+
catch { /* keep the text */ }
|
|
494
|
+
}
|
|
495
|
+
const name = p.type === "local_shell_call" ? "shell" : String(p.name ?? "tool");
|
|
496
|
+
if (Array.isArray(input?.command)) {
|
|
497
|
+
const cmd = input.command;
|
|
498
|
+
input = { ...input, command: cmd.map(String).join(" ") };
|
|
499
|
+
}
|
|
500
|
+
const s = this.add({ ...(id ? { id } : {}), kind: "tool", ts, tool: name, ...describeTool(name, input), status: "running" });
|
|
501
|
+
inputOf(s, input);
|
|
502
|
+
if (ts && id)
|
|
503
|
+
this.started.set(id, ts);
|
|
504
|
+
return [s];
|
|
505
|
+
}
|
|
506
|
+
case "function_call_output":
|
|
507
|
+
case "custom_tool_call_output": {
|
|
508
|
+
const id = String(p.call_id ?? "");
|
|
509
|
+
const s = this.close(id) ?? this.add({ id: id || undefined, kind: "tool", ts, tool: "", cat: "other", status: "running" });
|
|
510
|
+
const text = resultText(p.output);
|
|
511
|
+
const exit = CODEX_EXIT.exec(text), wall = CODEX_WALL.exec(text);
|
|
512
|
+
s.status = (exit && exit[1] !== "0") || /^failed to /.test(text) ? "failed" : "done";
|
|
513
|
+
if (exit)
|
|
514
|
+
s.exitCode = Number(exit[1]);
|
|
515
|
+
const t0 = this.started.get(id);
|
|
516
|
+
this.started.delete(id);
|
|
517
|
+
if (wall)
|
|
518
|
+
s.durationMs = Math.round(Number(wall[1]) * 1000);
|
|
519
|
+
else if (t0 && ts >= t0)
|
|
520
|
+
s.durationMs = ts - t0;
|
|
521
|
+
const out = /\nOutput:\n([\s\S]*)$/.exec(text);
|
|
522
|
+
if (text)
|
|
523
|
+
bodyOf(s, out ? out[1] : text, RESULT_MAX);
|
|
524
|
+
return [{ ...s }];
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return [];
|
|
528
|
+
}
|
|
529
|
+
item(it, ts) {
|
|
530
|
+
if (!it || typeof it.id !== "string")
|
|
531
|
+
return [];
|
|
532
|
+
const dur = it.duration;
|
|
533
|
+
const durationMs = dur && typeof dur.secs === "number" ? dur.secs * 1000 + Math.round((dur.nanos ?? 0) / 1e6) : undefined;
|
|
534
|
+
const failed = it.status === "failed" || (typeof it.exit_code === "number" && it.exit_code !== 0);
|
|
535
|
+
const base = { id: it.id, kind: "tool", ts, status: (failed ? "failed" : "done"), ...(durationMs !== undefined ? { durationMs } : {}) };
|
|
536
|
+
switch (it.type) {
|
|
537
|
+
case "CommandExecution": {
|
|
538
|
+
const cmd = Array.isArray(it.command) ? it.command.map(String).join(" ") : String(it.command ?? "");
|
|
539
|
+
const s = this.add({ ...base, tool: "command", cat: "run", subject: subjectOf(cmd), ...(typeof it.exit_code === "number" ? { exitCode: it.exit_code } : {}) });
|
|
540
|
+
const text = String(it.aggregated_output ?? it.formatted_output ?? "");
|
|
541
|
+
if (text)
|
|
542
|
+
bodyOf(s, text, RESULT_MAX);
|
|
543
|
+
return [s];
|
|
544
|
+
}
|
|
545
|
+
case "McpToolCall": {
|
|
546
|
+
const s = this.add({ ...base, tool: `${it.server}:${it.tool}`, cat: /^agend/.test(String(it.server)) ? "message" : "mcp", subject: subjectOf(`${it.server}:${it.tool}`) });
|
|
547
|
+
inputOf(s, it.arguments);
|
|
548
|
+
const text = resultText(it.result?.content);
|
|
549
|
+
if (text)
|
|
550
|
+
bodyOf(s, text, RESULT_MAX);
|
|
551
|
+
return [s];
|
|
552
|
+
}
|
|
553
|
+
case "FileChange": {
|
|
554
|
+
const files = Object.keys(it.changes ?? {});
|
|
555
|
+
const s = this.add({ ...base, tool: "file_change", cat: "edit", subject: subjectOf(files.join(", ")) });
|
|
556
|
+
const text = [it.stdout, it.stderr].filter((x) => typeof x === "string" && x).join("\n");
|
|
557
|
+
if (text)
|
|
558
|
+
bodyOf(s, text, RESULT_MAX);
|
|
559
|
+
return [s];
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return [];
|
|
563
|
+
}
|
|
564
|
+
/** A long rollout line: an output completes its call with the start of its text; a call starts by its name. */
|
|
565
|
+
long(head, bytes) {
|
|
566
|
+
if (!head.includes('"type":"response_item"'))
|
|
567
|
+
return [];
|
|
568
|
+
const type = /"payload":\{[^{}]{0,400}?"type":"(function_call|custom_tool_call|function_call_output|custom_tool_call_output)"/.exec(head)?.[1];
|
|
569
|
+
const id = /"call_id":"([^"]{1,200})"/.exec(head)?.[1];
|
|
570
|
+
if (!type || !id)
|
|
571
|
+
return [];
|
|
572
|
+
const ts = tsOf(/"timestamp":"([^"]{10,40})"/.exec(head)?.[1]);
|
|
573
|
+
if (type.endsWith("_output")) {
|
|
574
|
+
const s = this.close(id) ?? this.add({ id, kind: "tool", ts, tool: "", cat: "other", status: "running" });
|
|
575
|
+
s.status = "done";
|
|
576
|
+
s.bodyBytes = bytes;
|
|
577
|
+
const text = headString(head, "text") ?? headString(head, "output");
|
|
578
|
+
if (text)
|
|
579
|
+
bodyOf(s, text, RESULT_MAX);
|
|
580
|
+
return [{ ...s }];
|
|
581
|
+
}
|
|
582
|
+
const name = /"name":"([^"]{1,200})"/.exec(head)?.[1] ?? "tool";
|
|
583
|
+
return [this.add({ id, kind: "tool", ts, tool: name, ...describeTool(name, undefined), status: "running", bodyBytes: bytes })];
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
/** A kiro tool result's text: stdout and stderr of a command (with its exit status), or its text blocks. */
|
|
587
|
+
function kiroResultText(content) {
|
|
588
|
+
const blocks = Array.isArray(content) ? content : [];
|
|
589
|
+
const parts = [];
|
|
590
|
+
let exit;
|
|
591
|
+
for (const b of blocks) {
|
|
592
|
+
const blk = b;
|
|
593
|
+
// v2: { kind: "json" | "text", data } — classic: { Json: {...} } | { Text: "..." }
|
|
594
|
+
const json = (blk.kind === "json" ? blk.data : blk.Json);
|
|
595
|
+
const text = blk.kind === "text" ? blk.data : blk.Text;
|
|
596
|
+
if (json && typeof json === "object") {
|
|
597
|
+
const m = /(-?\d+)/.exec(String(json.exit_status ?? ""));
|
|
598
|
+
if (m)
|
|
599
|
+
exit = Number(m[1]);
|
|
600
|
+
for (const k of ["stdout", "stderr"])
|
|
601
|
+
if (typeof json[k] === "string" && json[k])
|
|
602
|
+
parts.push(json[k]);
|
|
603
|
+
if (!parts.length && !("stdout" in json) && !("stderr" in json))
|
|
604
|
+
parts.push(JSON.stringify(json));
|
|
605
|
+
}
|
|
606
|
+
else if (typeof text === "string")
|
|
607
|
+
parts.push(text);
|
|
608
|
+
}
|
|
609
|
+
return { text: parts.join("\n"), ...(exit !== undefined ? { exit } : {}) };
|
|
610
|
+
}
|
|
611
|
+
/** kiro's tool input without its own bookkeeping field (`__tool_use_purpose` is shown as the subject when nothing else is). */
|
|
612
|
+
function kiroInput(input) {
|
|
613
|
+
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
614
|
+
return { input };
|
|
615
|
+
const { __tool_use_purpose: purpose, ...rest } = input;
|
|
616
|
+
return { input: rest, ...(typeof purpose === "string" && purpose ? { purpose } : {}) };
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* kiro TUI (v2): the session's message log `<KIRO_HOME>/sessions/cli/<id>.jsonl` — `Prompt` (a turn: its text),
|
|
620
|
+
* `AssistantMessage` (`text`; `toolUse` running, by `toolUseId`; `thinking` never), `ToolResults` (`toolResult`
|
|
621
|
+
* completes its call: status, stdout/stderr, exit status). Only a Prompt carries a time.
|
|
622
|
+
*/
|
|
623
|
+
export class KiroV2StepParser extends StepParser {
|
|
624
|
+
skipped = new Set();
|
|
625
|
+
record(e) {
|
|
626
|
+
const d = e.data;
|
|
627
|
+
if (!d)
|
|
628
|
+
return [];
|
|
629
|
+
const content = Array.isArray(d.content) ? d.content : [];
|
|
630
|
+
if (e.kind === "Prompt") {
|
|
631
|
+
const meta = d.meta;
|
|
632
|
+
const raw = typeof meta?.timestamp === "number" ? meta.timestamp : 0;
|
|
633
|
+
const ts = raw > 1e12 ? raw : raw * 1000;
|
|
634
|
+
const text = content.filter(b => b.kind === "text" && typeof b.data === "string").map(b => b.data).join("\n");
|
|
635
|
+
if (!text.trim())
|
|
636
|
+
return [];
|
|
637
|
+
const out = this.newTurn(String(d.message_id ?? (this.at >= 0 ? `o${this.at}` : "")));
|
|
638
|
+
const s = this.add({ kind: "user", ts });
|
|
639
|
+
bodyOf(s, text, TEXT_MAX);
|
|
640
|
+
out.push(s);
|
|
641
|
+
return out;
|
|
642
|
+
}
|
|
643
|
+
const out = [];
|
|
644
|
+
if (e.kind === "AssistantMessage") {
|
|
645
|
+
for (const b of content) {
|
|
646
|
+
if (b.kind === "text" && typeof b.data === "string" && b.data.trim()) {
|
|
647
|
+
const s = this.add({ kind: "assistant", ts: 0 });
|
|
648
|
+
bodyOf(s, b.data, TEXT_MAX);
|
|
649
|
+
out.push(s);
|
|
650
|
+
}
|
|
651
|
+
else if (b.kind === "toolUse" && b.data && typeof b.data.toolUseId === "string") {
|
|
652
|
+
const u = b.data;
|
|
653
|
+
const name = String(u.name ?? "tool");
|
|
654
|
+
if (THINKING_TOOLS.has(name)) {
|
|
655
|
+
this.skipped.add(u.toolUseId);
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
const { input, purpose } = kiroInput(u.input);
|
|
659
|
+
const desc = describeTool(name, input);
|
|
660
|
+
const s = this.add({ id: u.toolUseId, kind: "tool", ts: 0, tool: name, ...desc, ...(desc.subject ? {} : purpose ? { subject: subjectOf(purpose) } : {}), status: "running" });
|
|
661
|
+
inputOf(s, input);
|
|
662
|
+
out.push(s);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
else if (e.kind === "ToolResults") {
|
|
667
|
+
for (const b of content) {
|
|
668
|
+
if (b.kind !== "toolResult" || !b.data)
|
|
669
|
+
continue;
|
|
670
|
+
const r = b.data;
|
|
671
|
+
const id = String(r.toolUseId ?? "");
|
|
672
|
+
if (this.skipped.has(id))
|
|
673
|
+
continue; // a thinking "tool": its result is thinking too
|
|
674
|
+
const s = this.close(id) ?? this.add({ id: id || undefined, kind: "tool", ts: 0, tool: "", cat: "other", status: "running" });
|
|
675
|
+
const { text, exit } = kiroResultText(r.content);
|
|
676
|
+
s.status = r.status === "error" || (exit !== undefined && exit !== 0) ? "failed" : "done";
|
|
677
|
+
if (exit !== undefined)
|
|
678
|
+
s.exitCode = exit;
|
|
679
|
+
if (text)
|
|
680
|
+
bodyOf(s, text, RESULT_MAX);
|
|
681
|
+
out.push({ ...s });
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return out;
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* A tool result too long to parse (a command's whole output): its first call completes with the start of its output
|
|
688
|
+
* and the exit code the head shows. A long reply or prompt is read whole by the Log instead (log-service.ts).
|
|
689
|
+
*/
|
|
690
|
+
long(head, bytes) {
|
|
691
|
+
if (!/^\{"version":"[^"]{1,20}","kind":"ToolResults"/.test(head))
|
|
692
|
+
return [];
|
|
693
|
+
const id = /"toolUseId":"([^"]{1,200})"/.exec(head)?.[1];
|
|
694
|
+
if (!id || this.skipped.has(id))
|
|
695
|
+
return [];
|
|
696
|
+
const s = this.close(id) ?? this.add({ id, kind: "tool", ts: 0, tool: "", cat: "other", status: "running" });
|
|
697
|
+
// The result's own status comes after its content (past the head): the exit code, near the start, decides. A long
|
|
698
|
+
// result without one is an output, not an error message (those are short).
|
|
699
|
+
const exit = /"exit_status":(?:"[^"\d-]{0,20})?(-?\d+)/.exec(head)?.[1];
|
|
700
|
+
s.status = exit !== undefined && Number(exit) !== 0 ? "failed" : "done";
|
|
701
|
+
if (exit !== undefined)
|
|
702
|
+
s.exitCode = Number(exit);
|
|
703
|
+
s.bodyBytes = bytes;
|
|
704
|
+
const text = headString(head, "stdout") ?? headString(head, "data");
|
|
705
|
+
if (text)
|
|
706
|
+
bodyOf(s, text, RESULT_MAX);
|
|
707
|
+
return [{ ...s }];
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* kiro classic (v1): one conversation row's `history` — each entry a user side (`Prompt`, `ToolUseResults`,
|
|
712
|
+
* `CancelledToolUses`) and an assistant side (`ToolUse` with its text and calls, or `Response`). The row is rewritten
|
|
713
|
+
* as the conversation goes on, so its steps are the whole history's; ids are by entry (`h<i>.<n>`) and calls by their
|
|
714
|
+
* own id. Read in the kiro worker (never on the fleet loop); `max` bounds what comes back.
|
|
715
|
+
*/
|
|
716
|
+
export function kiroClassicSteps(value, max = 2000) {
|
|
717
|
+
const history = Array.isArray(value?.history) ? value.history : [];
|
|
718
|
+
const p = new KiroClassicParser();
|
|
719
|
+
history.forEach((h, i) => p.entry(h, i));
|
|
720
|
+
return { steps: p.steps.slice(-max), entries: history.length };
|
|
721
|
+
}
|
|
722
|
+
class KiroClassicParser extends StepParser {
|
|
723
|
+
skipped = new Set();
|
|
724
|
+
record() { return []; }
|
|
725
|
+
entry(h, i) {
|
|
726
|
+
const e = (h ?? {});
|
|
727
|
+
const user = (e.user ?? {});
|
|
728
|
+
const ts = tsOf(user.timestamp);
|
|
729
|
+
const content = (user.content ?? {});
|
|
730
|
+
this.at = i;
|
|
731
|
+
const prompt = content.Prompt?.prompt;
|
|
732
|
+
const results = (content.ToolUseResults ?? content.CancelledToolUses)?.tool_use_results;
|
|
733
|
+
const cancelled = !!content.CancelledToolUses;
|
|
734
|
+
if (typeof prompt === "string" && prompt.trim()) {
|
|
735
|
+
this.newTurn(`h${i}`);
|
|
736
|
+
const s = this.add({ id: `h${i}.u`, kind: "user", ts });
|
|
737
|
+
bodyOf(s, prompt, TEXT_MAX);
|
|
738
|
+
}
|
|
739
|
+
for (const r of Array.isArray(results) ? results : []) {
|
|
740
|
+
const id = String(r.tool_use_id ?? "");
|
|
741
|
+
if (this.skipped.has(id))
|
|
742
|
+
continue;
|
|
743
|
+
const s = this.close(id) ?? this.add({ id: id || `h${i}.r`, kind: "tool", ts, tool: "", cat: "other", status: "running" });
|
|
744
|
+
const { text, exit } = kiroResultText(r.content);
|
|
745
|
+
s.status = cancelled ? "interrupted" : r.status === "Error" || r.status === "error" || (exit !== undefined && exit !== 0) ? "failed" : "done";
|
|
746
|
+
if (exit !== undefined)
|
|
747
|
+
s.exitCode = exit;
|
|
748
|
+
if (text)
|
|
749
|
+
bodyOf(s, text, RESULT_MAX);
|
|
750
|
+
}
|
|
751
|
+
// A cancel's prompt starts the next turn only after its results close the calls it cancelled (a new turn would
|
|
752
|
+
// interrupt them first, and their results would find no call).
|
|
753
|
+
if (cancelled) {
|
|
754
|
+
const cp = content.CancelledToolUses.prompt;
|
|
755
|
+
if (typeof cp === "string" && cp.trim()) {
|
|
756
|
+
this.newTurn(`h${i}`);
|
|
757
|
+
const s = this.add({ id: `h${i}.u`, kind: "user", ts });
|
|
758
|
+
bodyOf(s, cp, TEXT_MAX);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
const a = (e.assistant ?? {});
|
|
762
|
+
const tu = a.ToolUse, resp = a.Response;
|
|
763
|
+
const text = (tu ?? resp)?.content;
|
|
764
|
+
if (typeof text === "string" && text.trim()) {
|
|
765
|
+
const s = this.add({ id: `h${i}.a`, kind: "assistant", ts });
|
|
766
|
+
bodyOf(s, text, TEXT_MAX);
|
|
767
|
+
}
|
|
768
|
+
for (const u of Array.isArray(tu?.tool_uses) ? tu.tool_uses : []) {
|
|
769
|
+
const name = String(u.orig_name ?? u.name ?? "tool");
|
|
770
|
+
if (THINKING_TOOLS.has(name)) {
|
|
771
|
+
this.skipped.add(String(u.id ?? ""));
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
774
|
+
const { input, purpose } = kiroInput(u.orig_args ?? u.args);
|
|
775
|
+
const desc = describeTool(name, input);
|
|
776
|
+
const s = this.add({ id: String(u.id ?? `h${i}.t`), kind: "tool", ts, tool: name, ...desc, ...(desc.subject ? {} : purpose ? { subject: subjectOf(purpose) } : {}), status: "running" });
|
|
777
|
+
inputOf(s, input);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
/* ------------------------------------------------------------------ backfill: the end of a transcript */
|
|
782
|
+
export const BACKFILL_MAX_BYTES = 8 * 1024 * 1024;
|
|
783
|
+
const BACKFILL_CHUNK = 256 * 1024;
|
|
784
|
+
export async function readTailRecords(path, opts = {}) {
|
|
785
|
+
const maxBytes = Math.max(1, opts.maxBytes ?? BACKFILL_MAX_BYTES);
|
|
786
|
+
const maxLines = Math.max(1, opts.maxLines ?? 2000);
|
|
787
|
+
// `end`: read the records before this offset (a record start, from an earlier read: a line boundary) — older ones.
|
|
788
|
+
const end = opts.end !== undefined ? Math.min(opts.end, await lastLineBoundary(path)) : await lastLineBoundary(path);
|
|
789
|
+
const out = [];
|
|
790
|
+
if (end === 0)
|
|
791
|
+
return { records: out, truncated: false, end };
|
|
792
|
+
const KEEP = LINE_MAX_BYTES + LONG_HEAD_BYTES;
|
|
793
|
+
const fh = await open(path, "r");
|
|
794
|
+
try {
|
|
795
|
+
let pos = end - 1; // the last record's line end (excluded); records lie between line ends
|
|
796
|
+
let recEnd = end - 1; // where the record being assembled ends (its line end)
|
|
797
|
+
let acc = Buffer.alloc(0); // its bytes after the current chunk: its first KEEP bytes at most
|
|
798
|
+
let read = 0;
|
|
799
|
+
const emit = (bytes, total, at) => { if (total > 0)
|
|
800
|
+
out.push({ record: toRecord(bytes, total, at), at }); };
|
|
801
|
+
while (pos > 0 && read < maxBytes && out.length < maxLines) {
|
|
802
|
+
const length = Math.min(BACKFILL_CHUNK, pos, maxBytes - read);
|
|
803
|
+
const start = pos - length;
|
|
804
|
+
const buf = Buffer.alloc(length);
|
|
805
|
+
for (let filled = 0; filled < length;) {
|
|
806
|
+
const { bytesRead } = await fh.read(buf, filled, length - filled, start + filled);
|
|
807
|
+
if (bytesRead <= 0)
|
|
808
|
+
throw new Error(`readTailRecords: no progress reading ${path} at ${start + filled}`);
|
|
809
|
+
filled += bytesRead;
|
|
810
|
+
}
|
|
811
|
+
read += length;
|
|
812
|
+
let segEnd = length;
|
|
813
|
+
for (let i = length - 1; i >= 0 && out.length < maxLines; i--) {
|
|
814
|
+
if (buf[i] !== 0x0a)
|
|
815
|
+
continue;
|
|
816
|
+
const total = recEnd - (start + i + 1);
|
|
817
|
+
emit(Buffer.concat([buf.subarray(i + 1, segEnd), acc]), total, start + i + 1);
|
|
818
|
+
acc = Buffer.alloc(0);
|
|
819
|
+
recEnd = start + i;
|
|
820
|
+
segEnd = i;
|
|
821
|
+
}
|
|
822
|
+
if (out.length >= maxLines) {
|
|
823
|
+
pos = start + segEnd;
|
|
824
|
+
break;
|
|
825
|
+
}
|
|
826
|
+
acc = Buffer.concat([buf.subarray(0, segEnd), acc]);
|
|
827
|
+
if (acc.length > KEEP)
|
|
828
|
+
acc = acc.subarray(0, KEEP);
|
|
829
|
+
pos = start;
|
|
830
|
+
}
|
|
831
|
+
// The first record of the file, when the read reached it.
|
|
832
|
+
if (pos === 0 && out.length < maxLines) {
|
|
833
|
+
emit(acc, recEnd, 0);
|
|
834
|
+
recEnd = 0;
|
|
835
|
+
}
|
|
836
|
+
return { records: out.reverse(), truncated: recEnd > 0, end };
|
|
837
|
+
}
|
|
838
|
+
finally {
|
|
839
|
+
await fh.close();
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
function toRecord(piece, bytes, offset) {
|
|
843
|
+
if (bytes > LINE_MAX_BYTES) {
|
|
844
|
+
let endAt = Math.min(LONG_HEAD_BYTES, piece.length);
|
|
845
|
+
if (endAt < piece.length)
|
|
846
|
+
while (endAt > 0 && (piece[endAt] & 0xc0) === 0x80)
|
|
847
|
+
endAt--;
|
|
848
|
+
return { at: 0, head: piece.toString("utf-8", 0, endAt), bytes, complete: true, offset };
|
|
849
|
+
}
|
|
850
|
+
return piece.toString("utf-8");
|
|
851
|
+
}
|
|
852
|
+
/** Parse `records` (file order) with `parser` and return its last `max` steps. */
|
|
853
|
+
export function stepsOf(parser, records, max = 200) {
|
|
854
|
+
for (const r of records)
|
|
855
|
+
parser.push(r.record, r.at);
|
|
856
|
+
return parser.steps.slice(-max);
|
|
857
|
+
}
|
|
858
|
+
//# sourceMappingURL=log-steps.js.map
|