@omercnet/paseo-omp 1.0.3 → 1.1.0-next.197.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 +5 -5
- package/client/memory-panel.tsx +7 -6
- package/client/memory-popover.tsx +10 -8
- package/client/omp-config-styles.ts +1 -1
- package/index.client.tsx +2 -2
- package/package.json +7 -7
- package/paseo-plugin.json +1 -1
- package/server/package-version.ts +1 -1
- package/server/provider/catalog.ts +1 -1
- package/server/provider/omp-rpc-environment.ts +8 -1
- package/server/provider/omp-rpc-transport.ts +32 -3
- package/server/provider/omp-rpc.ts +10 -3
- package/server/provider/session-descriptors.ts +4 -2
- package/server/provider/session.ts +45 -12
- package/server/provider/timeline-projector.ts +4 -1
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ before capture.
|
|
|
24
24
|
|
|
25
25
|
## Quick start
|
|
26
26
|
|
|
27
|
-
Requirements: Paseo `^0.9.2 || ^0.10.0
|
|
27
|
+
Requirements: Paseo `^0.9.2 || ^0.10.0`, OMP `18.1.15` or newer, and OMP RPC protocol v2. Paseo 0.9.2 is required for reliable plugin-provider request failure containment, reload cleanup, and daemon shutdown ordering; 0.10.1 is verified against the same public plugin contracts.
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
30
|
paseo plugin install npm:@omercnet/paseo-omp@<version>
|
|
@@ -40,7 +40,7 @@ Open the **OMP** sidebar to review the provider-profile contract, choose which c
|
|
|
40
40
|
- [Core-provider issue and parity audit](docs/core-provider-issue-audit.md)
|
|
41
41
|
- [Alpha release checklist](docs/alpha-release-checklist.md)
|
|
42
42
|
|
|
43
|
-
The plugin uses public Paseo 0.9 and 0.10
|
|
43
|
+
The plugin uses public Paseo 0.9 and 0.10 provider and client contracts and registers distinct `omp-plugin` and `omp-plugin-<profile>` identities; it does not modify the bundled `omp` provider.
|
|
44
44
|
|
|
45
45
|
## Named OMP profiles
|
|
46
46
|
|
|
@@ -54,7 +54,7 @@ Profile selection is request-local: concurrent clients cannot change each other'
|
|
|
54
54
|
|
|
55
55
|
## Paseo provider SDK coverage
|
|
56
56
|
|
|
57
|
-
This matrix inventories the complete capability set exported by the pinned `@getpaseo/plugin` 0.
|
|
57
|
+
This matrix inventories the complete capability set exported by the pinned `@getpaseo/plugin` 0.10.1 provider SDK, whose public provider surface is unchanged since 0.9.0; compatibility is checked nightly against the 0.9, 0.10, and latest SDK channels. It measures strict SDK surface coverage, not general product quality.
|
|
58
58
|
|
|
59
59
|
**Current capability completeness: 61.8%.** The provider advertises 11 of 17 capabilities (64.7%); ten are complete and `session.configure` is partial.
|
|
60
60
|
|
|
@@ -117,7 +117,7 @@ Existing agents whose provider is `omp` remain owned by the bundled provider. Ne
|
|
|
117
117
|
|
|
118
118
|
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.
|
|
119
119
|
|
|
120
|
-
Paseo 0.9.2 is the minimum supported stable release, and 0.10.
|
|
120
|
+
Paseo 0.9.2 is the minimum supported stable release, and 0.10.1 is supported. The controlled Docker canary remains pinned to official 0.9.2; nightly SDK checks cover the newest 0.9, 0.10, and latest releases. The canary preserves nested provider-subagent ancestry and spawning-tool links, and validates npm-managed plugin sources, platform-owned external URL opening, and whole-item timeline transforms before Overview grouping.
|
|
121
121
|
|
|
122
122
|
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.
|
|
123
123
|
|
|
@@ -133,7 +133,7 @@ Unexpected or internal launch failures use fixed fallback messages rather than s
|
|
|
133
133
|
|
|
134
134
|
The repeatable Docker canary and every validation command are documented in [TESTING.md](TESTING.md). It covers discovery, modes, text, images, tools, MCP, permissions, steering, interruption, persistence, host-wide and scoped session listing, import, resume, subagents, Hub, usage, plugin RPCs, and conversation rewind.
|
|
135
135
|
|
|
136
|
-
Use `
|
|
136
|
+
Use `Deterministic Canary` for picker-label assertions. The optional local Ollama model is exploratory and nondeterministic.
|
|
137
137
|
|
|
138
138
|
## Support
|
|
139
139
|
|
package/client/memory-panel.tsx
CHANGED
|
@@ -23,7 +23,7 @@ export function OmpMemoryPanel({ theme, layout, workspaceId }: PluginWorkspacePa
|
|
|
23
23
|
const styles = useMemo(
|
|
24
24
|
() => ({
|
|
25
25
|
root: {
|
|
26
|
-
|
|
26
|
+
flexGrow: 1,
|
|
27
27
|
gap: layout.compact ? 10 : 14,
|
|
28
28
|
padding: layout.compact ? 16 : 24,
|
|
29
29
|
backgroundColor: theme.colors.surface0,
|
|
@@ -52,16 +52,17 @@ export function OmpMemoryPanel({ theme, layout, workspaceId }: PluginWorkspacePa
|
|
|
52
52
|
return (
|
|
53
53
|
<ScrollView contentContainerStyle={styles.root}>
|
|
54
54
|
<View style={{ gap: 4 }}>
|
|
55
|
-
<Text style={styles.title}>
|
|
55
|
+
<Text style={styles.title}>Local Mnemopi Memory</Text>
|
|
56
|
+
<Text style={styles.subtitle}>Shows local facts only.</Text>
|
|
56
57
|
<Text style={styles.subtitle}>
|
|
57
|
-
{memory.data?.bank ? `
|
|
58
|
+
{memory.data?.bank ? `Local bank: ${memory.data.bank}` : "Local Mnemopi workspace facts"}
|
|
58
59
|
</Text>
|
|
59
60
|
</View>
|
|
60
61
|
<OmpStorePicker theme={theme} store={store} onChange={setStore} />
|
|
61
|
-
{memory.isLoading ? <Text style={styles.subtitle}>Loading
|
|
62
|
-
{memory.error ? <Text style={styles.error}>Could not read
|
|
62
|
+
{memory.isLoading ? <Text style={styles.subtitle}>Loading local Mnemopi facts…</Text> : null}
|
|
63
|
+
{memory.error ? <Text style={styles.error}>Could not read local Mnemopi facts.</Text> : null}
|
|
63
64
|
{!memory.isLoading && !memory.error && (memory.data?.facts.length ?? 0) === 0 ? (
|
|
64
|
-
<Text style={styles.subtitle}>No
|
|
65
|
+
<Text style={styles.subtitle}>No local Mnemopi facts for this workspace.</Text>
|
|
65
66
|
) : null}
|
|
66
67
|
{memory.data?.facts.map((fact) => (
|
|
67
68
|
<View key={fact.id} style={styles.card}>
|
|
@@ -43,21 +43,23 @@ export function MemoryPopover(props: PluginButtonContentProps) {
|
|
|
43
43
|
[layout.compact, theme],
|
|
44
44
|
);
|
|
45
45
|
|
|
46
|
-
if (memory.isLoading) return <Text style={styles.muted}>Loading retained facts…</Text>;
|
|
47
|
-
if (memory.error) return <Text style={styles.error}>Could not read workspace memory.</Text>;
|
|
48
|
-
|
|
49
46
|
const facts = memory.data?.facts ?? [];
|
|
50
|
-
if (facts.length === 0) {
|
|
51
|
-
return <Text style={styles.muted}>No retained facts for this workspace.</Text>;
|
|
52
|
-
}
|
|
53
47
|
|
|
54
48
|
return (
|
|
55
49
|
<View style={styles.root}>
|
|
56
50
|
<View style={styles.header}>
|
|
57
|
-
<Text style={styles.title}>
|
|
51
|
+
<Text style={styles.title}>Local Mnemopi · {storeLabel(store)}</Text>
|
|
58
52
|
<Text style={styles.muted}>{facts.length} facts</Text>
|
|
59
53
|
</View>
|
|
60
|
-
<Text style={styles.muted}>
|
|
54
|
+
<Text style={styles.muted}>Shows local facts only.</Text>
|
|
55
|
+
{memory.isLoading ? <Text style={styles.muted}>Loading local Mnemopi facts…</Text> : null}
|
|
56
|
+
{memory.error ? <Text style={styles.error}>Could not read local Mnemopi facts.</Text> : null}
|
|
57
|
+
{!memory.isLoading && !memory.error && facts.length === 0 ? (
|
|
58
|
+
<Text style={styles.muted}>No local Mnemopi facts for this workspace.</Text>
|
|
59
|
+
) : null}
|
|
60
|
+
{!memory.isLoading && !memory.error && memory.data?.bank ? (
|
|
61
|
+
<Text style={styles.muted}>{memory.data.bank}</Text>
|
|
62
|
+
) : null}
|
|
61
63
|
{facts.slice(0, PREVIEW_LIMIT).map((fact) => (
|
|
62
64
|
<View key={fact.id} style={styles.card}>
|
|
63
65
|
<Text style={styles.fact}>{`${fact.subject} ${fact.predicate} ${fact.object}`}</Text>
|
package/index.client.tsx
CHANGED
|
@@ -133,7 +133,7 @@ export default function contribute(client: PluginClientContext) {
|
|
|
133
133
|
});
|
|
134
134
|
const removeOpenMemory = client.addCommandCenterItem({
|
|
135
135
|
id: "open-memory",
|
|
136
|
-
title: "Open
|
|
136
|
+
title: "Open local memory",
|
|
137
137
|
icon: "Brain",
|
|
138
138
|
keywords: ["omp", "memory", "facts", "recall"],
|
|
139
139
|
context: "workspace",
|
|
@@ -245,7 +245,7 @@ export default function contribute(client: PluginClientContext) {
|
|
|
245
245
|
workspaceId: entry.workspaceId,
|
|
246
246
|
agentId: agent.id,
|
|
247
247
|
button: {
|
|
248
|
-
title: "
|
|
248
|
+
title: "Local memory",
|
|
249
249
|
icon: "Brain",
|
|
250
250
|
label: "Memory",
|
|
251
251
|
behavior: { kind: "popover", Content: MemoryPopover },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omercnet/paseo-omp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.1.0-next.197.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Paseo integration for OMP, including its direct provider and workspace tooling.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -51,16 +51,16 @@
|
|
|
51
51
|
"typecheck": "tsc --noEmit -p tsconfig.client.json && tsc --noEmit -p tsconfig.server.json && tsc --noEmit -p tsconfig.tests.json"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@getpaseo/protocol": "0.
|
|
54
|
+
"@getpaseo/protocol": "0.10.1",
|
|
55
55
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
56
56
|
"yaml": "^2.9.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@biomejs/biome": "^2.5.10",
|
|
60
|
-
"@getpaseo/cli": "0.
|
|
61
|
-
"@getpaseo/client": "0.
|
|
62
|
-
"@getpaseo/plugin": "0.
|
|
63
|
-
"@getpaseo/server": "0.
|
|
60
|
+
"@getpaseo/cli": "0.10.1",
|
|
61
|
+
"@getpaseo/client": "0.10.1",
|
|
62
|
+
"@getpaseo/plugin": "0.10.1",
|
|
63
|
+
"@getpaseo/server": "0.10.1",
|
|
64
64
|
"@oh-my-pi/pi-agent-core": "18.2.0",
|
|
65
65
|
"@oh-my-pi/pi-coding-agent": "18.2.0",
|
|
66
66
|
"@tanstack/react-query": "^5.102.3",
|
|
@@ -68,9 +68,9 @@
|
|
|
68
68
|
"@types/react": "^19.2.18",
|
|
69
69
|
"@vitest/coverage-v8": "^5.0.0",
|
|
70
70
|
"esbuild": "0.28.2",
|
|
71
|
+
"pino": "10.3.1",
|
|
71
72
|
"react": "19.1.0",
|
|
72
73
|
"react-native": "0.81.5",
|
|
73
|
-
"pino": "10.3.1",
|
|
74
74
|
"tsx": "^4.20.6",
|
|
75
75
|
"typescript": "^7.0.2",
|
|
76
76
|
"vitest": "^5.0.0",
|
package/paseo-plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "paseo-omp",
|
|
3
3
|
"description": "Paseo integration for OMP, including its direct provider and workspace tooling.",
|
|
4
|
-
"requirements": { "paseo": "^0.9.2 || ^0.10.0
|
|
4
|
+
"requirements": { "paseo": "^0.9.2 || ^0.10.0" },
|
|
5
5
|
"build": [["node", "scripts/prepare-dependencies.mjs"]]
|
|
6
6
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Release Please keeps this bundle-safe constant aligned with package.json. */
|
|
2
|
-
export const PASEO_OMP_PACKAGE_VERSION = "1.0.
|
|
2
|
+
export const PASEO_OMP_PACKAGE_VERSION = "1.1.0-next.197.1"; // x-release-please-version
|
|
@@ -109,7 +109,7 @@ export function mapOmpModels(
|
|
|
109
109
|
const name = model.name ? serializer.text(model.name, 256) : modelId;
|
|
110
110
|
return {
|
|
111
111
|
id,
|
|
112
|
-
label:
|
|
112
|
+
label: name,
|
|
113
113
|
description: `${provider}/${modelId}`,
|
|
114
114
|
...(typeof model.contextWindow === "number"
|
|
115
115
|
? { contextWindowMaxTokens: model.contextWindow }
|
|
@@ -33,6 +33,8 @@ export interface OmpStartOptions {
|
|
|
33
33
|
requestTimeoutMs?: number;
|
|
34
34
|
/** Resume this exact native OMP session; never use this to start a new conversation. */
|
|
35
35
|
resumeSessionId?: string;
|
|
36
|
+
/** Authorized transcript of `resumeSessionId`; when set, OMP resumes this file directly. */
|
|
37
|
+
resumeSessionFile?: string;
|
|
36
38
|
noSession?: boolean;
|
|
37
39
|
signal?: AbortSignal;
|
|
38
40
|
}
|
|
@@ -340,7 +342,12 @@ export function buildOmpSpawnRequest(
|
|
|
340
342
|
validateBoundedText(options.sessionDir, "session directory", MAX_PATH_LENGTH),
|
|
341
343
|
);
|
|
342
344
|
}
|
|
343
|
-
if (options.
|
|
345
|
+
if (options.resumeSessionFile !== undefined) {
|
|
346
|
+
// OMP opens an absolute .jsonl directly, skipping its id lookup and global fallback.
|
|
347
|
+
const file = validateBoundedText(options.resumeSessionFile, "session file", MAX_PATH_LENGTH);
|
|
348
|
+
if (!isAbsolute(file) || !file.endsWith(".jsonl")) throw new Error("Invalid OMP session file");
|
|
349
|
+
args.push("--resume", file);
|
|
350
|
+
} else if (options.resumeSessionId !== undefined) {
|
|
344
351
|
args.push("--resume", validateNativeSessionId(options.resumeSessionId));
|
|
345
352
|
}
|
|
346
353
|
if (options.noSession) args.push("--no-session");
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type ProcessTreeCleanup,
|
|
13
13
|
stopWindowsTree,
|
|
14
14
|
terminatePosixProcessTree,
|
|
15
|
+
waitMs,
|
|
15
16
|
} from "./omp-rpc-process";
|
|
16
17
|
import {
|
|
17
18
|
createOptionalMetadataSanitizer,
|
|
@@ -56,7 +57,10 @@ import {
|
|
|
56
57
|
sanitizeHistoryResponseData,
|
|
57
58
|
sanitizeLiveDisplayFrame,
|
|
58
59
|
} from "./omp-rpc-protocol";
|
|
59
|
-
import { boundedJsonBytes, OmpPublicError, utf8Bytes } from "./security";
|
|
60
|
+
import { boundedJsonBytes, OmpCleanupFailure, OmpPublicError, utf8Bytes } from "./security";
|
|
61
|
+
|
|
62
|
+
// Unverified close keeps the native session quarantined; these retries let a slow exit clear it.
|
|
63
|
+
const TREE_CLEANUP_RETRY_DELAYS_MS = [500, 1_000, 2_000, 4_000];
|
|
60
64
|
|
|
61
65
|
type TimerHandle = number | NodeJS.Timeout;
|
|
62
66
|
type SpawnProcess = (request: OmpSpawnRequest) => ChildProcessWithoutNullStreams;
|
|
@@ -483,7 +487,12 @@ export class OmpRpcProcess {
|
|
|
483
487
|
}
|
|
484
488
|
}
|
|
485
489
|
const cleanup = await cleanupPromise;
|
|
486
|
-
if (cleanup !== "verified")
|
|
490
|
+
if (cleanup !== "verified") {
|
|
491
|
+
throw new OmpCleanupFailure(
|
|
492
|
+
"OMP RPC process tree cleanup failed",
|
|
493
|
+
this.reverifyTreeCleanup(),
|
|
494
|
+
);
|
|
495
|
+
}
|
|
487
496
|
if (process.platform === "win32" && !this.exited) {
|
|
488
497
|
try {
|
|
489
498
|
this.child.stdin.end();
|
|
@@ -496,8 +505,28 @@ export class OmpRpcProcess {
|
|
|
496
505
|
!this.exited &&
|
|
497
506
|
!(await this.waitForExit(PROCESS_STOP_TIMEOUT_MS))
|
|
498
507
|
) {
|
|
499
|
-
throw new
|
|
508
|
+
throw new OmpCleanupFailure(
|
|
509
|
+
"OMP RPC process did not close after tree cleanup",
|
|
510
|
+
this.reverifyTreeCleanup(),
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
private async reverifyTreeCleanup(): Promise<void> {
|
|
516
|
+
const pid = this.child.pid;
|
|
517
|
+
if (pid !== undefined) {
|
|
518
|
+
for (const delayMs of TREE_CLEANUP_RETRY_DELAYS_MS) {
|
|
519
|
+
await waitMs(delayMs);
|
|
520
|
+
const outcome = await this.terminateProcessTree(pid).catch(() => "failed");
|
|
521
|
+
if (
|
|
522
|
+
outcome === "verified" &&
|
|
523
|
+
(this.exited || (await this.waitForExit(PROCESS_STOP_TIMEOUT_MS)))
|
|
524
|
+
) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
500
528
|
}
|
|
529
|
+
throw new Error("OMP RPC process tree cleanup is unverified");
|
|
501
530
|
}
|
|
502
531
|
|
|
503
532
|
private startTreeCleanup(): Promise<ProcessTreeCleanup> {
|
|
@@ -159,6 +159,13 @@ export interface OmpRpcRuntimeOptions {
|
|
|
159
159
|
) => OmpSessionDescriptor[] | Promise<OmpSessionDescriptor[]>;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
// OMP accepts an empty message when images carry the prompt.
|
|
163
|
+
function promptText(message: string, images: readonly OmpImage[], field: string): string {
|
|
164
|
+
return message === "" && images.length > 0
|
|
165
|
+
? message
|
|
166
|
+
: validateBoundedText(message, field, MAX_TEXT_LENGTH);
|
|
167
|
+
}
|
|
168
|
+
|
|
162
169
|
class OmpRpcSession implements OmpRuntimeSession {
|
|
163
170
|
get maxHostToolFrameBytes(): number {
|
|
164
171
|
return this.process.outboundFrameLimit;
|
|
@@ -495,7 +502,7 @@ class OmpRpcSession implements OmpRuntimeSession {
|
|
|
495
502
|
onAccepted?: () => void,
|
|
496
503
|
onRequested?: (requestId: string) => void,
|
|
497
504
|
): Promise<{ requestId: string; agentInvoked?: boolean }> {
|
|
498
|
-
const safeMessage =
|
|
505
|
+
const safeMessage = promptText(message, images, "prompt");
|
|
499
506
|
let acknowledgement: z.infer<typeof OmpPromptAckSchema> | undefined;
|
|
500
507
|
const request = this.process.startRequest(
|
|
501
508
|
buildOmpPromptRequest(safeMessage, images),
|
|
@@ -511,7 +518,7 @@ class OmpRpcSession implements OmpRuntimeSession {
|
|
|
511
518
|
}
|
|
512
519
|
|
|
513
520
|
async steer(message: string, images: readonly OmpImage[] = []): Promise<void> {
|
|
514
|
-
const safeMessage =
|
|
521
|
+
const safeMessage = promptText(message, images, "steer");
|
|
515
522
|
await this.process.request({
|
|
516
523
|
type: "steer",
|
|
517
524
|
message: safeMessage,
|
|
@@ -519,7 +526,7 @@ class OmpRpcSession implements OmpRuntimeSession {
|
|
|
519
526
|
});
|
|
520
527
|
}
|
|
521
528
|
async followUp(message: string, images: readonly OmpImage[] = []): Promise<void> {
|
|
522
|
-
const safeMessage =
|
|
529
|
+
const safeMessage = promptText(message, images, "follow-up");
|
|
523
530
|
await this.process.request({
|
|
524
531
|
type: "follow_up",
|
|
525
532
|
message: safeMessage,
|
|
@@ -10,8 +10,10 @@ const MAX_DESCRIPTOR_PREFIX_BYTES = 64 * 1024;
|
|
|
10
10
|
const MAX_DESCRIPTOR_SUFFIX_BYTES = 64 * 1024;
|
|
11
11
|
const MAX_PROMPT_PREVIEW_CHARS = 160;
|
|
12
12
|
const MAX_DIRECTORY_DEPTH = 8;
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
// Count caps only bound memory; the byte and time budgets bound scan work. OMP keeps one
|
|
14
|
+
// directory per workspace, so these must exceed real session trees or lookups silently miss.
|
|
15
|
+
const MAX_SCAN_DIRECTORIES = 65_536;
|
|
16
|
+
const MAX_SCAN_FILES = 262_144;
|
|
15
17
|
const MAX_SCAN_BYTES = 16 * 1024 * 1024;
|
|
16
18
|
const MAX_SCAN_MS = 1_000;
|
|
17
19
|
const SCAN_YIELD_INTERVAL = 128;
|
|
@@ -490,7 +490,13 @@ export class OmpProviderSession {
|
|
|
490
490
|
...normalizedConfig,
|
|
491
491
|
// Thinking is authorized only after this runtime reports its exact model catalog.
|
|
492
492
|
thinkingOption: undefined,
|
|
493
|
-
...(resumeSessionId
|
|
493
|
+
...(resumeSessionId
|
|
494
|
+
? {
|
|
495
|
+
systemPrompt: undefined,
|
|
496
|
+
resumeSessionId,
|
|
497
|
+
resumeSessionFile: persistedDescriptor?.transcriptFile,
|
|
498
|
+
}
|
|
499
|
+
: {}),
|
|
494
500
|
signal,
|
|
495
501
|
environment,
|
|
496
502
|
};
|
|
@@ -1990,7 +1996,9 @@ export class OmpProviderSession {
|
|
|
1990
1996
|
recovered = await this.runtimeFactory.startSession({
|
|
1991
1997
|
...this.recoveryOptions,
|
|
1992
1998
|
...(recoverFromNativeConfig ? { model: undefined, thinkingOption: undefined } : {}),
|
|
1993
|
-
...(expectedSessionId
|
|
1999
|
+
...(expectedSessionId
|
|
2000
|
+
? { resumeSessionId: expectedSessionId, resumeSessionFile: this.nativeSessionFile }
|
|
2001
|
+
: {}),
|
|
1994
2002
|
signal: this.lifetime.signal,
|
|
1995
2003
|
});
|
|
1996
2004
|
} catch (error) {
|
|
@@ -2830,6 +2838,22 @@ export class OmpProviderSession {
|
|
|
2830
2838
|
turn.ambiguousTerminalTimer = undefined;
|
|
2831
2839
|
}
|
|
2832
2840
|
|
|
2841
|
+
private async cancelIndeterminateTerminal(
|
|
2842
|
+
turn: ActiveTurn,
|
|
2843
|
+
message: string,
|
|
2844
|
+
invalidateRuntime = false,
|
|
2845
|
+
): Promise<void> {
|
|
2846
|
+
this.recordOperationalFailure({ category: "terminal-outcome", stage: "unresolved" });
|
|
2847
|
+
this.projector.projectPassive({
|
|
2848
|
+
type: "notice",
|
|
2849
|
+
level: "warning",
|
|
2850
|
+
message: "OMP ended before its final response could be confirmed. Partial output was kept.",
|
|
2851
|
+
});
|
|
2852
|
+
if (invalidateRuntime) this.invalidateRuntime(message);
|
|
2853
|
+
this.subsessions?.terminalize("canceled");
|
|
2854
|
+
await this.finishTurn(turn, "canceled", undefined, true, true);
|
|
2855
|
+
}
|
|
2856
|
+
|
|
2833
2857
|
private async settleAmbiguousTerminal(
|
|
2834
2858
|
turn: ActiveTurn,
|
|
2835
2859
|
candidate: TerminalCandidate,
|
|
@@ -2848,16 +2872,19 @@ export class OmpProviderSession {
|
|
|
2848
2872
|
const state = await this.usage.boundedTerminalState(turn, FINAL_USAGE_WAIT_MS);
|
|
2849
2873
|
if (turn.terminal || this.activeTurn !== turn || turn.deferredAgentEnd !== candidate) return;
|
|
2850
2874
|
if (!state) {
|
|
2851
|
-
|
|
2852
|
-
|
|
2875
|
+
await this.cancelIndeterminateTerminal(
|
|
2876
|
+
turn,
|
|
2877
|
+
"OMP agent_end state could not be confirmed",
|
|
2878
|
+
true,
|
|
2879
|
+
);
|
|
2853
2880
|
return;
|
|
2854
2881
|
}
|
|
2855
2882
|
turn.deferredAgentEnd = undefined;
|
|
2856
2883
|
if (state.isStreaming || state.isCompacting) return;
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2884
|
+
await this.cancelIndeterminateTerminal(
|
|
2885
|
+
turn,
|
|
2886
|
+
"OMP unkeyed agent_end could not be correlated to the current prompt",
|
|
2887
|
+
);
|
|
2861
2888
|
}
|
|
2862
2889
|
|
|
2863
2890
|
private async completeLocalOnlyTurn(turn: ActiveTurn): Promise<void> {
|
|
@@ -2929,8 +2956,11 @@ export class OmpProviderSession {
|
|
|
2929
2956
|
) {
|
|
2930
2957
|
void this.completeAgentEnd(turn, candidate.event);
|
|
2931
2958
|
} else {
|
|
2932
|
-
|
|
2933
|
-
|
|
2959
|
+
void this.cancelIndeterminateTerminal(
|
|
2960
|
+
turn,
|
|
2961
|
+
"OMP agent_end state could not be confirmed",
|
|
2962
|
+
true,
|
|
2963
|
+
);
|
|
2934
2964
|
}
|
|
2935
2965
|
}, AGENT_END_SETTLE_MS);
|
|
2936
2966
|
this.finishFromAgentEnd(turn, candidate);
|
|
@@ -3043,8 +3073,11 @@ export class OmpProviderSession {
|
|
|
3043
3073
|
return;
|
|
3044
3074
|
}
|
|
3045
3075
|
if (turn.userEchoObserved) {
|
|
3046
|
-
|
|
3047
|
-
|
|
3076
|
+
await this.cancelIndeterminateTerminal(
|
|
3077
|
+
turn,
|
|
3078
|
+
"OMP agent_end state could not be confirmed",
|
|
3079
|
+
true,
|
|
3080
|
+
);
|
|
3048
3081
|
return;
|
|
3049
3082
|
}
|
|
3050
3083
|
if (turn.agentEndRetryTimer === undefined) {
|
|
@@ -1099,13 +1099,16 @@ export class OmpTimelineProjector {
|
|
|
1099
1099
|
for (const contentIndex of indexes) {
|
|
1100
1100
|
const block = stream.blocks.get(contentIndex);
|
|
1101
1101
|
if (!block) continue;
|
|
1102
|
-
|
|
1102
|
+
let publicText =
|
|
1103
1103
|
block.kind === "image"
|
|
1104
1104
|
? block.text
|
|
1105
1105
|
: truncateUtf8(
|
|
1106
1106
|
this.dataFilter.text(block.text, MAX_REDACTED_STREAM_TEXT_LENGTH),
|
|
1107
1107
|
remainingTextBytes,
|
|
1108
1108
|
);
|
|
1109
|
+
if (block.kind === "assistant_message") {
|
|
1110
|
+
publicText = publicText.replace(/\n*\[System Error\] Provider connection closed\s*$/, "");
|
|
1111
|
+
}
|
|
1109
1112
|
if (block.kind !== "image") {
|
|
1110
1113
|
remainingTextBytes = Math.max(0, remainingTextBytes - utf8Bytes(publicText));
|
|
1111
1114
|
}
|