@shivaedev/agent-claude-code 0.2.0 → 0.3.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.
Files changed (148) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +71 -1
  3. package/dist/SessionConnection.d.ts +10 -0
  4. package/dist/SessionConnection.d.ts.map +1 -0
  5. package/dist/SessionConnection.js +2 -0
  6. package/dist/SessionConnection.js.map +1 -0
  7. package/dist/adapters/ClaudeProvider.d.ts +15 -0
  8. package/dist/adapters/ClaudeProvider.d.ts.map +1 -0
  9. package/dist/adapters/ClaudeProvider.js +6 -0
  10. package/dist/adapters/ClaudeProvider.js.map +1 -0
  11. package/dist/adapters/InputQueue.d.ts +11 -0
  12. package/dist/adapters/InputQueue.d.ts.map +1 -0
  13. package/dist/adapters/InputQueue.js +84 -0
  14. package/dist/adapters/InputQueue.js.map +1 -0
  15. package/dist/adapters/blocks.d.ts +7 -0
  16. package/dist/adapters/blocks.d.ts.map +1 -0
  17. package/dist/adapters/blocks.js +57 -0
  18. package/dist/adapters/blocks.js.map +1 -0
  19. package/dist/adapters/consumeSession.d.ts +8 -0
  20. package/dist/adapters/consumeSession.d.ts.map +1 -0
  21. package/dist/adapters/consumeSession.js +26 -0
  22. package/dist/adapters/consumeSession.js.map +1 -0
  23. package/dist/adapters/loadClaudeSdk.d.ts +4 -0
  24. package/dist/adapters/loadClaudeSdk.d.ts.map +1 -0
  25. package/dist/adapters/loadClaudeSdk.js +7 -0
  26. package/dist/adapters/loadClaudeSdk.js.map +1 -0
  27. package/dist/adapters/mirrorKeys.d.ts +10 -0
  28. package/dist/adapters/mirrorKeys.d.ts.map +1 -0
  29. package/dist/adapters/mirrorKeys.js +26 -0
  30. package/dist/adapters/mirrorKeys.js.map +1 -0
  31. package/dist/adapters/openSession.d.ts +23 -0
  32. package/dist/adapters/openSession.d.ts.map +1 -0
  33. package/dist/adapters/openSession.js +78 -0
  34. package/dist/adapters/openSession.js.map +1 -0
  35. package/dist/adapters/openSessionMapping.d.ts +9 -0
  36. package/dist/adapters/openSessionMapping.d.ts.map +1 -0
  37. package/dist/adapters/openSessionMapping.js +65 -0
  38. package/dist/adapters/openSessionMapping.js.map +1 -0
  39. package/dist/adapters/openSubsessions.d.ts +10 -0
  40. package/dist/adapters/openSubsessions.d.ts.map +1 -0
  41. package/dist/adapters/openSubsessions.js +45 -0
  42. package/dist/adapters/openSubsessions.js.map +1 -0
  43. package/dist/adapters/openTurnUsage.d.ts +9 -0
  44. package/dist/adapters/openTurnUsage.d.ts.map +1 -0
  45. package/dist/adapters/openTurnUsage.js +40 -0
  46. package/dist/adapters/openTurnUsage.js.map +1 -0
  47. package/dist/adapters/openWorkflowNodes.d.ts +13 -0
  48. package/dist/adapters/openWorkflowNodes.d.ts.map +1 -0
  49. package/dist/adapters/openWorkflowNodes.js +60 -0
  50. package/dist/adapters/openWorkflowNodes.js.map +1 -0
  51. package/dist/adapters/rateLimitEvent.d.ts +7 -0
  52. package/dist/adapters/rateLimitEvent.d.ts.map +1 -0
  53. package/dist/adapters/rateLimitEvent.js +27 -0
  54. package/dist/adapters/rateLimitEvent.js.map +1 -0
  55. package/dist/adapters/rawPayload.d.ts +5 -0
  56. package/dist/adapters/rawPayload.d.ts.map +1 -0
  57. package/dist/adapters/rawPayload.js +12 -0
  58. package/dist/adapters/rawPayload.js.map +1 -0
  59. package/dist/adapters/repairSession.d.ts +5 -0
  60. package/dist/adapters/repairSession.d.ts.map +1 -0
  61. package/dist/adapters/repairSession.js +17 -0
  62. package/dist/adapters/repairSession.js.map +1 -0
  63. package/dist/adapters/sessionLanes.d.ts +22 -0
  64. package/dist/adapters/sessionLanes.d.ts.map +1 -0
  65. package/dist/adapters/sessionLanes.js +100 -0
  66. package/dist/adapters/sessionLanes.js.map +1 -0
  67. package/dist/adapters/sessionOptions.d.ts +6 -0
  68. package/dist/adapters/sessionOptions.d.ts.map +1 -0
  69. package/dist/adapters/sessionOptions.js +30 -0
  70. package/dist/adapters/sessionOptions.js.map +1 -0
  71. package/dist/adapters/spilledPreview.d.ts +5 -0
  72. package/dist/adapters/spilledPreview.d.ts.map +1 -0
  73. package/dist/adapters/spilledPreview.js +22 -0
  74. package/dist/adapters/spilledPreview.js.map +1 -0
  75. package/dist/adapters/subsessionEndings.d.ts +23 -0
  76. package/dist/adapters/subsessionEndings.d.ts.map +1 -0
  77. package/dist/adapters/subsessionEndings.js +54 -0
  78. package/dist/adapters/subsessionEndings.js.map +1 -0
  79. package/dist/adapters/systemEvents.d.ts +9 -0
  80. package/dist/adapters/systemEvents.d.ts.map +1 -0
  81. package/dist/adapters/systemEvents.js +39 -0
  82. package/dist/adapters/systemEvents.js.map +1 -0
  83. package/dist/adapters/toolIdentity.d.ts +5 -0
  84. package/dist/adapters/toolIdentity.d.ts.map +1 -0
  85. package/dist/adapters/toolIdentity.js +10 -0
  86. package/dist/adapters/toolIdentity.js.map +1 -0
  87. package/dist/adapters/toolServer.d.ts +4 -0
  88. package/dist/adapters/toolServer.d.ts.map +1 -0
  89. package/dist/adapters/toolServer.js +30 -0
  90. package/dist/adapters/toolServer.js.map +1 -0
  91. package/dist/adapters/transcriptEvents.d.ts +5 -0
  92. package/dist/adapters/transcriptEvents.d.ts.map +1 -0
  93. package/dist/adapters/transcriptEvents.js +18 -0
  94. package/dist/adapters/transcriptEvents.js.map +1 -0
  95. package/dist/adapters/workflowAdoption.d.ts +13 -0
  96. package/dist/adapters/workflowAdoption.d.ts.map +1 -0
  97. package/dist/adapters/workflowAdoption.js +48 -0
  98. package/dist/adapters/workflowAdoption.js.map +1 -0
  99. package/dist/adapters/workflowIdentity.d.ts +22 -0
  100. package/dist/adapters/workflowIdentity.d.ts.map +1 -0
  101. package/dist/adapters/workflowIdentity.js +50 -0
  102. package/dist/adapters/workflowIdentity.js.map +1 -0
  103. package/dist/claudeCode.d.ts +6 -1
  104. package/dist/claudeCode.d.ts.map +1 -1
  105. package/dist/claudeCode.js +101 -1
  106. package/dist/claudeCode.js.map +1 -1
  107. package/dist/options.d.ts +17 -0
  108. package/dist/options.d.ts.map +1 -0
  109. package/dist/options.js +2 -0
  110. package/dist/options.js.map +1 -0
  111. package/dist/protocol.d.ts +7 -0
  112. package/dist/protocol.d.ts.map +1 -0
  113. package/dist/protocol.js +2 -0
  114. package/dist/protocol.js.map +1 -0
  115. package/dist/runState.d.ts +584 -0
  116. package/dist/runState.d.ts.map +1 -0
  117. package/dist/runState.js +52 -0
  118. package/dist/runState.js.map +1 -0
  119. package/package.json +32 -4
  120. package/src/SessionConnection.ts +10 -0
  121. package/src/adapters/ClaudeProvider.ts +16 -0
  122. package/src/adapters/InputQueue.ts +89 -0
  123. package/src/adapters/blocks.ts +63 -0
  124. package/src/adapters/consumeSession.ts +35 -0
  125. package/src/adapters/loadClaudeSdk.ts +6 -0
  126. package/src/adapters/mirrorKeys.ts +33 -0
  127. package/src/adapters/openSession.ts +102 -0
  128. package/src/adapters/openSessionMapping.ts +84 -0
  129. package/src/adapters/openSubsessions.ts +70 -0
  130. package/src/adapters/openTurnUsage.ts +52 -0
  131. package/src/adapters/openWorkflowNodes.ts +73 -0
  132. package/src/adapters/rateLimitEvent.ts +45 -0
  133. package/src/adapters/rawPayload.ts +13 -0
  134. package/src/adapters/repairSession.ts +20 -0
  135. package/src/adapters/sessionLanes.ts +127 -0
  136. package/src/adapters/sessionOptions.ts +42 -0
  137. package/src/adapters/spilledPreview.ts +24 -0
  138. package/src/adapters/subsessionEndings.ts +80 -0
  139. package/src/adapters/systemEvents.ts +60 -0
  140. package/src/adapters/toolIdentity.ts +12 -0
  141. package/src/adapters/toolServer.ts +31 -0
  142. package/src/adapters/transcriptEvents.ts +21 -0
  143. package/src/adapters/workflowAdoption.ts +58 -0
  144. package/src/adapters/workflowIdentity.ts +75 -0
  145. package/src/claudeCode.ts +117 -1
  146. package/src/options.ts +19 -0
  147. package/src/protocol.ts +9 -0
  148. package/src/runState.ts +70 -0
@@ -0,0 +1,73 @@
1
+ import type { RunEvent } from "@shivaedev/agent/events.ts";
2
+ import { type WorkflowAgentRef, workflowRunRef } from "#adapters/mirrorKeys.ts";
3
+ import { claudeRaw } from "#adapters/rawPayload.ts";
4
+ import type { WorkflowIdentities, WorkflowIdentity } from "#adapters/workflowIdentity.ts";
5
+ import type { Origin } from "#protocol.ts";
6
+
7
+ interface NodeState {
8
+ named: boolean;
9
+ settled: boolean;
10
+ }
11
+ interface WorkflowNodes {
12
+ readonly known: (agentId: string) => boolean;
13
+ readonly opened: (ref: WorkflowAgentRef) => readonly RunEvent[];
14
+ readonly originOf: (ref: WorkflowAgentRef) => Origin;
15
+ readonly settled: () => readonly RunEvent[];
16
+ }
17
+ function openedEvent(ref: WorkflowAgentRef, identity: WorkflowIdentity | undefined, spawnedBy: string): RunEvent {
18
+ return {
19
+ ...(identity?.label === undefined ? {} : { label: identity.label }),
20
+ childRef: ref.agentId,
21
+ raw: claudeRaw("workflow/agent", { ...ref, ...identity }),
22
+ spawnedBy,
23
+ type: "child.opened",
24
+ };
25
+ }
26
+ export function openWorkflowNodes(identities: WorkflowIdentities): WorkflowNodes {
27
+ const nodes = new Map<string, NodeState>();
28
+ function spawnerOf(ref: WorkflowAgentRef): string {
29
+ return identities.of(ref.agentId)?.spawnedBy ?? workflowRunRef(ref.runId);
30
+ }
31
+ function opened(ref: WorkflowAgentRef): readonly RunEvent[] {
32
+ const identity = identities.of(ref.agentId);
33
+ const state = nodes.get(ref.agentId);
34
+ if (state === undefined) {
35
+ nodes.set(ref.agentId, {
36
+ named: identity?.label !== undefined,
37
+ settled: false,
38
+ });
39
+ return [openedEvent(ref, identity, spawnerOf(ref))];
40
+ }
41
+ if (state.named || identity?.label === undefined) {
42
+ return [];
43
+ }
44
+ state.named = true;
45
+ return [openedEvent(ref, identity, spawnerOf(ref))];
46
+ }
47
+ function settled(): readonly RunEvent[] {
48
+ return [...nodes].flatMap(([agentId, state]) => {
49
+ const ending = identities.of(agentId)?.ended;
50
+ if (state.settled || ending === undefined) {
51
+ return [];
52
+ }
53
+ state.settled = true;
54
+ return [
55
+ {
56
+ childRef: agentId,
57
+ outcome: ending,
58
+ raw: claudeRaw("workflow/agent-ended", {
59
+ agentId,
60
+ ...identities.of(agentId),
61
+ }),
62
+ type: "child.ended",
63
+ } satisfies RunEvent,
64
+ ];
65
+ });
66
+ }
67
+ return {
68
+ known: (agentId) => nodes.has(agentId),
69
+ opened,
70
+ originOf: (ref) => ({ node: ref.agentId, spawnedBy: spawnerOf(ref) }),
71
+ settled,
72
+ };
73
+ }
@@ -0,0 +1,45 @@
1
+ import type { SDKMessage } from "@anthropic-ai/claude-agent-sdk";
2
+ import type { RunEvent } from "@shivaedev/agent/events.ts";
3
+ import type { RawPayload } from "#protocol.ts";
4
+
5
+ type Info = Extract<
6
+ SDKMessage,
7
+ {
8
+ type: "rate_limit_event";
9
+ }
10
+ >["rate_limit_info"];
11
+ const STATUS = { allowed: "allowed", "allowed_warning": "warning", rejected: "rejected" } as const;
12
+ const WINDOWS = {
13
+ "five_hour": { durationMinutes: 300 },
14
+ overage: {},
15
+ "seven_day": { durationMinutes: 10_080 },
16
+ "seven_day_opus": { durationMinutes: 10_080, model: "opus" },
17
+ "seven_day_overage_included": { durationMinutes: 10_080 },
18
+ "seven_day_sonnet": { durationMinutes: 10_080, model: "sonnet" },
19
+ };
20
+ export function rateLimitEvent(
21
+ raw: RawPayload,
22
+ message: Extract<
23
+ SDKMessage,
24
+ {
25
+ type: "rate_limit_event";
26
+ }
27
+ >,
28
+ ): RunEvent {
29
+ const info: Info = message.rate_limit_info;
30
+ return {
31
+ raw,
32
+ status: STATUS[info.status],
33
+ type: "rate.limit",
34
+ windows:
35
+ info.utilization === undefined
36
+ ? []
37
+ : [
38
+ {
39
+ ...(info.rateLimitType === undefined ? {} : WINDOWS[info.rateLimitType]),
40
+ ...(info.resetsAt === undefined ? {} : { resetsAt: info.resetsAt * 1000 }),
41
+ usedPercent: Math.round(info.utilization * 100),
42
+ },
43
+ ],
44
+ };
45
+ }
@@ -0,0 +1,13 @@
1
+ import type { SDKMessage } from "@anthropic-ai/claude-agent-sdk";
2
+ import type { RawPayload } from "#protocol.ts";
3
+ export function claudeRaw(kind: string, payload: unknown): RawPayload {
4
+ return {
5
+ kind,
6
+ payload: JSON.stringify(payload),
7
+ source: "claude",
8
+ };
9
+ }
10
+ export function rawOf(message: SDKMessage): RawPayload {
11
+ const subtype = "subtype" in message && typeof message.subtype === "string" ? `/${message.subtype}` : "";
12
+ return claudeRaw(`${message.type}${subtype}`, message);
13
+ }
@@ -0,0 +1,20 @@
1
+ import { Effect } from "effect";
2
+ import { BackendUnavailable } from "@shivaedev/agent/failures.ts";
3
+ import { ClaudeProvider } from "#adapters/ClaudeProvider.ts";
4
+ import type { Repair } from "#adapters/workflowAdoption.ts";
5
+
6
+ export const repairSession = Effect.fn("ClaudeCode.repairSession")(function* (sessionId: string, cwd: string) {
7
+ const peer = yield* ClaudeProvider;
8
+ return yield* Effect.tryPromise({
9
+ catch: (error) => new BackendUnavailable({ backend: "claude-code", message: String(error) }),
10
+ try: async () => {
11
+ const ids = await peer.listSubagents(sessionId, { dir: cwd });
12
+ return Promise.all(ids.map(async (agentId) => ({ agentId, messages: await peer.getSubagentMessages(sessionId, agentId, { dir: cwd }) })));
13
+ },
14
+ }).pipe(
15
+ Effect.match({
16
+ onFailure: (error): Repair => ({ agents: [], failure: error.message }),
17
+ onSuccess: (agents): Repair => ({ agents, failure: undefined }),
18
+ }),
19
+ );
20
+ });
@@ -0,0 +1,127 @@
1
+ import type { SDKMessage, SessionKey, SessionStoreEntry } from "@anthropic-ai/claude-agent-sdk";
2
+ import type { RunEvent } from "@shivaedev/agent/events.ts";
3
+ import { agentFileRef, subagentRef, workflowAgentRef } from "#adapters/mirrorKeys.ts";
4
+ import { openSessionMapping } from "#adapters/openSessionMapping.ts";
5
+ import { openWorkflowNodes } from "#adapters/openWorkflowNodes.ts";
6
+ import { claudeRaw } from "#adapters/rawPayload.ts";
7
+ import { transcriptEvents } from "#adapters/transcriptEvents.ts";
8
+ import { adoptedEvents, censusGap, type Repair } from "#adapters/workflowAdoption.ts";
9
+ import { openWorkflowIdentities } from "#adapters/workflowIdentity.ts";
10
+ import type { Origin } from "#protocol.ts";
11
+ export interface MirrorWrite {
12
+ readonly entries: readonly SessionStoreEntry[];
13
+ readonly key: SessionKey;
14
+ }
15
+ export type Delivery =
16
+ | {
17
+ readonly kind: "frame";
18
+ readonly message: SDKMessage;
19
+ readonly inputId?: string;
20
+ }
21
+ | {
22
+ readonly kind: "mirror";
23
+ readonly write: MirrorWrite;
24
+ }
25
+ | {
26
+ readonly kind: "repair";
27
+ readonly repair: Repair;
28
+ };
29
+ export function openSessionLanes() {
30
+ const mapping = openSessionMapping();
31
+ const identities = openWorkflowIdentities();
32
+ const nodes = openWorkflowNodes(identities);
33
+ const seen = new Set<string>();
34
+ const mirroredOrigins = new Map<string, Origin>();
35
+ function recorded(id: string) {
36
+ return mirroredOrigins.has(id) || nodes.known(id) || mapping.spawnerOf(id) !== undefined;
37
+ }
38
+ function unique(uuid: string | undefined, events: readonly RunEvent[]): readonly RunEvent[] {
39
+ if (uuid === undefined) {
40
+ return events;
41
+ }
42
+ return events.filter((event, index) => {
43
+ if (event.type === "child.opened" || event.type === "child.ended") {
44
+ return true;
45
+ }
46
+ const key = `${uuid}:${event.type}:${index}`;
47
+ if (seen.has(key)) {
48
+ return false;
49
+ }
50
+ seen.add(key);
51
+ return true;
52
+ });
53
+ }
54
+ function mirrored({ entries, key }: MirrorWrite): readonly RunEvent[] {
55
+ const ref = workflowAgentRef(key);
56
+ const node = subagentRef(key);
57
+ let origin: Origin | undefined;
58
+ if (ref !== undefined) {
59
+ origin = nodes.originOf(ref);
60
+ } else if (node !== undefined) {
61
+ origin = { node, spawnedBy: mapping.spawnerOf(node) ?? agentFileRef(node) };
62
+ }
63
+ const opened: RunEvent[] =
64
+ node !== undefined && ref === undefined && !recorded(node)
65
+ ? [{ childRef: node, spawnedBy: origin?.spawnedBy ?? agentFileRef(node), type: "child.opened" }]
66
+ : [];
67
+ if (node !== undefined && origin !== undefined) {
68
+ mirroredOrigins.set(node, origin);
69
+ }
70
+ return [
71
+ ...opened,
72
+ ...(ref === undefined ? [] : nodes.opened(ref)),
73
+ ...entries.flatMap((entry) => unique(entry.uuid, transcriptEvents(entry, origin))),
74
+ ...nodes.settled(),
75
+ ];
76
+ }
77
+ function repaired(repair: Repair): readonly RunEvent[] {
78
+ return [
79
+ ...repair.agents.flatMap((agent): readonly RunEvent[] => {
80
+ if (!recorded(agent.agentId)) {
81
+ const missing = agent.messages.flatMap((message) =>
82
+ unique(
83
+ message.uuid,
84
+ transcriptEvents(message, { node: agent.agentId, spawnedBy: message.parent_tool_use_id ?? agentFileRef(agent.agentId) }),
85
+ ),
86
+ );
87
+ return adoptedEvents(agent, missing);
88
+ }
89
+ const origin = mirroredOrigins.get(agent.agentId) ?? {
90
+ node: agent.agentId,
91
+ spawnedBy: mapping.spawnerOf(agent.agentId) ?? agentFileRef(agent.agentId),
92
+ };
93
+ const missing = agent.messages.flatMap((message) => unique(message.uuid, transcriptEvents(message, origin)));
94
+ if (missing.length === 0) {
95
+ return [];
96
+ }
97
+ const gap: RunEvent = {
98
+ detail: "Stored transcript UUIDs reveal output absent from the stream and mirror; recovered output follows",
99
+ kind: "missing-output",
100
+ origin,
101
+ raw: claudeRaw("subagent/unrecorded-lines", { agentId: agent.agentId, uuids: agent.messages.map((message) => message.uuid) }),
102
+ type: "gap",
103
+ };
104
+ return [gap, ...missing];
105
+ }),
106
+ ...(repair.failure === undefined ? [] : [censusGap(repair.failure)]),
107
+ ];
108
+ }
109
+ return {
110
+ deliver: (delivery: Delivery): readonly RunEvent[] => {
111
+ if (delivery.kind === "mirror") {
112
+ return mirrored(delivery.write);
113
+ }
114
+ if (delivery.kind === "repair") {
115
+ return repaired(delivery.repair);
116
+ }
117
+ const message = delivery.message;
118
+ if (message.type === "system" && message.subtype === "task_progress") {
119
+ identities.observe(message);
120
+ return nodes.settled();
121
+ }
122
+ return unique(message.uuid, mapping.frame(message)).map((event) =>
123
+ event.type === "message" && delivery.inputId !== undefined ? { ...event, inputId: delivery.inputId } : event,
124
+ );
125
+ },
126
+ };
127
+ }
@@ -0,0 +1,42 @@
1
+ import { resolve } from "node:path";
2
+ import type { EffortLevel, Options, SessionStore } from "@anthropic-ai/claude-agent-sdk";
3
+ import type { StartRequest } from "@shivaedev/agent/run.ts";
4
+ import type { makeToolServer } from "#adapters/toolServer.ts";
5
+ import { TOOL_SERVER_NAME } from "#adapters/toolServer.ts";
6
+ import type { ClaudeOptions } from "#options.ts";
7
+
8
+ function harness(config: ClaudeOptions): Partial<Options> {
9
+ if (config.systemPrompt !== undefined) {
10
+ return { settingSources: [], strictMcpConfig: true, systemPrompt: config.systemPrompt, tools: [] };
11
+ }
12
+ return config.skills === undefined ? {} : { plugins: [{ path: config.skills, type: "local" }] };
13
+ }
14
+
15
+ export function sessionOptions(
16
+ request: StartRequest,
17
+ config: ClaudeOptions,
18
+ store: SessionStore,
19
+ effort: EffortLevel | undefined,
20
+ resume: string | undefined,
21
+ server: Awaited<ReturnType<typeof makeToolServer>> | undefined,
22
+ ): Options {
23
+ return {
24
+ cwd: resolve(request.cwd),
25
+ forwardSubagentText: true,
26
+ permissionMode: "auto",
27
+ sessionStore: store,
28
+ sessionStoreFlush: "eager",
29
+ settings: { env: { "CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS": "1" } },
30
+ ...(request.model === undefined ? {} : { model: request.model }),
31
+ ...(effort === undefined ? {} : { effort }),
32
+ ...(resume === undefined ? {} : { resume }),
33
+ ...(config.executable === undefined ? {} : { pathToClaudeCodeExecutable: config.executable }),
34
+ ...harness(config),
35
+ ...(server === undefined
36
+ ? {}
37
+ : {
38
+ allowedTools: (config.tools ?? []).map((tool) => `mcp__${TOOL_SERVER_NAME}__${tool.name}`),
39
+ mcpServers: { [TOOL_SERVER_NAME]: { instance: server, name: TOOL_SERVER_NAME, type: "sdk" } },
40
+ }),
41
+ };
42
+ }
@@ -0,0 +1,24 @@
1
+ import type { SDKMessage } from "@anthropic-ai/claude-agent-sdk";
2
+ import type { RunEvent } from "@shivaedev/agent/events.ts";
3
+ import { isRecord } from "#adapters/blocks.ts";
4
+ import type { Origin, RawPayload } from "#protocol.ts";
5
+ // Oversized tool results carry only a preview inline and name the stored full output in `persistedOutputPath`.
6
+ export function spilledPreview(raw: RawPayload, message: SDKMessage, origin: Origin | undefined): readonly RunEvent[] {
7
+ if (!("tool_use_result" in message && isRecord(message.tool_use_result))) {
8
+ return [];
9
+ }
10
+ const spill = message.tool_use_result;
11
+ if (typeof spill.persistedOutputPath !== "string") {
12
+ return [];
13
+ }
14
+ const size = typeof spill.persistedOutputSize === "number" ? ` (${spill.persistedOutputSize} bytes)` : "";
15
+ return [
16
+ {
17
+ detail: `full tool output spilled to ${spill.persistedOutputPath}${size}`,
18
+ kind: "spilled-preview",
19
+ ...(origin === undefined ? {} : { origin }),
20
+ raw,
21
+ type: "gap",
22
+ },
23
+ ];
24
+ }
@@ -0,0 +1,80 @@
1
+ import type { SDKMessage } from "@anthropic-ai/claude-agent-sdk";
2
+ import type { RunEvent } from "@shivaedev/agent/events.ts";
3
+ import { isRecord, textOf } from "#adapters/blocks.ts";
4
+ import type { ChildOutcome, RawPayload } from "#protocol.ts";
5
+
6
+ type SystemMessage = Extract<
7
+ SDKMessage,
8
+ {
9
+ type: "system";
10
+ }
11
+ >;
12
+ type TaskUpdated = Extract<
13
+ SystemMessage,
14
+ {
15
+ subtype: "task_updated";
16
+ }
17
+ >;
18
+ type TaskNotification = Extract<
19
+ SystemMessage,
20
+ {
21
+ subtype: "task_notification";
22
+ }
23
+ >;
24
+ export interface Ending {
25
+ readonly childRef: string;
26
+ readonly outcome: ChildOutcome;
27
+ readonly summary: string | undefined;
28
+ }
29
+ export function endedEvent(raw: RawPayload, ending: Ending): RunEvent {
30
+ return {
31
+ childRef: ending.childRef,
32
+ outcome: ending.outcome,
33
+ raw,
34
+ ...(ending.summary === undefined ? {} : { summary: ending.summary }),
35
+ type: "child.ended",
36
+ };
37
+ }
38
+ const LIVE = new Set(["paused", "pending", "running"]);
39
+ function patchedOutcome(status: string): ChildOutcome {
40
+ if (status === "completed") {
41
+ return "completed";
42
+ }
43
+ if (status === "failed") {
44
+ return "failed";
45
+ }
46
+ return status === "killed" ? "interrupted" : "unknown";
47
+ }
48
+ // Claude emits no task-completed frame; the first patch outside the live states is terminal.
49
+ export function updatedEnding(message: TaskUpdated): Ending | undefined {
50
+ const status = message.patch.status;
51
+ if (status === undefined || LIVE.has(status)) {
52
+ return undefined;
53
+ }
54
+ return {
55
+ childRef: message.task_id,
56
+ outcome: patchedOutcome(status),
57
+ summary: undefined,
58
+ };
59
+ }
60
+ export function notifiedEnding(message: TaskNotification): Ending {
61
+ return {
62
+ childRef: message.task_id,
63
+ outcome: message.status === "stopped" ? "interrupted" : message.status,
64
+ summary: message.summary,
65
+ };
66
+ }
67
+ export function reportedEnding(message: SDKMessage): Ending | undefined {
68
+ if (!("tool_use_result" in message && isRecord(message.tool_use_result))) {
69
+ return undefined;
70
+ }
71
+ const output = message.tool_use_result;
72
+ if (output.status !== "completed" || typeof output.agentId !== "string") {
73
+ return undefined;
74
+ }
75
+ return {
76
+ childRef: output.agentId,
77
+ outcome: "completed",
78
+ summary: Array.isArray(output.content) ? textOf(output.content) : undefined,
79
+ };
80
+ }
@@ -0,0 +1,60 @@
1
+ import type { SDKMessage } from "@anthropic-ai/claude-agent-sdk";
2
+ import type { RunEvent } from "@shivaedev/agent/events.ts";
3
+ import type { RawPayload } from "#protocol.ts";
4
+
5
+ type StateMessage = Extract<
6
+ SDKMessage,
7
+ {
8
+ subtype: "session_state_changed";
9
+ }
10
+ >;
11
+ type TasksMessage = Extract<
12
+ SDKMessage,
13
+ {
14
+ subtype: "background_tasks_changed";
15
+ }
16
+ >;
17
+ const STATE: Record<StateMessage["state"], "running" | "awaiting-input"> = {
18
+ idle: "awaiting-input",
19
+ "requires_action": "awaiting-input",
20
+ running: "running",
21
+ };
22
+ function stateEvent(raw: RawPayload, message: StateMessage): RunEvent {
23
+ return {
24
+ raw,
25
+ state: STATE[message.state],
26
+ type: "execution",
27
+ };
28
+ }
29
+ function backgroundEvent(raw: RawPayload, message: TasksMessage): RunEvent {
30
+ return {
31
+ raw,
32
+ tasks: message.tasks.map((task) => ({
33
+ description: task.description,
34
+ id: task.task_id,
35
+ kind: task.task_type,
36
+ })),
37
+ type: "session.background",
38
+ };
39
+ }
40
+ type SystemMessage = Extract<
41
+ SDKMessage,
42
+ {
43
+ type: "system";
44
+ }
45
+ >;
46
+ export function systemEvents(raw: RawPayload, message: SystemMessage): readonly RunEvent[] | undefined {
47
+ if (message.subtype === "init") {
48
+ return [{ raw, reference: message.session_id, type: "session.opened" }];
49
+ }
50
+ if (message.subtype === "session_state_changed") {
51
+ return [stateEvent(raw, message)];
52
+ }
53
+ if (message.subtype === "background_tasks_changed") {
54
+ return [backgroundEvent(raw, message)];
55
+ }
56
+ if (message.subtype === "thinking_tokens" || message.subtype === "task_progress") {
57
+ return [];
58
+ }
59
+ return undefined;
60
+ }
@@ -0,0 +1,12 @@
1
+ import { TOOL_SERVER_NAME } from "#adapters/toolServer.ts";
2
+ export function toolIdentity(wireName: string): {
3
+ name: string;
4
+ providerName?: string;
5
+ } {
6
+ const match = /^mcp__(?<server>.+?)__(?<tool>.+)$/u.exec(wireName);
7
+ const server = match?.groups?.server;
8
+ const tool = match?.groups?.tool;
9
+ return server === undefined || tool === undefined
10
+ ? { name: wireName }
11
+ : { name: server === TOOL_SERVER_NAME ? tool : `${server}: ${tool}`, providerName: wireName };
12
+ }
@@ -0,0 +1,31 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { Effect } from "effect";
3
+ import type { ClaudeTool } from "#options.ts";
4
+ export const TOOL_SERVER_NAME = "platform";
5
+ export async function makeToolServer(tools: readonly ClaudeTool[]) {
6
+ const [{ McpServer }, { CallToolRequestSchema, ListToolsRequestSchema }] = await Promise.all([
7
+ import("@modelcontextprotocol/sdk/server/mcp.js"),
8
+ import("@modelcontextprotocol/sdk/types.js"),
9
+ ]);
10
+ const namespace = randomUUID();
11
+ const byName = new Map(tools.map((tool) => [tool.name, tool]));
12
+ const server = new McpServer({ name: TOOL_SERVER_NAME, version: "0.0.0" });
13
+ server.server.registerCapabilities({ tools: { listChanged: true } });
14
+ server.server.setRequestHandler(ListToolsRequestSchema, () => ({
15
+ tools: tools.map((tool) => ({
16
+ "_meta": { "anthropic/alwaysLoad": true },
17
+ description: tool.description,
18
+ inputSchema: { ...tool.inputSchema, type: "object" as const },
19
+ name: tool.name,
20
+ })),
21
+ }));
22
+ server.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
23
+ const tool = byName.get(request.params.name);
24
+ const outcome =
25
+ tool === undefined
26
+ ? { ok: false, text: `No tool named ${request.params.name}` }
27
+ : await Effect.runPromise(tool.call(request.params.arguments, `${namespace}:${String(extra.requestId)}`), { signal: extra.signal });
28
+ return { content: [{ text: outcome.text, type: "text" }], isError: !outcome.ok };
29
+ });
30
+ return server;
31
+ }
@@ -0,0 +1,21 @@
1
+ import type { SessionMessage, SessionStoreEntry } from "@anthropic-ai/claude-agent-sdk";
2
+ import type { RunEvent } from "@shivaedev/agent/events.ts";
3
+ import { blockEvent, contentBlocks } from "#adapters/blocks.ts";
4
+ import { claudeRaw } from "#adapters/rawPayload.ts";
5
+ import type { Origin } from "#protocol.ts";
6
+
7
+ const ROLES = { assistant: "agent", user: "user" } as const;
8
+ function roleOf(type: string): "agent" | "user" | undefined {
9
+ return type === "assistant" || type === "user" ? ROLES[type] : undefined;
10
+ }
11
+ export function transcriptEvents(entry: SessionStoreEntry | SessionMessage, origin: Origin | undefined): readonly RunEvent[] {
12
+ const raw = claudeRaw(`transcript/${entry.type}`, entry);
13
+ const role = roleOf(entry.type);
14
+ if (role === undefined) {
15
+ return [{ raw, type: "raw" }];
16
+ }
17
+ const events = contentBlocks(entry)
18
+ .map((block) => blockEvent(raw, role, block, origin))
19
+ .filter((event): event is RunEvent => event !== undefined);
20
+ return events.length === 0 ? [{ raw, type: "raw" }] : events;
21
+ }
@@ -0,0 +1,58 @@
1
+ import type { SessionMessage } from "@anthropic-ai/claude-agent-sdk";
2
+ import type { RunEvent } from "@shivaedev/agent/events.ts";
3
+ import { agentFileRef } from "#adapters/mirrorKeys.ts";
4
+ import { claudeRaw } from "#adapters/rawPayload.ts";
5
+ import type { Origin } from "#protocol.ts";
6
+ export interface AdoptedAgent {
7
+ readonly agentId: string;
8
+ readonly messages: readonly SessionMessage[];
9
+ }
10
+ export interface Repair {
11
+ readonly agents: readonly AdoptedAgent[];
12
+ readonly failure: string | undefined;
13
+ }
14
+ function admissionOrigin(agent: AdoptedAgent): Origin {
15
+ return {
16
+ node: agent.agentId,
17
+ spawnedBy: agent.messages[0]?.parent_tool_use_id ?? agentFileRef(agent.agentId),
18
+ };
19
+ }
20
+ function adoptedGap(agent: AdoptedAgent, origin: Origin): RunEvent {
21
+ return {
22
+ detail: `this node was read back from its stored transcript after the live record missed it, ${agent.messages.length} messages already written`,
23
+ kind: "adopted-late",
24
+ origin,
25
+ raw: claudeRaw("workflow/adopted-late", { agentId: agent.agentId }),
26
+ type: "gap",
27
+ };
28
+ }
29
+ function adoptedEnding(agent: AdoptedAgent): RunEvent {
30
+ return {
31
+ childRef: agent.agentId,
32
+ outcome: "unknown",
33
+ raw: claudeRaw("workflow/adopted-late", { agentId: agent.agentId }),
34
+ type: "child.ended",
35
+ };
36
+ }
37
+ export function censusGap(failure: string): RunEvent {
38
+ return {
39
+ detail: `subagent backfill source unreachable; this session's workflow census could not be checked: ${failure}`,
40
+ kind: "unknown",
41
+ raw: claudeRaw("workflow/census-unreadable", { failure }),
42
+ type: "gap",
43
+ };
44
+ }
45
+ export function adoptedEvents(agent: AdoptedAgent, recovered: readonly RunEvent[]): readonly RunEvent[] {
46
+ const origin = admissionOrigin(agent);
47
+ return [
48
+ {
49
+ childRef: agent.agentId,
50
+ raw: claudeRaw("workflow/adopted-late", { agentId: agent.agentId }),
51
+ spawnedBy: origin.spawnedBy,
52
+ type: "child.opened",
53
+ },
54
+ adoptedGap(agent, origin),
55
+ ...recovered,
56
+ adoptedEnding(agent),
57
+ ];
58
+ }