@herdctl/core 0.0.1 → 0.0.2
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/dist/config/__tests__/agent.test.js +31 -13
- package/dist/config/__tests__/agent.test.js.map +1 -1
- package/dist/config/__tests__/merge.test.js +9 -2
- package/dist/config/__tests__/merge.test.js.map +1 -1
- package/dist/config/__tests__/schema.test.js +350 -1
- package/dist/config/__tests__/schema.test.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +3 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/schema.d.ts +828 -24
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +118 -6
- package/dist/config/schema.js.map +1 -1
- package/dist/fleet-manager/__tests__/coverage.test.js +11 -332
- package/dist/fleet-manager/__tests__/coverage.test.js.map +1 -1
- package/dist/fleet-manager/__tests__/errors.test.js +1 -49
- package/dist/fleet-manager/__tests__/errors.test.js.map +1 -1
- package/dist/fleet-manager/__tests__/integration.test.js +109 -0
- package/dist/fleet-manager/__tests__/integration.test.js.map +1 -1
- package/dist/fleet-manager/__tests__/reload.test.js +1 -1
- package/dist/fleet-manager/__tests__/reload.test.js.map +1 -1
- package/dist/fleet-manager/config-reload.d.ts +164 -0
- package/dist/fleet-manager/config-reload.d.ts.map +1 -0
- package/dist/fleet-manager/config-reload.js +445 -0
- package/dist/fleet-manager/config-reload.js.map +1 -0
- package/dist/fleet-manager/context.d.ts +76 -0
- package/dist/fleet-manager/context.d.ts.map +1 -0
- package/dist/fleet-manager/context.js +11 -0
- package/dist/fleet-manager/context.js.map +1 -0
- package/dist/fleet-manager/errors.d.ts +0 -25
- package/dist/fleet-manager/errors.d.ts.map +1 -1
- package/dist/fleet-manager/errors.js +0 -38
- package/dist/fleet-manager/errors.js.map +1 -1
- package/dist/fleet-manager/event-emitters.d.ts +123 -0
- package/dist/fleet-manager/event-emitters.d.ts.map +1 -0
- package/dist/fleet-manager/event-emitters.js +136 -0
- package/dist/fleet-manager/event-emitters.js.map +1 -0
- package/dist/fleet-manager/event-types.d.ts +0 -15
- package/dist/fleet-manager/event-types.d.ts.map +1 -1
- package/dist/fleet-manager/fleet-manager.d.ts +40 -653
- package/dist/fleet-manager/fleet-manager.d.ts.map +1 -1
- package/dist/fleet-manager/fleet-manager.js +95 -1720
- package/dist/fleet-manager/fleet-manager.js.map +1 -1
- package/dist/fleet-manager/index.d.ts +13 -2
- package/dist/fleet-manager/index.d.ts.map +1 -1
- package/dist/fleet-manager/index.js +19 -6
- package/dist/fleet-manager/index.js.map +1 -1
- package/dist/fleet-manager/job-control.d.ts +64 -0
- package/dist/fleet-manager/job-control.d.ts.map +1 -0
- package/dist/fleet-manager/job-control.js +296 -0
- package/dist/fleet-manager/job-control.js.map +1 -0
- package/dist/fleet-manager/log-streaming.d.ts +171 -0
- package/dist/fleet-manager/log-streaming.d.ts.map +1 -0
- package/dist/fleet-manager/log-streaming.js +503 -0
- package/dist/fleet-manager/log-streaming.js.map +1 -0
- package/dist/fleet-manager/schedule-executor.d.ts +63 -0
- package/dist/fleet-manager/schedule-executor.d.ts.map +1 -0
- package/dist/fleet-manager/schedule-executor.js +209 -0
- package/dist/fleet-manager/schedule-executor.js.map +1 -0
- package/dist/fleet-manager/schedule-management.d.ts +71 -0
- package/dist/fleet-manager/schedule-management.d.ts.map +1 -0
- package/dist/fleet-manager/schedule-management.js +171 -0
- package/dist/fleet-manager/schedule-management.js.map +1 -0
- package/dist/fleet-manager/status-queries.d.ts +105 -0
- package/dist/fleet-manager/status-queries.d.ts.map +1 -0
- package/dist/fleet-manager/status-queries.js +247 -0
- package/dist/fleet-manager/status-queries.js.map +1 -0
- package/dist/fleet-manager/types.d.ts +0 -39
- package/dist/fleet-manager/types.d.ts.map +1 -1
- package/dist/runner/__tests__/job-executor.test.js +206 -1
- package/dist/runner/__tests__/job-executor.test.js.map +1 -1
- package/dist/runner/job-executor.d.ts +9 -0
- package/dist/runner/job-executor.d.ts.map +1 -1
- package/dist/runner/job-executor.js +78 -4
- package/dist/runner/job-executor.js.map +1 -1
- package/dist/runner/types.d.ts +2 -0
- package/dist/runner/types.d.ts.map +1 -1
- package/dist/scheduler/__tests__/cron.test.d.ts +2 -0
- package/dist/scheduler/__tests__/cron.test.d.ts.map +1 -0
- package/dist/scheduler/__tests__/cron.test.js +867 -0
- package/dist/scheduler/__tests__/cron.test.js.map +1 -0
- package/dist/scheduler/__tests__/scheduler.test.js +164 -5
- package/dist/scheduler/__tests__/scheduler.test.js.map +1 -1
- package/dist/scheduler/cron.d.ts +126 -0
- package/dist/scheduler/cron.d.ts.map +1 -0
- package/dist/scheduler/cron.js +390 -0
- package/dist/scheduler/cron.js.map +1 -0
- package/dist/scheduler/errors.d.ts +81 -1
- package/dist/scheduler/errors.d.ts.map +1 -1
- package/dist/scheduler/errors.js +81 -6
- package/dist/scheduler/errors.js.map +1 -1
- package/dist/scheduler/index.d.ts +1 -0
- package/dist/scheduler/index.d.ts.map +1 -1
- package/dist/scheduler/index.js +2 -0
- package/dist/scheduler/index.js.map +1 -1
- package/dist/scheduler/schedule-runner.d.ts +2 -2
- package/dist/scheduler/schedule-runner.d.ts.map +1 -1
- package/dist/scheduler/schedule-runner.js +20 -8
- package/dist/scheduler/schedule-runner.js.map +1 -1
- package/dist/scheduler/scheduler.d.ts +4 -4
- package/dist/scheduler/scheduler.d.ts.map +1 -1
- package/dist/scheduler/scheduler.js +86 -20
- package/dist/scheduler/scheduler.js.map +1 -1
- package/dist/scheduler/types.d.ts +1 -1
- package/dist/scheduler/types.d.ts.map +1 -1
- package/dist/state/schemas/job-metadata.d.ts +2 -2
- package/package.json +33 -8
- package/.turbo/turbo-build.log +0 -4
- package/.turbo/turbo-test.log +0 -219
- package/.turbo/turbo-typecheck.log +0 -4
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/coverage-final.json +0 -51
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -251
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -210
- package/coverage/src/config/index.html +0 -191
- package/coverage/src/config/index.ts.html +0 -442
- package/coverage/src/config/interpolate.ts.html +0 -652
- package/coverage/src/config/loader.ts.html +0 -1501
- package/coverage/src/config/merge.ts.html +0 -823
- package/coverage/src/config/parser.ts.html +0 -1213
- package/coverage/src/config/schema.ts.html +0 -1123
- package/coverage/src/fleet-manager/errors.ts.html +0 -2326
- package/coverage/src/fleet-manager/event-types.ts.html +0 -1219
- package/coverage/src/fleet-manager/fleet-manager.ts.html +0 -7030
- package/coverage/src/fleet-manager/index.html +0 -206
- package/coverage/src/fleet-manager/index.ts.html +0 -469
- package/coverage/src/fleet-manager/job-manager.ts.html +0 -2074
- package/coverage/src/fleet-manager/job-queue.ts.html +0 -2479
- package/coverage/src/fleet-manager/types.ts.html +0 -2602
- package/coverage/src/index.html +0 -116
- package/coverage/src/index.ts.html +0 -181
- package/coverage/src/runner/errors.ts.html +0 -1006
- package/coverage/src/runner/index.html +0 -191
- package/coverage/src/runner/index.ts.html +0 -256
- package/coverage/src/runner/job-executor.ts.html +0 -1429
- package/coverage/src/runner/message-processor.ts.html +0 -1150
- package/coverage/src/runner/sdk-adapter.ts.html +0 -658
- package/coverage/src/runner/types.ts.html +0 -559
- package/coverage/src/scheduler/errors.ts.html +0 -388
- package/coverage/src/scheduler/index.html +0 -206
- package/coverage/src/scheduler/index.ts.html +0 -244
- package/coverage/src/scheduler/interval.ts.html +0 -652
- package/coverage/src/scheduler/schedule-runner.ts.html +0 -1411
- package/coverage/src/scheduler/schedule-state.ts.html +0 -718
- package/coverage/src/scheduler/scheduler.ts.html +0 -1795
- package/coverage/src/scheduler/types.ts.html +0 -733
- package/coverage/src/state/directory.ts.html +0 -736
- package/coverage/src/state/errors.ts.html +0 -376
- package/coverage/src/state/fleet-state.ts.html +0 -937
- package/coverage/src/state/index.html +0 -221
- package/coverage/src/state/index.ts.html +0 -322
- package/coverage/src/state/job-metadata.ts.html +0 -1420
- package/coverage/src/state/job-output.ts.html +0 -1033
- package/coverage/src/state/schemas/fleet-state.ts.html +0 -445
- package/coverage/src/state/schemas/index.html +0 -176
- package/coverage/src/state/schemas/index.ts.html +0 -286
- package/coverage/src/state/schemas/job-metadata.ts.html +0 -628
- package/coverage/src/state/schemas/job-output.ts.html +0 -616
- package/coverage/src/state/schemas/session-info.ts.html +0 -361
- package/coverage/src/state/session.ts.html +0 -844
- package/coverage/src/state/types.ts.html +0 -262
- package/coverage/src/state/utils/atomic.ts.html +0 -748
- package/coverage/src/state/utils/index.html +0 -146
- package/coverage/src/state/utils/index.ts.html +0 -103
- package/coverage/src/state/utils/reads.ts.html +0 -1621
- package/coverage/src/work-sources/adapters/github.ts.html +0 -3583
- package/coverage/src/work-sources/adapters/index.html +0 -131
- package/coverage/src/work-sources/adapters/index.ts.html +0 -277
- package/coverage/src/work-sources/errors.ts.html +0 -298
- package/coverage/src/work-sources/index.html +0 -176
- package/coverage/src/work-sources/index.ts.html +0 -529
- package/coverage/src/work-sources/manager.ts.html +0 -1324
- package/coverage/src/work-sources/registry.ts.html +0 -619
- package/coverage/src/work-sources/types.ts.html +0 -568
- package/dist/fleet-manager/__tests__/event-helpers.test.d.ts +0 -7
- package/dist/fleet-manager/__tests__/event-helpers.test.d.ts.map +0 -1
- package/dist/fleet-manager/__tests__/event-helpers.test.js +0 -368
- package/dist/fleet-manager/__tests__/event-helpers.test.js.map +0 -1
- package/src/config/__tests__/agent.test.ts +0 -864
- package/src/config/__tests__/interpolate.test.ts +0 -644
- package/src/config/__tests__/loader.test.ts +0 -784
- package/src/config/__tests__/merge.test.ts +0 -751
- package/src/config/__tests__/parser.test.ts +0 -533
- package/src/config/__tests__/schema.test.ts +0 -873
- package/src/config/index.ts +0 -119
- package/src/config/interpolate.ts +0 -189
- package/src/config/loader.ts +0 -472
- package/src/config/merge.ts +0 -246
- package/src/config/parser.ts +0 -376
- package/src/config/schema.ts +0 -346
- package/src/fleet-manager/__tests__/coverage.test.ts +0 -2869
- package/src/fleet-manager/__tests__/errors.test.ts +0 -660
- package/src/fleet-manager/__tests__/event-helpers.test.ts +0 -448
- package/src/fleet-manager/__tests__/integration.test.ts +0 -1209
- package/src/fleet-manager/__tests__/job-control.test.ts +0 -283
- package/src/fleet-manager/__tests__/job-manager.test.ts +0 -869
- package/src/fleet-manager/__tests__/job-queue.test.ts +0 -401
- package/src/fleet-manager/__tests__/reload.test.ts +0 -751
- package/src/fleet-manager/__tests__/status-queries.test.ts +0 -595
- package/src/fleet-manager/__tests__/trigger.test.ts +0 -601
- package/src/fleet-manager/errors.ts +0 -747
- package/src/fleet-manager/event-types.ts +0 -378
- package/src/fleet-manager/fleet-manager.ts +0 -2315
- package/src/fleet-manager/index.ts +0 -128
- package/src/fleet-manager/job-manager.ts +0 -663
- package/src/fleet-manager/job-queue.ts +0 -798
- package/src/fleet-manager/types.ts +0 -839
- package/src/index.ts +0 -32
- package/src/runner/__tests__/errors.test.ts +0 -382
- package/src/runner/__tests__/job-executor.test.ts +0 -1708
- package/src/runner/__tests__/message-processor.test.ts +0 -960
- package/src/runner/__tests__/sdk-adapter.test.ts +0 -626
- package/src/runner/errors.ts +0 -307
- package/src/runner/index.ts +0 -57
- package/src/runner/job-executor.ts +0 -448
- package/src/runner/message-processor.ts +0 -355
- package/src/runner/sdk-adapter.ts +0 -191
- package/src/runner/types.ts +0 -158
- package/src/scheduler/__tests__/errors.test.ts +0 -159
- package/src/scheduler/__tests__/interval.test.ts +0 -515
- package/src/scheduler/__tests__/schedule-runner.test.ts +0 -798
- package/src/scheduler/__tests__/schedule-state.test.ts +0 -671
- package/src/scheduler/__tests__/scheduler.test.ts +0 -1280
- package/src/scheduler/errors.ts +0 -101
- package/src/scheduler/index.ts +0 -53
- package/src/scheduler/interval.ts +0 -189
- package/src/scheduler/schedule-runner.ts +0 -442
- package/src/scheduler/schedule-state.ts +0 -211
- package/src/scheduler/scheduler.ts +0 -570
- package/src/scheduler/types.ts +0 -216
- package/src/state/__tests__/directory.test.ts +0 -595
- package/src/state/__tests__/fleet-state.test.ts +0 -868
- package/src/state/__tests__/job-metadata-schema.test.ts +0 -414
- package/src/state/__tests__/job-metadata.test.ts +0 -831
- package/src/state/__tests__/job-output.test.ts +0 -856
- package/src/state/__tests__/session-schema.test.ts +0 -378
- package/src/state/__tests__/session.test.ts +0 -604
- package/src/state/directory.ts +0 -217
- package/src/state/errors.ts +0 -97
- package/src/state/fleet-state.ts +0 -284
- package/src/state/index.ts +0 -79
- package/src/state/job-metadata.ts +0 -445
- package/src/state/job-output.ts +0 -316
- package/src/state/schemas/__tests__/job-output.test.ts +0 -338
- package/src/state/schemas/fleet-state.ts +0 -120
- package/src/state/schemas/index.ts +0 -67
- package/src/state/schemas/job-metadata.ts +0 -181
- package/src/state/schemas/job-output.ts +0 -177
- package/src/state/schemas/session-info.ts +0 -92
- package/src/state/session.ts +0 -253
- package/src/state/types.ts +0 -59
- package/src/state/utils/__tests__/atomic.test.ts +0 -723
- package/src/state/utils/__tests__/reads.test.ts +0 -1071
- package/src/state/utils/atomic.ts +0 -221
- package/src/state/utils/index.ts +0 -6
- package/src/state/utils/reads.ts +0 -512
- package/src/work-sources/__tests__/github.test.ts +0 -1800
- package/src/work-sources/__tests__/manager.test.ts +0 -529
- package/src/work-sources/__tests__/registry.test.ts +0 -477
- package/src/work-sources/__tests__/types.test.ts +0 -479
- package/src/work-sources/adapters/github.ts +0 -1166
- package/src/work-sources/adapters/index.ts +0 -64
- package/src/work-sources/errors.ts +0 -71
- package/src/work-sources/index.ts +0 -148
- package/src/work-sources/manager.ts +0 -413
- package/src/work-sources/registry.ts +0 -178
- package/src/work-sources/types.ts +0 -161
- package/tsconfig.json +0 -9
- package/vitest.config.ts +0 -19
|
@@ -1,355 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Message processor for transforming SDK messages to job output format
|
|
3
|
-
*
|
|
4
|
-
* Handles all Claude SDK message types and converts them to the format
|
|
5
|
-
* expected by the job output logging system. Includes robust handling
|
|
6
|
-
* of malformed or unexpected SDK responses.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { SDKMessage, ProcessedMessage } from "./types.js";
|
|
10
|
-
import type { JobOutputInput } from "../state/index.js";
|
|
11
|
-
|
|
12
|
-
// =============================================================================
|
|
13
|
-
// Validation Helpers
|
|
14
|
-
// =============================================================================
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Safely extract a string value from an unknown field
|
|
18
|
-
*/
|
|
19
|
-
function safeString(value: unknown): string | undefined {
|
|
20
|
-
if (typeof value === "string") {
|
|
21
|
-
return value;
|
|
22
|
-
}
|
|
23
|
-
if (value === null || value === undefined) {
|
|
24
|
-
return undefined;
|
|
25
|
-
}
|
|
26
|
-
// Convert other types to string for safety
|
|
27
|
-
try {
|
|
28
|
-
return String(value);
|
|
29
|
-
} catch {
|
|
30
|
-
return undefined;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Safely extract a boolean value from an unknown field
|
|
36
|
-
*/
|
|
37
|
-
function safeBoolean(value: unknown): boolean | undefined {
|
|
38
|
-
if (typeof value === "boolean") {
|
|
39
|
-
return value;
|
|
40
|
-
}
|
|
41
|
-
return undefined;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Check if a value is a valid SDK message type
|
|
46
|
-
*/
|
|
47
|
-
function isValidMessageType(
|
|
48
|
-
type: unknown
|
|
49
|
-
): type is "system" | "assistant" | "tool_use" | "tool_result" | "error" {
|
|
50
|
-
return (
|
|
51
|
-
type === "system" ||
|
|
52
|
-
type === "assistant" ||
|
|
53
|
-
type === "tool_use" ||
|
|
54
|
-
type === "tool_result" ||
|
|
55
|
-
type === "error"
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// =============================================================================
|
|
60
|
-
// Message Type Handlers
|
|
61
|
-
// =============================================================================
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Process a system message from the SDK
|
|
65
|
-
*
|
|
66
|
-
* Session ID is specifically extracted from messages with subtype "init",
|
|
67
|
-
* as this is when the Claude SDK provides the session identifier.
|
|
68
|
-
*/
|
|
69
|
-
function processSystemMessage(message: SDKMessage): ProcessedMessage {
|
|
70
|
-
const output: JobOutputInput = {
|
|
71
|
-
type: "system",
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
if (message.content) {
|
|
75
|
-
output.content = message.content;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (message.subtype) {
|
|
79
|
-
output.subtype = message.subtype;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Extract session ID specifically from init messages
|
|
83
|
-
// The Claude SDK provides session_id in the system message with subtype "init"
|
|
84
|
-
const sessionId =
|
|
85
|
-
message.subtype === "init" ? message.session_id : undefined;
|
|
86
|
-
|
|
87
|
-
return {
|
|
88
|
-
output,
|
|
89
|
-
sessionId,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Process an assistant message from the SDK
|
|
95
|
-
*/
|
|
96
|
-
function processAssistantMessage(message: SDKMessage): ProcessedMessage {
|
|
97
|
-
const output: JobOutputInput = {
|
|
98
|
-
type: "assistant",
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
if (message.content !== undefined) {
|
|
102
|
-
output.content = message.content;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (message.partial !== undefined) {
|
|
106
|
-
output.partial = message.partial as boolean;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Handle usage statistics
|
|
110
|
-
if (message.usage) {
|
|
111
|
-
const usage = message.usage as {
|
|
112
|
-
input_tokens?: number;
|
|
113
|
-
output_tokens?: number;
|
|
114
|
-
};
|
|
115
|
-
output.usage = {};
|
|
116
|
-
if (usage.input_tokens !== undefined) {
|
|
117
|
-
output.usage.input_tokens = usage.input_tokens;
|
|
118
|
-
}
|
|
119
|
-
if (usage.output_tokens !== undefined) {
|
|
120
|
-
output.usage.output_tokens = usage.output_tokens;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return { output };
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Process a tool use message from the SDK
|
|
129
|
-
*/
|
|
130
|
-
function processToolUseMessage(message: SDKMessage): ProcessedMessage {
|
|
131
|
-
// Tool name is required - try multiple possible field names
|
|
132
|
-
const toolName = message.tool_name ?? message.name ?? "unknown";
|
|
133
|
-
|
|
134
|
-
const output: JobOutputInput = {
|
|
135
|
-
type: "tool_use",
|
|
136
|
-
tool_name: toolName as string,
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
if (message.tool_use_id) {
|
|
140
|
-
output.tool_use_id = message.tool_use_id;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (message.input !== undefined) {
|
|
144
|
-
output.input = message.input;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return { output };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Process a tool result message from the SDK
|
|
152
|
-
*/
|
|
153
|
-
function processToolResultMessage(message: SDKMessage): ProcessedMessage {
|
|
154
|
-
const output: JobOutputInput = {
|
|
155
|
-
type: "tool_result",
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
if (message.tool_use_id) {
|
|
159
|
-
output.tool_use_id = message.tool_use_id;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (message.result !== undefined) {
|
|
163
|
-
output.result = message.result;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (message.success !== undefined) {
|
|
167
|
-
output.success = message.success as boolean;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (message.error !== undefined) {
|
|
171
|
-
output.error = message.error as string;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return { output };
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Process an error message from the SDK
|
|
179
|
-
*/
|
|
180
|
-
function processErrorMessage(message: SDKMessage): ProcessedMessage {
|
|
181
|
-
const output: JobOutputInput = {
|
|
182
|
-
type: "error",
|
|
183
|
-
message: (message.message as string) ?? "Unknown error",
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
if (message.code) {
|
|
187
|
-
output.code = message.code;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (message.stack) {
|
|
191
|
-
output.stack = message.stack as string;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return {
|
|
195
|
-
output,
|
|
196
|
-
isFinal: true,
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// =============================================================================
|
|
201
|
-
// Main Processing Function
|
|
202
|
-
// =============================================================================
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Process an SDK message into job output format
|
|
206
|
-
*
|
|
207
|
-
* Takes a message from the Claude Agent SDK and transforms it into
|
|
208
|
-
* the format expected by the job output logging system. This function
|
|
209
|
-
* handles malformed responses gracefully without crashing.
|
|
210
|
-
*
|
|
211
|
-
* @param message - The SDK message to process
|
|
212
|
-
* @returns Processed message with output and optional metadata
|
|
213
|
-
*
|
|
214
|
-
* @example
|
|
215
|
-
* ```typescript
|
|
216
|
-
* const sdkMessage = { type: "assistant", content: "Hello!" };
|
|
217
|
-
* const { output, sessionId } = processSDKMessage(sdkMessage);
|
|
218
|
-
* await appendJobOutput(jobsDir, jobId, output);
|
|
219
|
-
* ```
|
|
220
|
-
*/
|
|
221
|
-
export function processSDKMessage(message: SDKMessage): ProcessedMessage {
|
|
222
|
-
// Handle null/undefined messages - log as system warning, not error
|
|
223
|
-
// to avoid terminating execution due to malformed SDK responses
|
|
224
|
-
if (message === null || message === undefined) {
|
|
225
|
-
return {
|
|
226
|
-
output: {
|
|
227
|
-
type: "system",
|
|
228
|
-
content: "Received null or undefined SDK message",
|
|
229
|
-
subtype: "malformed_message",
|
|
230
|
-
},
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Handle non-object messages - log as system warning
|
|
235
|
-
if (typeof message !== "object") {
|
|
236
|
-
return {
|
|
237
|
-
output: {
|
|
238
|
-
type: "system",
|
|
239
|
-
content: `Expected object message, received ${typeof message}`,
|
|
240
|
-
subtype: "malformed_message",
|
|
241
|
-
},
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Validate message type
|
|
246
|
-
const messageType = message.type;
|
|
247
|
-
|
|
248
|
-
if (!isValidMessageType(messageType)) {
|
|
249
|
-
// Handle unknown or missing message types gracefully
|
|
250
|
-
const unknownType = safeString(messageType) ?? "undefined";
|
|
251
|
-
return {
|
|
252
|
-
output: {
|
|
253
|
-
type: "system",
|
|
254
|
-
content: `Unknown message type: ${unknownType}`,
|
|
255
|
-
subtype: "unknown_type",
|
|
256
|
-
},
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Process known message types
|
|
261
|
-
switch (messageType) {
|
|
262
|
-
case "system":
|
|
263
|
-
return processSystemMessage(message);
|
|
264
|
-
|
|
265
|
-
case "assistant":
|
|
266
|
-
return processAssistantMessage(message);
|
|
267
|
-
|
|
268
|
-
case "tool_use":
|
|
269
|
-
return processToolUseMessage(message);
|
|
270
|
-
|
|
271
|
-
case "tool_result":
|
|
272
|
-
return processToolResultMessage(message);
|
|
273
|
-
|
|
274
|
-
case "error":
|
|
275
|
-
return processErrorMessage(message);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Check if a message indicates the end of execution
|
|
281
|
-
*
|
|
282
|
-
* @param message - The SDK message to check
|
|
283
|
-
* @returns true if this is a terminal message
|
|
284
|
-
*/
|
|
285
|
-
export function isTerminalMessage(message: SDKMessage): boolean {
|
|
286
|
-
// Handle null/undefined/non-object messages - these are not terminal
|
|
287
|
-
if (message === null || message === undefined || typeof message !== "object") {
|
|
288
|
-
return false;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Error messages are terminal
|
|
292
|
-
if (message.type === "error") {
|
|
293
|
-
return true;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// System messages with certain subtypes indicate completion
|
|
297
|
-
if (message.type === "system") {
|
|
298
|
-
const subtype = message.subtype as string | undefined;
|
|
299
|
-
if (
|
|
300
|
-
subtype === "end" ||
|
|
301
|
-
subtype === "complete" ||
|
|
302
|
-
subtype === "session_end"
|
|
303
|
-
) {
|
|
304
|
-
return true;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return false;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/** Maximum summary length in characters */
|
|
312
|
-
const MAX_SUMMARY_LENGTH = 500;
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Truncate a string to maximum length, adding ellipsis if truncated
|
|
316
|
-
*/
|
|
317
|
-
function truncateSummary(text: string): string {
|
|
318
|
-
if (text.length <= MAX_SUMMARY_LENGTH) {
|
|
319
|
-
return text;
|
|
320
|
-
}
|
|
321
|
-
return text.slice(0, MAX_SUMMARY_LENGTH - 3) + "...";
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Extract the final summary from a message if present
|
|
326
|
-
*
|
|
327
|
-
* Looks for summaries in the following order:
|
|
328
|
-
* 1. Explicit `summary` field on the message (truncated to 500 chars)
|
|
329
|
-
* 2. Short assistant message content (≤500 chars, non-partial)
|
|
330
|
-
*
|
|
331
|
-
* @param message - The SDK message to extract summary from
|
|
332
|
-
* @returns Summary string if present, undefined otherwise
|
|
333
|
-
*/
|
|
334
|
-
export function extractSummary(message: SDKMessage): string | undefined {
|
|
335
|
-
// Handle null/undefined/non-object messages
|
|
336
|
-
if (message === null || message === undefined || typeof message !== "object") {
|
|
337
|
-
return undefined;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Check for explicit summary field (truncate if too long)
|
|
341
|
-
if (message.summary) {
|
|
342
|
-
const summaryStr = String(message.summary);
|
|
343
|
-
return truncateSummary(summaryStr);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// For assistant messages, use content as potential summary
|
|
347
|
-
if (message.type === "assistant" && message.content && !message.partial) {
|
|
348
|
-
// Only use if it looks like a conclusion (short enough)
|
|
349
|
-
if (message.content.length <= MAX_SUMMARY_LENGTH) {
|
|
350
|
-
return message.content;
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
return undefined;
|
|
355
|
-
}
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SDK Adapter for transforming agent configuration to SDK options
|
|
3
|
-
*
|
|
4
|
-
* Transforms ResolvedAgent configuration to the format expected by
|
|
5
|
-
* the Claude Agent SDK's query function.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { ResolvedAgent, McpServer } from "../config/index.js";
|
|
9
|
-
import type {
|
|
10
|
-
SDKQueryOptions,
|
|
11
|
-
SDKMcpServerConfig,
|
|
12
|
-
SDKSystemPrompt,
|
|
13
|
-
} from "./types.js";
|
|
14
|
-
|
|
15
|
-
// =============================================================================
|
|
16
|
-
// Constants
|
|
17
|
-
// =============================================================================
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Default permission mode when not specified in agent config
|
|
21
|
-
*/
|
|
22
|
-
const DEFAULT_PERMISSION_MODE = "acceptEdits" as const;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Default setting sources for SDK initialization
|
|
26
|
-
*/
|
|
27
|
-
const DEFAULT_SETTING_SOURCES = ["project", "local"] as const;
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Default preset when no system prompt is specified
|
|
31
|
-
*/
|
|
32
|
-
const DEFAULT_PRESET = "claude_code";
|
|
33
|
-
|
|
34
|
-
// =============================================================================
|
|
35
|
-
// MCP Server Transformation
|
|
36
|
-
// =============================================================================
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Transform a single MCP server configuration to SDK format
|
|
40
|
-
*
|
|
41
|
-
* @param server - MCP server configuration from agent config
|
|
42
|
-
* @returns SDK-formatted MCP server configuration
|
|
43
|
-
*/
|
|
44
|
-
export function transformMcpServer(server: McpServer): SDKMcpServerConfig {
|
|
45
|
-
const result: SDKMcpServerConfig = {};
|
|
46
|
-
|
|
47
|
-
// HTTP-based MCP server
|
|
48
|
-
if (server.url) {
|
|
49
|
-
result.type = "http";
|
|
50
|
-
result.url = server.url;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Process-based MCP server
|
|
54
|
-
if (server.command) {
|
|
55
|
-
result.command = server.command;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (server.args && server.args.length > 0) {
|
|
59
|
-
result.args = server.args;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (server.env && Object.keys(server.env).length > 0) {
|
|
63
|
-
result.env = server.env;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return result;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Transform MCP servers configuration to SDK format
|
|
71
|
-
*
|
|
72
|
-
* @param mcpServers - MCP servers configuration from agent config
|
|
73
|
-
* @returns SDK-formatted MCP servers map (empty object if no servers configured)
|
|
74
|
-
*/
|
|
75
|
-
export function transformMcpServers(
|
|
76
|
-
mcpServers: Record<string, McpServer> | undefined
|
|
77
|
-
): Record<string, SDKMcpServerConfig> {
|
|
78
|
-
const result: Record<string, SDKMcpServerConfig> = {};
|
|
79
|
-
|
|
80
|
-
if (!mcpServers) {
|
|
81
|
-
return result;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
for (const [name, server] of Object.entries(mcpServers)) {
|
|
85
|
-
result[name] = transformMcpServer(server);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return result;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// =============================================================================
|
|
92
|
-
// System Prompt Transformation
|
|
93
|
-
// =============================================================================
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Build the system prompt configuration for SDK
|
|
97
|
-
*
|
|
98
|
-
* @param agent - Resolved agent configuration
|
|
99
|
-
* @returns System prompt configuration for SDK
|
|
100
|
-
*/
|
|
101
|
-
export function buildSystemPrompt(agent: ResolvedAgent): SDKSystemPrompt {
|
|
102
|
-
// If agent has a custom system prompt, use it
|
|
103
|
-
if (agent.system_prompt) {
|
|
104
|
-
return {
|
|
105
|
-
type: "custom",
|
|
106
|
-
content: agent.system_prompt,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Default to claude_code preset
|
|
111
|
-
return {
|
|
112
|
-
type: "preset",
|
|
113
|
-
preset: DEFAULT_PRESET,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// =============================================================================
|
|
118
|
-
// Main Transformation Function
|
|
119
|
-
// =============================================================================
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Options for SDK transformation
|
|
123
|
-
*/
|
|
124
|
-
export interface ToSDKOptionsParams {
|
|
125
|
-
/** Session ID to resume */
|
|
126
|
-
resume?: string;
|
|
127
|
-
/** Whether to fork the session */
|
|
128
|
-
fork?: boolean;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Transform agent configuration to SDK query options
|
|
133
|
-
*
|
|
134
|
-
* This function converts a ResolvedAgent configuration to the format
|
|
135
|
-
* expected by the Claude Agent SDK's query function.
|
|
136
|
-
*
|
|
137
|
-
* @param agent - Fully resolved agent configuration
|
|
138
|
-
* @param options - Additional options for resume/fork
|
|
139
|
-
* @returns SDK query options ready for use with the SDK
|
|
140
|
-
*
|
|
141
|
-
* @example
|
|
142
|
-
* ```typescript
|
|
143
|
-
* const agent = await loadConfig().then(c => c.agents[0]);
|
|
144
|
-
* const sdkOptions = toSDKOptions(agent);
|
|
145
|
-
*
|
|
146
|
-
* for await (const message of query({ prompt, options: sdkOptions })) {
|
|
147
|
-
* console.log(message);
|
|
148
|
-
* }
|
|
149
|
-
* ```
|
|
150
|
-
*/
|
|
151
|
-
export function toSDKOptions(
|
|
152
|
-
agent: ResolvedAgent,
|
|
153
|
-
options: ToSDKOptionsParams = {}
|
|
154
|
-
): SDKQueryOptions {
|
|
155
|
-
const result: SDKQueryOptions = {};
|
|
156
|
-
|
|
157
|
-
// Permission mode (defaults to acceptEdits)
|
|
158
|
-
result.permissionMode =
|
|
159
|
-
agent.permissions?.mode ??
|
|
160
|
-
agent.permission_mode ??
|
|
161
|
-
DEFAULT_PERMISSION_MODE;
|
|
162
|
-
|
|
163
|
-
// Allowed and denied tools
|
|
164
|
-
if (agent.permissions?.allowed_tools?.length) {
|
|
165
|
-
result.allowedTools = agent.permissions.allowed_tools;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (agent.permissions?.denied_tools?.length) {
|
|
169
|
-
result.deniedTools = agent.permissions.denied_tools;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// System prompt
|
|
173
|
-
result.systemPrompt = buildSystemPrompt(agent);
|
|
174
|
-
|
|
175
|
-
// Setting sources for proper settings discovery
|
|
176
|
-
result.settingSources = [...DEFAULT_SETTING_SOURCES];
|
|
177
|
-
|
|
178
|
-
// MCP servers (always include, even if empty)
|
|
179
|
-
result.mcpServers = transformMcpServers(agent.mcp_servers);
|
|
180
|
-
|
|
181
|
-
// Session resume/fork
|
|
182
|
-
if (options.resume) {
|
|
183
|
-
result.resume = options.resume;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (options.fork) {
|
|
187
|
-
result.forkSession = true;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return result;
|
|
191
|
-
}
|
package/src/runner/types.ts
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Type definitions for the agent runner module
|
|
3
|
-
*
|
|
4
|
-
* Defines options, results, and SDK-related types for agent execution
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { ResolvedAgent } from "../config/index.js";
|
|
8
|
-
import type { TriggerType, JobOutputInput } from "../state/index.js";
|
|
9
|
-
|
|
10
|
-
// =============================================================================
|
|
11
|
-
// Runner Options Types
|
|
12
|
-
// =============================================================================
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Options for running an agent
|
|
16
|
-
*/
|
|
17
|
-
export interface RunnerOptions {
|
|
18
|
-
/** Fully resolved agent configuration */
|
|
19
|
-
agent: ResolvedAgent;
|
|
20
|
-
/** The prompt to send to the agent */
|
|
21
|
-
prompt: string;
|
|
22
|
-
/** Path to the .herdctl directory */
|
|
23
|
-
stateDir: string;
|
|
24
|
-
/** How this run was triggered */
|
|
25
|
-
triggerType?: TriggerType;
|
|
26
|
-
/** Schedule name (if triggered by schedule) */
|
|
27
|
-
schedule?: string;
|
|
28
|
-
/** Session ID to resume (mutually exclusive with fork) */
|
|
29
|
-
resume?: string;
|
|
30
|
-
/** Fork from this session ID */
|
|
31
|
-
fork?: string;
|
|
32
|
-
/** Parent job ID when forking (used with fork option) */
|
|
33
|
-
forkedFrom?: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* SDK message types (as received from Claude Agent SDK)
|
|
38
|
-
*/
|
|
39
|
-
export interface SDKMessage {
|
|
40
|
-
type: "system" | "assistant" | "tool_use" | "tool_result" | "error";
|
|
41
|
-
subtype?: string;
|
|
42
|
-
content?: string;
|
|
43
|
-
session_id?: string;
|
|
44
|
-
name?: string;
|
|
45
|
-
input?: unknown;
|
|
46
|
-
tool_use_id?: string;
|
|
47
|
-
message?: string;
|
|
48
|
-
code?: string;
|
|
49
|
-
// Allow additional SDK-specific fields
|
|
50
|
-
[key: string]: unknown;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Callback for receiving messages during execution
|
|
55
|
-
*/
|
|
56
|
-
export type MessageCallback = (message: SDKMessage) => void | Promise<void>;
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Extended options including callbacks
|
|
60
|
-
*/
|
|
61
|
-
export interface RunnerOptionsWithCallbacks extends RunnerOptions {
|
|
62
|
-
/** Called for each message from the SDK */
|
|
63
|
-
onMessage?: MessageCallback;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// =============================================================================
|
|
67
|
-
// Runner Result Types
|
|
68
|
-
// =============================================================================
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Detailed error information for failed runs
|
|
72
|
-
*/
|
|
73
|
-
export interface RunnerErrorDetails {
|
|
74
|
-
/** The error message */
|
|
75
|
-
message: string;
|
|
76
|
-
/** Error code if available (e.g., ETIMEDOUT, ECONNREFUSED) */
|
|
77
|
-
code?: string;
|
|
78
|
-
/** The type of error (for categorization) */
|
|
79
|
-
type?: "initialization" | "streaming" | "malformed_response" | "unknown";
|
|
80
|
-
/** Whether this error is potentially recoverable (e.g., rate limit, network) */
|
|
81
|
-
recoverable?: boolean;
|
|
82
|
-
/** Number of messages received before error (for streaming errors) */
|
|
83
|
-
messagesReceived?: number;
|
|
84
|
-
/** Stack trace if available */
|
|
85
|
-
stack?: string;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Result of running an agent
|
|
90
|
-
*/
|
|
91
|
-
export interface RunnerResult {
|
|
92
|
-
/** Whether the run completed successfully */
|
|
93
|
-
success: boolean;
|
|
94
|
-
/** The job ID for this run */
|
|
95
|
-
jobId: string;
|
|
96
|
-
/** The session ID (for resume/fork) */
|
|
97
|
-
sessionId?: string;
|
|
98
|
-
/** Brief summary of what was accomplished */
|
|
99
|
-
summary?: string;
|
|
100
|
-
/** Error if the run failed */
|
|
101
|
-
error?: Error;
|
|
102
|
-
/** Detailed error information for programmatic access */
|
|
103
|
-
errorDetails?: RunnerErrorDetails;
|
|
104
|
-
/** Duration in seconds */
|
|
105
|
-
durationSeconds?: number;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// =============================================================================
|
|
109
|
-
// SDK Option Types
|
|
110
|
-
// =============================================================================
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* MCP server configuration for SDK
|
|
114
|
-
*/
|
|
115
|
-
export interface SDKMcpServerConfig {
|
|
116
|
-
type?: "http";
|
|
117
|
-
url?: string;
|
|
118
|
-
command?: string;
|
|
119
|
-
args?: string[];
|
|
120
|
-
env?: Record<string, string>;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* System prompt configuration for SDK
|
|
125
|
-
*/
|
|
126
|
-
export type SDKSystemPrompt =
|
|
127
|
-
| { type: "preset"; preset: string }
|
|
128
|
-
| { type: "custom"; content: string };
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* SDK query options (matching Claude Agent SDK types)
|
|
132
|
-
*/
|
|
133
|
-
export interface SDKQueryOptions {
|
|
134
|
-
allowedTools?: string[];
|
|
135
|
-
deniedTools?: string[];
|
|
136
|
-
permissionMode?: "default" | "acceptEdits" | "bypassPermissions" | "plan";
|
|
137
|
-
systemPrompt?: SDKSystemPrompt;
|
|
138
|
-
settingSources?: string[];
|
|
139
|
-
mcpServers?: Record<string, SDKMcpServerConfig>;
|
|
140
|
-
resume?: string;
|
|
141
|
-
forkSession?: boolean;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// =============================================================================
|
|
145
|
-
// Message Processing Types
|
|
146
|
-
// =============================================================================
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Result of processing an SDK message
|
|
150
|
-
*/
|
|
151
|
-
export interface ProcessedMessage {
|
|
152
|
-
/** The message transformed for job output */
|
|
153
|
-
output: JobOutputInput;
|
|
154
|
-
/** Session ID if this was an init message */
|
|
155
|
-
sessionId?: string;
|
|
156
|
-
/** Whether this is the final message */
|
|
157
|
-
isFinal?: boolean;
|
|
158
|
-
}
|