@bbigbang/agent-node 0.1.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 (47) hide show
  1. package/dist/agentHost.js +483 -0
  2. package/dist/appVersion.js +14 -0
  3. package/dist/assetCachePaths.js +35 -0
  4. package/dist/attachmentInput.js +588 -0
  5. package/dist/attachmentMaterializer.js +230 -0
  6. package/dist/bigbangCli.js +17 -0
  7. package/dist/bigbangMessageSendDetection.js +284 -0
  8. package/dist/builtinSkillRoots.js +54 -0
  9. package/dist/claudeConfig.js +32 -0
  10. package/dist/claudeDirectRuntime.js +1960 -0
  11. package/dist/claudeSessionControls.js +78 -0
  12. package/dist/claudeTranscriptFs.js +147 -0
  13. package/dist/codexAppServerClient.js +188 -0
  14. package/dist/codexAppServerEnv.js +14 -0
  15. package/dist/codexAppServerRpc.js +273 -0
  16. package/dist/codexAppServerRuntime.js +3495 -0
  17. package/dist/codexBuiltinPrompt.js +117 -0
  18. package/dist/codexConversationSummarizer.js +76 -0
  19. package/dist/codexTranscriptFs.js +145 -0
  20. package/dist/config.js +129 -0
  21. package/dist/connection.js +151 -0
  22. package/dist/dispatchQueueStore.js +39 -0
  23. package/dist/dreamEnv.js +1 -0
  24. package/dist/dreamMemoryFallback.js +118 -0
  25. package/dist/dreamToolPolicy.js +293 -0
  26. package/dist/droidMissionRunner.js +808 -0
  27. package/dist/executor.js +1078 -0
  28. package/dist/hostRuntime.js +1 -0
  29. package/dist/libraryAuthorityFs.js +74 -0
  30. package/dist/libraryMirror.js +183 -0
  31. package/dist/main.js +1659 -0
  32. package/dist/native-worker/native-worker.mjs +475 -0
  33. package/dist/nativeMissionAgentDispatch.js +463 -0
  34. package/dist/nativeMissionRunner.js +461 -0
  35. package/dist/nativeSkillMounts.js +204 -0
  36. package/dist/nativeWorkerHost.js +142 -0
  37. package/dist/nodeSink.js +142 -0
  38. package/dist/panelHttpFetch.js +334 -0
  39. package/dist/runtimeDrivers.js +62 -0
  40. package/dist/skillFs.js +229 -0
  41. package/dist/soloHost.js +165 -0
  42. package/dist/soloNodeSink.js +138 -0
  43. package/dist/terminalManager.js +254 -0
  44. package/dist/workspaceFs.js +1020 -0
  45. package/dist/workspaceGit.js +694 -0
  46. package/dist/workspaceInspect.js +22 -0
  47. package/package.json +49 -0
@@ -0,0 +1,230 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+ import { log } from '@bbigbang/runtime-acp';
5
+ import { buildConversationAssetCacheRoot, resolveScopedAssetCachePath } from './assetCachePaths.js';
6
+ const DEFAULT_ASSET_FETCH_TIMEOUT_MS = 15_000;
7
+ const DEFAULT_ASSET_MATERIALIZE_CONCURRENCY = 3;
8
+ const DEFAULT_ASSET_MATERIALIZE_MAX_BYTES = 16 * 1024 * 1024;
9
+ export class RuntimeAttachmentMaterializer {
10
+ sessionKey;
11
+ runtimeLabel;
12
+ assetAccessConfig;
13
+ assetCacheRoot;
14
+ fetchTimeoutMs;
15
+ maxBytes;
16
+ isCancelled;
17
+ onMaterialized;
18
+ abortControllers = new Set();
19
+ constructor(options) {
20
+ this.sessionKey = options.sessionKey;
21
+ this.runtimeLabel = options.runtimeLabel;
22
+ this.assetAccessConfig = options.assetAccessConfig;
23
+ this.assetCacheRoot = options.assetCacheRoot?.trim()
24
+ || (options.assetAccessConfig
25
+ ? buildConversationAssetCacheRoot(options.assetAccessConfig.agentId, options.assetAccessConfig.conversationId)
26
+ : null);
27
+ this.fetchTimeoutMs = options.fetchTimeoutMs ?? DEFAULT_ASSET_FETCH_TIMEOUT_MS;
28
+ this.maxBytes = options.maxBytes ?? DEFAULT_ASSET_MATERIALIZE_MAX_BYTES;
29
+ this.isCancelled = options.isCancelled ?? (() => false);
30
+ this.onMaterialized = options.onMaterialized;
31
+ }
32
+ get cacheRoot() {
33
+ return this.assetCacheRoot;
34
+ }
35
+ abort() {
36
+ if (this.abortControllers.size === 0)
37
+ return;
38
+ const controllers = Array.from(this.abortControllers);
39
+ this.abortControllers.clear();
40
+ for (const controller of controllers) {
41
+ controller.abort(new Error('Asset materialization cancelled'));
42
+ }
43
+ }
44
+ async materialize(attachments, context) {
45
+ if (!attachments?.length)
46
+ return [];
47
+ return mapWithConcurrencyLimit(attachments, DEFAULT_ASSET_MATERIALIZE_CONCURRENCY, (attachment) => this.materializeOne(attachment, context));
48
+ }
49
+ async materializeOne(attachment, context) {
50
+ if (this.isCancelled())
51
+ return attachment;
52
+ const assetId = normalizeOptionalString(attachment.assetId) ?? extractAttachmentIdFromUri(attachment.uri);
53
+ if (!assetId || !this.assetAccessConfig || !this.assetCacheRoot)
54
+ return attachment;
55
+ const absoluteTarget = resolveScopedAssetCachePath({
56
+ cacheRoot: this.assetCacheRoot,
57
+ preferredLocalPath: normalizeOptionalString(attachment.preferredLocalPath),
58
+ assetId,
59
+ filename: attachment.filename,
60
+ });
61
+ try {
62
+ await this.ensureAssetAccessAuthorized(assetId);
63
+ if (!fs.existsSync(absoluteTarget)) {
64
+ fs.mkdirSync(path.dirname(absoluteTarget), { recursive: true });
65
+ await this.fetchAuthorizedAssetToFile(assetId, absoluteTarget);
66
+ }
67
+ this.onMaterialized?.({
68
+ assetId,
69
+ conversationId: this.assetAccessConfig.conversationId,
70
+ runId: context?.runId ?? null,
71
+ localPath: absoluteTarget,
72
+ materializedAt: Date.now(),
73
+ });
74
+ return {
75
+ ...attachment,
76
+ uri: pathToFileURL(absoluteTarget).toString(),
77
+ preferredLocalPath: absoluteTarget,
78
+ };
79
+ }
80
+ catch (error) {
81
+ log.warn(`[${this.runtimeLabel}] asset materialization failed; falling back to attachment reference`, {
82
+ sessionKey: this.sessionKey,
83
+ assetId,
84
+ conversationId: this.assetAccessConfig.conversationId,
85
+ error: String(error?.message ?? error),
86
+ cancelled: this.isCancelled(),
87
+ });
88
+ return {
89
+ ...attachment,
90
+ preferredLocalPath: undefined,
91
+ };
92
+ }
93
+ }
94
+ async ensureAssetAccessAuthorized(assetId) {
95
+ const response = await this.fetchAuthorizedAsset(assetId, { metadataOnly: true });
96
+ await response.arrayBuffer();
97
+ }
98
+ async fetchAuthorizedAsset(assetId, options) {
99
+ if (!this.assetAccessConfig) {
100
+ throw new Error('Asset access is unavailable for this runtime.');
101
+ }
102
+ if (this.isCancelled()) {
103
+ throw new Error('Asset materialization cancelled');
104
+ }
105
+ const headers = {};
106
+ if (this.assetAccessConfig.authToken) {
107
+ headers.Authorization = `Bearer ${this.assetAccessConfig.authToken}`;
108
+ }
109
+ const abortController = new AbortController();
110
+ this.abortControllers.add(abortController);
111
+ const timeout = setTimeout(() => {
112
+ abortController.abort(new Error(`Asset materialization timed out after ${this.fetchTimeoutMs}ms`));
113
+ }, this.fetchTimeoutMs);
114
+ try {
115
+ const endpoint = options?.metadataOnly ? 'meta' : '';
116
+ const suffix = endpoint ? `/${endpoint}` : '';
117
+ const res = await fetch(`${this.assetAccessConfig.serverUrl}/api/assets/${encodeURIComponent(assetId)}${suffix}?agentId=${encodeURIComponent(this.assetAccessConfig.agentId)}&conversationId=${encodeURIComponent(this.assetAccessConfig.conversationId)}`, { headers, signal: abortController.signal });
118
+ if (!res.ok) {
119
+ throw new Error(`Failed to materialize asset ${assetId}: ${res.status}`);
120
+ }
121
+ return res;
122
+ }
123
+ finally {
124
+ clearTimeout(timeout);
125
+ this.abortControllers.delete(abortController);
126
+ }
127
+ }
128
+ async fetchAuthorizedAssetToFile(assetId, absoluteTarget) {
129
+ if (!this.assetAccessConfig) {
130
+ throw new Error('Asset access is unavailable for this runtime.');
131
+ }
132
+ if (this.isCancelled()) {
133
+ throw new Error('Asset materialization cancelled');
134
+ }
135
+ const headers = {};
136
+ if (this.assetAccessConfig.authToken) {
137
+ headers.Authorization = `Bearer ${this.assetAccessConfig.authToken}`;
138
+ }
139
+ const abortController = new AbortController();
140
+ this.abortControllers.add(abortController);
141
+ const timeout = setTimeout(() => {
142
+ abortController.abort(new Error(`Asset materialization timed out after ${this.fetchTimeoutMs}ms`));
143
+ }, this.fetchTimeoutMs);
144
+ const tempTarget = `${absoluteTarget}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
145
+ try {
146
+ const res = await fetch(`${this.assetAccessConfig.serverUrl}/api/assets/${encodeURIComponent(assetId)}?agentId=${encodeURIComponent(this.assetAccessConfig.agentId)}&conversationId=${encodeURIComponent(this.assetAccessConfig.conversationId)}`, { headers, signal: abortController.signal });
147
+ if (!res.ok) {
148
+ throw new Error(`Failed to materialize asset ${assetId}: ${res.status}`);
149
+ }
150
+ await writeResponseBodyToFile(res, tempTarget, this.maxBytes, abortController.signal, () => this.isCancelled());
151
+ if (this.isCancelled()) {
152
+ throw new Error('Asset materialization cancelled');
153
+ }
154
+ fs.renameSync(tempTarget, absoluteTarget);
155
+ }
156
+ catch (error) {
157
+ fs.rmSync(tempTarget, { force: true });
158
+ throw error;
159
+ }
160
+ finally {
161
+ clearTimeout(timeout);
162
+ this.abortControllers.delete(abortController);
163
+ }
164
+ }
165
+ }
166
+ export function extractAttachmentIdFromUri(uri) {
167
+ const normalized = normalizeOptionalString(uri);
168
+ if (!normalized || !normalized.startsWith('attachment:'))
169
+ return null;
170
+ return normalizeOptionalString(normalized.slice('attachment:'.length));
171
+ }
172
+ async function mapWithConcurrencyLimit(items, limit, worker) {
173
+ const results = new Array(items.length);
174
+ let nextIndex = 0;
175
+ const runWorker = async () => {
176
+ while (true) {
177
+ const currentIndex = nextIndex;
178
+ nextIndex += 1;
179
+ if (currentIndex >= items.length)
180
+ return;
181
+ results[currentIndex] = await worker(items[currentIndex], currentIndex);
182
+ }
183
+ };
184
+ const workers = Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, () => runWorker());
185
+ await Promise.all(workers);
186
+ return results;
187
+ }
188
+ function normalizeOptionalString(value) {
189
+ if (typeof value !== 'string')
190
+ return null;
191
+ const normalized = value.trim();
192
+ return normalized ? normalized : null;
193
+ }
194
+ async function writeResponseBodyToFile(response, targetPath, maxBytes, signal, isCancelled) {
195
+ const contentLength = Number(response.headers.get('content-length') ?? '');
196
+ if (Number.isFinite(contentLength) && contentLength > maxBytes) {
197
+ throw new Error(`Asset exceeds materialization limit of ${maxBytes} bytes`);
198
+ }
199
+ const body = response.body;
200
+ if (!body) {
201
+ const buffer = Buffer.from(await response.arrayBuffer());
202
+ if (buffer.length > maxBytes) {
203
+ throw new Error(`Asset exceeds materialization limit of ${maxBytes} bytes`);
204
+ }
205
+ fs.writeFileSync(targetPath, buffer);
206
+ return;
207
+ }
208
+ const fd = fs.openSync(targetPath, 'w');
209
+ let written = 0;
210
+ try {
211
+ const reader = body.getReader();
212
+ while (true) {
213
+ if (signal.aborted || isCancelled()) {
214
+ throw signal.reason instanceof Error ? signal.reason : new Error('Asset materialization cancelled');
215
+ }
216
+ const { done, value } = await reader.read();
217
+ if (done)
218
+ break;
219
+ const chunk = Buffer.from(value);
220
+ written += chunk.length;
221
+ if (written > maxBytes) {
222
+ throw new Error(`Asset exceeds materialization limit of ${maxBytes} bytes`);
223
+ }
224
+ fs.writeSync(fd, chunk);
225
+ }
226
+ }
227
+ finally {
228
+ fs.closeSync(fd);
229
+ }
230
+ }
@@ -0,0 +1,17 @@
1
+ import fs from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ export function resolveBigbangCliEntry() {
6
+ try {
7
+ const req = createRequire(import.meta.url);
8
+ return req.resolve('@bbigbang/cli');
9
+ }
10
+ catch {
11
+ const fallback = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../packages/bigbang-cli/src/index.ts');
12
+ return fs.existsSync(fallback) ? fallback : null;
13
+ }
14
+ }
15
+ export function isBigbangCliAvailable() {
16
+ return resolveBigbangCliEntry() !== null;
17
+ }
@@ -0,0 +1,284 @@
1
+ import { parseBigbangReceipts } from '@bbigbang/agent-command';
2
+ export function getBigbangMessageSendInfo(item) {
3
+ if (item.type !== 'commandExecution' && item.type !== 'shellToolResult')
4
+ return null;
5
+ const command = normalizeOptionalString(item.command);
6
+ if (!command || !containsBigbangMessageSendInvocation(command))
7
+ return null;
8
+ const outputText = [
9
+ normalizeOptionalString(item.aggregatedOutput ?? undefined),
10
+ typeof item.output === 'string' ? normalizeOptionalString(item.output) : null,
11
+ typeof item.result === 'string' ? normalizeOptionalString(item.result) : null,
12
+ ].filter((value) => Boolean(value)).join('\n');
13
+ const receipts = parseBigbangReceipts(outputText).filter((receipt) => receipt.op === 'message.send' && receipt.ok === true);
14
+ const receipt = receipts.at(-1);
15
+ if (receipt) {
16
+ return { kind: receipt.kind, success: true };
17
+ }
18
+ const legacyMessageSentText = /\bMessage sent to [\s\S]*?\bMessage ID:\s*[^\s.]+/i.test(outputText);
19
+ const kindMatch = /(?:^|\s)--kind(?:=|\s+)(?:"([^"]+)"|'([^']+)'|([^\s"']+))/i.exec(command);
20
+ const kind = normalizeOptionalString(kindMatch?.[1] ?? kindMatch?.[2] ?? kindMatch?.[3])?.toLowerCase() ?? null;
21
+ if (legacyMessageSentText) {
22
+ return { kind, success: true };
23
+ }
24
+ return {
25
+ kind,
26
+ success: false,
27
+ failureKind: classifyBigbangMessageSendFailure(outputText, item),
28
+ };
29
+ }
30
+ function containsBigbangMessageSendInvocation(command) {
31
+ if (containsBigbangMessageSendShellPayload(command))
32
+ return true;
33
+ const payload = extractShellCommandStringPayload(command);
34
+ return payload ? containsBigbangMessageSendShellPayload(payload) : false;
35
+ }
36
+ function shellUnquotedTokens(command) {
37
+ const tokens = [];
38
+ let current = '';
39
+ let quote = null;
40
+ let tokenHasQuotedPart = false;
41
+ for (let index = 0; index < command.length; index += 1) {
42
+ const char = command[index];
43
+ if (quote) {
44
+ tokenHasQuotedPart = true;
45
+ if (char === quote)
46
+ quote = null;
47
+ continue;
48
+ }
49
+ if (char === '"' || char === "'") {
50
+ quote = char;
51
+ tokenHasQuotedPart = true;
52
+ continue;
53
+ }
54
+ if (/\s|[;&|(){}]/.test(char)) {
55
+ if (current && !tokenHasQuotedPart)
56
+ tokens.push(current);
57
+ current = '';
58
+ tokenHasQuotedPart = false;
59
+ continue;
60
+ }
61
+ current += char;
62
+ }
63
+ if (current && !tokenHasQuotedPart)
64
+ tokens.push(current);
65
+ return tokens;
66
+ }
67
+ function extractShellCommandStringPayload(command) {
68
+ const words = shellWords(command);
69
+ for (let index = 0; index < words.length; index += 1) {
70
+ const word = words[index];
71
+ if (!/^(?:\/(?:usr\/)?bin\/)?(?:ba|z|fi)?sh$/.test(word.text))
72
+ continue;
73
+ for (let optionIndex = index + 1; optionIndex < words.length; optionIndex += 1) {
74
+ const option = words[optionIndex];
75
+ if (!/^-[A-Za-z]*c[A-Za-z]*$/.test(option.text))
76
+ continue;
77
+ return words[optionIndex + 1]?.text ?? null;
78
+ }
79
+ }
80
+ return null;
81
+ }
82
+ function containsBigbangMessageSendShellPayload(payload) {
83
+ return splitShellCommandSegments(payload)
84
+ .some((segment) => containsBigbangMessageSendAtCommandHead(segment));
85
+ }
86
+ function containsBigbangMessageSendAtCommandHead(segment) {
87
+ const tokens = shellUnquotedTokens(segment);
88
+ let index = 0;
89
+ while (index < tokens.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[index])) {
90
+ index += 1;
91
+ }
92
+ if (tokens[index] === 'command' || tokens[index] === 'exec')
93
+ index += 1;
94
+ if (tokens[index] === 'env') {
95
+ index += 1;
96
+ while (index < tokens.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[index])) {
97
+ index += 1;
98
+ }
99
+ }
100
+ return tokens[index] === 'bigbang'
101
+ && tokens[index + 1] === 'message'
102
+ && tokens[index + 2] === 'send';
103
+ }
104
+ function splitShellCommandSegments(value) {
105
+ const segments = [];
106
+ let current = '';
107
+ let quote = null;
108
+ for (let index = 0; index < value.length; index += 1) {
109
+ const char = value[index];
110
+ const next = value[index + 1];
111
+ if (quote) {
112
+ current += char;
113
+ if (char === quote && value[index - 1] !== '\\')
114
+ quote = null;
115
+ continue;
116
+ }
117
+ if (char === '"' || char === "'") {
118
+ quote = char;
119
+ current += char;
120
+ continue;
121
+ }
122
+ if (char === ';' || (char === '&' && next === '&') || (char === '|' && next === '|')) {
123
+ if (current.trim())
124
+ segments.push(current);
125
+ current = '';
126
+ if ((char === '&' && next === '&') || (char === '|' && next === '|'))
127
+ index += 1;
128
+ continue;
129
+ }
130
+ current += char;
131
+ }
132
+ if (current.trim())
133
+ segments.push(current);
134
+ return segments;
135
+ }
136
+ function shellWords(command) {
137
+ const words = [];
138
+ let current = '';
139
+ let quote = null;
140
+ let quoted = false;
141
+ for (let index = 0; index < command.length; index += 1) {
142
+ const char = command[index];
143
+ if (quote) {
144
+ if (char === quote && command[index - 1] !== '\\') {
145
+ quote = null;
146
+ quoted = true;
147
+ }
148
+ else {
149
+ current += char;
150
+ }
151
+ continue;
152
+ }
153
+ if (char === '"' || char === "'") {
154
+ quote = char;
155
+ quoted = true;
156
+ continue;
157
+ }
158
+ if (/\s|[;&|(){}]/.test(char)) {
159
+ if (current)
160
+ words.push({ text: current, quoted });
161
+ current = '';
162
+ quoted = false;
163
+ continue;
164
+ }
165
+ current += char;
166
+ }
167
+ if (current)
168
+ words.push({ text: current, quoted });
169
+ return words;
170
+ }
171
+ export function buildBigbangMessageSendRepairGuidance(failureKind) {
172
+ const header = 'Bigbang message-send failure guidance: do not blindly repeat the previous command.';
173
+ switch (failureKind) {
174
+ case 'response_lost':
175
+ return [
176
+ header,
177
+ 'The previous `bigbang message send` result was ambiguous, likely a response-lost or transient transport failure without a posted receipt.',
178
+ 'Reuse the original operation id only when rerunning the exact same original message command with identical target, kind, content, and attachments/panels/tools; otherwise it can conflict.',
179
+ 'If you cannot exactly replay the original send, read the latest surface context first, then send a new final message only if one is still needed.',
180
+ ].join('\n');
181
+ case 'held_or_stale':
182
+ return [
183
+ header,
184
+ 'The previous `bigbang message send` appears held, stale, suppressed, or otherwise non-posted.',
185
+ 'Read the latest surface context or held draft details, revise the content if needed, then send again; do not treat the held draft as visible delivery.',
186
+ ].join('\n');
187
+ case 'validation_or_permission':
188
+ return [
189
+ header,
190
+ 'The previous `bigbang message send` appears to have a validation, permission, not-found, or target/scope error.',
191
+ 'Do not retry the same payload unchanged. Fix the target, scope, flags, permissions, or payload first, then send once.',
192
+ ].join('\n');
193
+ case 'operation_conflict':
194
+ return [
195
+ header,
196
+ 'The previous `bigbang message send` hit `CLIENT_MESSAGE_ID_CONFLICT` or an operation-id conflict.',
197
+ 'Do not blindly retry. Reuse that operation id only for the exact same target, kind, content, and attachments/panels/tools; use a new operation id only if you intentionally need to post a different new message.',
198
+ ].join('\n');
199
+ case 'panel_version_conflict':
200
+ return [
201
+ header,
202
+ 'A related panel mutation appears to have a version conflict.',
203
+ 'Read the latest panel state/events/rows, merge your intended change, and write with the current version; do not replay a stale panel mutation unchanged.',
204
+ ].join('\n');
205
+ case 'unknown_failure':
206
+ return [
207
+ header,
208
+ 'The previous `bigbang message send` failed or produced no posted receipt, but the runtime could not classify the reason.',
209
+ 'Inspect the command output, read the latest surface context if delivery is uncertain, and only send again when you can avoid duplicate or stale output.',
210
+ ].join('\n');
211
+ }
212
+ }
213
+ function classifyBigbangMessageSendFailure(outputText, item) {
214
+ const text = outputText.toLowerCase();
215
+ if (text.includes('client_message_id_conflict')
216
+ || text.includes('clientmessageid conflict')
217
+ || text.includes('operation-id conflict')
218
+ || text.includes('operation id conflict')) {
219
+ return 'operation_conflict';
220
+ }
221
+ if (text.includes('panel_version_conflict')
222
+ || text.includes('version conflict')
223
+ || text.includes('expected_version')
224
+ || text.includes('expected version')
225
+ || (text.includes('panel') && text.includes('conflict'))) {
226
+ return 'panel_version_conflict';
227
+ }
228
+ if (text.includes('draft held')
229
+ || text.includes('nothing was posted')
230
+ || text.includes('"held": true')
231
+ || text.includes('"stale": true')
232
+ || text.includes('newer messages exist')
233
+ || text.includes('held draft')
234
+ || text.includes('suppressed')) {
235
+ return 'held_or_stale';
236
+ }
237
+ if (text.includes('permission')
238
+ || text.includes('forbidden')
239
+ || text.includes('unauthorized')
240
+ || text.includes('not_found')
241
+ || text.includes('not found')
242
+ || text.includes('validation')
243
+ || text.includes('invalid_arg')
244
+ || text.includes('request_failed')
245
+ || text.includes('content must not be empty')
246
+ || text.includes('does not belong')
247
+ || text.includes('must not')
248
+ || text.includes('must be')
249
+ || text.includes('required option')
250
+ || text.includes('required field')
251
+ || text.includes('missing required')
252
+ || text.includes('invalid input')
253
+ || text.includes('invalid target')
254
+ || text.includes('unknown target')
255
+ || text.includes('cannot resolve')
256
+ || text.includes('400')
257
+ || text.includes('401')
258
+ || text.includes('403')
259
+ || text.includes('404')) {
260
+ return 'validation_or_permission';
261
+ }
262
+ if (text.includes('network_error')
263
+ || text.includes('network error')
264
+ || text.includes('server_5xx')
265
+ || text.includes('request to bigbang core failed')
266
+ || text.includes('econnreset')
267
+ || text.includes('econnrefused')
268
+ || text.includes('etimedout')
269
+ || text.includes('timed out')
270
+ || text.includes('502')
271
+ || text.includes('503')
272
+ || text.includes('504')) {
273
+ return 'response_lost';
274
+ }
275
+ const status = normalizeOptionalString(item.status)?.toLowerCase();
276
+ if (status === 'failed' || item.success === false || (typeof item.exitCode === 'number' && item.exitCode !== 0)) {
277
+ return 'unknown_failure';
278
+ }
279
+ return 'unknown_failure';
280
+ }
281
+ function normalizeOptionalString(value) {
282
+ const trimmed = value?.trim() ?? '';
283
+ return trimmed ? trimmed : null;
284
+ }
@@ -0,0 +1,54 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { BUILTIN_LIBRARY_DOCUMENTS_SKILL_ROOT_SENTINEL, BUILTIN_UI_PANEL_SKILL_ROOT_SENTINEL, BUILTIN_WORKSPACE_TOOL_SKILL_ROOT_SENTINEL, } from '@bbigbang/protocol';
6
+ const AGENT_NODE_DIR = path.dirname(fileURLToPath(import.meta.url));
7
+ const REPO_ROOT = path.resolve(AGENT_NODE_DIR, '../../..');
8
+ const BUILTIN_SKILLS_ROOT = path.join(REPO_ROOT, 'skills');
9
+ const BUILTIN_SKILL_LINK_ROOT = path.join(os.tmpdir(), 'bigbang-builtin-skill-roots');
10
+ const BUILTIN_SKILL_SENTINELS = new Map([
11
+ [BUILTIN_WORKSPACE_TOOL_SKILL_ROOT_SENTINEL, 'workspace-tool-builder'],
12
+ [BUILTIN_UI_PANEL_SKILL_ROOT_SENTINEL, 'ui-panel'],
13
+ [BUILTIN_LIBRARY_DOCUMENTS_SKILL_ROOT_SENTINEL, 'library-documents'],
14
+ ]);
15
+ export function expandSkillRoot(value) {
16
+ const trimmed = value.trim();
17
+ const builtinSkillName = BUILTIN_SKILL_SENTINELS.get(trimmed);
18
+ if (builtinSkillName) {
19
+ return ensureBuiltinSkillRoot(builtinSkillName);
20
+ }
21
+ return path.resolve(trimmed);
22
+ }
23
+ export function normalizeSkillRootsWithBuiltins(skillRoots) {
24
+ return skillRoots
25
+ .map((value) => value.trim())
26
+ .filter(Boolean)
27
+ .map((value) => expandSkillRoot(value))
28
+ .filter((value, index, list) => list.indexOf(value) === index);
29
+ }
30
+ function ensureBuiltinSkillRoot(skillName) {
31
+ const sourcePath = path.join(BUILTIN_SKILLS_ROOT, skillName);
32
+ const skillFile = path.join(sourcePath, 'SKILL.md');
33
+ if (!(fs.statSync(skillFile, { throwIfNoEntry: false })?.isFile() ?? false)) {
34
+ throw new Error(`Built-in ${skillName} skill is missing on agent-node: ${skillFile}`);
35
+ }
36
+ const rootPath = path.join(BUILTIN_SKILL_LINK_ROOT, skillName);
37
+ const linkPath = path.join(rootPath, skillName);
38
+ fs.mkdirSync(rootPath, { recursive: true });
39
+ const existing = fs.lstatSync(linkPath, { throwIfNoEntry: false });
40
+ if (existing) {
41
+ if (existing.isSymbolicLink()) {
42
+ const currentTarget = fs.readlinkSync(linkPath);
43
+ const resolvedTarget = path.resolve(rootPath, currentTarget);
44
+ if (resolvedTarget === sourcePath)
45
+ return rootPath;
46
+ fs.unlinkSync(linkPath);
47
+ }
48
+ else {
49
+ throw new Error(`Built-in ${skillName} skill link path is occupied: ${linkPath}`);
50
+ }
51
+ }
52
+ fs.symlinkSync(sourcePath, linkPath, 'dir');
53
+ return rootPath;
54
+ }
@@ -0,0 +1,32 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ const CLAUDE_RUNTIME_DIRNAME = '.claude-runtime';
5
+ export function getIsolatedClaudeConfigDir(workspaceRoot) {
6
+ return path.join(path.resolve(workspaceRoot), CLAUDE_RUNTIME_DIRNAME);
7
+ }
8
+ export function getIsolatedClaudeStatePath(workspaceRoot) {
9
+ return `${getIsolatedClaudeConfigDir(workspaceRoot)}.json`;
10
+ }
11
+ export function ensureIsolatedClaudeConfig(workspaceRoot) {
12
+ const configDir = getIsolatedClaudeConfigDir(workspaceRoot);
13
+ const statePath = getIsolatedClaudeStatePath(workspaceRoot);
14
+ fs.mkdirSync(configDir, { recursive: true });
15
+ writeJsonIfChanged(path.join(configDir, 'settings.json'), {});
16
+ writeJsonIfChanged(path.join(configDir, 'settings.local.json'), {});
17
+ writeJsonIfChanged(statePath, {});
18
+ // Keep Claude auth working without inheriting the user's full runtime config.
19
+ const defaultCredentialsPath = path.join(os.homedir(), '.claude', '.credentials.json');
20
+ const isolatedCredentialsPath = path.join(configDir, '.credentials.json');
21
+ if (!fs.existsSync(isolatedCredentialsPath) && fs.existsSync(defaultCredentialsPath)) {
22
+ fs.copyFileSync(defaultCredentialsPath, isolatedCredentialsPath);
23
+ }
24
+ return configDir;
25
+ }
26
+ function writeJsonIfChanged(filePath, value) {
27
+ const next = `${JSON.stringify(value, null, 2)}\n`;
28
+ const current = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : null;
29
+ if (current === next)
30
+ return;
31
+ fs.writeFileSync(filePath, next, 'utf8');
32
+ }