negotium 0.22.3 → 0.22.5

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.
Files changed (45) hide show
  1. package/bin/negotium.mjs +0 -0
  2. package/dist/agent-helpers.js +615 -48
  3. package/dist/agent-helpers.js.map +11 -9
  4. package/dist/{chunk-6c637pwe.js → chunk-7rpbcn1j.js} +3 -3
  5. package/dist/{chunk-6c637pwe.js.map → chunk-7rpbcn1j.js.map} +2 -2
  6. package/dist/{chunk-bn1tt2qw.js → chunk-a3me2mje.js} +3 -3
  7. package/dist/chunk-a3me2mje.js.map +10 -0
  8. package/dist/{chunk-kn8y5scr.js → chunk-vh8px000.js} +12 -3
  9. package/dist/{chunk-kn8y5scr.js.map → chunk-vh8px000.js.map} +4 -4
  10. package/dist/hosted-agent.js +13 -4
  11. package/dist/hosted-agent.js.map +5 -5
  12. package/dist/main.js +1168 -264
  13. package/dist/main.js.map +25 -23
  14. package/dist/mcp-factories.js +634 -67
  15. package/dist/mcp-factories.js.map +11 -9
  16. package/dist/registry.js +3 -3
  17. package/dist/rollout.js +2 -2
  18. package/dist/runtime/src/agents/mcp-tools/request-vault.ts +616 -0
  19. package/dist/runtime/src/agents/rollout/claude.ts +6 -1
  20. package/dist/runtime/src/application/submit-runtime-gateway-turn.ts +6 -4
  21. package/dist/runtime/src/application/topic-service.ts +7 -0
  22. package/dist/runtime/src/index.ts +9 -0
  23. package/dist/runtime/src/mcp-runtime-host.ts +1 -0
  24. package/dist/runtime/src/node-host.ts +6 -0
  25. package/dist/runtime/src/platform/mcp-config.ts +60 -28
  26. package/dist/runtime/src/runtime/turn-event-stream.ts +2 -0
  27. package/dist/runtime/src/runtime/turn-runner.ts +9 -4
  28. package/dist/runtime/src/storage/api-messages.ts +8 -2
  29. package/dist/runtime/src/storage/vault-request-gates.ts +359 -0
  30. package/dist/runtime/src/types/api.ts +23 -1
  31. package/dist/runtime/src/version.ts +1 -1
  32. package/dist/runtime-helpers.js +4 -1
  33. package/dist/runtime-helpers.js.map +3 -3
  34. package/dist/storage.js +8 -4
  35. package/dist/storage.js.map +3 -3
  36. package/dist/types/packages/core/src/agents/mcp-tools/request-vault.d.ts +120 -0
  37. package/dist/types/packages/core/src/agents/rollout/claude.d.ts +5 -0
  38. package/dist/types/packages/core/src/platform/mcp-config.d.ts +8 -0
  39. package/dist/types/packages/core/src/runtime/turn-runner.d.ts +6 -2
  40. package/dist/types/packages/core/src/storage/api-messages.d.ts +1 -0
  41. package/dist/types/packages/core/src/storage/vault-request-gates.d.ts +69 -0
  42. package/dist/types/packages/core/src/types/api.d.ts +15 -1
  43. package/dist/types/packages/core/src/version.d.ts +1 -1
  44. package/package.json +1 -1
  45. package/dist/chunk-bn1tt2qw.js.map +0 -10
@@ -0,0 +1,120 @@
1
+ import { type SharedMcpTool } from "./common";
2
+ import { type RuntimeProcessLease, type RuntimeProcessLeaseHandle } from "../../storage/runtime-process-leases";
3
+ import { type ClaimVaultRequestGateResult, type PrepareVaultRequestGateResult, type VaultRequestGateCardUpdate } from "../../storage/vault-request-gates";
4
+ import type { AgentKind } from "../../types";
5
+ import type { MessageDto, TopicDto } from "../../types/api";
6
+ export interface RequestVaultToolContext {
7
+ userId: string;
8
+ topicId: string;
9
+ queryId?: string;
10
+ agent: AgentKind;
11
+ model?: string;
12
+ threadRootId?: string;
13
+ /**
14
+ * Set when this turn's surface cannot mask text input (Telegram has no
15
+ * native password field, so anything a person types becomes a plaintext
16
+ * message that lingers in chat history). When true the tool refuses
17
+ * immediately and never creates a card, instead of asking for a secret on
18
+ * a surface that cannot protect it.
19
+ */
20
+ unsupportedSurface?: boolean;
21
+ }
22
+ export type AnswerVaultSecretRequestResult = {
23
+ ok: true;
24
+ queryId?: string;
25
+ outcome: "fulfilled" | "declined";
26
+ answerMessage: MessageDto;
27
+ } | {
28
+ ok: false;
29
+ error: string;
30
+ };
31
+ export interface RequestVaultRuntimeHost {
32
+ messaging: {
33
+ persistence: {
34
+ getMessage(topicId: string, messageId: string): MessageDto | null | undefined;
35
+ appendMessage(message: MessageDto): void;
36
+ };
37
+ publication: {
38
+ publishMessage(topicId: string, message: MessageDto): void;
39
+ publishCardUpdate(update: VaultRequestGateCardUpdate): void;
40
+ };
41
+ };
42
+ topics: {
43
+ getTopic(topicId: string): Pick<TopicDto, "participants"> | null | undefined;
44
+ getConfig(topicId: string): {
45
+ model?: string;
46
+ } | undefined;
47
+ };
48
+ vault: {
49
+ hasKey(userId: string, key: string): boolean;
50
+ /** Writes the secret. Called before the gate is claimed, so a storage
51
+ * failure never leaves a card marked "fulfilled" without a stored value. */
52
+ set(userId: string, key: string, value: string, description: string): void;
53
+ };
54
+ durability: {
55
+ gates: {
56
+ prepare(args: {
57
+ gateId: string;
58
+ topicId: string;
59
+ queryId?: string;
60
+ idempotencyKey: string;
61
+ bodyHash: string;
62
+ messageId: string;
63
+ ownerId: string;
64
+ now: string;
65
+ }): PrepareVaultRequestGateResult;
66
+ claimAndAnswer(args: {
67
+ topicId: string;
68
+ messageId: string;
69
+ outcome: "fulfilled" | "declined";
70
+ userId: string;
71
+ ownerId: string;
72
+ source: string;
73
+ now: string;
74
+ }): ClaimVaultRequestGateResult;
75
+ cancel(topicId: string, messageId: string, ownerId: string, now: string): VaultRequestGateCardUpdate | null;
76
+ quarantine(gateId: string, ownerId: string, now: string): VaultRequestGateCardUpdate | null;
77
+ quarantineForeign(liveOwnerIds: ReadonlySet<string>, now: string): VaultRequestGateCardUpdate[];
78
+ };
79
+ processLeases: {
80
+ acquire(role: string, ownerId: string): RuntimeProcessLeaseHandle | null;
81
+ list(rolePrefix: string): RuntimeProcessLease[];
82
+ isAlive(lease: RuntimeProcessLease): boolean;
83
+ removeDead(rolePrefix: string): void;
84
+ };
85
+ };
86
+ runtime: {
87
+ ownerId: string;
88
+ createId(): string;
89
+ now(): string;
90
+ };
91
+ }
92
+ export interface RequestVaultRuntime {
93
+ startGateOwner(): void;
94
+ stopGateOwner(): void;
95
+ reconcilePendingGates(): number;
96
+ answerPendingRequest(topicId: string, messageId: string, userId: string, value: string | null): AnswerVaultSecretRequestResult;
97
+ cancelPendingRequests(topicId: string, queryId: string): void;
98
+ createToolDefinition(ctx: RequestVaultToolContext): SharedMcpTool;
99
+ }
100
+ /**
101
+ * Create an isolated request-vault-secret lifecycle over host-provided
102
+ * messaging, topic, vault, durability, and runtime services. Mirrors
103
+ * `createAskUserRuntime`'s durable-gate shape, but the thing being
104
+ * exchanged is "did the user supply a value", never the value itself.
105
+ */
106
+ export declare function createRequestVaultRuntime(host: RequestVaultRuntimeHost): RequestVaultRuntime;
107
+ export declare const defaultRequestVaultDurabilityHost: RequestVaultRuntimeHost["durability"];
108
+ export declare function startRequestVaultGateOwner(): void;
109
+ export declare function stopRequestVaultGateOwner(): void;
110
+ export declare function reconcilePendingRequestVaultGates(): number;
111
+ /**
112
+ * Host-neutral answer path for pending vault-request cards. Terminal and
113
+ * Otium should call this rather than reimplementing pending-map and
114
+ * persistence ordering. `value: null` means the user declined; any other
115
+ * string is the secret, stored via `vaultSet` before the gate is claimed,
116
+ * and never echoed back in the result.
117
+ */
118
+ export declare function answerPendingVaultSecretRequest(topicId: string, messageId: string, userId: string, value: string | null): AnswerVaultSecretRequestResult;
119
+ export declare function cancelPendingVaultSecretRequests(topicId: string, queryId: string): void;
120
+ export declare function createRequestVaultToolDefinition(ctx: RequestVaultToolContext): SharedMcpTool;
@@ -26,6 +26,11 @@ import type { ConversationEntry } from "../../storage/conversations";
26
26
  * synthetic rollouts landed at `…-user_6407801418/` while the SDK looked
27
27
  * at `…-user-6407801418/` — every `set_agent` switch surfaced as "No
28
28
  * conversation found with session ID" on the next message.
29
+ *
30
+ * Nothing is prepended: a POSIX path starts with `/`, which already becomes
31
+ * the leading `-`. A Windows path does not, and the SDK writes
32
+ * `C:\Users\x` as `C--Users-x`; forcing a `-` in front sent Windows rollouts
33
+ * to `-C--Users-x/`, where the SDK never looks.
29
34
  */
30
35
  export declare function encodeClaudeCwd(cwd: string): string;
31
36
  export interface ClaudeRolloutOptions {
@@ -2,6 +2,7 @@ import { type RuntimeMcpScope } from "./mcp-catalog-policy";
2
2
  import { browserOwnerCapability } from "./playwright/capability";
3
3
  import { buildPlaywrightMcpTransport, CODEX_BROWSER_CAPABILITY_ENV } from "./playwright/mcp-transport";
4
4
  import type { ActorTopicScope, AgentKind, AgentQueryOptions, PeerRuntimeBridgeContext, RemoteSessionGrant } from "../types";
5
+ import type { TopicDto } from "../types/api";
5
6
  export type { RuntimeMcpScope } from "./mcp-catalog-policy";
6
7
  /**
7
8
  * Build the stdio transport spec for a Otium MCP server entry.
@@ -172,6 +173,13 @@ export declare function setNodeMcpServers(entries: NodeMcpEntry[]): void;
172
173
  export declare function getNodeMcpServers(): readonly NodeMcpEntry[];
173
174
  /** Prevent host grants from replacing node-owned MCP servers. */
174
175
  export declare function isReservedRuntimeMcpServerName(name: string): boolean;
176
+ /**
177
+ * Why `topic` may not receive host MCP servers named `names`, or null when it
178
+ * may. A manager room takes any of them. An ordinary room takes everything but
179
+ * the manager administration surface, so a host can scope its own tools to a
180
+ * room. A subagent room takes none.
181
+ */
182
+ export declare function hostMcpGrantRefusal(topic: Pick<TopicDto, "kind" | "isSubagent">, names: readonly string[]): string | null;
175
183
  /**
176
184
  * Lazily prepare per-topic HTTP manifest entries before a provider resolves
177
185
  * its otherwise-synchronous MCP config. One failed custom server is omitted
@@ -186,8 +186,12 @@ export declare function resolveTurnToolCapabilities(topicId: string, params: {
186
186
  visualTools: boolean;
187
187
  fileDeliveryTools: boolean;
188
188
  };
189
- /** Require both room kind and execution scope so cron/ephemeral turns cannot inherit the grant. */
190
- export declare function resolveTurnHostMcpServers(topic: Pick<TopicDto, "id" | "kind">, sessionType?: "dm" | "forum" | "ephemeral" | "manager" | "cron"): Record<string, import("../runtime-gateway").HostMcpServerSpec> | undefined;
189
+ /**
190
+ * A manager room's grant reaches only manager executions and an ordinary
191
+ * room's only forum executions, so cron/ephemeral turns cannot inherit it.
192
+ * Subagent rooms never take one.
193
+ */
194
+ export declare function resolveTurnHostMcpServers(topic: Pick<TopicDto, "id" | "kind" | "isSubagent">, sessionType?: "dm" | "forum" | "ephemeral" | "manager" | "cron"): Record<string, import("../runtime-gateway").HostMcpServerSpec> | undefined;
191
195
  export interface StartAiTurnParams extends AiTurnExecutionOptions {
192
196
  topic: AiTurnTopic;
193
197
  userId: string;
@@ -20,6 +20,7 @@ export interface ApiMessageRow {
20
20
  ask_user_question: string | null;
21
21
  tell_card: string | null;
22
22
  subagent_card: string | null;
23
+ vault_secret_request: string | null;
23
24
  mentions: string | null;
24
25
  thread_root_id: string | null;
25
26
  created_at: string;
@@ -0,0 +1,69 @@
1
+ import type { VaultSecretRequestDto } from "../types/api";
2
+ export type VaultRequestGateState = "pending" | "claimed" | "answered" | "cancelled" | "quarantined";
3
+ export interface VaultRequestGateRecord {
4
+ gateId: string;
5
+ topicId: string;
6
+ queryId?: string;
7
+ idempotencyKey: string;
8
+ bodyHash: string;
9
+ messageId: string;
10
+ ownerId: string;
11
+ state: VaultRequestGateState;
12
+ outcome?: "fulfilled" | "declined";
13
+ answeredBy?: string;
14
+ }
15
+ export interface VaultRequestGateCardUpdate {
16
+ topicId: string;
17
+ messageId: string;
18
+ vaultSecretRequest: VaultSecretRequestDto;
19
+ editedAt: string;
20
+ }
21
+ export type PrepareVaultRequestGateResult = {
22
+ outcome: "created";
23
+ gate: VaultRequestGateRecord;
24
+ } | {
25
+ outcome: "pending";
26
+ gate: VaultRequestGateRecord;
27
+ } | {
28
+ outcome: "replay";
29
+ gate: VaultRequestGateRecord;
30
+ } | {
31
+ outcome: "conflict";
32
+ gate: VaultRequestGateRecord;
33
+ };
34
+ export declare function prepareVaultRequestGate(args: {
35
+ gateId: string;
36
+ topicId: string;
37
+ queryId?: string;
38
+ idempotencyKey: string;
39
+ bodyHash: string;
40
+ messageId: string;
41
+ ownerId: string;
42
+ now: string;
43
+ }): PrepareVaultRequestGateResult;
44
+ export declare function quarantineVaultRequestGate(gateId: string, ownerId: string, now?: string): VaultRequestGateCardUpdate | null;
45
+ export declare function quarantineForeignVaultRequestGates(liveOwnerIds: ReadonlySet<string>, now?: string): VaultRequestGateCardUpdate[];
46
+ export type ClaimVaultRequestGateResult = {
47
+ outcome: "claimed";
48
+ gate: VaultRequestGateRecord;
49
+ vaultSecretRequest: VaultSecretRequestDto;
50
+ editedAt: string;
51
+ } | {
52
+ outcome: "unavailable";
53
+ };
54
+ /**
55
+ * Claim the gate and mark the card's outcome. This never touches the secret
56
+ * value itself — the caller is responsible for writing to the vault (via
57
+ * `vaultSet`) *before* calling this, so the card only ever reflects
58
+ * "fulfilled"/"declined", never the plaintext.
59
+ */
60
+ export declare function claimVaultRequestGateAndAnswer(args: {
61
+ topicId: string;
62
+ messageId: string;
63
+ outcome: "fulfilled" | "declined";
64
+ userId: string;
65
+ ownerId: string;
66
+ source: string;
67
+ now: string;
68
+ }): ClaimVaultRequestGateResult;
69
+ export declare function cancelVaultRequestGate(topicId: string, messageId: string, ownerId: string, now?: string): VaultRequestGateCardUpdate | null;
@@ -249,8 +249,9 @@ export interface MessageDto {
249
249
  threadLastReplyAt?: string;
250
250
  /** Emoji reactions, one entry per (user, emoji). */
251
251
  reactions?: ReactionDto[];
252
- kind?: "message" | "system" | "tool" | "ask_user_question" | "subagent" | "tell";
252
+ kind?: "message" | "system" | "tool" | "ask_user_question" | "vault_secret_request" | "subagent" | "tell";
253
253
  askUserQuestion?: AskUserQuestionDto;
254
+ vaultSecretRequest?: VaultSecretRequestDto;
254
255
  subagentCard?: SubagentCardDto;
255
256
  tellCard?: TellCardDto;
256
257
  createdAt: string;
@@ -264,6 +265,18 @@ export interface AskUserQuestionDto {
264
265
  selectedLabel?: string;
265
266
  expired?: boolean;
266
267
  }
268
+ /**
269
+ * Blocking card for `request_vault_secret`: the agent proposes a key and
270
+ * description, a person types only the value on a surface that can mask
271
+ * input, and the plaintext never flows back through this DTO, a tool
272
+ * result, or chat history — only the outcome does.
273
+ */
274
+ export interface VaultSecretRequestDto {
275
+ key: string;
276
+ description?: string;
277
+ status: "pending" | "fulfilled" | "declined" | "expired";
278
+ answeredBy?: string;
279
+ }
267
280
  /** Structured cross-session delivery. Never inferred from system-message text. */
268
281
  export interface TellCardDto {
269
282
  fromLabel: string;
@@ -461,6 +474,7 @@ export type WsServerMessage = {
461
474
  editedAt?: string;
462
475
  usage?: MessageTokenUsage;
463
476
  askUserQuestion?: AskUserQuestionDto;
477
+ vaultSecretRequest?: VaultSecretRequestDto;
464
478
  subagentCard?: SubagentCardDto;
465
479
  } | {
466
480
  type: "reaction_update";
@@ -1 +1 @@
1
- export declare const NEGOTIUM_VERSION = "0.22.3";
1
+ export declare const NEGOTIUM_VERSION = "0.22.5";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "negotium",
3
- "version": "0.22.3",
3
+ "version": "0.22.5",
4
4
  "type": "module",
5
5
  "description": "Install the Negotium multi-agent runtime and CLI with one package",
6
6
  "license": "Apache-2.0",
@@ -1,10 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../packages/core/src/agents/rollout/claude.ts"],
4
- "sourcesContent": [
5
- "/**\n * Claude rollout encoder.\n *\n * Materializes a synthetic JSONL at `~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl`\n * so a subsequent `claude.query({ resume: sessionId })` continues the\n * cross-agent conversation as if it had always been native. Verified by\n * sandbox PoCs (test-7c/test-7d): the SDK accepts these without checksum\n * gating, requiring only `user`/`assistant` messages and a small per-turn\n * attachment chain captured into `agents/fixtures/claude-attachments.jsonl`.\n *\n * Tool history is folded into assistant text as `[Tool: ...]` annotations\n * (see shared.ts:extractChatPairs) — synthetic tool_use IDs across SDKs are\n * too fragile to reconstruct structurally.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport {\n assertUuidLike,\n type ChatPair,\n clone,\n ensureCwdExists,\n extractChatPairs,\n FIXTURES_DIR,\n} from \"#agents/rollout/shared\";\nimport { parseJsonlText, writeJsonlFile } from \"#platform/jsonl\";\nimport { logger } from \"#platform/logger\";\nimport type { ConversationEntry } from \"#storage/conversations\";\n\ninterface ClaudeAttachments {\n deferredToolsDelta: Record<string, unknown>;\n skillListing: Record<string, unknown>;\n}\n\nlet _attachmentsCache: ClaudeAttachments | null = null;\nfunction loadClaudeAttachments(): ClaudeAttachments {\n if (_attachmentsCache) return _attachmentsCache;\n const raw = readFileSync(join(FIXTURES_DIR, \"claude-attachments.jsonl\"), \"utf8\");\n const lines = parseJsonlText<Record<string, unknown>>(raw);\n if (lines.length < 2) {\n throw new Error(\n `loadClaudeAttachments: expected >=2 entries in claude-attachments.jsonl, got ${lines.length}`,\n );\n }\n _attachmentsCache = {\n deferredToolsDelta: lines[0],\n skillListing: lines[1],\n };\n return _attachmentsCache;\n}\n\n/**\n * Version strings stamped onto synthetic rollouts so they look indistinguishable\n * from a live SDK session. These do not affect resume behavior — the SDKs do\n * not gate on them — but keeping them current avoids confusion in observability\n * tooling that scans rollout files.\n */\nconst CLAUDE_SDK_VERSION = \"2.1.126\";\nconst CLAUDE_DEFAULT_MODEL = \"claude-sonnet-5\";\nconst CLAUDE_DEFAULT_GIT_BRANCH = \"HEAD\";\n\n/**\n * Encode a directory path the way Claude SDK expects in\n * `~/.claude/projects/<encoded-cwd>/`. macOS resolves `/tmp` to\n * `/private/tmp`, mirroring what the SDK records.\n *\n * The SDK normalizes ANY non-alphanumeric character to `-` (verified\n * empirically on disk: `/Users/maestrobot/.../user_6407801418` →\n * `-Users-...-user-6407801418`, i.e. both `/` AND `_` collapse to `-`).\n * A previous implementation only replaced `/` and left `_` intact, so\n * synthetic rollouts landed at `…-user_6407801418/` while the SDK looked\n * at `…-user-6407801418/` — every `set_agent` switch surfaced as \"No\n * conversation found with session ID\" on the next message.\n */\nexport function encodeClaudeCwd(cwd: string): string {\n const realCwd = cwd.startsWith(\"/tmp/\") ? cwd.replace(/^\\/tmp\\//, \"/private/tmp/\") : cwd;\n return `-${realCwd.replaceAll(/[^a-zA-Z0-9]/g, \"-\").replace(/^-/, \"\")}`;\n}\n\nexport interface ClaudeRolloutOptions {\n /** Working directory the resumed Claude session will report. */\n cwd: string;\n /** Optional override; default = freshly generated UUIDv4. */\n sessionId?: string;\n /**\n * Pairs to encode. If omitted, derived from `entries` via extractChatPairs.\n * Pass explicit pairs when the caller has already shaped the dialogue.\n */\n pairs?: ChatPair[];\n /** When `pairs` is omitted, the source UnifiedEvent log to digest. */\n entries?: ConversationEntry[];\n /** Full provider model id stamped on synthesized assistant history. */\n model?: string;\n}\n\nexport interface ClaudeRolloutResult {\n sessionId: string;\n rolloutPath: string;\n}\n\n/**\n * Materialize a Claude rollout JSONL with the provided dialogue and place it\n * at `~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl` so a subsequent\n * `claude.query({ resume: sessionId })` will continue the conversation.\n */\nexport function writeClaudeRollout(opts: ClaudeRolloutOptions): ClaudeRolloutResult {\n const sessionId = opts.sessionId ?? randomUUID();\n assertUuidLike(\"sessionId\", sessionId);\n ensureCwdExists(opts.cwd);\n const pairs = opts.pairs ?? extractChatPairs(opts.entries ?? []);\n const cwdReal = opts.cwd.startsWith(\"/tmp/\")\n ? opts.cwd.replace(/^\\/tmp\\//, \"/private/tmp/\")\n : opts.cwd;\n\n const lines: unknown[] = [];\n const ts = () => new Date().toISOString();\n let lastUuid: string | null = null;\n\n // Queue-operation noise that the live SDK records around each turn. Not\n // strictly required for resume but mirrors what the SDK itself writes,\n // keeping the synthetic file indistinguishable.\n lines.push({ type: \"queue-operation\", operation: \"enqueue\", timestamp: ts(), sessionId });\n lines.push({ type: \"queue-operation\", operation: \"dequeue\", timestamp: ts(), sessionId });\n\n // Per-turn attachment chain captured from a real Claude rollout. claude SDK\n // emits `deferred_tools_delta` followed by `skill_listing` immediately after\n // every user message, building a parentUuid chain user → att1 → att2 →\n // assistant. PoC-7d preserved this shape; replaying it keeps the resumed\n // session indistinguishable from a native one.\n const attachments = loadClaudeAttachments();\n\n for (const pair of pairs) {\n const userUuid = randomUUID();\n lines.push({\n parentUuid: lastUuid,\n isSidechain: false,\n promptId: randomUUID(),\n type: \"user\",\n message: { role: \"user\", content: [{ type: \"text\", text: pair.userText }] },\n uuid: userUuid,\n timestamp: ts(),\n permissionMode: \"bypassPermissions\",\n userType: \"external\",\n entrypoint: \"sdk-ts\",\n cwd: cwdReal,\n sessionId,\n version: CLAUDE_SDK_VERSION,\n gitBranch: CLAUDE_DEFAULT_GIT_BRANCH,\n });\n\n // Attachment 1: deferred_tools_delta — chain from user.\n const att1Uuid = randomUUID();\n const att1 = clone(attachments.deferredToolsDelta);\n (att1 as Record<string, unknown>).parentUuid = userUuid;\n (att1 as Record<string, unknown>).uuid = att1Uuid;\n (att1 as Record<string, unknown>).timestamp = ts();\n (att1 as Record<string, unknown>).sessionId = sessionId;\n (att1 as Record<string, unknown>).cwd = cwdReal;\n lines.push(att1);\n\n // Attachment 2: skill_listing — chain from att1.\n const att2Uuid = randomUUID();\n const att2 = clone(attachments.skillListing);\n (att2 as Record<string, unknown>).parentUuid = att1Uuid;\n (att2 as Record<string, unknown>).uuid = att2Uuid;\n (att2 as Record<string, unknown>).timestamp = ts();\n (att2 as Record<string, unknown>).sessionId = sessionId;\n (att2 as Record<string, unknown>).cwd = cwdReal;\n lines.push(att2);\n\n const assistantUuid = randomUUID();\n lines.push({\n parentUuid: att2Uuid,\n isSidechain: false,\n message: {\n model: opts.model ?? CLAUDE_DEFAULT_MODEL,\n id: `msg_${randomUUID().replace(/-/g, \"\").slice(0, 24)}`,\n type: \"message\",\n role: \"assistant\",\n content: [{ type: \"text\", text: pair.assistantText }],\n stop_reason: \"end_turn\",\n stop_sequence: null,\n stop_details: null,\n usage: {\n input_tokens: 0,\n cache_creation_input_tokens: 0,\n cache_read_input_tokens: 0,\n output_tokens: 0,\n server_tool_use: { web_search_requests: 0, web_fetch_requests: 0 },\n service_tier: \"standard\",\n cache_creation: { ephemeral_1h_input_tokens: 0, ephemeral_5m_input_tokens: 0 },\n inference_geo: \"\",\n iterations: [],\n speed: \"standard\",\n },\n diagnostics: null,\n },\n requestId: `req_${randomUUID().replace(/-/g, \"\").slice(0, 22)}`,\n type: \"assistant\",\n uuid: assistantUuid,\n timestamp: ts(),\n userType: \"external\",\n entrypoint: \"sdk-ts\",\n cwd: cwdReal,\n sessionId,\n version: CLAUDE_SDK_VERSION,\n gitBranch: CLAUDE_DEFAULT_GIT_BRANCH,\n });\n lastUuid = assistantUuid;\n }\n\n const projectsDir = join(homedir(), \".claude\", \"projects\", encodeClaudeCwd(opts.cwd));\n const path = join(projectsDir, `${sessionId}.jsonl`);\n writeJsonlFile(path, lines);\n logger.info(\n { sessionId, path, pairs: pairs.length },\n \"writeClaudeRollout: synthetic rollout placed\",\n );\n return { sessionId, rolloutPath: path };\n}\n\n/**\n * Remove the latest poisoned thinking turn and everything after it from the\n * session JSONL.\n *\n * When a turn ends with stop_reason=\"max_tokens\" while extended thinking is\n * active, the Claude CLI writes the truncated thinking block verbatim to the\n * rollout file. The Anthropic API rejects any subsequent resume that replays\n * this incomplete block with 400 \"thinking/redacted_thinking blocks cannot be\n * modified\". Dropping the entire poisoned turn (user entry + attachments +\n * truncated assistant) leaves the JSONL in a clean state so the next\n * claude.query({ resume }) can proceed without a 400.\n *\n * Returns true when the JSONL was found and repaired, false otherwise\n * (file missing, no poisoned assistant turn found, I/O error). Error-path\n * callers fall back to clearing the session ID when false is returned.\n */\nexport function repairPoisonedRollout(sessionId: string, cwd: string): boolean {\n try {\n const path = join(homedir(), \".claude\", \"projects\", encodeClaudeCwd(cwd), `${sessionId}.jsonl`);\n if (!existsSync(path)) return false;\n\n const lines = readFileSync(path, \"utf8\")\n .split(\"\\n\")\n .filter((l) => l.trim());\n\n // Walk backwards to find the latest assistant turn that actually contains\n // the poisoned shape: max_tokens plus a thinking/redacted_thinking block.\n // A resume failure can append a fresh user entry after the poisoned turn;\n // trimming the last user would remove only the retry and leave the bad\n // assistant in place.\n let poisonedAssistantIdx = -1;\n for (let i = lines.length - 1; i >= 0; i--) {\n const entry = parseClaudeRolloutLine(lines[i]);\n if (entry && isPoisonedAssistantEntry(entry)) {\n poisonedAssistantIdx = i;\n break;\n }\n }\n\n if (poisonedAssistantIdx < 0) {\n logger.warn({ sessionId, path }, \"repairPoisonedRollout: no poisoned assistant entry found\");\n return false;\n }\n\n let turnStartIdx = -1;\n for (let i = poisonedAssistantIdx - 1; i >= 0; i--) {\n const entry = parseClaudeRolloutLine(lines[i]);\n if (entry?.type === \"user\") {\n turnStartIdx = i;\n break;\n }\n }\n\n if (turnStartIdx < 0) {\n logger.warn(\n { sessionId, path, poisonedAssistantIdx },\n \"repairPoisonedRollout: poisoned assistant has no preceding user entry\",\n );\n return false;\n }\n\n const kept = lines.slice(0, turnStartIdx);\n writeFileSync(path, kept.length ? `${kept.join(\"\\n\")}\\n` : \"\");\n logger.warn(\n {\n sessionId,\n path,\n poisonedAssistantIdx,\n turnStartIdx,\n droppedEntries: lines.length - turnStartIdx,\n keptEntries: kept.length,\n },\n \"repairPoisonedRollout: truncated turn removed from rollout JSONL\",\n );\n return true;\n } catch (err) {\n logger.warn({ err, sessionId, cwd }, \"repairPoisonedRollout: I/O error\");\n return false;\n }\n}\n\ntype ClaudeRolloutEntry = {\n type?: string;\n message?: {\n stop_reason?: string | null;\n content?: unknown;\n };\n};\n\nfunction parseClaudeRolloutLine(line: string): ClaudeRolloutEntry | null {\n try {\n return JSON.parse(line) as ClaudeRolloutEntry;\n } catch {\n return null;\n }\n}\n\nfunction isPoisonedAssistantEntry(entry: ClaudeRolloutEntry): boolean {\n if (entry.type !== \"assistant\") return false;\n if (entry.message?.stop_reason !== \"max_tokens\") return false;\n const content = entry.message.content;\n if (!Array.isArray(content)) return false;\n return content.some((block) => {\n if (!block || typeof block !== \"object\") return false;\n const type = (block as { type?: unknown }).type;\n return type === \"thinking\" || type === \"redacted_thinking\";\n });\n}\n"
6
- ],
7
- "mappings": "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",
8
- "debugId": "1ECE58AFF7B086BE64756E2164756E21",
9
- "names": []
10
- }