@songsid/agend 2.2.0-beta.1 → 2.2.0-beta.10
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 +21 -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 +2 -2
- package/dist/auth-api.js +4 -2
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/antigravity.d.ts +3 -0
- package/dist/backend/antigravity.js +9 -0
- 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 +1 -1
- package/dist/backend/effort-metadata.js +3 -2
- 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 +33 -0
- package/dist/backend/launch-effort.js +123 -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 +33 -4
- package/dist/backend/types.js +2 -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 +24 -1
- package/dist/channel/adapters/discord.js +87 -2
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +24 -2
- package/dist/channel/adapters/telegram.js +118 -20
- 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 +24 -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.js +9 -3
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/channel/types.d.ts +39 -3
- package/dist/cli.js +133 -28
- 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 +764 -89
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.d.ts +62 -7
- package/dist/delivery-outbox.js +309 -22
- 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 +345 -10
- package/dist/fleet-manager.js +1868 -174
- 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 +9 -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 +200 -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 +263 -96
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +26 -15
- package/dist/outbound-schemas.js +24 -7
- 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 +11 -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 +3 -0
- package/dist/reply-buttons-controller.js +3 -2
- package/dist/reply-buttons-controller.js.map +1 -1
- 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-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 +92 -7
- 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 +42 -2
- package/dist/ui/chat-store.js +2 -2
- package/dist/ui/chat-strings.js +38 -20
- package/dist/ui/chat-thread.js +69 -12
- package/dist/ui/chat-time.js +80 -0
- package/dist/ui/panel-chat.js +39 -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 +137 -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 +46 -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 +23 -5
- package/dist/ui/shared/app.css +155 -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-install.d.ts +28 -2
- package/dist/update-install.js +22 -4
- package/dist/update-install.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 +49 -2
- package/dist/web-api.js +421 -31
- 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-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-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 +63 -0
- package/dist/web-prefs.js +265 -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-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 +36 -1
- package/dist/web-session.js +53 -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
|
@@ -26,17 +26,125 @@ import { measureSyncWork } from "./sync-work-attribution.js";
|
|
|
26
26
|
* - Every poll re-resolves which transcript is active. Pinning to the first
|
|
27
27
|
* one found silently goes blind when the CLI starts a new session.
|
|
28
28
|
*/
|
|
29
|
-
import {
|
|
30
|
-
import { readdir, readFile, stat } from "node:fs/promises";
|
|
29
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
30
|
+
import { open as openFile, readdir, readFile, stat } from "node:fs/promises";
|
|
31
31
|
import { homedir } from "node:os";
|
|
32
32
|
import { join, resolve } from "node:path";
|
|
33
33
|
import { sharedRolloutIndex } from "./rollout-index.js";
|
|
34
34
|
import Database from "better-sqlite3";
|
|
35
35
|
import { sharedKiroTranscriptLane } from "./kiro-transcript-lane.js";
|
|
36
36
|
export { extractKiroAssistantStrings } from "./kiro-db-reader.js";
|
|
37
|
-
import { lastLineBoundary, readNewLines } from "./transcript-jsonl.js";
|
|
37
|
+
import { LINE_MAX_BYTES, LONG_HEAD_BYTES, LOOKS_PER_POLL, READ_MAX_BYTES, lastLineBoundary, readNewLines, readWholeLine, resolveLongLines } from "./transcript-jsonl.js";
|
|
38
38
|
import { museSessionCwd, readFileHeadSync } from "./backend/muse.js";
|
|
39
39
|
import { performance } from "node:perf_hooks";
|
|
40
|
+
/**
|
|
41
|
+
* #1641 R3: the complete records in [from, to) that `keep` accepts — `to` is a line boundary (a checkpoint's), so
|
|
42
|
+
* nothing is cut. #1218 P0: read in READ_MAX_BYTES chunks (the range a checkpoint skips can be many MB), and only kept
|
|
43
|
+
* lines are held. A line over LINE_MAX_BYTES is judged by its head: kept, it is read whole (≤ WHOLE_LINE_MAX_BYTES —
|
|
44
|
+
* a turn's record must not be lost to the limit, #1786 review); otherwise it is skipped without being decoded.
|
|
45
|
+
*/
|
|
46
|
+
export async function readLineRange(path, from, to, keep = () => true) {
|
|
47
|
+
const fh = await openFile(path, "r");
|
|
48
|
+
const out = [];
|
|
49
|
+
const whole = async (offset, bytes, head) => {
|
|
50
|
+
if (!keep(head))
|
|
51
|
+
return;
|
|
52
|
+
const line = await readWholeLine(path, offset, bytes);
|
|
53
|
+
if (line !== null && line.trim() && keep(line))
|
|
54
|
+
out.push(line);
|
|
55
|
+
};
|
|
56
|
+
try {
|
|
57
|
+
let pos = from;
|
|
58
|
+
let long = null; // a long line that started in an earlier chunk
|
|
59
|
+
while (pos < to) {
|
|
60
|
+
const buffer = Buffer.alloc(Math.min(READ_MAX_BYTES, to - pos));
|
|
61
|
+
for (let filled = 0; filled < buffer.length;) {
|
|
62
|
+
const { bytesRead } = await fh.read(buffer, filled, buffer.length - filled, pos + filled);
|
|
63
|
+
if (bytesRead <= 0)
|
|
64
|
+
throw new Error(`readLineRange: no progress reading ${path} at ${pos + filled}`);
|
|
65
|
+
filled += bytesRead;
|
|
66
|
+
}
|
|
67
|
+
// Up to the last line end in this chunk (the range's end is one); a chunk with none is inside one long line.
|
|
68
|
+
const complete = buffer.lastIndexOf(0x0a) + 1;
|
|
69
|
+
if (complete === 0) {
|
|
70
|
+
long ??= { start: pos, head: buffer.toString("utf8", 0, Math.min(LONG_HEAD_BYTES, buffer.length)) };
|
|
71
|
+
pos += buffer.length;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
let start = 0;
|
|
75
|
+
while (start < complete) {
|
|
76
|
+
const end = buffer.indexOf(0x0a, start);
|
|
77
|
+
if (long) {
|
|
78
|
+
await whole(long.start, pos + end - long.start, long.head);
|
|
79
|
+
long = null;
|
|
80
|
+
}
|
|
81
|
+
else if (end - start > LINE_MAX_BYTES) {
|
|
82
|
+
await whole(pos + start, end - start, buffer.toString("utf8", start, start + LONG_HEAD_BYTES));
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
const line = buffer.toString("utf8", start, end);
|
|
86
|
+
if (line.trim() && keep(line))
|
|
87
|
+
out.push(line);
|
|
88
|
+
}
|
|
89
|
+
start = end + 1;
|
|
90
|
+
}
|
|
91
|
+
pos += complete;
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
await fh.close();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** #1641 R2: how far back from the baseline a rollout is scanned for the last turn boundary (a turn writes a lot). */
|
|
100
|
+
export const ROLLOUT_TURN_SCAN_MAX_BYTES = 16 * 1024 * 1024;
|
|
101
|
+
const ROLLOUT_TURN_SCAN_CHUNK = 256 * 1024;
|
|
102
|
+
/**
|
|
103
|
+
* #1641 R2: the last turn boundary (task_started / task_complete / turn_aborted) a codex rollout holds before `end`,
|
|
104
|
+
* scanning back at most ROLLOUT_TURN_SCAN_MAX_BYTES: `none` when the whole prefix holds none (no turn ever started),
|
|
105
|
+
* null when the bound ran out first (unknown). Nothing else is read or replayed.
|
|
106
|
+
*/
|
|
107
|
+
export async function lastRolloutTurnBoundary(path, end, maxBytes = ROLLOUT_TURN_SCAN_MAX_BYTES) {
|
|
108
|
+
const fh = await openFile(path, "r");
|
|
109
|
+
try {
|
|
110
|
+
const stop = Math.max(0, end - maxBytes);
|
|
111
|
+
let pos = end;
|
|
112
|
+
let carry = Buffer.alloc(0);
|
|
113
|
+
while (pos > stop) {
|
|
114
|
+
const from = Math.max(stop, pos - ROLLOUT_TURN_SCAN_CHUNK);
|
|
115
|
+
const chunk = Buffer.alloc(pos - from);
|
|
116
|
+
await fh.read(chunk, 0, chunk.length, from);
|
|
117
|
+
pos = from;
|
|
118
|
+
const buf = Buffer.concat([chunk, carry]);
|
|
119
|
+
const lines = buf.toString("utf8").split("\n");
|
|
120
|
+
// The first piece may be the tail of a line that starts in an earlier chunk (unless this is the scan's start).
|
|
121
|
+
carry = pos > stop ? Buffer.from(lines.shift() ?? "", "utf8") : Buffer.alloc(0);
|
|
122
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
123
|
+
const line = lines[i];
|
|
124
|
+
if (!/"(?:task_started|task_complete|turn_aborted)"/.test(line))
|
|
125
|
+
continue;
|
|
126
|
+
let entry;
|
|
127
|
+
try {
|
|
128
|
+
entry = JSON.parse(line);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const p = entry.payload;
|
|
134
|
+
if (entry.type !== "event_msg" || !p || typeof p.turn_id !== "string")
|
|
135
|
+
continue;
|
|
136
|
+
if (p.type === "task_started")
|
|
137
|
+
return { kind: "start", turnId: p.turn_id };
|
|
138
|
+
if (p.type === "task_complete" || p.type === "turn_aborted")
|
|
139
|
+
return { kind: "end", turnId: p.turn_id };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return stop === 0 ? { kind: "none" } : null;
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
await fh.close();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
40
148
|
const EMPTY = { toolUses: [], toolResults: [], assistantTexts: [] };
|
|
41
149
|
function emptyEvents() {
|
|
42
150
|
return { toolUses: [], toolResults: [], assistantTexts: [] };
|
|
@@ -51,7 +159,13 @@ export class CodexRolloutSource {
|
|
|
51
159
|
workingDirectory;
|
|
52
160
|
sessionsDir;
|
|
53
161
|
currentFile = null;
|
|
162
|
+
/** #1218: the rollout followed now. */
|
|
163
|
+
currentPath() { return this.currentFile; }
|
|
54
164
|
byteOffset = 0;
|
|
165
|
+
/** #1218 P0: the offset is inside a long line being skipped (readNewLines `skipping`). */
|
|
166
|
+
skipping = false;
|
|
167
|
+
/** #1218 P0: a long line being skipped that is to be read whole once its end is found. */
|
|
168
|
+
pendingLong = null;
|
|
55
169
|
/** EOF snapshots taken when the monitor attaches; existing history is skipped. */
|
|
56
170
|
initialOffsets = new Map();
|
|
57
171
|
/** Files whose session_meta was read and did NOT match our cwd. */
|
|
@@ -71,29 +185,137 @@ export class CodexRolloutSource {
|
|
|
71
185
|
this.index = index ?? sharedRolloutIndex(sessionsDir);
|
|
72
186
|
this.snapshotExistingFiles();
|
|
73
187
|
}
|
|
188
|
+
/** #1641: the last poll found this instance's rollout and read it (see TranscriptSource.following). */
|
|
189
|
+
lastPollFollowed = false;
|
|
190
|
+
/** #1641 R2: the turn open in the current file (its task_started read, no end yet), and whether that is known. */
|
|
191
|
+
openTurn = null;
|
|
192
|
+
turnKnown = false;
|
|
193
|
+
/** #1641 R2: boundaries decided at adoption (the old file's open turn ended, the new file's open turn carried in). */
|
|
194
|
+
pendingTurns = [];
|
|
195
|
+
following() {
|
|
196
|
+
return this.lastPollFollowed && this.currentFile !== null;
|
|
197
|
+
}
|
|
198
|
+
turnStateKnown() {
|
|
199
|
+
return this.turnKnown;
|
|
200
|
+
}
|
|
201
|
+
/** #1641 R3: turn records decided outside a poll (a checkpoint's adoption or skipped range), handed over now. */
|
|
202
|
+
takeTurns() {
|
|
203
|
+
return this.pendingTurns.splice(0);
|
|
204
|
+
}
|
|
74
205
|
reset() {
|
|
206
|
+
this.lastPollFollowed = false;
|
|
207
|
+
this.openTurn = null;
|
|
208
|
+
this.turnKnown = false;
|
|
209
|
+
this.pendingTurns = [];
|
|
75
210
|
this.currentFile = null;
|
|
76
211
|
this.byteOffset = 0;
|
|
212
|
+
this.skipping = false;
|
|
213
|
+
this.pendingLong = null;
|
|
77
214
|
this.openTurnId = "";
|
|
78
215
|
this.rejected.clear();
|
|
79
216
|
this.accepted.clear();
|
|
80
217
|
this.snapshotExistingFiles();
|
|
81
218
|
}
|
|
82
219
|
async checkpoint() {
|
|
83
|
-
const active = this.
|
|
220
|
+
const active = await this.firstOwned(this.candidateFiles(true));
|
|
84
221
|
if (!active)
|
|
85
222
|
return null;
|
|
86
223
|
try {
|
|
87
224
|
// A line boundary, not the size: a record being written now is read once complete (#1250).
|
|
88
225
|
const offset = await lastLineBoundary(active.path);
|
|
226
|
+
if (active.path !== this.currentFile)
|
|
227
|
+
await this.adopt(active.path, offset); // a switch, whoever notices it
|
|
228
|
+
else if (offset > this.byteOffset) {
|
|
229
|
+
// #1641 R3: the range this checkpoint skips is never polled — its turn boundaries (and user records) are taken
|
|
230
|
+
// now, so the turn state stays the file's. Tool and text records stay skipped, as before.
|
|
231
|
+
for (const line of await readLineRange(active.path, this.byteOffset, offset, (l) => l.includes('"event_msg"') || l.includes('"role":"user"'))) {
|
|
232
|
+
let entry;
|
|
233
|
+
try {
|
|
234
|
+
entry = JSON.parse(line);
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
this.turnRecord(entry, this.pendingTurns);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
89
242
|
this.currentFile = active.path;
|
|
90
243
|
this.byteOffset = offset;
|
|
244
|
+
this.skipping = false;
|
|
245
|
+
this.pendingLong = null;
|
|
91
246
|
return { path: active.path, offset, sessionId: active.path };
|
|
92
247
|
}
|
|
93
248
|
catch {
|
|
94
249
|
return null;
|
|
95
250
|
}
|
|
96
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* Follow `path` from `offset` (#1641 R2). A baseline past an existing file's start skips its history — but not the
|
|
254
|
+
* state of its current turn: the last boundary before the baseline says whether a turn is running now (carried in as
|
|
255
|
+
* its start; no tool, user or text record is replayed), and none within the scan bound leaves it unknown until one is
|
|
256
|
+
* read. A turn left open in the file being replaced belongs to that file, no longer followed: its busy evidence is
|
|
257
|
+
* withdrawn (retracted, not ended — nothing says how it ended).
|
|
258
|
+
*/
|
|
259
|
+
async adopt(path, offset) {
|
|
260
|
+
let baseline = null;
|
|
261
|
+
let known = offset === 0; // read from its start: every boundary will be read
|
|
262
|
+
if (offset > 0) {
|
|
263
|
+
try {
|
|
264
|
+
baseline = await lastRolloutTurnBoundary(path, offset);
|
|
265
|
+
known = baseline !== null; // `none`: the whole prefix holds no turn — known closed
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
known = false;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (this.openTurn)
|
|
272
|
+
this.pendingTurns.push({ kind: "retract", turnId: this.openTurn });
|
|
273
|
+
this.openTurn = baseline?.kind === "start" ? baseline.turnId : null;
|
|
274
|
+
if (this.openTurn)
|
|
275
|
+
this.pendingTurns.push({ kind: "start", turnId: this.openTurn });
|
|
276
|
+
this.turnKnown = known;
|
|
277
|
+
this.currentFile = path;
|
|
278
|
+
this.byteOffset = offset;
|
|
279
|
+
this.skipping = false;
|
|
280
|
+
this.pendingLong = null;
|
|
281
|
+
this.openTurnId = this.openTurn ?? ""; // a steer after the baseline belongs to the carried-in turn
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* A turn record (a boundary, or a user message) into `turns`, keeping the open turn current; true when `entry` was
|
|
285
|
+
* one (or another event_msg), so the caller looks no further. #1510: codex 0.162 writes one task_started per turn and
|
|
286
|
+
* ends it with task_complete (last_agent_message null after a provider error) or turn_aborted; a steer lands inside
|
|
287
|
+
* the open turn as another user message.
|
|
288
|
+
*/
|
|
289
|
+
turnRecord(entry, turns) {
|
|
290
|
+
const p = entry.payload;
|
|
291
|
+
if (!p)
|
|
292
|
+
return false;
|
|
293
|
+
if (entry.type === "event_msg") {
|
|
294
|
+
if (p.type === "task_started" && typeof p.turn_id === "string") {
|
|
295
|
+
this.openTurnId = p.turn_id;
|
|
296
|
+
this.openTurn = p.turn_id;
|
|
297
|
+
this.turnKnown = true;
|
|
298
|
+
turns.push({ kind: "start", turnId: p.turn_id });
|
|
299
|
+
}
|
|
300
|
+
else if ((p.type === "task_complete" || p.type === "turn_aborted") && typeof p.turn_id === "string") {
|
|
301
|
+
const end = p.type === "turn_aborted" ? "aborted" : p.last_agent_message == null ? "error" : "complete";
|
|
302
|
+
if (this.openTurn === p.turn_id)
|
|
303
|
+
this.openTurn = null;
|
|
304
|
+
this.turnKnown = true;
|
|
305
|
+
turns.push({ kind: "end", turnId: p.turn_id, end });
|
|
306
|
+
}
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
if (entry.type === "response_item" && p.type === "message" && p.role === "user") {
|
|
310
|
+
const text = (p.content ?? [])
|
|
311
|
+
.map(block => typeof block.text === "string" ? block.text : "").join("\n");
|
|
312
|
+
const at = Date.parse(String(entry.timestamp ?? ""));
|
|
313
|
+
if (text.trim() && Number.isFinite(at))
|
|
314
|
+
turns.push({ kind: "user", turnId: this.openTurnId, text, at });
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
97
319
|
snapshotExistingFiles() {
|
|
98
320
|
this.initialOffsets = new Map(this.candidateFiles(true).map(file => [file.path, file.size]));
|
|
99
321
|
}
|
|
@@ -111,23 +333,31 @@ export class CodexRolloutSource {
|
|
|
111
333
|
candidateFiles(fresh = false) {
|
|
112
334
|
return this.index.list(fresh);
|
|
113
335
|
}
|
|
114
|
-
|
|
336
|
+
/** The first of `files` that is ours (in order), or undefined. Verdicts are cached, so a known file costs no read. */
|
|
337
|
+
async firstOwned(files) {
|
|
338
|
+
for (const file of files)
|
|
339
|
+
if (await this.fileBelongsToUs(file.path))
|
|
340
|
+
return file;
|
|
341
|
+
return undefined;
|
|
342
|
+
}
|
|
343
|
+
async fileBelongsToUs(path) {
|
|
115
344
|
if (this.rejected.has(path))
|
|
116
345
|
return false;
|
|
117
346
|
if (this.accepted.has(path))
|
|
118
347
|
return true;
|
|
119
348
|
try {
|
|
120
349
|
// session_meta is the first line. It can carry long instructions, so
|
|
121
|
-
// give it headroom — but never read the whole rollout.
|
|
122
|
-
|
|
350
|
+
// give it headroom — but never read the whole rollout. #1218 P0: read asynchronously, off the fleet loop's
|
|
351
|
+
// critical path (it was a synchronous 64 KiB read per new rollout).
|
|
352
|
+
const fh = await openFile(path, "r");
|
|
123
353
|
let head;
|
|
124
354
|
try {
|
|
125
355
|
const buf = Buffer.alloc(65536);
|
|
126
|
-
const
|
|
127
|
-
head = buf.toString("utf-8", 0,
|
|
356
|
+
const { bytesRead } = await fh.read(buf, 0, buf.length, 0);
|
|
357
|
+
head = buf.toString("utf-8", 0, bytesRead);
|
|
128
358
|
}
|
|
129
359
|
finally {
|
|
130
|
-
|
|
360
|
+
await fh.close();
|
|
131
361
|
}
|
|
132
362
|
const firstLine = head.split("\n")[0];
|
|
133
363
|
const meta = JSON.parse(firstLine);
|
|
@@ -148,9 +378,13 @@ export class CodexRolloutSource {
|
|
|
148
378
|
// Re-resolve every poll: a restarted codex writes a NEW rollout, and
|
|
149
379
|
// staying pinned to the old one goes silently blind (#528 trap 1).
|
|
150
380
|
const candidates = this.candidateFiles();
|
|
151
|
-
const active =
|
|
152
|
-
|
|
381
|
+
const active = await this.firstOwned(candidates);
|
|
382
|
+
// Cleared only by a real loss (none found, an adoption or read that failed) — never for the length of a poll's
|
|
383
|
+
// await, when a capture reading following() would drop the veto mid-turn.
|
|
384
|
+
if (!active) {
|
|
385
|
+
this.lastPollFollowed = false;
|
|
153
386
|
return EMPTY;
|
|
387
|
+
}
|
|
154
388
|
if (active.path !== this.currentFile) {
|
|
155
389
|
// Existing rollout: continue at the EOF captured when the source was
|
|
156
390
|
// created. New rollout: read from the start. Using current mtime here is
|
|
@@ -165,18 +399,43 @@ export class CodexRolloutSource {
|
|
|
165
399
|
offset = await lastLineBoundary(active.path, captured);
|
|
166
400
|
}
|
|
167
401
|
catch {
|
|
402
|
+
this.lastPollFollowed = false;
|
|
168
403
|
return EMPTY;
|
|
169
404
|
}
|
|
170
405
|
}
|
|
171
|
-
this.
|
|
172
|
-
|
|
173
|
-
|
|
406
|
+
await this.adopt(active.path, offset);
|
|
407
|
+
}
|
|
408
|
+
// #1218 P0: bounded looks (READ_MAX_BYTES each), a few per poll, each awaited (a sampled rollout was 322 MB).
|
|
409
|
+
const records = [];
|
|
410
|
+
for (let look = 0; look < LOOKS_PER_POLL; look++) {
|
|
411
|
+
let read;
|
|
412
|
+
try {
|
|
413
|
+
read = await readNewLines(active.path, this.byteOffset, { skipping: this.skipping });
|
|
414
|
+
}
|
|
415
|
+
catch (err) {
|
|
416
|
+
this.lastPollFollowed = false;
|
|
417
|
+
throw err;
|
|
418
|
+
}
|
|
419
|
+
this.skipping = read.skipping;
|
|
420
|
+
const moved = read.newOffset !== this.byteOffset;
|
|
421
|
+
this.byteOffset = read.newOffset;
|
|
422
|
+
// #1786 review: a long turn record (task_complete with a long final reply, a pasted prompt) is read whole — a
|
|
423
|
+
// turn's end must never be lost to the line limit. Long outputs stay head-only.
|
|
424
|
+
const resolved = await resolveLongLines(active.path, read, this.pendingLong, codexWholeLine);
|
|
425
|
+
this.pendingLong = resolved.pending;
|
|
426
|
+
records.push(...resolved.records);
|
|
427
|
+
if (!moved || !read.more)
|
|
428
|
+
break;
|
|
174
429
|
}
|
|
175
|
-
|
|
176
|
-
this.byteOffset = newOffset;
|
|
430
|
+
this.lastPollFollowed = true;
|
|
177
431
|
const events = emptyEvents();
|
|
178
|
-
const turns =
|
|
179
|
-
for (const
|
|
432
|
+
const turns = this.pendingTurns.splice(0);
|
|
433
|
+
for (const record of records) {
|
|
434
|
+
if ("long" in record) {
|
|
435
|
+
codexLongHead(record.long.head, events);
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
const line = record.line;
|
|
180
439
|
let entry;
|
|
181
440
|
try {
|
|
182
441
|
entry = JSON.parse(line);
|
|
@@ -187,29 +446,10 @@ export class CodexRolloutSource {
|
|
|
187
446
|
const p = entry.payload;
|
|
188
447
|
if (!p)
|
|
189
448
|
continue;
|
|
190
|
-
|
|
191
|
-
// after a provider error) or turn_aborted; a steer lands inside the open turn as another user message.
|
|
192
|
-
if (entry.type === "event_msg") {
|
|
193
|
-
if (p.type === "task_started" && typeof p.turn_id === "string") {
|
|
194
|
-
this.openTurnId = p.turn_id;
|
|
195
|
-
turns.push({ kind: "start", turnId: p.turn_id });
|
|
196
|
-
}
|
|
197
|
-
else if ((p.type === "task_complete" || p.type === "turn_aborted") && typeof p.turn_id === "string") {
|
|
198
|
-
const end = p.type === "turn_aborted" ? "aborted" : p.last_agent_message == null ? "error" : "complete";
|
|
199
|
-
turns.push({ kind: "end", turnId: p.turn_id, end });
|
|
200
|
-
}
|
|
449
|
+
if (this.turnRecord(entry, turns))
|
|
201
450
|
continue;
|
|
202
|
-
}
|
|
203
451
|
if (entry.type !== "response_item")
|
|
204
452
|
continue;
|
|
205
|
-
if (p.type === "message" && p.role === "user") {
|
|
206
|
-
const text = (p.content ?? [])
|
|
207
|
-
.map(block => typeof block.text === "string" ? block.text : "").join("\n");
|
|
208
|
-
const at = Date.parse(String(entry.timestamp ?? ""));
|
|
209
|
-
if (text.trim() && Number.isFinite(at))
|
|
210
|
-
turns.push({ kind: "user", turnId: this.openTurnId, text, at });
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
453
|
if (p.type === "function_call") {
|
|
214
454
|
let input = p.arguments;
|
|
215
455
|
if (typeof input === "string") {
|
|
@@ -244,6 +484,25 @@ export class CodexRolloutSource {
|
|
|
244
484
|
return events;
|
|
245
485
|
}
|
|
246
486
|
}
|
|
487
|
+
/** #1786 review: the long rollout lines read whole — turn records (event_msg) and messages (a pasted prompt, a long reply). */
|
|
488
|
+
function codexWholeLine(head) {
|
|
489
|
+
return head.includes('"type":"event_msg"') || (head.includes('"type":"response_item"') && /"payload":\{"type":"message"/.test(head));
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* #1218 P0: a rollout line too long to parse (an output of MBs, a huge call input) — what its head still says, so the
|
|
493
|
+
* bubble keeps the step: a call by its name, or an output. Turn records and messages are read whole (codexWholeLine).
|
|
494
|
+
*/
|
|
495
|
+
function codexLongHead(head, events) {
|
|
496
|
+
if (!head.includes('"type":"response_item"'))
|
|
497
|
+
return;
|
|
498
|
+
const type = /"payload":\{[^{}]{0,400}?"type":"(function_call|custom_tool_call|function_call_output|custom_tool_call_output)"/.exec(head)?.[1];
|
|
499
|
+
if (type === "function_call" || type === "custom_tool_call") {
|
|
500
|
+
const name = /"name":"([^"]{1,200})"/.exec(head)?.[1];
|
|
501
|
+
events.toolUses.push({ name: name ?? "unknown", input: undefined });
|
|
502
|
+
}
|
|
503
|
+
else if (type)
|
|
504
|
+
events.toolResults.push({ name: "unknown" });
|
|
505
|
+
}
|
|
247
506
|
/* -------------------------------------------------------------------- muse */
|
|
248
507
|
/**
|
|
249
508
|
* #1510: muse's turn boundaries, from its session logs (`<root>/<YYYY>/<MM>/<DD>/<id>/session.jsonl`, recorded on
|
|
@@ -277,6 +536,9 @@ export class MuseSessionSource {
|
|
|
277
536
|
baselineComplete = false;
|
|
278
537
|
rejected = new Set();
|
|
279
538
|
sizes = new Map();
|
|
539
|
+
/** #1218 P0: per log, whether its offset is inside a long line being skipped, and a long turn record held for its end. */
|
|
540
|
+
skipping = new Map();
|
|
541
|
+
pendingLong = new Map();
|
|
280
542
|
listedAt = Number.NEGATIVE_INFINITY;
|
|
281
543
|
baselined = false;
|
|
282
544
|
/** intent_id → its text and time, until it materializes (bounded). */
|
|
@@ -293,6 +555,8 @@ export class MuseSessionSource {
|
|
|
293
555
|
this.rejected.clear();
|
|
294
556
|
this.sizes.clear();
|
|
295
557
|
this.intents.clear();
|
|
558
|
+
this.skipping.clear();
|
|
559
|
+
this.pendingLong.clear();
|
|
296
560
|
this.listedAt = Number.NEGATIVE_INFINITY;
|
|
297
561
|
this.baselined = false;
|
|
298
562
|
this.baselineComplete = false;
|
|
@@ -352,22 +616,45 @@ export class MuseSessionSource {
|
|
|
352
616
|
continue;
|
|
353
617
|
}
|
|
354
618
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
continue;
|
|
361
|
-
}
|
|
362
|
-
this.files.set(path, read.newOffset);
|
|
363
|
-
this.sizes.set(path, size);
|
|
364
|
-
// One record at a time: a record of an unexpected shape is skipped, never the batch it came in.
|
|
365
|
-
for (const line of read.lines) {
|
|
619
|
+
// #1218 P0 (#1786 review): bounded looks, as the other sources; the size is remembered only once the log has been
|
|
620
|
+
// read to it, so the rest of a delta over the look budget is read next poll, not when the log grows again.
|
|
621
|
+
let caughtUp = false;
|
|
622
|
+
for (let look = 0; look < LOOKS_PER_POLL; look++) {
|
|
623
|
+
let read;
|
|
366
624
|
try {
|
|
367
|
-
this.
|
|
625
|
+
read = await readNewLines(path, from, { skipping: this.skipping.get(path) ?? false });
|
|
626
|
+
}
|
|
627
|
+
catch {
|
|
628
|
+
break;
|
|
629
|
+
}
|
|
630
|
+
this.skipping.set(path, read.skipping);
|
|
631
|
+
const moved = read.newOffset !== from;
|
|
632
|
+
from = read.newOffset;
|
|
633
|
+
this.files.set(path, from);
|
|
634
|
+
// A long turn record (a pasted prompt in user_intent.accepted) is read whole, bounded.
|
|
635
|
+
const resolved = await resolveLongLines(path, read, this.pendingLong.get(path) ?? null, (head) => head.includes('"payload_type":"runtime.'));
|
|
636
|
+
if (resolved.pending)
|
|
637
|
+
this.pendingLong.set(path, resolved.pending);
|
|
638
|
+
else
|
|
639
|
+
this.pendingLong.delete(path);
|
|
640
|
+
// One record at a time: a record of an unexpected shape is skipped, never the batch it came in.
|
|
641
|
+
for (const record of resolved.records) {
|
|
642
|
+
if (!("line" in record))
|
|
643
|
+
continue;
|
|
644
|
+
try {
|
|
645
|
+
this.parse(record.line, turns);
|
|
646
|
+
}
|
|
647
|
+
catch { /* skipped */ }
|
|
368
648
|
}
|
|
369
|
-
|
|
649
|
+
if (!read.more) {
|
|
650
|
+
caughtUp = !read.skipping;
|
|
651
|
+
break;
|
|
652
|
+
}
|
|
653
|
+
if (!moved)
|
|
654
|
+
break;
|
|
370
655
|
}
|
|
656
|
+
if (caughtUp)
|
|
657
|
+
this.sizes.set(path, size);
|
|
371
658
|
}
|
|
372
659
|
const events = emptyEvents();
|
|
373
660
|
if (turns.length)
|
|
@@ -564,6 +851,10 @@ export class KiroSessionSource {
|
|
|
564
851
|
lane;
|
|
565
852
|
currentFile = null;
|
|
566
853
|
byteOffset = 0;
|
|
854
|
+
/** #1218 P0: the offset is inside a long line being skipped (readNewLines `skipping`). */
|
|
855
|
+
skipping = false;
|
|
856
|
+
/** #1218 P0: a long line being skipped that is to be read whole once its end is found. */
|
|
857
|
+
pendingLong = null;
|
|
567
858
|
createdAt;
|
|
568
859
|
generation = 0;
|
|
569
860
|
cursor;
|
|
@@ -597,6 +888,8 @@ export class KiroSessionSource {
|
|
|
597
888
|
this.close();
|
|
598
889
|
this.currentFile = null;
|
|
599
890
|
this.byteOffset = 0;
|
|
891
|
+
this.skipping = false;
|
|
892
|
+
this.pendingLong = null;
|
|
600
893
|
this.cursor = undefined;
|
|
601
894
|
this.needsBaseline = true;
|
|
602
895
|
void this.initialize();
|
|
@@ -734,6 +1027,8 @@ export class KiroSessionSource {
|
|
|
734
1027
|
if (active.createdAtMs >= this.createdAt) {
|
|
735
1028
|
this.currentFile = active.jsonlPath;
|
|
736
1029
|
this.byteOffset = 0; // our own fresh session — observable from the start
|
|
1030
|
+
this.skipping = false;
|
|
1031
|
+
this.pendingLong = null;
|
|
737
1032
|
}
|
|
738
1033
|
else {
|
|
739
1034
|
// An older session is attached at its last line boundary. If that cannot be read now, nothing is adopted
|
|
@@ -749,23 +1044,39 @@ export class KiroSessionSource {
|
|
|
749
1044
|
return EMPTY;
|
|
750
1045
|
this.currentFile = active.jsonlPath;
|
|
751
1046
|
this.byteOffset = offset;
|
|
1047
|
+
this.skipping = false;
|
|
1048
|
+
this.pendingLong = null;
|
|
752
1049
|
return EMPTY;
|
|
753
1050
|
}
|
|
754
1051
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
return EMPTY;
|
|
758
|
-
this.byteOffset = newOffset;
|
|
1052
|
+
// #1218 P0: bounded looks, as the other JSONL sources. Kiro's nesting needs the whole record: a long line is read
|
|
1053
|
+
// whole up to WHOLE_LINE_MAX_BYTES (as every line was before), and skipped beyond it.
|
|
759
1054
|
const events = emptyEvents();
|
|
760
|
-
for (
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
1055
|
+
for (let look = 0; look < LOOKS_PER_POLL; look++) {
|
|
1056
|
+
const read = await readNewLines(this.currentFile, this.byteOffset, { skipping: this.skipping });
|
|
1057
|
+
if (generation !== this.generation)
|
|
1058
|
+
return EMPTY;
|
|
1059
|
+
this.skipping = read.skipping;
|
|
1060
|
+
const moved = read.newOffset !== this.byteOffset;
|
|
1061
|
+
this.byteOffset = read.newOffset;
|
|
1062
|
+
const resolved = await resolveLongLines(this.currentFile, read, this.pendingLong, () => true);
|
|
1063
|
+
if (generation !== this.generation)
|
|
1064
|
+
return EMPTY;
|
|
1065
|
+
this.pendingLong = resolved.pending;
|
|
1066
|
+
for (const record of resolved.records) {
|
|
1067
|
+
if (!("line" in record))
|
|
1068
|
+
continue;
|
|
1069
|
+
let entry;
|
|
1070
|
+
try {
|
|
1071
|
+
entry = JSON.parse(record.line);
|
|
1072
|
+
}
|
|
1073
|
+
catch {
|
|
1074
|
+
continue;
|
|
1075
|
+
}
|
|
1076
|
+
collectKiroEvents(entry, events);
|
|
767
1077
|
}
|
|
768
|
-
|
|
1078
|
+
if (!moved || !read.more)
|
|
1079
|
+
break;
|
|
769
1080
|
}
|
|
770
1081
|
return events;
|
|
771
1082
|
}
|