@omercnet/paseo-omp 0.4.0-next.129.1 → 0.4.0-next.133.1
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 +4 -4
- package/client/omp-routing-editor.tsx +0 -2
- package/package.json +15 -16
- package/paseo-plugin.json +1 -1
- package/scripts/prepare-dependencies.mjs +44 -11
- package/server/package-version.ts +1 -1
- package/server/provider/omp-rpc-transport.ts +19 -1
- package/server/provider/omp-rpc.ts +4 -3
- package/server/provider/prompt-payload.ts +12 -6
- package/server/provider/timeline-projector.ts +46 -5
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ before capture.
|
|
|
20
20
|
|
|
21
21
|
## Quick start
|
|
22
22
|
|
|
23
|
-
Requirements: Paseo `>=0.9.0
|
|
23
|
+
Requirements: Paseo `>=0.9.0 <0.10.0`, OMP `18.1.15` or newer, and OMP RPC protocol v2. Paseo 0.9 is required for the Help report's supported copy and external-link APIs, nested provider-subagent ancestry, spawning-tool links, npm-managed installation, and the complete client integration.
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
26
|
paseo plugin install npm:@omercnet/paseo-omp@<version>
|
|
@@ -36,7 +36,7 @@ Open the **OMP** sidebar to review the provider-profile contract, choose which c
|
|
|
36
36
|
- [Core-provider issue and parity audit](docs/core-provider-issue-audit.md)
|
|
37
37
|
- [Alpha release checklist](docs/alpha-release-checklist.md)
|
|
38
38
|
|
|
39
|
-
The plugin uses public Paseo 0.9
|
|
39
|
+
The plugin uses public Paseo 0.9 provider and client contracts and registers distinct `omp-plugin` and `omp-plugin-<profile>` identities; it does not modify the bundled `omp` provider.
|
|
40
40
|
|
|
41
41
|
## Named OMP profiles
|
|
42
42
|
|
|
@@ -50,7 +50,7 @@ Profile selection is request-local: concurrent clients cannot change each other'
|
|
|
50
50
|
|
|
51
51
|
## Paseo provider SDK coverage
|
|
52
52
|
|
|
53
|
-
This matrix inventories the complete capability set exported by the pinned `@getpaseo/plugin` 0.9.0
|
|
53
|
+
This matrix inventories the complete capability set exported by the pinned `@getpaseo/plugin` 0.9.0 provider SDK. It measures strict SDK surface coverage, not general product quality.
|
|
54
54
|
|
|
55
55
|
**Current capability completeness: 61.8%.** The provider advertises 11 of 17 capabilities (64.7%); ten are complete and `session.configure` is partial.
|
|
56
56
|
|
|
@@ -113,7 +113,7 @@ Existing agents whose provider is `omp` remain owned by the bundled provider. Ne
|
|
|
113
113
|
|
|
114
114
|
OMP `18.1.15` is the oldest version tested end to end. The direct provider's hard compatibility gate is `rpc-ui` protocol v2: metadata-free legacy ready frames and v1-only runtimes are rejected before a provider session opens because they cannot support the advertised persistence and conversation-rewind capabilities. Typed tool approvals remain capability-gated and fall back as described above. For terminal completion, a matching `agent_end.requestId` is authoritative and mismatches are ignored before state changes. Released OMP 18.2.x builds that omit that field use an ordered fallback requiring a fresh correlated native user entry followed by current-turn assistant activity, an idle non-compacting runtime, and no conflicting permission, tool, steer, or child-session work. Ambiguous events while OMP is active are ignored; unresolved ambiguity after confirmed idle fails only the Paseo turn. This trades a bounded residual same-agent stale-event risk for compatibility with released OMP instead of terminating and lazily restarting the runtime after every later prompt. Paseo clients, including mobile clients inside reconnect grace, do not own or terminate the daemon-managed OMP session.
|
|
115
115
|
|
|
116
|
-
Paseo 0.9.0-beta.1
|
|
116
|
+
Paseo 0.9.0 is the minimum supported release. The verified controlled canary evidence remains on the 0.9.0-beta.1 image, where the full Docker canary passed with OMP 18.1.15 and 18.2.0. That canary preserved nested provider-subagent ancestry and spawning-tool links, and validated npm-managed plugin sources, platform-owned external URL opening, and whole-item timeline transforms before Overview grouping.
|
|
117
117
|
|
|
118
118
|
Install, update, disable, or remove `paseo-omp` independently of the bundled provider. Verify the provider snapshot contains `omp-plugin` after installation; a bundled `omp` entry may remain present and is not modified by this plugin.
|
|
119
119
|
|
|
@@ -99,7 +99,6 @@ function OrderedRoleEditor({
|
|
|
99
99
|
return (
|
|
100
100
|
<View style={styles.recordList}>
|
|
101
101
|
{roles.map((role, index) => (
|
|
102
|
-
// biome-ignore lint/suspicious/noArrayIndexKey: ordered rows are edited by position and must keep input identity while their value changes.
|
|
103
102
|
<View key={index} style={styles.recordRow}>
|
|
104
103
|
<Text style={styles.recordKey}>{index + 1}</Text>
|
|
105
104
|
<TextInput
|
|
@@ -172,7 +171,6 @@ function RecordRoutingEditor({
|
|
|
172
171
|
? rawValue
|
|
173
172
|
: "";
|
|
174
173
|
return (
|
|
175
|
-
// biome-ignore lint/suspicious/noArrayIndexKey: record keys are editable, so row position preserves input identity.
|
|
176
174
|
<View key={index} style={styles.recordRow}>
|
|
177
175
|
<TextInput
|
|
178
176
|
accessibilityLabel={`${formatOmpSettingLabel(setting.path)} ${keyLabel} ${index + 1}`}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omercnet/paseo-omp",
|
|
3
|
-
"version": "0.4.0-next.
|
|
3
|
+
"version": "0.4.0-next.133.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Paseo integration for OMP, including its direct provider and workspace tooling.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"publishConfig": {
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
|
-
"packageManager": "
|
|
19
|
+
"packageManager": "bun@1.4.2",
|
|
20
20
|
"engines": {
|
|
21
21
|
"node": ">=22.18.0"
|
|
22
22
|
},
|
|
@@ -40,42 +40,41 @@
|
|
|
40
40
|
"paseo-plugin.json"
|
|
41
41
|
],
|
|
42
42
|
"scripts": {
|
|
43
|
-
"check": "biome check
|
|
43
|
+
"check": "biome check .",
|
|
44
44
|
"check:write": "biome check --write .",
|
|
45
|
-
"test": "run
|
|
45
|
+
"test": "vitest run",
|
|
46
46
|
"test:coverage": "PASEO_OMP_COVERAGE=1 vitest run --coverage",
|
|
47
47
|
"test:integration:docker": "node --import tsx scripts/test-docker-host-tools.ts",
|
|
48
48
|
"test:integration:wsl": "node --import tsx scripts/test-wsl-host-tools.ts",
|
|
49
49
|
"test:integration:canary": "node --import tsx scripts/test-canary.ts",
|
|
50
50
|
"test:integration:install": "node --import tsx scripts/test-package-install.ts",
|
|
51
|
-
"typecheck": "tsc --noEmit"
|
|
52
|
-
"test:ci:unit": "vitest run",
|
|
53
|
-
"build": "node ../.github/scripts/build-plugin.mjs"
|
|
51
|
+
"typecheck": "tsc --noEmit"
|
|
54
52
|
},
|
|
55
53
|
"dependencies": {
|
|
56
|
-
"@getpaseo/protocol": "0.9.0
|
|
57
|
-
"@modelcontextprotocol/sdk": "1.
|
|
54
|
+
"@getpaseo/protocol": "0.9.0",
|
|
55
|
+
"@modelcontextprotocol/sdk": "1.30.0",
|
|
58
56
|
"yaml": "^2.9.0"
|
|
59
57
|
},
|
|
60
58
|
"devDependencies": {
|
|
61
|
-
"@biomejs/biome": "^2.5.
|
|
62
|
-
"@getpaseo/cli": "0.9.0
|
|
63
|
-
"@getpaseo/client": "0.9.0
|
|
64
|
-
"@getpaseo/plugin": "0.9.0
|
|
65
|
-
"@getpaseo/server": "0.9.0
|
|
59
|
+
"@biomejs/biome": "^2.5.10",
|
|
60
|
+
"@getpaseo/cli": "0.9.0",
|
|
61
|
+
"@getpaseo/client": "0.9.0",
|
|
62
|
+
"@getpaseo/plugin": "0.9.0",
|
|
63
|
+
"@getpaseo/server": "0.9.0",
|
|
66
64
|
"@oh-my-pi/pi-agent-core": "18.2.0",
|
|
67
65
|
"@oh-my-pi/pi-coding-agent": "18.2.0",
|
|
68
66
|
"@tanstack/react-query": "^5.102.3",
|
|
69
67
|
"@types/node": "^24.10.1",
|
|
70
68
|
"@types/react": "^19.2.18",
|
|
71
69
|
"@vitest/coverage-v8": "^5.0.0",
|
|
72
|
-
"
|
|
70
|
+
"esbuild": "0.28.2",
|
|
71
|
+
"pino": "10.3.1",
|
|
73
72
|
"react": "19.1.0",
|
|
74
73
|
"react-native": "0.81.5",
|
|
75
74
|
"tsx": "^4.20.6",
|
|
76
75
|
"typescript": "^7.0.2",
|
|
77
76
|
"vitest": "^5.0.0",
|
|
78
|
-
"zod": "
|
|
77
|
+
"zod": "4.4.3"
|
|
79
78
|
},
|
|
80
79
|
"overrides": {
|
|
81
80
|
"qs": "6.16.0"
|
package/paseo-plugin.json
CHANGED
|
@@ -1,21 +1,54 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
|
|
6
6
|
const projectRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
function loadRootCatalog(root) {
|
|
9
|
+
for (let directory = root; ; directory = dirname(directory)) {
|
|
10
|
+
const manifestPath = join(directory, "package.json");
|
|
11
|
+
if (existsSync(manifestPath)) {
|
|
12
|
+
const catalog = JSON.parse(readFileSync(manifestPath, "utf8")).catalog;
|
|
13
|
+
if (catalog) return catalog;
|
|
14
|
+
}
|
|
15
|
+
if (directory === dirname(directory)) return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function stageManifest(root, catalog, install) {
|
|
20
|
+
const manifestPath = join(root, "package.json");
|
|
21
|
+
const source = readFileSync(manifestPath, "utf8");
|
|
22
|
+
const manifest = JSON.parse(source);
|
|
23
|
+
|
|
24
|
+
for (const [name, spec] of Object.entries(manifest.dependencies ?? {})) {
|
|
25
|
+
if (spec !== "catalog:") continue;
|
|
26
|
+
if (typeof catalog[name] !== "string") throw new Error(`Missing catalog entry for ${name}`);
|
|
27
|
+
manifest.dependencies[name] = catalog[name];
|
|
28
|
+
}
|
|
29
|
+
delete manifest.devDependencies;
|
|
10
30
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
31
|
+
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
32
|
+
try {
|
|
33
|
+
install();
|
|
34
|
+
} finally {
|
|
35
|
+
writeFileSync(manifestPath, source);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function prepareDependencies(root = projectRoot, execute = execFileSync) {
|
|
40
|
+
const catalog = loadRootCatalog(root);
|
|
41
|
+
if (!catalog) return false;
|
|
42
|
+
stageManifest(root, catalog, () => {
|
|
43
|
+
execute(
|
|
44
|
+
process.platform === "win32" ? "npm.cmd" : "npm",
|
|
45
|
+
["install", "--omit=dev", "--ignore-scripts", "--no-package-lock", "--workspaces=false"],
|
|
46
|
+
{
|
|
47
|
+
cwd: root,
|
|
48
|
+
stdio: "inherit",
|
|
49
|
+
},
|
|
50
|
+
);
|
|
51
|
+
});
|
|
19
52
|
return true;
|
|
20
53
|
}
|
|
21
54
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Release Please keeps this bundle-safe constant aligned with package.json. */
|
|
2
|
-
export const PASEO_OMP_PACKAGE_VERSION = "0.4.0-next.
|
|
2
|
+
export const PASEO_OMP_PACKAGE_VERSION = "0.4.0-next.133.1"; // x-release-please-version
|
|
@@ -159,6 +159,7 @@ export class OmpRpcProcess {
|
|
|
159
159
|
private readonly terminateProcessTree: (pid: number) => Promise<ProcessTreeCleanup>;
|
|
160
160
|
private readonly streamedBlocks = new Map<number, string>();
|
|
161
161
|
private readonly activeToolCallIds = new Set<string>();
|
|
162
|
+
private readonly announcedToolCallIds = new Set<string>();
|
|
162
163
|
private pendingWriteBytes = 0;
|
|
163
164
|
private commandTextLength = 0;
|
|
164
165
|
private lineParts: Buffer[] = [];
|
|
@@ -1112,6 +1113,7 @@ export class OmpRpcProcess {
|
|
|
1112
1113
|
if (event.data.type === "turn_end") {
|
|
1113
1114
|
this.commandTextLength = 0;
|
|
1114
1115
|
this.activeToolCallIds.clear();
|
|
1116
|
+
this.announcedToolCallIds.clear();
|
|
1115
1117
|
}
|
|
1116
1118
|
}
|
|
1117
1119
|
|
|
@@ -1133,6 +1135,7 @@ export class OmpRpcProcess {
|
|
|
1133
1135
|
this.streamedBlocks.clear();
|
|
1134
1136
|
this.commandTextLength = 0;
|
|
1135
1137
|
this.activeToolCallIds.clear();
|
|
1138
|
+
this.announcedToolCallIds.clear();
|
|
1136
1139
|
return true;
|
|
1137
1140
|
}
|
|
1138
1141
|
if (event.type === "command_output") {
|
|
@@ -1151,12 +1154,19 @@ export class OmpRpcProcess {
|
|
|
1151
1154
|
this.activeToolCallIds.add(event.toolCallId);
|
|
1152
1155
|
return true;
|
|
1153
1156
|
}
|
|
1154
|
-
if (event.type === "
|
|
1157
|
+
if (event.type === "tool_stream_update") {
|
|
1158
|
+
return (
|
|
1159
|
+
this.activeToolCallIds.has(event.toolCallId) ||
|
|
1160
|
+
this.announcedToolCallIds.has(event.toolCallId)
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
if (event.type === "tool_execution_update") {
|
|
1155
1164
|
return this.activeToolCallIds.has(event.toolCallId);
|
|
1156
1165
|
}
|
|
1157
1166
|
if (event.type === "tool_execution_end") {
|
|
1158
1167
|
if (!this.activeToolCallIds.has(event.toolCallId)) return false;
|
|
1159
1168
|
this.activeToolCallIds.delete(event.toolCallId);
|
|
1169
|
+
this.announcedToolCallIds.delete(event.toolCallId);
|
|
1160
1170
|
return true;
|
|
1161
1171
|
}
|
|
1162
1172
|
if (
|
|
@@ -1174,6 +1184,14 @@ export class OmpRpcProcess {
|
|
|
1174
1184
|
nextBlocks.set(0, content);
|
|
1175
1185
|
} else if (Array.isArray(content)) {
|
|
1176
1186
|
for (const [index, part] of content.entries()) {
|
|
1187
|
+
if (
|
|
1188
|
+
part.type === "toolCall" &&
|
|
1189
|
+
typeof part.id === "string" &&
|
|
1190
|
+
(this.announcedToolCallIds.has(part.id) ||
|
|
1191
|
+
this.announcedToolCallIds.size < MAX_ACTIVE_TOOLS)
|
|
1192
|
+
) {
|
|
1193
|
+
this.announcedToolCallIds.add(part.id);
|
|
1194
|
+
}
|
|
1177
1195
|
const text =
|
|
1178
1196
|
part.type === "text" ? part.text : part.type === "thinking" ? part.thinking : undefined;
|
|
1179
1197
|
if (text !== undefined) nextBlocks.set(index, text);
|
|
@@ -63,6 +63,7 @@ import {
|
|
|
63
63
|
OmpThinkingLevelSchema,
|
|
64
64
|
validateBoundedText,
|
|
65
65
|
} from "./omp-rpc-values";
|
|
66
|
+
import { buildOmpPromptRequest } from "./prompt-payload";
|
|
66
67
|
import { OmpCleanupFailure, OmpPublicError } from "./security";
|
|
67
68
|
import {
|
|
68
69
|
listOmpSessionDescriptors,
|
|
@@ -497,7 +498,7 @@ class OmpRpcSession implements OmpRuntimeSession {
|
|
|
497
498
|
const safeMessage = validateBoundedText(message, "prompt", MAX_TEXT_LENGTH);
|
|
498
499
|
let acknowledgement: z.infer<typeof OmpPromptAckSchema> | undefined;
|
|
499
500
|
const request = this.process.startRequest(
|
|
500
|
-
|
|
501
|
+
buildOmpPromptRequest(safeMessage, images),
|
|
501
502
|
undefined,
|
|
502
503
|
(value) => {
|
|
503
504
|
acknowledgement = OmpPromptAckSchema.parse(value) ?? {};
|
|
@@ -511,7 +512,7 @@ class OmpRpcSession implements OmpRuntimeSession {
|
|
|
511
512
|
|
|
512
513
|
async steer(message: string, images: readonly OmpImage[] = []): Promise<void> {
|
|
513
514
|
const safeMessage = validateBoundedText(message, "steer", MAX_TEXT_LENGTH);
|
|
514
|
-
await this.process.
|
|
515
|
+
await this.process.request({
|
|
515
516
|
type: "steer",
|
|
516
517
|
message: safeMessage,
|
|
517
518
|
...(images.length > 0 ? { images } : {}),
|
|
@@ -519,7 +520,7 @@ class OmpRpcSession implements OmpRuntimeSession {
|
|
|
519
520
|
}
|
|
520
521
|
async followUp(message: string, images: readonly OmpImage[] = []): Promise<void> {
|
|
521
522
|
const safeMessage = validateBoundedText(message, "follow-up", MAX_TEXT_LENGTH);
|
|
522
|
-
await this.process.
|
|
523
|
+
await this.process.request({
|
|
523
524
|
type: "follow_up",
|
|
524
525
|
message: safeMessage,
|
|
525
526
|
...(images.length > 0 ? { images } : {}),
|
|
@@ -14,6 +14,14 @@ export function isSafeCommandName(name: string): boolean {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export type OmpPromptPayload = { text: string; images: OmpImage[]; commandName?: string };
|
|
17
|
+
export function buildOmpPromptRequest(message: string, images: readonly OmpImage[] = []) {
|
|
18
|
+
return {
|
|
19
|
+
type: "prompt" as const,
|
|
20
|
+
message,
|
|
21
|
+
streamingBehavior: "followUp" as const,
|
|
22
|
+
...(images.length > 0 ? { images } : {}),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
17
25
|
|
|
18
26
|
const REVIEW_LINE_MARKERS = { add: "+", remove: "-", context: " " } as const;
|
|
19
27
|
|
|
@@ -222,12 +230,10 @@ export function inlinePromptFrameBytes(
|
|
|
222
230
|
delivery: "prompt" | "steer",
|
|
223
231
|
): number {
|
|
224
232
|
const images = payload.images.map(({ type, mimeType }) => ({ type, data: "", mimeType }));
|
|
225
|
-
const frame =
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
...(delivery === "prompt" ? { id: "" } : {}),
|
|
230
|
-
};
|
|
233
|
+
const frame =
|
|
234
|
+
delivery === "prompt"
|
|
235
|
+
? { ...buildOmpPromptRequest(payload.text, images), id: "" }
|
|
236
|
+
: { type: "steer", message: payload.text, ...(images.length > 0 ? { images } : {}) };
|
|
231
237
|
const requestIdBytes = delivery === "prompt" ? RPC_REQUEST_ID_BYTES : 0;
|
|
232
238
|
return (
|
|
233
239
|
utf8Bytes(JSON.stringify(frame)) +
|
|
@@ -80,6 +80,10 @@ type ToolSnapshot = {
|
|
|
80
80
|
specializedRendered: boolean;
|
|
81
81
|
silent: boolean;
|
|
82
82
|
};
|
|
83
|
+
type PendingToolStream = {
|
|
84
|
+
output: JsonValue;
|
|
85
|
+
retainedBytes: number;
|
|
86
|
+
};
|
|
83
87
|
|
|
84
88
|
export interface OmpTimelineScheduler {
|
|
85
89
|
set(callback: () => void | Promise<void>, delayMs: number): unknown;
|
|
@@ -354,6 +358,8 @@ type CompactionSlot = {
|
|
|
354
358
|
|
|
355
359
|
export class OmpTimelineProjector {
|
|
356
360
|
private readonly tools = new Map<string, ToolSnapshot>();
|
|
361
|
+
private readonly pendingToolStreams = new Map<string, PendingToolStream>();
|
|
362
|
+
private pendingToolStreamBytes = 0;
|
|
357
363
|
private stream: StreamSnapshot | null = null;
|
|
358
364
|
private flushTimer: unknown;
|
|
359
365
|
private currentTurnId: string | null = null;
|
|
@@ -493,6 +499,11 @@ export class OmpTimelineProjector {
|
|
|
493
499
|
return;
|
|
494
500
|
case "tool_execution_start": {
|
|
495
501
|
this.flush(true);
|
|
502
|
+
const pendingStream = this.pendingToolStreams.get(event.toolCallId);
|
|
503
|
+
if (pendingStream) {
|
|
504
|
+
this.pendingToolStreams.delete(event.toolCallId);
|
|
505
|
+
this.pendingToolStreamBytes -= pendingStream.retainedBytes;
|
|
506
|
+
}
|
|
496
507
|
if (this.toolIdentitySaturated || this.retiredToolCallIds.has(event.toolCallId)) return;
|
|
497
508
|
const previous = this.tools.get(event.toolCallId);
|
|
498
509
|
if (!previous && this.tools.size >= MAX_ACTIVE_TOOLS) return;
|
|
@@ -501,7 +512,17 @@ export class OmpTimelineProjector {
|
|
|
501
512
|
MAX_PUBLIC_TOOL_PAYLOAD_BYTES,
|
|
502
513
|
MAX_PUBLIC_TOOL_PAYLOAD_BYTES,
|
|
503
514
|
);
|
|
504
|
-
const
|
|
515
|
+
const inputBytes = boundedJsonBytes(input, MAX_PUBLIC_TOOL_PAYLOAD_BYTES);
|
|
516
|
+
let output = pendingStream?.output ?? null;
|
|
517
|
+
let retainedBytes = inputBytes + (pendingStream?.retainedBytes ?? 0);
|
|
518
|
+
if (
|
|
519
|
+
pendingStream &&
|
|
520
|
+
this.activeToolBytes - (previous?.retainedBytes ?? 0) + retainedBytes >
|
|
521
|
+
MAX_ACTIVE_TOOL_BYTES
|
|
522
|
+
) {
|
|
523
|
+
output = null;
|
|
524
|
+
retainedBytes = inputBytes;
|
|
525
|
+
}
|
|
505
526
|
if (
|
|
506
527
|
this.activeToolBytes - (previous?.retainedBytes ?? 0) + retainedBytes >
|
|
507
528
|
MAX_ACTIVE_TOOL_BYTES
|
|
@@ -520,7 +541,7 @@ export class OmpTimelineProjector {
|
|
|
520
541
|
nativeName: event.toolName,
|
|
521
542
|
name: this.dataFilter.text(displayName, 256),
|
|
522
543
|
input,
|
|
523
|
-
output
|
|
544
|
+
output,
|
|
524
545
|
retainedBytes,
|
|
525
546
|
turnId,
|
|
526
547
|
generation: this.runtimeGeneration,
|
|
@@ -534,15 +555,28 @@ export class OmpTimelineProjector {
|
|
|
534
555
|
}
|
|
535
556
|
case "tool_execution_update":
|
|
536
557
|
case "tool_stream_update": {
|
|
537
|
-
const previous = this.tools.get(event.toolCallId);
|
|
538
|
-
if (!previous) return;
|
|
539
|
-
if (previous.turnId !== turnId || previous.generation !== this.runtimeGeneration) return;
|
|
540
558
|
const output = this.dataFilter.json(
|
|
541
559
|
event.type === "tool_execution_update" ? event.partialResult : event.update,
|
|
542
560
|
MAX_PUBLIC_TOOL_PAYLOAD_BYTES,
|
|
543
561
|
MAX_PUBLIC_TOOL_PAYLOAD_BYTES,
|
|
544
562
|
);
|
|
545
563
|
const outputBytes = boundedJsonBytes(output, MAX_PUBLIC_TOOL_PAYLOAD_BYTES);
|
|
564
|
+
const previous = this.tools.get(event.toolCallId);
|
|
565
|
+
if (!previous) {
|
|
566
|
+
if (event.type !== "tool_stream_update") return;
|
|
567
|
+
const pending = this.pendingToolStreams.get(event.toolCallId);
|
|
568
|
+
if (!pending && this.pendingToolStreams.size >= MAX_ACTIVE_TOOLS) return;
|
|
569
|
+
if (
|
|
570
|
+
this.pendingToolStreamBytes - (pending?.retainedBytes ?? 0) + outputBytes >
|
|
571
|
+
MAX_ACTIVE_TOOL_BYTES
|
|
572
|
+
) {
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
this.pendingToolStreamBytes += outputBytes - (pending?.retainedBytes ?? 0);
|
|
576
|
+
this.pendingToolStreams.set(event.toolCallId, { output, retainedBytes: outputBytes });
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
if (previous.turnId !== turnId || previous.generation !== this.runtimeGeneration) return;
|
|
546
580
|
const inputBytes = boundedJsonBytes(previous.input, MAX_PUBLIC_TOOL_PAYLOAD_BYTES);
|
|
547
581
|
const retainedBytes = inputBytes + outputBytes;
|
|
548
582
|
if (this.activeToolBytes - previous.retainedBytes + retainedBytes > MAX_ACTIVE_TOOL_BYTES)
|
|
@@ -869,6 +903,7 @@ export class OmpTimelineProjector {
|
|
|
869
903
|
this.activeToolBytes = 0;
|
|
870
904
|
this.replayToolCalls.clear();
|
|
871
905
|
this.tools.clear();
|
|
906
|
+
this.clearPendingToolStreams();
|
|
872
907
|
this.commandText = "";
|
|
873
908
|
this.commandPublishedText = "";
|
|
874
909
|
this.revertEntryByToken.clear();
|
|
@@ -1136,6 +1171,7 @@ export class OmpTimelineProjector {
|
|
|
1136
1171
|
this.clearFlushTimer();
|
|
1137
1172
|
this.stream = null;
|
|
1138
1173
|
this.tools.clear();
|
|
1174
|
+
this.clearPendingToolStreams();
|
|
1139
1175
|
this.replayToolCalls.clear();
|
|
1140
1176
|
this.activeToolBytes = 0;
|
|
1141
1177
|
this.replayCandidates.clear();
|
|
@@ -1176,6 +1212,11 @@ export class OmpTimelineProjector {
|
|
|
1176
1212
|
}
|
|
1177
1213
|
this.activeToolBytes = 0;
|
|
1178
1214
|
this.tools.clear();
|
|
1215
|
+
this.clearPendingToolStreams();
|
|
1216
|
+
}
|
|
1217
|
+
private clearPendingToolStreams(): void {
|
|
1218
|
+
this.pendingToolStreams.clear();
|
|
1219
|
+
this.pendingToolStreamBytes = 0;
|
|
1179
1220
|
}
|
|
1180
1221
|
private ensureTurn(turnId: string): void {
|
|
1181
1222
|
if (this.currentTurnId === turnId) return;
|