@inhuman.tools/turn 0.0.0-stage → 0.1.0
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/LICENSE.md +7 -0
- package/dist/agent-calls/acceptance.d.ts +12 -0
- package/dist/agent-calls/acceptance.d.ts.map +1 -0
- package/dist/agent-calls/acceptance.js +64 -0
- package/dist/agent-calls/acceptance.js.map +1 -0
- package/dist/agent-calls/callbacks.d.ts +14 -0
- package/dist/agent-calls/callbacks.d.ts.map +1 -0
- package/dist/agent-calls/callbacks.js +59 -0
- package/dist/agent-calls/callbacks.js.map +1 -0
- package/dist/agent-calls/delivery.d.ts +7 -0
- package/dist/agent-calls/delivery.d.ts.map +1 -0
- package/dist/agent-calls/delivery.js +16 -0
- package/dist/agent-calls/delivery.js.map +1 -0
- package/dist/agent-calls/index.d.ts +7 -0
- package/dist/agent-calls/index.d.ts.map +1 -0
- package/dist/agent-calls/index.js +6 -0
- package/dist/agent-calls/index.js.map +1 -0
- package/dist/agent-calls/operator.d.ts +21 -0
- package/dist/agent-calls/operator.d.ts.map +1 -0
- package/dist/agent-calls/operator.js +78 -0
- package/dist/agent-calls/operator.js.map +1 -0
- package/dist/agent-calls/runtime.d.ts +99 -0
- package/dist/agent-calls/runtime.d.ts.map +1 -0
- package/dist/agent-calls/runtime.js +245 -0
- package/dist/agent-calls/runtime.js.map +1 -0
- package/dist/agent-calls/types.d.ts +31 -0
- package/dist/agent-calls/types.d.ts.map +1 -0
- package/dist/agent-calls/types.js +2 -0
- package/dist/agent-calls/types.js.map +1 -0
- package/dist/context/compact-floors.d.ts +43 -0
- package/dist/context/compact-floors.d.ts.map +1 -0
- package/dist/context/compact-floors.js +52 -0
- package/dist/context/compact-floors.js.map +1 -0
- package/dist/context/compact.d.ts +42 -0
- package/dist/context/compact.d.ts.map +1 -0
- package/dist/context/compact.js +155 -0
- package/dist/context/compact.js.map +1 -0
- package/dist/context/delivery.d.ts +13 -0
- package/dist/context/delivery.d.ts.map +1 -0
- package/dist/context/delivery.js +6 -0
- package/dist/context/delivery.js.map +1 -0
- package/dist/context/history.d.ts +66 -0
- package/dist/context/history.d.ts.map +1 -0
- package/dist/context/history.js +573 -0
- package/dist/context/history.js.map +1 -0
- package/dist/context/index.d.ts +10 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +10 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/observations.d.ts +7 -0
- package/dist/context/observations.d.ts.map +1 -0
- package/dist/context/observations.js +11 -0
- package/dist/context/observations.js.map +1 -0
- package/dist/context/retention/defaults.d.ts +10 -0
- package/dist/context/retention/defaults.d.ts.map +1 -0
- package/dist/context/retention/defaults.js +47 -0
- package/dist/context/retention/defaults.js.map +1 -0
- package/dist/context/retention/index.d.ts +14 -0
- package/dist/context/retention/index.d.ts.map +1 -0
- package/dist/context/retention/index.js +21 -0
- package/dist/context/retention/index.js.map +1 -0
- package/dist/context/retention/narrow.d.ts +22 -0
- package/dist/context/retention/narrow.d.ts.map +1 -0
- package/dist/context/retention/narrow.js +140 -0
- package/dist/context/retention/narrow.js.map +1 -0
- package/dist/context/retention/render.d.ts +37 -0
- package/dist/context/retention/render.d.ts.map +1 -0
- package/dist/context/retention/render.js +149 -0
- package/dist/context/retention/render.js.map +1 -0
- package/dist/context/retention/resolve.d.ts +51 -0
- package/dist/context/retention/resolve.d.ts.map +1 -0
- package/dist/context/retention/resolve.js +254 -0
- package/dist/context/retention/resolve.js.map +1 -0
- package/dist/context/retention/schema.d.ts +345 -0
- package/dist/context/retention/schema.d.ts.map +1 -0
- package/dist/context/retention/schema.js +149 -0
- package/dist/context/retention/schema.js.map +1 -0
- package/dist/context/retention/stamp.d.ts +25 -0
- package/dist/context/retention/stamp.d.ts.map +1 -0
- package/dist/context/retention/stamp.js +103 -0
- package/dist/context/retention/stamp.js.map +1 -0
- package/dist/context/retention/step.d.ts +33 -0
- package/dist/context/retention/step.d.ts.map +1 -0
- package/dist/context/retention/step.js +74 -0
- package/dist/context/retention/step.js.map +1 -0
- package/dist/context/retention/view.d.ts +57 -0
- package/dist/context/retention/view.d.ts.map +1 -0
- package/dist/context/retention/view.js +121 -0
- package/dist/context/retention/view.js.map +1 -0
- package/dist/context/squeeze.d.ts +45 -0
- package/dist/context/squeeze.d.ts.map +1 -0
- package/dist/context/squeeze.js +182 -0
- package/dist/context/squeeze.js.map +1 -0
- package/dist/context/tools.d.ts +16 -0
- package/dist/context/tools.d.ts.map +1 -0
- package/dist/context/tools.js +73 -0
- package/dist/context/tools.js.map +1 -0
- package/dist/context/transcript.d.ts +151 -0
- package/dist/context/transcript.d.ts.map +1 -0
- package/dist/context/transcript.js +659 -0
- package/dist/context/transcript.js.map +1 -0
- package/dist/policy-processor/bypass.d.ts +95 -0
- package/dist/policy-processor/bypass.d.ts.map +1 -0
- package/dist/policy-processor/bypass.js +188 -0
- package/dist/policy-processor/bypass.js.map +1 -0
- package/dist/policy-processor/classifier.d.ts +47 -0
- package/dist/policy-processor/classifier.d.ts.map +1 -0
- package/dist/policy-processor/classifier.js +671 -0
- package/dist/policy-processor/classifier.js.map +1 -0
- package/dist/policy-processor/cluster.d.ts +7 -0
- package/dist/policy-processor/cluster.d.ts.map +1 -0
- package/dist/policy-processor/cluster.js +32 -0
- package/dist/policy-processor/cluster.js.map +1 -0
- package/dist/policy-processor/evidence.d.ts +26 -0
- package/dist/policy-processor/evidence.d.ts.map +1 -0
- package/dist/policy-processor/evidence.js +204 -0
- package/dist/policy-processor/evidence.js.map +1 -0
- package/dist/policy-processor/explain.d.ts +59 -0
- package/dist/policy-processor/explain.d.ts.map +1 -0
- package/dist/policy-processor/explain.js +224 -0
- package/dist/policy-processor/explain.js.map +1 -0
- package/dist/policy-processor/index.d.ts +6 -0
- package/dist/policy-processor/index.d.ts.map +1 -0
- package/dist/policy-processor/index.js +6 -0
- package/dist/policy-processor/index.js.map +1 -0
- package/dist/policy-processor/license.d.ts +23 -0
- package/dist/policy-processor/license.d.ts.map +1 -0
- package/dist/policy-processor/license.js +47 -0
- package/dist/policy-processor/license.js.map +1 -0
- package/dist/policy-processor/peel-cwd.d.ts +2 -0
- package/dist/policy-processor/peel-cwd.d.ts.map +1 -0
- package/dist/policy-processor/peel-cwd.js +2 -0
- package/dist/policy-processor/peel-cwd.js.map +1 -0
- package/dist/policy-processor/real-path.d.ts +7 -0
- package/dist/policy-processor/real-path.d.ts.map +1 -0
- package/dist/policy-processor/real-path.js +42 -0
- package/dist/policy-processor/real-path.js.map +1 -0
- package/dist/policy-processor/scope.d.ts +111 -0
- package/dist/policy-processor/scope.d.ts.map +1 -0
- package/dist/policy-processor/scope.js +629 -0
- package/dist/policy-processor/scope.js.map +1 -0
- package/dist/policy-processor/self-protect.d.ts +10 -0
- package/dist/policy-processor/self-protect.d.ts.map +1 -0
- package/dist/policy-processor/self-protect.js +48 -0
- package/dist/policy-processor/self-protect.js.map +1 -0
- package/dist/policy-processor/soft-deny.d.ts +75 -0
- package/dist/policy-processor/soft-deny.d.ts.map +1 -0
- package/dist/policy-processor/soft-deny.js +185 -0
- package/dist/policy-processor/soft-deny.js.map +1 -0
- package/dist/policy-processor/unattended-guard.d.ts +28 -0
- package/dist/policy-processor/unattended-guard.d.ts.map +1 -0
- package/dist/policy-processor/unattended-guard.js +145 -0
- package/dist/policy-processor/unattended-guard.js.map +1 -0
- package/dist/session/cancel.d.ts +14 -0
- package/dist/session/cancel.d.ts.map +1 -0
- package/dist/session/cancel.js +32 -0
- package/dist/session/cancel.js.map +1 -0
- package/dist/session/commands.d.ts +41 -0
- package/dist/session/commands.d.ts.map +1 -0
- package/dist/session/commands.js +241 -0
- package/dist/session/commands.js.map +1 -0
- package/dist/session/compact-actor.d.ts +23 -0
- package/dist/session/compact-actor.d.ts.map +1 -0
- package/dist/session/compact-actor.js +91 -0
- package/dist/session/compact-actor.js.map +1 -0
- package/dist/session/compact-env.d.ts +54 -0
- package/dist/session/compact-env.d.ts.map +1 -0
- package/dist/session/compact-env.js +345 -0
- package/dist/session/compact-env.js.map +1 -0
- package/dist/session/compact.d.ts +42 -0
- package/dist/session/compact.d.ts.map +1 -0
- package/dist/session/compact.js +68 -0
- package/dist/session/compact.js.map +1 -0
- package/dist/session/index.d.ts +9 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +8 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/park.d.ts +16 -0
- package/dist/session/park.d.ts.map +1 -0
- package/dist/session/park.js +73 -0
- package/dist/session/park.js.map +1 -0
- package/dist/session/side-channel-reminder.d.ts +41 -0
- package/dist/session/side-channel-reminder.d.ts.map +1 -0
- package/dist/session/side-channel-reminder.js +132 -0
- package/dist/session/side-channel-reminder.js.map +1 -0
- package/dist/session/types.d.ts +44 -0
- package/dist/session/types.d.ts.map +1 -0
- package/dist/session/types.js +2 -0
- package/dist/session/types.js.map +1 -0
- package/dist/tool-catalog/catalog.d.ts +77 -0
- package/dist/tool-catalog/catalog.d.ts.map +1 -0
- package/dist/tool-catalog/catalog.js +380 -0
- package/dist/tool-catalog/catalog.js.map +1 -0
- package/dist/tool-catalog/index.d.ts +4 -0
- package/dist/tool-catalog/index.d.ts.map +1 -0
- package/dist/tool-catalog/index.js +3 -0
- package/dist/tool-catalog/index.js.map +1 -0
- package/dist/tool-catalog/recipes.d.ts +36 -0
- package/dist/tool-catalog/recipes.d.ts.map +1 -0
- package/dist/tool-catalog/recipes.js +121 -0
- package/dist/tool-catalog/recipes.js.map +1 -0
- package/dist/tool-catalog/types.d.ts +103 -0
- package/dist/tool-catalog/types.d.ts.map +1 -0
- package/dist/tool-catalog/types.js +5 -0
- package/dist/tool-catalog/types.js.map +1 -0
- package/dist/tool-runtime/approval/approved.d.ts +13 -0
- package/dist/tool-runtime/approval/approved.d.ts.map +1 -0
- package/dist/tool-runtime/approval/approved.js +20 -0
- package/dist/tool-runtime/approval/approved.js.map +1 -0
- package/dist/tool-runtime/approval/dist.d.ts +6 -0
- package/dist/tool-runtime/approval/dist.d.ts.map +1 -0
- package/dist/tool-runtime/approval/dist.js +28 -0
- package/dist/tool-runtime/approval/dist.js.map +1 -0
- package/dist/tool-runtime/approval/inbound.d.ts +7 -0
- package/dist/tool-runtime/approval/inbound.d.ts.map +1 -0
- package/dist/tool-runtime/approval/inbound.js +48 -0
- package/dist/tool-runtime/approval/inbound.js.map +1 -0
- package/dist/tool-runtime/approval/unattended.d.ts +8 -0
- package/dist/tool-runtime/approval/unattended.d.ts.map +1 -0
- package/dist/tool-runtime/approval/unattended.js +23 -0
- package/dist/tool-runtime/approval/unattended.js.map +1 -0
- package/dist/tool-runtime/blob-port.d.ts +46 -0
- package/dist/tool-runtime/blob-port.d.ts.map +1 -0
- package/dist/tool-runtime/blob-port.js +47 -0
- package/dist/tool-runtime/blob-port.js.map +1 -0
- package/dist/tool-runtime/extra-root.d.ts +3 -0
- package/dist/tool-runtime/extra-root.d.ts.map +1 -0
- package/dist/tool-runtime/extra-root.js +14 -0
- package/dist/tool-runtime/extra-root.js.map +1 -0
- package/dist/tool-runtime/handler.d.ts +29 -0
- package/dist/tool-runtime/handler.d.ts.map +1 -0
- package/dist/tool-runtime/handler.js +460 -0
- package/dist/tool-runtime/handler.js.map +1 -0
- package/dist/tool-runtime/index.d.ts +14 -0
- package/dist/tool-runtime/index.d.ts.map +1 -0
- package/dist/tool-runtime/index.js +14 -0
- package/dist/tool-runtime/index.js.map +1 -0
- package/dist/tool-runtime/peel.d.ts +19 -0
- package/dist/tool-runtime/peel.d.ts.map +1 -0
- package/dist/tool-runtime/peel.js +74 -0
- package/dist/tool-runtime/peel.js.map +1 -0
- package/dist/tool-runtime/plugin-context.d.ts +13 -0
- package/dist/tool-runtime/plugin-context.d.ts.map +1 -0
- package/dist/tool-runtime/plugin-context.js +55 -0
- package/dist/tool-runtime/plugin-context.js.map +1 -0
- package/dist/tool-runtime/recall.d.ts +18 -0
- package/dist/tool-runtime/recall.d.ts.map +1 -0
- package/dist/tool-runtime/recall.js +100 -0
- package/dist/tool-runtime/recall.js.map +1 -0
- package/dist/tool-runtime/run-system-tool.d.ts +13 -0
- package/dist/tool-runtime/run-system-tool.d.ts.map +1 -0
- package/dist/tool-runtime/run-system-tool.js +230 -0
- package/dist/tool-runtime/run-system-tool.js.map +1 -0
- package/dist/tool-runtime/tool-error.d.ts +6 -0
- package/dist/tool-runtime/tool-error.d.ts.map +1 -0
- package/dist/tool-runtime/tool-error.js +6 -0
- package/dist/tool-runtime/tool-error.js.map +1 -0
- package/dist/tool-runtime/worker-tools.d.ts +8 -0
- package/dist/tool-runtime/worker-tools.d.ts.map +1 -0
- package/dist/tool-runtime/worker-tools.js +65 -0
- package/dist/tool-runtime/worker-tools.js.map +1 -0
- package/dist/tool-runtime/write-admit.d.ts +21 -0
- package/dist/tool-runtime/write-admit.d.ts.map +1 -0
- package/dist/tool-runtime/write-admit.js +89 -0
- package/dist/tool-runtime/write-admit.js.map +1 -0
- package/package.json +114 -3
- package/policy/auto-mode-ask.scm +201 -0
- package/policy/bypass-detect.prompt +55 -0
- package/policy/classifier-bash-incomplete.prompt +46 -0
- package/policy/classifier-bash.prompt +45 -0
- package/policy/classifier-mcp.prompt +45 -0
- package/policy/classifier-read.prompt +44 -0
- package/policy/classifier-write-outside.prompt +46 -0
- package/policy/classifier-write-risky.prompt +45 -0
- package/policy/classifier-write-workspace.prompt +46 -0
- package/policy/classifier-write.prompt +45 -0
- package/policy/soft-deny-round1.prompt +51 -0
- package/policy/soft-deny-round2.prompt +60 -0
- package/src/agent-calls/acceptance.ts +71 -0
- package/src/agent-calls/callbacks.ts +75 -0
- package/src/agent-calls/delivery.ts +15 -0
- package/src/agent-calls/index.ts +16 -0
- package/src/agent-calls/operator.ts +99 -0
- package/src/agent-calls/runtime.ts +310 -0
- package/src/agent-calls/types.ts +33 -0
- package/src/context/compact-floors.ts +76 -0
- package/src/context/compact.ts +176 -0
- package/src/context/delivery.ts +15 -0
- package/src/context/history.ts +725 -0
- package/src/context/index.ts +35 -0
- package/src/context/observations.ts +13 -0
- package/src/context/retention/defaults.ts +58 -0
- package/src/context/retention/index.ts +45 -0
- package/src/context/retention/narrow.ts +123 -0
- package/src/context/retention/render.ts +177 -0
- package/src/context/retention/resolve.ts +306 -0
- package/src/context/retention/schema.ts +173 -0
- package/src/context/retention/stamp.ts +121 -0
- package/src/context/retention/step.ts +85 -0
- package/src/context/retention/view.ts +161 -0
- package/src/context/squeeze.ts +218 -0
- package/src/context/tools.ts +92 -0
- package/src/context/transcript.ts +705 -0
- package/src/policy-processor/README.md +43 -0
- package/src/policy-processor/bypass.ts +246 -0
- package/src/policy-processor/classifier.ts +711 -0
- package/src/policy-processor/cluster.ts +31 -0
- package/src/policy-processor/evidence.ts +214 -0
- package/src/policy-processor/explain.ts +310 -0
- package/src/policy-processor/index.ts +6 -0
- package/src/policy-processor/license.ts +45 -0
- package/src/policy-processor/peel-cwd.ts +1 -0
- package/src/policy-processor/real-path.ts +41 -0
- package/src/policy-processor/scope.ts +655 -0
- package/src/policy-processor/self-protect.ts +46 -0
- package/src/policy-processor/soft-deny.ts +225 -0
- package/src/policy-processor/unattended-guard.ts +158 -0
- package/src/session/cancel.ts +34 -0
- package/src/session/commands.ts +292 -0
- package/src/session/compact-actor.ts +132 -0
- package/src/session/compact-env.ts +539 -0
- package/src/session/compact.ts +95 -0
- package/src/session/index.ts +18 -0
- package/src/session/park.ts +73 -0
- package/src/session/side-channel-reminder.ts +148 -0
- package/src/session/types.ts +50 -0
- package/src/tool-catalog/catalog.ts +282 -0
- package/src/tool-catalog/index.ts +26 -0
- package/src/tool-catalog/recipes.ts +154 -0
- package/src/tool-catalog/types.ts +110 -0
- package/src/tool-runtime/approval/approved.ts +20 -0
- package/src/tool-runtime/approval/dist.ts +29 -0
- package/src/tool-runtime/approval/inbound.ts +50 -0
- package/src/tool-runtime/approval/unattended.ts +24 -0
- package/src/tool-runtime/blob-port.ts +64 -0
- package/src/tool-runtime/extra-root.ts +16 -0
- package/src/tool-runtime/handler.ts +576 -0
- package/src/tool-runtime/index.ts +21 -0
- package/src/tool-runtime/peel.ts +91 -0
- package/src/tool-runtime/plugin-context.ts +68 -0
- package/src/tool-runtime/recall.ts +104 -0
- package/src/tool-runtime/run-system-tool.ts +273 -0
- package/src/tool-runtime/tool-error.ts +6 -0
- package/src/tool-runtime/worker-tools.ts +76 -0
- package/src/tool-runtime/write-admit.ts +90 -0
- package/README.md +0 -3
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compact-strategy transcript: an ordered eat slice plus the cap that rides it.
|
|
3
|
+
*
|
|
4
|
+
* Cells are completions, tool calls, card/ack message chunks, or text cells.
|
|
5
|
+
*
|
|
6
|
+
* THE WALK: `walkTranscript` is the only eat iteration. A cutoff
|
|
7
|
+
* (`CompactJob`) projects the retained context; a completion anchors a round; tools and scheme
|
|
8
|
+
* programs attach to the open completion. Notification cells, approval notes,
|
|
9
|
+
* and steers collect parallel to tools — they never anchor or break a round.
|
|
10
|
+
* Inner scheme invocations
|
|
11
|
+
* live on `ProgrammaticJob` children, not this walk. `messagesFrom` and
|
|
12
|
+
* `Transcript.fromWork` walk it. Remaining eat cells
|
|
13
|
+
* (`MessageChunk`, text cell) yield `cell` so `fromWork` stays a walk —
|
|
14
|
+
* the last-eat and rounds views ignore them. A new chunk kind is a union
|
|
15
|
+
* member plus per-view handling. An uncommitted compact, prompt, ReACT, and
|
|
16
|
+
* non-eat values are silent.
|
|
17
|
+
*
|
|
18
|
+
* Rounds here group the materialized cell list so cutoff card cells
|
|
19
|
+
* can themselves be rounds. A successful `CompactJob` replaces the prefix and
|
|
20
|
+
* marks its `card` cells as prior cards
|
|
21
|
+
* (`transcript/compaction-results`). Cells before the first round anchor
|
|
22
|
+
* (prior cards) belong to the oldest slice: `sliceRounds(0, …)` starts at
|
|
23
|
+
* cell 0, so prefix ∪ keep = full.
|
|
24
|
+
*
|
|
25
|
+
* Occupancy: a stamped `totalTokens` is `usage.total_tokens` of that call —
|
|
26
|
+
* prompt + response, where the prompt carries the whole history. It is a
|
|
27
|
+
* monotone window GAUGE, not a per-cell weight; summing it across completions
|
|
28
|
+
* over-counts quadratically. `tokensUsed()` therefore diffs the gauge at the
|
|
29
|
+
* slice boundary (`baseGauge` = last reading before the slice) and falls back
|
|
30
|
+
* to `contentTokensOf` (ceil chars/4) for cells past the last reading.
|
|
31
|
+
* `contentTokensOf` is the raw content meter (`tokens/count`).
|
|
32
|
+
*
|
|
33
|
+
* There is no card-kind field on `CompletionExpectation`; card identity is the
|
|
34
|
+
* card set (plus `MessageChunk.card`). That is the hook the models
|
|
35
|
+
* give us today.
|
|
36
|
+
*/
|
|
37
|
+
import { arrival } from "@here.build/arrival/host-internals";
|
|
38
|
+
import {
|
|
39
|
+
CompactJob,
|
|
40
|
+
CompletionExpectation,
|
|
41
|
+
type CompletionMessage,
|
|
42
|
+
type ContextNeed,
|
|
43
|
+
contextNeedFromChunks,
|
|
44
|
+
DEFAULT_COMPACT_KEEP_TURNS as COMPACT_KEEP_DIAL,
|
|
45
|
+
jobTriggersReactLoop,
|
|
46
|
+
ProgrammaticJob,
|
|
47
|
+
SessionRoot,
|
|
48
|
+
SessionTurn,
|
|
49
|
+
tapeCompletion,
|
|
50
|
+
ToolCallJob,
|
|
51
|
+
type TurnChunk,
|
|
52
|
+
TurnApprovalNoteChunk,
|
|
53
|
+
TurnNotificationChunk,
|
|
54
|
+
TurnSteerChunk,
|
|
55
|
+
} from "@inhuman.tools/harness-model";
|
|
56
|
+
|
|
57
|
+
import { keepFloorsOfJob } from "./compact-floors.js";
|
|
58
|
+
import { compactToolLine, toolCallRepresentation } from "./squeeze.js";
|
|
59
|
+
|
|
60
|
+
export type MessageRole = "user" | "assistant";
|
|
61
|
+
|
|
62
|
+
export class MessageChunk {
|
|
63
|
+
readonly role: MessageRole;
|
|
64
|
+
readonly text: string;
|
|
65
|
+
readonly ack: boolean;
|
|
66
|
+
readonly checkIn: boolean;
|
|
67
|
+
|
|
68
|
+
constructor(role: MessageRole, text: string, flags?: { readonly ack?: boolean; readonly checkIn?: boolean }) {
|
|
69
|
+
this.role = role;
|
|
70
|
+
this.text = text;
|
|
71
|
+
this.ack = flags?.ack === true;
|
|
72
|
+
this.checkIn = flags?.checkIn === true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
asCheckIn(): MessageChunk {
|
|
76
|
+
return this.checkIn ? this : new MessageChunk(this.role, this.text, { ack: this.ack, checkIn: true });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type EatCell = CompletionMessage | MessageChunk;
|
|
81
|
+
|
|
82
|
+
export type TranscriptEvent =
|
|
83
|
+
| { readonly kind: "compact"; readonly chunk: CompactJob }
|
|
84
|
+
| { readonly kind: "completion"; readonly chunk: CompletionExpectation }
|
|
85
|
+
| { readonly kind: "tool"; readonly chunk: ToolCallJob }
|
|
86
|
+
| { readonly kind: "scheme"; readonly chunk: ProgrammaticJob }
|
|
87
|
+
| { readonly kind: "notify"; readonly chunk: TurnNotificationChunk }
|
|
88
|
+
| { readonly kind: "steer"; readonly chunk: TurnSteerChunk }
|
|
89
|
+
| { readonly kind: "approval-note"; readonly chunk: TurnApprovalNoteChunk }
|
|
90
|
+
| { readonly kind: "cell"; readonly chunk: MessageChunk | string };
|
|
91
|
+
|
|
92
|
+
export function turnSpine(turn: SessionTurn): TurnChunk[] {
|
|
93
|
+
return [...turn.doneWork, ...turn.queue];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export type CompactRead = {
|
|
97
|
+
/** `compactKeepTurns` dial. A stamped CompactJob keeps its own reading. */
|
|
98
|
+
readonly configFloor?: number;
|
|
99
|
+
readonly kv?: Readonly<Record<string, string>>;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/** Compact-dropped `read_file` jobs are not in this projection (compact policy). */
|
|
103
|
+
export function historyBehind(turn: SessionTurn, read?: CompactRead): CompletionMessage[] {
|
|
104
|
+
const ctx = historyRead(turn, read);
|
|
105
|
+
const own = messagesFrom(turnSpine(turn), ctx, () =>
|
|
106
|
+
turn.turnTail instanceof SessionTurn ? historyBehind(turn.turnTail, read) : [],
|
|
107
|
+
);
|
|
108
|
+
return finishHistory(own, () => (turn.turnTail instanceof SessionTurn ? historyBehind(turn.turnTail, read) : []));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Eat a successful compact saw: history strictly before the job, tail included. */
|
|
112
|
+
export function eatBeforeCompact(turn: SessionTurn, job: CompactJob, read?: CompactRead): CompletionMessage[] {
|
|
113
|
+
const ctx = historyRead(turn, read);
|
|
114
|
+
const own = messagesFrom(
|
|
115
|
+
turnSpine(turn),
|
|
116
|
+
ctx,
|
|
117
|
+
() => (turn.turnTail instanceof SessionTurn ? historyBehind(turn.turnTail, read) : []),
|
|
118
|
+
job,
|
|
119
|
+
);
|
|
120
|
+
return finishHistory(own, () => (turn.turnTail instanceof SessionTurn ? historyBehind(turn.turnTail, read) : []));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function finishHistory(own: HistorySlice, tail: () => CompletionMessage[]): CompletionMessage[] {
|
|
124
|
+
if (own.anchored) return own.messages;
|
|
125
|
+
if (own.replaced || own.messages.length > 0) return [...own.messages, ...own.pending];
|
|
126
|
+
return [...tail(), ...own.pending];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
type HistoryRead = {
|
|
130
|
+
readonly tailNeed: ContextNeed | null;
|
|
131
|
+
readonly kv: Readonly<Record<string, string>>;
|
|
132
|
+
readonly configFloor: number;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
function historyRead(turn: SessionTurn, read: CompactRead | undefined): HistoryRead {
|
|
136
|
+
return {
|
|
137
|
+
tailNeed: turn.turnTail instanceof SessionTurn ? turn.turnTail.contextNeed : null,
|
|
138
|
+
kv: read?.kv ?? sessionKv(turn),
|
|
139
|
+
configFloor: read?.configFloor != null && read.configFloor >= 1 ? read.configFloor : COMPACT_KEEP_DIAL,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function sessionKv(turn: SessionTurn): Readonly<Record<string, string>> {
|
|
144
|
+
const parent = turn.parent;
|
|
145
|
+
if (parent instanceof SessionRoot) return parent.kvs;
|
|
146
|
+
return {};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
type HistorySlice = {
|
|
150
|
+
readonly messages: CompletionMessage[];
|
|
151
|
+
readonly anchored: boolean;
|
|
152
|
+
readonly replaced: boolean;
|
|
153
|
+
readonly pending: Array<TurnNotificationChunk | TurnApprovalNoteChunk | TurnSteerChunk>;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
function messagesFrom(
|
|
157
|
+
chunks: readonly TurnChunk[],
|
|
158
|
+
read: HistoryRead,
|
|
159
|
+
seed: () => CompletionMessage[],
|
|
160
|
+
stopBefore?: CompactJob,
|
|
161
|
+
): HistorySlice {
|
|
162
|
+
let eaten: CompletionMessage[] | null = null;
|
|
163
|
+
let last: CompletionExpectation | null = null;
|
|
164
|
+
const tools: Array<ToolCallJob | ProgrammaticJob> = [];
|
|
165
|
+
const notifies: TurnNotificationChunk[] = [];
|
|
166
|
+
const notes: TurnApprovalNoteChunk[] = [];
|
|
167
|
+
const steers: TurnSteerChunk[] = [];
|
|
168
|
+
const resetRound = (): void => {
|
|
169
|
+
last = null;
|
|
170
|
+
tools.length = 0;
|
|
171
|
+
notifies.length = 0;
|
|
172
|
+
notes.length = 0;
|
|
173
|
+
steers.length = 0;
|
|
174
|
+
};
|
|
175
|
+
for (const event of walkTranscript(chunks)) {
|
|
176
|
+
switch (event.kind) {
|
|
177
|
+
case "compact": {
|
|
178
|
+
const eat =
|
|
179
|
+
last != null
|
|
180
|
+
? [...last.messages, last, ...tools, ...notifies, ...notes, ...steers]
|
|
181
|
+
: [...(eaten ?? seed()), ...notifies, ...notes, ...steers];
|
|
182
|
+
if (stopBefore != null && event.chunk === stopBefore) {
|
|
183
|
+
return {
|
|
184
|
+
messages: eat,
|
|
185
|
+
anchored: last != null,
|
|
186
|
+
replaced: true,
|
|
187
|
+
pending: [],
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
eaten = projectCompactMessages(eat, event.chunk, chunks, read);
|
|
191
|
+
resetRound();
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
case "completion": {
|
|
195
|
+
last = event.chunk;
|
|
196
|
+
tools.length = 0;
|
|
197
|
+
notifies.length = 0;
|
|
198
|
+
notes.length = 0;
|
|
199
|
+
steers.length = 0;
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
case "tool": {
|
|
203
|
+
if (jobTriggersReactLoop(event.chunk)) tools.push(event.chunk);
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
case "scheme": {
|
|
207
|
+
tools.push(event.chunk);
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
case "notify": {
|
|
211
|
+
notifies.push(event.chunk);
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
case "approval-note": {
|
|
215
|
+
notes.push(event.chunk);
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
case "steer": {
|
|
219
|
+
steers.push(event.chunk);
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
default:
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if (last != null) {
|
|
227
|
+
return {
|
|
228
|
+
messages: [...last.messages, last, ...tools, ...notifies, ...notes, ...steers],
|
|
229
|
+
anchored: true,
|
|
230
|
+
replaced: false,
|
|
231
|
+
pending: [],
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
messages: eaten ?? [],
|
|
236
|
+
anchored: false,
|
|
237
|
+
replaced: eaten != null,
|
|
238
|
+
pending: [...notifies, ...notes, ...steers],
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function* walkTranscript(chunks: readonly unknown[]): Generator<TranscriptEvent> {
|
|
243
|
+
for (const chunk of chunks) {
|
|
244
|
+
if (chunk instanceof CompactJob && chunk.cutoff) {
|
|
245
|
+
yield { kind: "compact", chunk };
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
const pew = tapeCompletion(chunk);
|
|
249
|
+
if (pew != null) {
|
|
250
|
+
yield { kind: "completion", chunk: pew };
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (chunk instanceof CompletionExpectation) {
|
|
254
|
+
yield { kind: "completion", chunk };
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (chunk instanceof ToolCallJob) {
|
|
258
|
+
yield { kind: "tool", chunk };
|
|
259
|
+
} else if (chunk instanceof ProgrammaticJob) {
|
|
260
|
+
yield { kind: "scheme", chunk };
|
|
261
|
+
} else if (chunk instanceof TurnNotificationChunk) {
|
|
262
|
+
yield { kind: "notify", chunk };
|
|
263
|
+
} else if (chunk instanceof TurnApprovalNoteChunk) {
|
|
264
|
+
yield { kind: "approval-note", chunk };
|
|
265
|
+
} else if (chunk instanceof TurnSteerChunk) {
|
|
266
|
+
yield { kind: "steer", chunk };
|
|
267
|
+
} else if (typeof chunk === "string" || chunk instanceof MessageChunk) {
|
|
268
|
+
yield { kind: "cell", chunk };
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export type TranscriptRound = {
|
|
274
|
+
readonly start: number;
|
|
275
|
+
readonly end: number;
|
|
276
|
+
readonly prompt: string;
|
|
277
|
+
readonly response: CompletionExpectation;
|
|
278
|
+
readonly toolCalls: ReadonlyArray<ToolCallJob | ProgrammaticJob>;
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
@arrival.private
|
|
282
|
+
export class Transcript {
|
|
283
|
+
static empty(cap = 0): Transcript {
|
|
284
|
+
return new Transcript([], cap);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
static fromCells(cells: readonly EatCell[], cap?: number, cards?: Iterable<EatCell>): Transcript {
|
|
288
|
+
return new Transcript(cells, cap ?? inferCap(cells), cards);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
static fromWork(work: readonly unknown[], cap?: number): Transcript {
|
|
292
|
+
const cells: EatCell[] = [];
|
|
293
|
+
const cards = new Set<EatCell>();
|
|
294
|
+
for (const event of walkTranscript(work)) {
|
|
295
|
+
if (event.kind === "compact") {
|
|
296
|
+
const projected = projectCompactMessages(cells, event.chunk, work, {
|
|
297
|
+
tailNeed: null,
|
|
298
|
+
kv: {},
|
|
299
|
+
configFloor: COMPACT_KEEP_DIAL,
|
|
300
|
+
});
|
|
301
|
+
cells.length = 0;
|
|
302
|
+
cards.clear();
|
|
303
|
+
for (const cell of projected) {
|
|
304
|
+
cells.push(cell);
|
|
305
|
+
if (cell instanceof CompactJob) cards.add(cell);
|
|
306
|
+
}
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
cells.push(event.chunk);
|
|
310
|
+
}
|
|
311
|
+
return new Transcript(cells, cap ?? inferCap(cells), cards);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** Occupancy window start — see preamble. */
|
|
315
|
+
readonly baseGauge: number;
|
|
316
|
+
private readonly checkIns: ReadonlySet<EatCell>;
|
|
317
|
+
|
|
318
|
+
constructor(
|
|
319
|
+
readonly cells: readonly EatCell[],
|
|
320
|
+
readonly cap: number,
|
|
321
|
+
checkIns?: Iterable<EatCell>,
|
|
322
|
+
baseGauge = 0,
|
|
323
|
+
) {
|
|
324
|
+
this.baseGauge = baseGauge;
|
|
325
|
+
this.checkIns = checkIns instanceof Set ? checkIns : new Set(checkIns);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
isCheckIn(cell: EatCell): boolean {
|
|
329
|
+
return this.checkIns.has(cell) || cell instanceof CompactJob || (cell instanceof MessageChunk && cell.checkIn);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
checkInCells(): EatCell[] {
|
|
333
|
+
return this.cells.filter((cell) => this.isCheckIn(cell));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** The transcript without its prior-card cells: what happened since, not what was already folded. */
|
|
337
|
+
withoutCheckIns(): Transcript {
|
|
338
|
+
return new Transcript(
|
|
339
|
+
this.cells.filter((cell) => !this.isCheckIn(cell)),
|
|
340
|
+
this.cap,
|
|
341
|
+
[],
|
|
342
|
+
this.baseGauge,
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
sliceCells(from: number, to: number = this.cells.length): Transcript {
|
|
347
|
+
const lo = Math.max(0, from);
|
|
348
|
+
const hi = Math.min(this.cells.length, to);
|
|
349
|
+
const sliced = this.cells.slice(lo, hi);
|
|
350
|
+
let baseGauge = this.baseGauge;
|
|
351
|
+
for (let i = Math.min(lo, this.cells.length) - 1; i >= 0; i--) {
|
|
352
|
+
const cell = this.cells[i];
|
|
353
|
+
if (cell instanceof CompletionExpectation && cell.totalTokens != null && cell.totalTokens > 0) {
|
|
354
|
+
baseGauge = cell.totalTokens;
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return new Transcript(sliced, this.cap, this.checkIns, baseGauge);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** `sliceRounds(0, …)` includes prior cards — see preamble. */
|
|
362
|
+
sliceRounds(from: number, to: number): Transcript {
|
|
363
|
+
const rounds = this.rounds();
|
|
364
|
+
if (from >= to || rounds.length === 0) {
|
|
365
|
+
return new Transcript([], this.cap, this.checkIns, this.baseGauge);
|
|
366
|
+
}
|
|
367
|
+
const start = from === 0 ? 0 : (rounds[from]?.start ?? this.cells.length);
|
|
368
|
+
const end = rounds[to - 1]?.end ?? start;
|
|
369
|
+
return this.sliceCells(start, end);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
rounds(): TranscriptRound[] {
|
|
373
|
+
const out: TranscriptRound[] = [];
|
|
374
|
+
let response: CompletionExpectation | null = null;
|
|
375
|
+
let toolCalls: Array<ToolCallJob | ProgrammaticJob> = [];
|
|
376
|
+
let start = 0;
|
|
377
|
+
|
|
378
|
+
const flush = (end: number): void => {
|
|
379
|
+
if (response == null) return;
|
|
380
|
+
out.push({
|
|
381
|
+
start,
|
|
382
|
+
end,
|
|
383
|
+
prompt: response.userPrompt,
|
|
384
|
+
response,
|
|
385
|
+
toolCalls,
|
|
386
|
+
});
|
|
387
|
+
response = null;
|
|
388
|
+
toolCalls = [];
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
for (const [i, cell_] of this.cells.entries()) {
|
|
392
|
+
const cell = cell_!;
|
|
393
|
+
if (cell instanceof CompletionExpectation) {
|
|
394
|
+
flush(i);
|
|
395
|
+
response = cell;
|
|
396
|
+
start = i;
|
|
397
|
+
} else if ((cell instanceof ToolCallJob || cell instanceof ProgrammaticJob) && response != null) {
|
|
398
|
+
toolCalls.push(cell);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
flush(this.cells.length);
|
|
402
|
+
return out;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/** Occupancy — see preamble. */
|
|
406
|
+
tokensUsed(): number {
|
|
407
|
+
let lastGauge = -1;
|
|
408
|
+
let lastIndex = -1;
|
|
409
|
+
for (let i = 0; i < this.cells.length; i++) {
|
|
410
|
+
const cell = this.cells[i];
|
|
411
|
+
if (cell instanceof CompletionExpectation && cell.totalTokens != null && cell.totalTokens > 0) {
|
|
412
|
+
lastGauge = cell.totalTokens;
|
|
413
|
+
lastIndex = i;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (lastIndex === -1) return contentTokensOf(this.cells);
|
|
417
|
+
const gauged = Math.max(0, lastGauge - this.baseGauge);
|
|
418
|
+
return gauged + contentTokensOf(this.cells.slice(lastIndex + 1));
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* The frontier is the OLDEST round of this transcript. During the classify
|
|
423
|
+
* walk `preserve-transcript` runs frontier → newest (chronological), so the
|
|
424
|
+
* round being decided is the first one.
|
|
425
|
+
*/
|
|
426
|
+
frontierRound(): Transcript {
|
|
427
|
+
const rounds = this.rounds();
|
|
428
|
+
if (rounds.length === 0) return new Transcript([], this.cap, this.checkIns, this.baseGauge);
|
|
429
|
+
return this.sliceRounds(0, 1);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/** `transcript/newer`: preserve without the frontier — drop the oldest round. */
|
|
433
|
+
withoutFrontier(): Transcript {
|
|
434
|
+
const rounds = this.rounds();
|
|
435
|
+
if (rounds.length <= 1) return new Transcript([], this.cap, this.checkIns, this.baseGauge);
|
|
436
|
+
return this.sliceRounds(1, rounds.length);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/** `transcript/older`: compact without the frontier — drop the newest round. */
|
|
440
|
+
withoutNewest(): Transcript {
|
|
441
|
+
const rounds = this.rounds();
|
|
442
|
+
if (rounds.length <= 1) return new Transcript([], this.cap, this.checkIns, this.baseGauge);
|
|
443
|
+
return this.sliceRounds(0, rounds.length - 1);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
completeRounds(): Transcript {
|
|
447
|
+
const rounds = this.rounds();
|
|
448
|
+
if (rounds.length === 0) return new Transcript([], this.cap, this.checkIns, this.baseGauge);
|
|
449
|
+
const last = rounds.at(-1);
|
|
450
|
+
if (last && isRoundComplete(last)) return this.sliceRounds(0, rounds.length);
|
|
451
|
+
if (rounds.length === 1) return new Transcript([], this.cap, this.checkIns, this.baseGauge);
|
|
452
|
+
return this.sliceRounds(0, rounds.length - 1);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
partialRound(): Transcript | null {
|
|
456
|
+
const rounds = this.rounds();
|
|
457
|
+
if (rounds.length === 0) return null;
|
|
458
|
+
const last = rounds.at(-1);
|
|
459
|
+
if (last && isRoundComplete(last)) return null;
|
|
460
|
+
return this.sliceRounds(rounds.length - 1, rounds.length);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
concat(other: Transcript): Transcript {
|
|
464
|
+
const checkIns = new Set(this.checkIns);
|
|
465
|
+
for (const cell of other.checkIns) checkIns.add(cell);
|
|
466
|
+
for (const cell of other.cells) {
|
|
467
|
+
if (other.isCheckIn(cell)) checkIns.add(cell);
|
|
468
|
+
}
|
|
469
|
+
return new Transcript([...this.cells, ...other.cells], this.cap || other.cap, checkIns, this.baseGauge);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Content-token stand-in: `ceil(chars / 4)`. Replace this one function when
|
|
475
|
+
* per-cell prompt/response tokens exist on the model.
|
|
476
|
+
*/
|
|
477
|
+
export function estimateTokens(text: string): number {
|
|
478
|
+
if (text.length === 0) return 0;
|
|
479
|
+
return Math.ceil(text.length / 4);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/** Content meter — see preamble. */
|
|
483
|
+
export function contentTokensOf(cells: readonly unknown[]): number {
|
|
484
|
+
let used = 0;
|
|
485
|
+
for (const cell of cells) used += estimateTokens(textOfCell(cell));
|
|
486
|
+
return used;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export function charsOf(cells: readonly unknown[]): number {
|
|
490
|
+
let n = 0;
|
|
491
|
+
for (const cell of cells) n += textOfCell(cell).length;
|
|
492
|
+
return n;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
export function inferCap(cells: readonly unknown[], fallback = 0): number {
|
|
496
|
+
for (let i = cells.length - 1; i >= 0; i--) {
|
|
497
|
+
const cell = cells[i];
|
|
498
|
+
if (cell instanceof CompletionExpectation && (cell.contextLimit ?? 0) > 0) {
|
|
499
|
+
return cell.contextLimit ?? 0;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
return fallback;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
export function payloadSuffix(transcript: Transcript, n: number): EatCell[] {
|
|
506
|
+
const cells = transcript.cells.filter((cell) => !transcript.isCheckIn(cell));
|
|
507
|
+
if (n <= 0 || cells.length === 0) return [];
|
|
508
|
+
const start = Math.max(0, cells.length - Math.floor(n));
|
|
509
|
+
let owner = start;
|
|
510
|
+
while (
|
|
511
|
+
owner > 0 &&
|
|
512
|
+
(cells[owner] instanceof ToolCallJob ||
|
|
513
|
+
cells[owner] instanceof ProgrammaticJob ||
|
|
514
|
+
cells[owner] instanceof TurnNotificationChunk ||
|
|
515
|
+
cells[owner] instanceof TurnApprovalNoteChunk ||
|
|
516
|
+
cells[owner] instanceof TurnSteerChunk)
|
|
517
|
+
) {
|
|
518
|
+
owner--;
|
|
519
|
+
}
|
|
520
|
+
if (!(cells[owner] instanceof CompletionExpectation)) owner = start;
|
|
521
|
+
return cells.slice(owner);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function isRoundComplete(round: TranscriptRound): boolean {
|
|
525
|
+
const requested = round.response.responseCalls.length;
|
|
526
|
+
const hasResponse = round.response.responseText !== "" || (round.response.totalTokens ?? 0) > 0;
|
|
527
|
+
if (requested === 0) return hasResponse;
|
|
528
|
+
if (round.toolCalls.length < requested) return false;
|
|
529
|
+
return round.toolCalls.every((tool) => {
|
|
530
|
+
const json = tool instanceof ToolCallJob ? tool.sealedOutcomeJson() : tool.receiptJson;
|
|
531
|
+
return json != null && json !== "";
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/** Floors are non-zero by default; a single-round eat legitimately keeps all. */
|
|
536
|
+
export function sliceCompactEat(
|
|
537
|
+
eat: Transcript,
|
|
538
|
+
floors: { readonly userTurns: number; readonly rounds: number },
|
|
539
|
+
): { readonly prefix: Transcript; readonly keep: Transcript } {
|
|
540
|
+
const rounds = eat.rounds();
|
|
541
|
+
const emptyKeep = eat.sliceCells(eat.cells.length);
|
|
542
|
+
if (rounds.length === 0) return { prefix: eat, keep: emptyKeep };
|
|
543
|
+
let keepRounds = floors.rounds;
|
|
544
|
+
const userTurns = floors.userTurns;
|
|
545
|
+
if (keepRounds > 0 || userTurns > 0) keepRounds = Math.max(keepRounds, 1);
|
|
546
|
+
let keepFrom = rounds.length;
|
|
547
|
+
if (keepRounds > 0) keepFrom = Math.min(keepFrom, Math.max(0, rounds.length - keepRounds));
|
|
548
|
+
if (userTurns > 0) {
|
|
549
|
+
let counted = 0;
|
|
550
|
+
let idx = rounds.length;
|
|
551
|
+
for (let i = rounds.length - 1; i >= 0; i -= 1) {
|
|
552
|
+
if (rounds[i]!.prompt !== "") counted += 1;
|
|
553
|
+
idx = i;
|
|
554
|
+
if (counted >= userTurns) break;
|
|
555
|
+
}
|
|
556
|
+
if (counted > 0) keepFrom = Math.min(keepFrom, idx);
|
|
557
|
+
}
|
|
558
|
+
// Preserve floors are a preference, not a right to keep everything. Clamp so
|
|
559
|
+
// >= 2 rounds always leaves one round in the prefix. One round still keeps all.
|
|
560
|
+
if (rounds.length >= 2 && keepFrom < 1) keepFrom = 1;
|
|
561
|
+
if (keepFrom >= rounds.length) return { prefix: eat, keep: emptyKeep };
|
|
562
|
+
if (keepFrom <= 0) return { prefix: eat.sliceRounds(0, 0), keep: eat };
|
|
563
|
+
return {
|
|
564
|
+
prefix: eat.sliceRounds(0, keepFrom),
|
|
565
|
+
keep: eat.sliceRounds(keepFrom, rounds.length),
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Keep is {@link sliceCompactEat}'s suffix. Squeeze is up to `floors.rounds`
|
|
571
|
+
* rounds immediately before that keep, taken from the prefix. If the squeeze
|
|
572
|
+
* would empty the prefix and at least two rounds exist, shrink the squeeze
|
|
573
|
+
* (to zero when the prefix is a single round) so one round stays in it.
|
|
574
|
+
*/
|
|
575
|
+
export function sliceCompactZones(
|
|
576
|
+
eat: Transcript,
|
|
577
|
+
floors: { readonly userTurns: number; readonly rounds: number },
|
|
578
|
+
): {
|
|
579
|
+
readonly prefix: Transcript;
|
|
580
|
+
readonly squeeze: Transcript;
|
|
581
|
+
readonly keep: Transcript;
|
|
582
|
+
} {
|
|
583
|
+
const sliced = sliceCompactEat(eat, floors);
|
|
584
|
+
const empty = eat.sliceRounds(0, 0);
|
|
585
|
+
const prefixRounds = sliced.prefix.rounds();
|
|
586
|
+
let squeezeCount = Math.min(Math.max(0, floors.rounds), prefixRounds.length);
|
|
587
|
+
if (eat.rounds().length >= 2 && prefixRounds.length - squeezeCount < 1) {
|
|
588
|
+
squeezeCount = Math.max(0, prefixRounds.length - 1);
|
|
589
|
+
}
|
|
590
|
+
if (squeezeCount <= 0) return { prefix: sliced.prefix, squeeze: empty, keep: sliced.keep };
|
|
591
|
+
const prefixEnd = prefixRounds.length - squeezeCount;
|
|
592
|
+
return {
|
|
593
|
+
prefix: sliced.prefix.sliceRounds(0, prefixEnd),
|
|
594
|
+
squeeze: sliced.prefix.sliceRounds(prefixEnd, prefixRounds.length),
|
|
595
|
+
keep: sliced.keep,
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
export type CompactProjectionRow =
|
|
600
|
+
| { readonly kind: "user"; readonly text: string }
|
|
601
|
+
| { readonly kind: "assistant"; readonly text: string }
|
|
602
|
+
| { readonly kind: "keep"; readonly cell: CompletionMessage };
|
|
603
|
+
|
|
604
|
+
/** Squeeze band as role rows, then keep cells verbatim. The card is separate. */
|
|
605
|
+
export function projectCompactRows(
|
|
606
|
+
eat: Transcript,
|
|
607
|
+
floors: { readonly userTurns: number; readonly rounds: number },
|
|
608
|
+
): {
|
|
609
|
+
readonly zones: ReturnType<typeof sliceCompactZones>;
|
|
610
|
+
readonly rows: CompactProjectionRow[];
|
|
611
|
+
} {
|
|
612
|
+
const zones = sliceCompactZones(eat, floors);
|
|
613
|
+
const rows: CompactProjectionRow[] = [];
|
|
614
|
+
for (const round of zones.squeeze.rounds()) {
|
|
615
|
+
const promptText = round.prompt.trim();
|
|
616
|
+
const response = round.response.responseText.trim();
|
|
617
|
+
if (promptText !== "") rows.push({ kind: "user", text: promptText });
|
|
618
|
+
if (response !== "") rows.push({ kind: "assistant", text: response });
|
|
619
|
+
for (const tool of round.toolCalls) {
|
|
620
|
+
if (tool instanceof ToolCallJob && !jobTriggersReactLoop(tool)) continue;
|
|
621
|
+
const line = compactToolLine(tool).trim();
|
|
622
|
+
if (line === "") continue;
|
|
623
|
+
const name = (tool instanceof ToolCallJob ? tool.name : tool.toolName).trim();
|
|
624
|
+
rows.push({
|
|
625
|
+
kind: "user",
|
|
626
|
+
text: `[${name === "" ? "tool" : name}] ${line}`,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
for (const cell of zones.keep.cells) {
|
|
631
|
+
if (cell instanceof MessageChunk) continue;
|
|
632
|
+
if (isCompletionMessage(cell)) rows.push({ kind: "keep", cell });
|
|
633
|
+
}
|
|
634
|
+
return { zones, rows };
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* The card is the `CompactJob` itself, a typed cell (F10): `Transcript` then
|
|
639
|
+
* knows it is a prior card, and `transcript/compaction-results` is no longer
|
|
640
|
+
* always empty. Its text is `standIn`.
|
|
641
|
+
*/
|
|
642
|
+
export function projectionMessages(card: CompactJob, rows: readonly CompactProjectionRow[]): CompletionMessage[] {
|
|
643
|
+
const out: CompletionMessage[] = [];
|
|
644
|
+
if (card.standIn.trim() !== "") out.push(card);
|
|
645
|
+
for (const row of rows) {
|
|
646
|
+
if (row.kind === "keep") out.push(row.cell);
|
|
647
|
+
else if (row.text !== "") out.push(row.text);
|
|
648
|
+
}
|
|
649
|
+
return out;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function floorsFor(job: CompactJob, chunks: readonly unknown[], read: HistoryRead) {
|
|
653
|
+
const index = chunks.indexOf(job);
|
|
654
|
+
const before = index === -1 ? chunks : chunks.slice(0, index);
|
|
655
|
+
const need = contextNeedFromChunks(before, read.tailNeed);
|
|
656
|
+
return keepFloorsOfJob(job, need, read);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/** Card, squeeze lines, and verbatim keep cells for the wire and the next eat. */
|
|
660
|
+
export function compactLeadRows(turn: SessionTurn, job: CompactJob, read?: CompactRead): CompactProjectionRow[] {
|
|
661
|
+
const ctx = historyRead(turn, read);
|
|
662
|
+
const eat = eatBeforeCompact(turn, job, read);
|
|
663
|
+
const { rows } = projectCompactRows(Transcript.fromCells(eat), floorsFor(job, turnSpine(turn), ctx));
|
|
664
|
+
const card = job.standIn.trim();
|
|
665
|
+
return card === "" ? rows : [{ kind: "user" as const, text: card }, ...rows];
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function projectCompactMessages(
|
|
669
|
+
eat: readonly EatCell[],
|
|
670
|
+
job: CompactJob,
|
|
671
|
+
chunks: readonly unknown[],
|
|
672
|
+
read: HistoryRead,
|
|
673
|
+
): CompletionMessage[] {
|
|
674
|
+
const { rows } = projectCompactRows(Transcript.fromCells(eat), floorsFor(job, chunks, read));
|
|
675
|
+
return projectionMessages(job, rows);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function isCompletionMessage(cell: EatCell): cell is CompletionMessage {
|
|
679
|
+
return !(cell instanceof MessageChunk);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
export function textOfCell(cell: unknown): string {
|
|
683
|
+
if (typeof cell === "string") return cell;
|
|
684
|
+
if (cell instanceof CompactJob) return cell.standIn.trim();
|
|
685
|
+
if (cell instanceof MessageChunk) return cell.text;
|
|
686
|
+
if (cell instanceof TurnNotificationChunk) return cell.notificationText();
|
|
687
|
+
if (cell instanceof TurnApprovalNoteChunk) return cell.approvalNoteText();
|
|
688
|
+
if (cell instanceof TurnSteerChunk) return cell.completionMessage;
|
|
689
|
+
if (cell instanceof ToolCallJob) {
|
|
690
|
+
const scheme = cell.children.find((child): child is ProgrammaticJob => child instanceof ProgrammaticJob);
|
|
691
|
+
if (scheme !== undefined) {
|
|
692
|
+
return `scheme ${scheme.expr} ${scheme.resultText()}`.trim();
|
|
693
|
+
}
|
|
694
|
+
const painted = toolCallRepresentation(cell);
|
|
695
|
+
return `${painted.name} ${painted.args} ${painted.result}`.trim();
|
|
696
|
+
}
|
|
697
|
+
if (cell instanceof ProgrammaticJob) {
|
|
698
|
+
return `scheme ${cell.expr} ${cell.resultText()}`.trim();
|
|
699
|
+
}
|
|
700
|
+
const pew = tapeCompletion(cell) ?? (cell instanceof CompletionExpectation ? cell : null);
|
|
701
|
+
if (pew != null) {
|
|
702
|
+
return `${pew.userPrompt}\n${pew.responseText}`.trim();
|
|
703
|
+
}
|
|
704
|
+
return "";
|
|
705
|
+
}
|