@goondan/openharness-base 0.0.1-alpha1

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 (83) hide show
  1. package/README.md +11 -0
  2. package/dist/extensions/compaction.d.ts +12 -0
  3. package/dist/extensions/compaction.d.ts.map +1 -0
  4. package/dist/extensions/compaction.js +162 -0
  5. package/dist/extensions/compaction.js.map +1 -0
  6. package/dist/extensions/context-message.d.ts +9 -0
  7. package/dist/extensions/context-message.d.ts.map +1 -0
  8. package/dist/extensions/context-message.js +451 -0
  9. package/dist/extensions/context-message.js.map +1 -0
  10. package/dist/extensions/index.d.ts +13 -0
  11. package/dist/extensions/index.d.ts.map +1 -0
  12. package/dist/extensions/index.js +7 -0
  13. package/dist/extensions/index.js.map +1 -0
  14. package/dist/extensions/logging.d.ts +11 -0
  15. package/dist/extensions/logging.d.ts.map +1 -0
  16. package/dist/extensions/logging.js +140 -0
  17. package/dist/extensions/logging.js.map +1 -0
  18. package/dist/extensions/message-integrity.d.ts +8 -0
  19. package/dist/extensions/message-integrity.d.ts.map +1 -0
  20. package/dist/extensions/message-integrity.js +88 -0
  21. package/dist/extensions/message-integrity.js.map +1 -0
  22. package/dist/extensions/message-window.d.ts +7 -0
  23. package/dist/extensions/message-window.d.ts.map +1 -0
  24. package/dist/extensions/message-window.js +60 -0
  25. package/dist/extensions/message-window.js.map +1 -0
  26. package/dist/extensions/required-tools-guard.d.ts +9 -0
  27. package/dist/extensions/required-tools-guard.d.ts.map +1 -0
  28. package/dist/extensions/required-tools-guard.js +74 -0
  29. package/dist/extensions/required-tools-guard.js.map +1 -0
  30. package/dist/extensions/tool-search.d.ts +10 -0
  31. package/dist/extensions/tool-search.d.ts.map +1 -0
  32. package/dist/extensions/tool-search.js +198 -0
  33. package/dist/extensions/tool-search.js.map +1 -0
  34. package/dist/harness.yaml +503 -0
  35. package/dist/index.d.ts +5 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +5 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/manifests/base.d.ts +8 -0
  40. package/dist/manifests/base.d.ts.map +1 -0
  41. package/dist/manifests/base.js +352 -0
  42. package/dist/manifests/base.js.map +1 -0
  43. package/dist/manifests/index.d.ts +3 -0
  44. package/dist/manifests/index.d.ts.map +1 -0
  45. package/dist/manifests/index.js +2 -0
  46. package/dist/manifests/index.js.map +1 -0
  47. package/dist/tools/bash.d.ts +8 -0
  48. package/dist/tools/bash.d.ts.map +1 -0
  49. package/dist/tools/bash.js +119 -0
  50. package/dist/tools/bash.js.map +1 -0
  51. package/dist/tools/file-system.d.ts +12 -0
  52. package/dist/tools/file-system.d.ts.map +1 -0
  53. package/dist/tools/file-system.js +117 -0
  54. package/dist/tools/file-system.js.map +1 -0
  55. package/dist/tools/http-fetch.d.ts +8 -0
  56. package/dist/tools/http-fetch.d.ts.map +1 -0
  57. package/dist/tools/http-fetch.js +149 -0
  58. package/dist/tools/http-fetch.js.map +1 -0
  59. package/dist/tools/index.d.ts +7 -0
  60. package/dist/tools/index.d.ts.map +1 -0
  61. package/dist/tools/index.js +7 -0
  62. package/dist/tools/index.js.map +1 -0
  63. package/dist/tools/json-query.d.ts +12 -0
  64. package/dist/tools/json-query.d.ts.map +1 -0
  65. package/dist/tools/json-query.js +176 -0
  66. package/dist/tools/json-query.js.map +1 -0
  67. package/dist/tools/text-transform.d.ts +16 -0
  68. package/dist/tools/text-transform.d.ts.map +1 -0
  69. package/dist/tools/text-transform.js +127 -0
  70. package/dist/tools/text-transform.js.map +1 -0
  71. package/dist/tools/wait.d.ts +6 -0
  72. package/dist/tools/wait.d.ts.map +1 -0
  73. package/dist/tools/wait.js +32 -0
  74. package/dist/tools/wait.js.map +1 -0
  75. package/dist/types.d.ts +4 -0
  76. package/dist/types.d.ts.map +1 -0
  77. package/dist/types.js +6 -0
  78. package/dist/types.js.map +1 -0
  79. package/dist/utils.d.ts +17 -0
  80. package/dist/utils.d.ts.map +1 -0
  81. package/dist/utils.js +155 -0
  82. package/dist/utils.js.map +1 -0
  83. package/package.json +45 -0
package/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # @goondan/openharness-base
2
+
3
+ OpenHarness에 바로 붙일 수 있는 reference tool, extension, manifest 번들을 제공합니다.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @goondan/openharness-base
9
+ ```
10
+
11
+ manifest 산출물과 기본 확장 정책은 [`packages/base/AGENTS.md`](./AGENTS.md)를 참고하세요.
@@ -0,0 +1,12 @@
1
+ import type { ExtensionApi } from '../types.js';
2
+ export interface CompactionExtensionConfig {
3
+ maxMessages?: number;
4
+ maxCharacters?: number;
5
+ retainLastMessages?: number;
6
+ mode?: 'remove' | 'truncate';
7
+ appendSummary?: boolean;
8
+ summaryPrefix?: string;
9
+ }
10
+ export declare function registerCompactionExtension(api: ExtensionApi, config?: CompactionExtensionConfig): void;
11
+ export declare function register(api: ExtensionApi, config?: CompactionExtensionConfig): void;
12
+ //# sourceMappingURL=compaction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../../src/extensions/compaction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EAIb,MAAM,aAAa,CAAC;AAIrB,MAAM,WAAW,yBAAyB;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,IAAI,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC7B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAwKD,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,YAAY,EACjB,MAAM,CAAC,EAAE,yBAAyB,GACjC,IAAI,CA6BN;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,yBAAyB,GAAG,IAAI,CAEpF"}
@@ -0,0 +1,162 @@
1
+ import { createId, estimateMessageLength } from '../utils.js';
2
+ import { normalizeRemovalTargets } from './message-integrity.js';
3
+ const DEFAULT_CONFIG = {
4
+ maxMessages: 40,
5
+ maxCharacters: 12_000,
6
+ retainLastMessages: 8,
7
+ mode: 'remove',
8
+ appendSummary: true,
9
+ summaryPrefix: '[message-compaction]',
10
+ };
11
+ function isPinned(message) {
12
+ return message.metadata.pinned === true;
13
+ }
14
+ function createSummaryMessage(config, result) {
15
+ const summaryText = `${config.summaryPrefix} mode=${result.mode} removed=${result.removedCount} chars=${result.removedCharacters}`;
16
+ return {
17
+ id: createId('msg'),
18
+ data: {
19
+ role: 'user',
20
+ content: summaryText,
21
+ },
22
+ metadata: {
23
+ 'compaction.summary': true,
24
+ 'compaction.mode': result.mode,
25
+ 'compaction.removedCount': result.removedCount,
26
+ 'compaction.removedCharacters': result.removedCharacters,
27
+ },
28
+ createdAt: new Date(),
29
+ source: {
30
+ type: 'extension',
31
+ extensionName: 'message-compaction',
32
+ },
33
+ };
34
+ }
35
+ function emitEvents(ctx, events) {
36
+ for (const event of events) {
37
+ ctx.emitMessageEvent(event);
38
+ }
39
+ }
40
+ function compactByRemoval(messages, config) {
41
+ const candidateRemovedIds = new Set();
42
+ const protectedIds = new Set();
43
+ const keepFrom = Math.max(0, messages.length - config.retainLastMessages);
44
+ for (let index = keepFrom; index < messages.length; index += 1) {
45
+ const protectedMessage = messages[index];
46
+ if (!protectedMessage) {
47
+ continue;
48
+ }
49
+ protectedIds.add(protectedMessage.id);
50
+ }
51
+ let remainingCount = messages.length;
52
+ let remainingCharacters = messages.reduce((sum, message) => sum + estimateMessageLength(message), 0);
53
+ for (const message of messages) {
54
+ const overMessageLimit = remainingCount > config.maxMessages;
55
+ const overCharacterLimit = remainingCharacters > config.maxCharacters;
56
+ if (!overMessageLimit && !overCharacterLimit) {
57
+ break;
58
+ }
59
+ if (protectedIds.has(message.id) || isPinned(message)) {
60
+ continue;
61
+ }
62
+ const messageSize = estimateMessageLength(message);
63
+ remainingCount -= 1;
64
+ remainingCharacters -= messageSize;
65
+ candidateRemovedIds.add(message.id);
66
+ }
67
+ const finalRemovedIds = normalizeRemovalTargets(messages, candidateRemovedIds);
68
+ const events = [];
69
+ let finalRemainingCount = 0;
70
+ let finalRemainingCharacters = 0;
71
+ let finalRemovedCount = 0;
72
+ let finalRemovedCharacters = 0;
73
+ for (const message of messages) {
74
+ const messageSize = estimateMessageLength(message);
75
+ if (finalRemovedIds.has(message.id)) {
76
+ finalRemovedCount += 1;
77
+ finalRemovedCharacters += messageSize;
78
+ events.push({
79
+ type: 'remove',
80
+ targetId: message.id,
81
+ });
82
+ continue;
83
+ }
84
+ finalRemainingCount += 1;
85
+ finalRemainingCharacters += messageSize;
86
+ }
87
+ const overflow = finalRemainingCount > config.maxMessages || finalRemainingCharacters > config.maxCharacters;
88
+ return {
89
+ events,
90
+ result: {
91
+ mode: 'remove',
92
+ removedCount: finalRemovedCount,
93
+ removedCharacters: finalRemovedCharacters,
94
+ },
95
+ overflow,
96
+ };
97
+ }
98
+ function compactByTruncate() {
99
+ return {
100
+ events: [{ type: 'truncate' }],
101
+ result: {
102
+ mode: 'truncate',
103
+ removedCount: 0,
104
+ removedCharacters: 0,
105
+ },
106
+ };
107
+ }
108
+ function shouldCompact(messages, config) {
109
+ if (messages.length > config.maxMessages) {
110
+ return true;
111
+ }
112
+ const totalCharacters = messages.reduce((sum, message) => sum + estimateMessageLength(message), 0);
113
+ return totalCharacters > config.maxCharacters;
114
+ }
115
+ function mergeConfig(config) {
116
+ return {
117
+ maxMessages: config?.maxMessages ?? DEFAULT_CONFIG.maxMessages,
118
+ maxCharacters: config?.maxCharacters ?? DEFAULT_CONFIG.maxCharacters,
119
+ retainLastMessages: config?.retainLastMessages ?? DEFAULT_CONFIG.retainLastMessages,
120
+ mode: config?.mode ?? DEFAULT_CONFIG.mode,
121
+ appendSummary: config?.appendSummary ?? DEFAULT_CONFIG.appendSummary,
122
+ summaryPrefix: config?.summaryPrefix ?? DEFAULT_CONFIG.summaryPrefix,
123
+ };
124
+ }
125
+ function maybeAppendSummary(events, config, result) {
126
+ if (!config.appendSummary) {
127
+ return;
128
+ }
129
+ events.push({
130
+ type: 'append',
131
+ message: createSummaryMessage(config, result),
132
+ });
133
+ }
134
+ export function registerCompactionExtension(api, config) {
135
+ const settings = mergeConfig(config);
136
+ api.pipeline.register('turn', async (ctx) => {
137
+ const messages = ctx.conversationState.nextMessages;
138
+ if (!shouldCompact(messages, settings)) {
139
+ return ctx.next();
140
+ }
141
+ if (settings.mode === 'truncate') {
142
+ const { events, result } = compactByTruncate();
143
+ maybeAppendSummary(events, settings, result);
144
+ emitEvents(ctx, events);
145
+ return ctx.next();
146
+ }
147
+ const removal = compactByRemoval(messages, settings);
148
+ if (removal.overflow) {
149
+ const truncate = compactByTruncate();
150
+ maybeAppendSummary(truncate.events, settings, truncate.result);
151
+ emitEvents(ctx, truncate.events);
152
+ return ctx.next();
153
+ }
154
+ maybeAppendSummary(removal.events, settings, removal.result);
155
+ emitEvents(ctx, removal.events);
156
+ return ctx.next();
157
+ });
158
+ }
159
+ export function register(api, config) {
160
+ registerCompactionExtension(api, config);
161
+ }
162
+ //# sourceMappingURL=compaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compaction.js","sourceRoot":"","sources":["../../src/extensions/compaction.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { ExtensionApi } from '../types.js';
2
+ export interface ContextMessageExtensionConfig {
3
+ includeAgentPrompt?: boolean;
4
+ includeInboundContext?: boolean;
5
+ includeRouteSummary?: boolean;
6
+ includeInboundInput?: boolean;
7
+ }
8
+ export declare function register(api: ExtensionApi, rawConfig?: unknown): void;
9
+ //# sourceMappingURL=context-message.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-message.d.ts","sourceRoot":"","sources":["../../src/extensions/context-message.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAA6E,MAAM,aAAa,CAAC;AAmC3H,MAAM,WAAW,6BAA6B;IAC5C,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAgiBD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,YAAY,EAAE,SAAS,CAAC,EAAE,OAAO,GAAG,IAAI,CAOrE"}
@@ -0,0 +1,451 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { createId, isJsonObject } from '../utils.js';
3
+ const EXTENSION_NAME = 'context-message';
4
+ const EXTENSION_EVENTS_METADATA_KEY = 'extension.events';
5
+ const CONTEXT_MESSAGE_MARKER_KEY = '__goondanContextMessage';
6
+ const INBOUND_MESSAGE_METADATA_KEY = '__goondanInbound';
7
+ const DEFAULT_CONFIG = {
8
+ includeAgentPrompt: true,
9
+ includeInboundContext: false,
10
+ includeRouteSummary: false,
11
+ includeInboundInput: true,
12
+ };
13
+ function readConfig(raw) {
14
+ if (!isJsonObject(raw)) {
15
+ return { ...DEFAULT_CONFIG };
16
+ }
17
+ const config = { ...DEFAULT_CONFIG };
18
+ if (typeof raw.includeAgentPrompt === 'boolean') {
19
+ config.includeAgentPrompt = raw.includeAgentPrompt;
20
+ }
21
+ if (typeof raw.includeInboundContext === 'boolean') {
22
+ config.includeInboundContext = raw.includeInboundContext;
23
+ }
24
+ if (typeof raw.includeRouteSummary === 'boolean') {
25
+ config.includeRouteSummary = raw.includeRouteSummary;
26
+ }
27
+ if (typeof raw.includeInboundInput === 'boolean') {
28
+ config.includeInboundInput = raw.includeInboundInput;
29
+ }
30
+ return config;
31
+ }
32
+ function createPromptHash(input) {
33
+ return createHash('sha256').update(`${input.role}\n${input.content}`).digest('hex');
34
+ }
35
+ function readPromptHashMarker(message) {
36
+ const marker = readContextMessageMarker(message);
37
+ return marker?.promptHash;
38
+ }
39
+ function readContextMessageMarker(message) {
40
+ if (!isJsonObject(message.metadata)) {
41
+ return undefined;
42
+ }
43
+ const rawMarker = message.metadata[CONTEXT_MESSAGE_MARKER_KEY];
44
+ if (!isJsonObject(rawMarker)) {
45
+ return undefined;
46
+ }
47
+ const promptHash = rawMarker.promptHash;
48
+ if (typeof promptHash !== 'string' || promptHash.length === 0) {
49
+ return undefined;
50
+ }
51
+ const marker = {
52
+ promptHash,
53
+ };
54
+ if (rawMarker.role === 'system' || rawMarker.role === 'user') {
55
+ marker.role = rawMarker.role;
56
+ }
57
+ if (Array.isArray(rawMarker.segmentIds)) {
58
+ marker.segmentIds = rawMarker.segmentIds
59
+ .filter((segmentId) => typeof segmentId === 'string' && segmentId.length > 0);
60
+ }
61
+ return marker;
62
+ }
63
+ function markerIncludesSegment(marker, segmentId) {
64
+ if (!marker || !Array.isArray(marker.segmentIds)) {
65
+ return false;
66
+ }
67
+ return marker.segmentIds.includes(segmentId);
68
+ }
69
+ function hasPromptHashMarker(messages, promptHash, role, content) {
70
+ for (const message of messages) {
71
+ const existingHash = readPromptHashMarker(message);
72
+ if (existingHash === promptHash) {
73
+ return true;
74
+ }
75
+ if (message.source.type === 'extension'
76
+ && message.source.extensionName === EXTENSION_NAME
77
+ && message.data.role === role
78
+ && message.data.content === content) {
79
+ return true;
80
+ }
81
+ }
82
+ return false;
83
+ }
84
+ function createMessageMarker(promptHash, role, segmentIds) {
85
+ const marker = {
86
+ promptHash,
87
+ role,
88
+ segmentIds,
89
+ };
90
+ return marker;
91
+ }
92
+ function createContextMessage(promptHash, role, content, segmentIds) {
93
+ const metadata = {
94
+ [CONTEXT_MESSAGE_MARKER_KEY]: createMessageMarker(promptHash, role, segmentIds),
95
+ };
96
+ if (role === 'system') {
97
+ metadata.pinned = true;
98
+ }
99
+ return {
100
+ id: createId('msg'),
101
+ data: {
102
+ role,
103
+ content,
104
+ },
105
+ metadata,
106
+ createdAt: new Date(),
107
+ source: {
108
+ type: 'extension',
109
+ extensionName: EXTENSION_NAME,
110
+ },
111
+ };
112
+ }
113
+ function createInboundInputMetadata(ctx) {
114
+ const inbound = ctx.runtime.inbound;
115
+ const payload = {
116
+ sourceKind: inbound.kind,
117
+ sourceName: inbound.sourceName,
118
+ eventName: inbound.eventType,
119
+ };
120
+ if (typeof inbound.connectionName === 'string' && inbound.connectionName.length > 0) {
121
+ payload.connectionName = inbound.connectionName;
122
+ }
123
+ if (typeof inbound.conversationId === 'string' && inbound.conversationId.length > 0) {
124
+ payload.conversationId = inbound.conversationId;
125
+ }
126
+ if (inbound.kind === 'connector') {
127
+ if (Object.keys(inbound.properties).length > 0) {
128
+ payload.properties = inbound.properties;
129
+ }
130
+ if (ctx.inputEvent.rawPayload !== undefined) {
131
+ payload.rawPayload = ctx.inputEvent.rawPayload;
132
+ }
133
+ }
134
+ return {
135
+ [INBOUND_MESSAGE_METADATA_KEY]: payload,
136
+ };
137
+ }
138
+ function createInboundInputMessage(ctx, content) {
139
+ return {
140
+ id: createId('msg'),
141
+ data: {
142
+ role: 'user',
143
+ content,
144
+ },
145
+ metadata: createInboundInputMetadata(ctx),
146
+ createdAt: new Date(),
147
+ source: {
148
+ type: 'user',
149
+ },
150
+ };
151
+ }
152
+ function renderInboundParts(parts) {
153
+ const textBlocks = [];
154
+ const attachmentBlocks = [];
155
+ for (const part of parts) {
156
+ if (part.type === 'text') {
157
+ const trimmed = part.text.trim();
158
+ if (trimmed.length > 0) {
159
+ textBlocks.push(trimmed);
160
+ }
161
+ continue;
162
+ }
163
+ if (part.type === 'image') {
164
+ const label = typeof part.alt === 'string' && part.alt.trim().length > 0 ? ` ${part.alt.trim()}` : '';
165
+ attachmentBlocks.push(`[image]${label} ${part.url}`.trim());
166
+ continue;
167
+ }
168
+ const mimeLabel = typeof part.mimeType === 'string' && part.mimeType.trim().length > 0
169
+ ? ` (${part.mimeType.trim()})`
170
+ : '';
171
+ attachmentBlocks.push(`[file] ${part.name}${mimeLabel}: ${part.url}`);
172
+ }
173
+ return [...textBlocks, ...attachmentBlocks].join('\n\n').trim();
174
+ }
175
+ function appendRuntimeEvent(ctx, name, data = undefined) {
176
+ const entries = Array.isArray(ctx.metadata[EXTENSION_EVENTS_METADATA_KEY])
177
+ ? [...ctx.metadata[EXTENSION_EVENTS_METADATA_KEY]]
178
+ : [];
179
+ const entry = {
180
+ name,
181
+ actor: EXTENSION_NAME,
182
+ at: new Date().toISOString(),
183
+ };
184
+ if (data !== undefined && Object.keys(data).length > 0) {
185
+ entry.data = data;
186
+ }
187
+ entries.push(entry);
188
+ ctx.metadata[EXTENSION_EVENTS_METADATA_KEY] = entries;
189
+ }
190
+ function resolveAgentPrompt(metadata) {
191
+ const prompt = metadata.prompt;
192
+ if (!prompt) {
193
+ return null;
194
+ }
195
+ const inlinePrompt = typeof prompt.system === 'string' && prompt.system.trim().length > 0
196
+ ? prompt.system
197
+ : undefined;
198
+ if (!inlinePrompt) {
199
+ return null;
200
+ }
201
+ return inlinePrompt;
202
+ }
203
+ function stringOrUndefined(value) {
204
+ if (typeof value !== 'string') {
205
+ return undefined;
206
+ }
207
+ const trimmed = value.trim();
208
+ if (trimmed.length === 0) {
209
+ return undefined;
210
+ }
211
+ return trimmed;
212
+ }
213
+ function resolveInboundContextSegment(ctx) {
214
+ const inbound = ctx.runtime.inbound;
215
+ const lines = [
216
+ '[runtime_inbound]',
217
+ `eventId=${inbound.eventId}`,
218
+ `eventType=${inbound.eventType}`,
219
+ `kind=${inbound.kind}`,
220
+ `sourceName=${inbound.sourceName}`,
221
+ `createdAt=${inbound.createdAt}`,
222
+ ];
223
+ if (typeof inbound.connectionName === 'string' && inbound.connectionName.length > 0) {
224
+ lines.push(`connectionName=${inbound.connectionName}`);
225
+ }
226
+ if (typeof inbound.conversationId === 'string' && inbound.conversationId.length > 0) {
227
+ lines.push(`conversationId=${inbound.conversationId}`);
228
+ }
229
+ if (Object.keys(inbound.properties).length > 0) {
230
+ lines.push(`properties=${JSON.stringify(inbound.properties)}`);
231
+ }
232
+ if (inbound.content.length > 0) {
233
+ lines.push(`content=${JSON.stringify(inbound.content)}`);
234
+ }
235
+ if (inbound.rawPayload !== undefined) {
236
+ lines.push(`rawPayload=${JSON.stringify(inbound.rawPayload)}`);
237
+ }
238
+ lines.push('[/runtime_inbound]');
239
+ return {
240
+ id: 'runtime.inbound',
241
+ role: 'user',
242
+ content: lines.join('\n'),
243
+ };
244
+ }
245
+ function resolveRouteSummarySegment(ctx) {
246
+ const inbound = ctx.runtime.inbound;
247
+ const inboundConversationId = stringOrUndefined(inbound.conversationId);
248
+ const lines = [
249
+ '[runtime_route]',
250
+ 'precedence=inbound',
251
+ `senderKind=${inbound.kind}`,
252
+ `senderName=${inbound.sourceName}`,
253
+ `eventType=${inbound.eventType}`,
254
+ `eventId=${inbound.eventId}`,
255
+ ];
256
+ if (inboundConversationId) {
257
+ lines.push(`senderConversationId=${inboundConversationId}`);
258
+ }
259
+ lines.push('[/runtime_route]');
260
+ return {
261
+ id: 'runtime.route.summary',
262
+ role: 'user',
263
+ content: lines.join('\n'),
264
+ };
265
+ }
266
+ const SEGMENT_ORDER = [
267
+ 'agent.prompt.system',
268
+ 'runtime.inbound',
269
+ 'runtime.route.summary',
270
+ ];
271
+ function sortSegmentsByOrder(segments) {
272
+ const order = new Map();
273
+ SEGMENT_ORDER.forEach((id, index) => {
274
+ order.set(id, index);
275
+ });
276
+ return [...segments].sort((left, right) => {
277
+ const leftOrder = order.get(left.id) ?? Number.MAX_SAFE_INTEGER;
278
+ const rightOrder = order.get(right.id) ?? Number.MAX_SAFE_INTEGER;
279
+ if (leftOrder !== rightOrder) {
280
+ return leftOrder - rightOrder;
281
+ }
282
+ return left.id.localeCompare(right.id);
283
+ });
284
+ }
285
+ function resolveContextSegments(ctx, config) {
286
+ const resolutions = [];
287
+ const segments = [];
288
+ if (config.includeAgentPrompt) {
289
+ const system = resolveAgentPrompt({ prompt: ctx.runtime.agent.prompt });
290
+ if (system && system.trim().length > 0) {
291
+ segments.push({
292
+ id: 'agent.prompt.system',
293
+ role: 'system',
294
+ content: system,
295
+ });
296
+ resolutions.push({ id: 'agent.prompt.system', included: true });
297
+ }
298
+ else {
299
+ resolutions.push({ id: 'agent.prompt.system', included: false });
300
+ }
301
+ }
302
+ if (config.includeInboundContext) {
303
+ segments.push(resolveInboundContextSegment(ctx));
304
+ resolutions.push({ id: 'runtime.inbound', included: true });
305
+ }
306
+ if (config.includeRouteSummary && config.includeInboundContext) {
307
+ segments.push(resolveRouteSummarySegment(ctx));
308
+ resolutions.push({ id: 'runtime.route.summary', included: true });
309
+ }
310
+ return {
311
+ resolutions,
312
+ segments: sortSegmentsByOrder(segments),
313
+ };
314
+ }
315
+ function composeMessageGroups(segments) {
316
+ const groups = [];
317
+ for (const segment of segments) {
318
+ const last = groups[groups.length - 1];
319
+ if (last && last.role === segment.role) {
320
+ last.content = `${last.content}\n\n${segment.content}`;
321
+ last.segmentIds.push(segment.id);
322
+ continue;
323
+ }
324
+ groups.push({
325
+ role: segment.role,
326
+ content: segment.content,
327
+ segmentIds: [segment.id],
328
+ });
329
+ }
330
+ return groups;
331
+ }
332
+ async function emitContextMessages(ctx, config) {
333
+ const composed = resolveContextSegments(ctx, config);
334
+ for (const resolution of composed.resolutions) {
335
+ appendRuntimeEvent(ctx, 'context.segment.resolved', {
336
+ id: resolution.id,
337
+ included: resolution.included,
338
+ });
339
+ }
340
+ if (composed.segments.length === 0) {
341
+ appendRuntimeEvent(ctx, 'context.message.empty');
342
+ return;
343
+ }
344
+ const existingMessages = ctx.conversationState.nextMessages;
345
+ const groups = composeMessageGroups(composed.segments);
346
+ const desiredSystemGroup = groups.find((group) => group.role === 'system' && group.segmentIds.includes('agent.prompt.system'));
347
+ const desiredSystemHash = desiredSystemGroup
348
+ ? createPromptHash({ role: desiredSystemGroup.role, content: desiredSystemGroup.content })
349
+ : undefined;
350
+ const pendingGroups = [];
351
+ for (const group of groups) {
352
+ const promptHash = createPromptHash({ role: group.role, content: group.content });
353
+ if (hasPromptHashMarker(existingMessages, promptHash, group.role, group.content)) {
354
+ appendRuntimeEvent(ctx, 'context.message.duplicate', {
355
+ promptHash,
356
+ role: group.role,
357
+ });
358
+ continue;
359
+ }
360
+ pendingGroups.push({ group, promptHash });
361
+ }
362
+ let emittedContextMutation = false;
363
+ if (desiredSystemGroup && desiredSystemHash) {
364
+ const existingSystems = existingMessages
365
+ .filter((message) => message.data.role === 'system')
366
+ .map((message) => ({
367
+ message,
368
+ marker: readContextMessageMarker(message),
369
+ }));
370
+ if (existingSystems.length > 0) {
371
+ const preferred = existingSystems.find((entry) => entry.marker?.promptHash === desiredSystemHash)
372
+ ?? existingSystems.find((entry) => markerIncludesSegment(entry.marker, 'agent.prompt.system'))
373
+ ?? existingSystems[0];
374
+ if (preferred) {
375
+ const targetMessage = preferred.message;
376
+ const targetMarker = preferred.marker;
377
+ const targetMatchesDesired = targetMarker?.promptHash === desiredSystemHash
378
+ || targetMessage.data.content === desiredSystemGroup.content;
379
+ if (!targetMatchesDesired) {
380
+ ctx.emitMessageEvent({
381
+ type: 'replace',
382
+ targetId: targetMessage.id,
383
+ message: createContextMessage(desiredSystemHash, 'system', desiredSystemGroup.content, desiredSystemGroup.segmentIds),
384
+ });
385
+ appendRuntimeEvent(ctx, 'context.message.replaced', {
386
+ targetId: targetMessage.id,
387
+ promptHash: desiredSystemHash,
388
+ role: 'system',
389
+ segmentIds: desiredSystemGroup.segmentIds,
390
+ });
391
+ emittedContextMutation = true;
392
+ }
393
+ for (const entry of existingSystems) {
394
+ if (entry.message.id === targetMessage.id) {
395
+ continue;
396
+ }
397
+ ctx.emitMessageEvent({
398
+ type: 'remove',
399
+ targetId: entry.message.id,
400
+ });
401
+ appendRuntimeEvent(ctx, 'context.message.removed', {
402
+ targetId: entry.message.id,
403
+ role: 'system',
404
+ });
405
+ emittedContextMutation = true;
406
+ }
407
+ const duplicateSystemIndex = pendingGroups.findIndex((pending) => pending.group.role === 'system' && pending.group.segmentIds.includes('agent.prompt.system'));
408
+ if (duplicateSystemIndex >= 0) {
409
+ pendingGroups.splice(duplicateSystemIndex, 1);
410
+ }
411
+ }
412
+ }
413
+ }
414
+ for (const pending of pendingGroups) {
415
+ const group = pending.group;
416
+ ctx.emitMessageEvent({
417
+ type: 'append',
418
+ message: createContextMessage(pending.promptHash, group.role, group.content, group.segmentIds),
419
+ });
420
+ appendRuntimeEvent(ctx, 'context.message.appended', {
421
+ promptHash: pending.promptHash,
422
+ role: group.role,
423
+ segmentIds: group.segmentIds,
424
+ });
425
+ emittedContextMutation = true;
426
+ }
427
+ const renderedInboundInput = ctx.runtime.inbound.kind === 'connector'
428
+ ? renderInboundParts(ctx.runtime.inbound.content)
429
+ : '';
430
+ const inboundInput = renderedInboundInput.trim().length > 0
431
+ ? renderedInboundInput
432
+ : (typeof ctx.inputEvent.input === 'string' ? ctx.inputEvent.input : '');
433
+ if (config.includeInboundInput && inboundInput.trim().length > 0) {
434
+ ctx.emitMessageEvent({
435
+ type: 'append',
436
+ message: createInboundInputMessage(ctx, inboundInput),
437
+ });
438
+ appendRuntimeEvent(ctx, 'context.inbound.appended');
439
+ }
440
+ if (!emittedContextMutation) {
441
+ appendRuntimeEvent(ctx, 'context.message.empty');
442
+ }
443
+ }
444
+ export function register(api, rawConfig) {
445
+ const config = readConfig(rawConfig);
446
+ api.pipeline.register('turn', async (ctx) => {
447
+ await emitContextMessages(ctx, config);
448
+ return ctx.next();
449
+ });
450
+ }
451
+ //# sourceMappingURL=context-message.js.map