amalgm 0.1.44 → 0.1.47

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.
@@ -2,12 +2,12 @@
2
2
  * Agents storage — SQLite-backed local agent registry + conversation logs.
3
3
  *
4
4
  * The registry is the Local Live Store source of truth for agent capabilities.
5
- * Built-in harnesses are seeded as non-deletable rows. Custom agents are rows
6
- * in the same table, so discovery, snapshots, and agent-to-agent lookup use one
7
- * shape.
5
+ * Default harness agents are seeded as normal rows. User-created and default
6
+ * agents use the same editable/deletable shape.
8
7
  */
9
8
 
10
9
  const crypto = require('crypto');
10
+ const fs = require('fs');
11
11
  const os = require('os');
12
12
  const path = require('path');
13
13
  const {
@@ -25,10 +25,13 @@ const {
25
25
  } = require('../lib/storage');
26
26
  const { openLocalDb } = require('../state/db');
27
27
  const { insertStateEvent, publishStateEvent } = require('../state/events');
28
+ const { getPreferredAuthMethod } = require('../lib/prefs');
29
+ const credentialAdapter = require('../../credential-adapter');
28
30
 
29
31
  const DEFAULT_AGENT_STATUS = 'unknown';
30
32
  const CUSTOM_AGENT_STATUS = 'ready';
31
33
  const BUILTIN_AGENT_IDS = ['claude_code', 'codex', 'opencode', 'pi'];
34
+ const DEFAULT_AGENTS_SEEDED_FILE = path.join(STORAGE_DIR, '.default-agents-seeded');
32
35
  const AMALGM_COMPUTER_ID = process.env.AMALGM_COMPUTER_ID || '';
33
36
 
34
37
  const BUILTIN_AGENT_BLUEPRINTS = [
@@ -76,9 +79,9 @@ const BUILTIN_AGENTS = BUILTIN_AGENT_BLUEPRINTS.map((agent) => ({
76
79
  nativeMcps: [],
77
80
  tools: { mode: 'all', selectedToolIds: [] },
78
81
  mcp: { inheritAll: true, customServers: [], appIds: [], nativeMcps: [] },
79
- builtin: true,
80
- deletable: false,
81
- editable: false,
82
+ builtin: false,
83
+ deletable: true,
84
+ editable: true,
82
85
  status: DEFAULT_AGENT_STATUS,
83
86
  }));
84
87
 
@@ -133,6 +136,26 @@ function computerName() {
133
136
  || 'This computer';
134
137
  }
135
138
 
139
+ function persistedAuthMethod(baseHarnessId) {
140
+ const localFallback = typeof credentialAdapter.getPersistedAuthMode === 'function'
141
+ ? credentialAdapter.getPersistedAuthMode(baseHarnessId)
142
+ : 'amalgm';
143
+ if (typeof getPreferredAuthMethod === 'function') {
144
+ return getPreferredAuthMethod(baseHarnessId, localFallback);
145
+ }
146
+ return localFallback;
147
+ }
148
+
149
+ function defaultAgentAuthMethod(baseHarnessId, existing) {
150
+ const persisted = persistedAuthMethod(baseHarnessId);
151
+ const existingAuth = nonEmptyString(existing?.authMethod);
152
+ if (!existingAuth) return persisted;
153
+ if (existingAuth === 'amalgm' && persisted && persisted !== 'amalgm') {
154
+ return persisted;
155
+ }
156
+ return existingAuth;
157
+ }
158
+
136
159
  function normalizeLocation(inputLocation, fallback = {}) {
137
160
  const id = nonEmptyString(fallback.ownerComputerId) || computerId();
138
161
  const name = nonEmptyString(fallback.locationName) || computerName();
@@ -195,7 +218,7 @@ function normalizeToolConfig(tools, legacyMcp) {
195
218
  function normalizeAgent(input, existing) {
196
219
  if (!isObject(input)) throw new Error('agent must be an object');
197
220
  const id = nonEmptyString(input.id) || `custom-${crypto.randomUUID()}`;
198
- const builtin = input.builtin === true || BUILTIN_AGENT_IDS.includes(id);
221
+ const builtin = input.builtin === true;
199
222
  const baseHarnessId = nonEmptyString(input.baseHarnessId || input.base_harness_id) || id;
200
223
  const adapter = nonEmptyString(input.adapter) || baseHarnessId;
201
224
  const mcpConfig = normalizeMcpConfig(input.mcp, input.mcpAppIds, input.nativeMcps);
@@ -227,7 +250,7 @@ function normalizeAgent(input, existing) {
227
250
  ...mcpConfig,
228
251
  inheritAll: toolConfig.mode !== 'selected',
229
252
  },
230
- authMethod: input.authMethod || existing?.authMethod || 'amalgm',
253
+ authMethod: input.authMethod || existing?.authMethod || persistedAuthMethod(baseHarnessId),
231
254
  location,
232
255
  locationName: locationLabel(location),
233
256
  ownerComputerId,
@@ -238,8 +261,8 @@ function normalizeAgent(input, existing) {
238
261
  ? { ...defaultCapabilities({ tools: toolConfig }), ...input.capabilities }
239
262
  : defaultCapabilities({ tools: toolConfig }),
240
263
  builtin,
241
- deletable: input.deletable === false || builtin ? false : true,
242
- editable: input.editable === false || builtin ? false : true,
264
+ deletable: input.deletable === false ? false : true,
265
+ editable: input.editable === false ? false : true,
243
266
  createdAt,
244
267
  updatedAt,
245
268
  ...(input.configDir ? { configDir: input.configDir } : existing?.configDir ? { configDir: existing.configDir } : {}),
@@ -351,7 +374,6 @@ function writeAgentsJson(customAgents) {
351
374
  function readCustomAgentsFromDb(db = openLocalDb()) {
352
375
  return db.prepare(`
353
376
  SELECT * FROM agents
354
- WHERE builtin = 0
355
377
  ORDER BY updated_at DESC, id ASC
356
378
  `).all()
357
379
  .map(rowToAgent)
@@ -372,29 +394,36 @@ function customAgentRowCount(db = openLocalDb()) {
372
394
 
373
395
  function seedBuiltinAgents(options = {}) {
374
396
  const db = openLocalDb();
397
+ const alreadySeeded = fs.existsSync(DEFAULT_AGENTS_SEEDED_FILE);
375
398
  const events = db.transaction(() => {
376
399
  const inserted = [];
377
400
  for (const blueprint of BUILTIN_AGENT_BLUEPRINTS) {
378
401
  const existing = readAgentRow(db, blueprint.id);
402
+ if (!existing && alreadySeeded) continue;
379
403
  const next = normalizeAgent({
380
404
  ...blueprint,
381
- systemPrompt: '',
382
- files: [],
383
- skills: [],
384
- mcpAppIds: [],
385
- nativeMcps: [],
386
- tools: { mode: 'all', selectedToolIds: [] },
387
- mcp: { inheritAll: true, customServers: [], appIds: [], nativeMcps: [] },
388
- builtin: true,
389
- deletable: false,
390
- editable: false,
405
+ name: existing?.name || blueprint.name,
406
+ description: existing?.description ?? blueprint.description,
407
+ adapter: existing?.adapter || blueprint.adapter,
408
+ baseHarnessId: existing?.baseHarnessId || blueprint.baseHarnessId,
409
+ baseModelId: existing?.baseModelId || blueprint.baseModelId,
410
+ systemPrompt: existing?.systemPrompt || '',
411
+ files: existing?.files || [],
412
+ skills: existing?.skills || [],
413
+ mcpAppIds: existing?.mcpAppIds || [],
414
+ nativeMcps: existing?.nativeMcps || [],
415
+ tools: existing?.tools || { mode: 'all', selectedToolIds: [] },
416
+ mcp: existing?.mcp || { inheritAll: true, customServers: [], appIds: [], nativeMcps: [] },
417
+ builtin: false,
418
+ deletable: true,
419
+ editable: true,
391
420
  status: existing?.status || DEFAULT_AGENT_STATUS,
392
421
  installStatus: existing?.installStatus || 'unknown',
393
422
  authStatus: existing?.authStatus || 'unknown',
394
423
  authDetails: existing?.authDetails,
395
424
  configDir: existing?.configDir,
396
425
  createdAt: existing?.createdAt,
397
- authMethod: existing?.authMethod || 'amalgm',
426
+ authMethod: defaultAgentAuthMethod(existing?.baseHarnessId || blueprint.baseHarnessId, existing),
398
427
  }, existing);
399
428
  if (existing && agentsEqualForWrite(existing, next)) continue;
400
429
  upsertAgentRow(db, next);
@@ -406,13 +435,17 @@ function seedBuiltinAgents(options = {}) {
406
435
  }
407
436
  return inserted;
408
437
  })();
438
+ try {
439
+ ensureDir(STORAGE_DIR);
440
+ fs.writeFileSync(DEFAULT_AGENTS_SEEDED_FILE, nowIso());
441
+ } catch {
442
+ // Best-effort marker; default seeding still works without it.
443
+ }
409
444
  publishEvents(events);
410
445
  }
411
446
 
412
447
  function migrateLegacyAgentsJsonIfNeeded() {
413
448
  const db = openLocalDb();
414
- if (customAgentRowCount(db) > 0) return;
415
-
416
449
  const legacy = readJson(AGENTS_FILE, { version: 1, agents: [] });
417
450
  const legacyAgents = Array.isArray(legacy?.agents) ? legacy.agents : [];
418
451
  if (legacyAgents.length === 0) return;
@@ -421,6 +454,7 @@ function migrateLegacyAgentsJsonIfNeeded() {
421
454
  for (const agent of legacyAgents) {
422
455
  const normalized = normalizeAgent(agent, null);
423
456
  if (normalized.builtin) continue;
457
+ if (readAgentRow(db, normalized.id)) continue;
424
458
  upsertAgentRow(db, normalized);
425
459
  }
426
460
  })();
@@ -523,10 +557,6 @@ function updateAgent(agentId, updates, options = {}) {
523
557
  const db = openLocalDb();
524
558
  const existing = readAgentRow(db, agentId);
525
559
  if (!existing) throw new Error(`Agent not found: ${agentId}`);
526
- if (existing.builtin && options.allowBuiltinUpdate !== true) {
527
- throw new Error(`Built-in agent "${existing.name}" cannot be edited`);
528
- }
529
-
530
560
  const requestedName = nonEmptyString(updates?.name);
531
561
  if (requestedName) {
532
562
  const duplicate = getAllAgentsWithBuiltins()
@@ -569,12 +599,12 @@ function deleteAgent(agentId, options = {}) {
569
599
  const db = openLocalDb();
570
600
  const existing = readAgentRow(db, agentId);
571
601
  if (!existing) throw new Error(`Agent not found: ${agentId}`);
572
- if (existing.builtin || existing.deletable === false) {
573
- throw new Error(`Built-in agent "${existing.name}" cannot be deleted`);
602
+ if (existing.deletable === false) {
603
+ throw new Error(`Agent "${existing.name}" cannot be deleted`);
574
604
  }
575
605
 
576
606
  const event = db.transaction(() => {
577
- db.prepare('DELETE FROM agents WHERE id = ? AND builtin = 0').run(agentId);
607
+ db.prepare('DELETE FROM agents WHERE id = ?').run(agentId);
578
608
  return insertAgentEvent(db, 'delete', existing, {
579
609
  source: options.source || 'agents:delete',
580
610
  });
@@ -14,7 +14,7 @@ const crypto = require('crypto');
14
14
  const path = require('path');
15
15
  const os = require('os');
16
16
 
17
- const { AMALGM_COMPUTER_ID, AMALGM_USER_ID, DEFAULT_CWD, STORAGE_DIR } = require('../config');
17
+ const { AMALGM_USER_ID, DEFAULT_CWD, STORAGE_DIR } = require('../config');
18
18
  const { runThroughChatServer } = require('../lib/chat-runner');
19
19
  const {
20
20
  hasSupabase,
@@ -31,11 +31,7 @@ const {
31
31
  } = require('../lib/prefs');
32
32
  const { textResult, errorResult } = require('../lib/tool-result');
33
33
  const { ensureDir } = require('../lib/storage');
34
- const {
35
- appendAgentConvoLog,
36
- normalizeConversationId,
37
- resolveAgentByNameOrId,
38
- } = require('./store');
34
+ const { appendAgentConvoLog, normalizeConversationId, resolveAgentByNameOrId } = require('./store');
39
35
  const {
40
36
  chatInputToLegacyFields,
41
37
  getChatInputText,
@@ -465,8 +461,6 @@ async function deliverResponseToCallerSession({
465
461
  callerResponseDelivered: true,
466
462
  callerResponseDeliveredAt: deliveredAt,
467
463
  callerResponseTarget: targetSessionId,
468
- callerResponseText: messageText,
469
- callerResponseDescription: description || null,
470
464
  });
471
465
  appendStatusLog(sourceSessionId, 'caller_response_delivered', {
472
466
  targetConversationId: targetSessionId,
@@ -537,6 +531,7 @@ async function handleTalkToAgent(args, context = {}) {
537
531
  `Agent not found: ${agentLookup}. Use agents_list to see available agents.`,
538
532
  );
539
533
  }
534
+
540
535
  const isNewConversation = !normalizedConversationId;
541
536
  const sessionId = normalizedConversationId || crypto.randomUUID();
542
537
  const userMessageId = crypto.randomUUID();
@@ -552,10 +547,13 @@ async function handleTalkToAgent(args, context = {}) {
552
547
  const persistedAuthMethod = credentialAdapter.VALID_HARNESS_IDS.includes(agent.baseHarnessId)
553
548
  ? credentialAdapter.getPersistedAuthMode(agent.baseHarnessId)
554
549
  : 'amalgm';
550
+ const preferredAuthMethod = getPreferredAuthMethod(agent.baseHarnessId, persistedAuthMethod);
551
+ const isDefaultAgentClass = agent.id === agent.baseHarnessId;
555
552
  const defaultAuthMethod = coerceAuthMethodForHarness(
556
553
  agent.baseHarnessId,
557
- agent.authMethod
558
- || getPreferredAuthMethod(agent.baseHarnessId, persistedAuthMethod),
554
+ isDefaultAgentClass && agent.authMethod === 'amalgm' && preferredAuthMethod !== 'amalgm'
555
+ ? preferredAuthMethod
556
+ : agent.authMethod || preferredAuthMethod,
559
557
  );
560
558
  const agentFiles = normalizeStringList(agent.files);
561
559
  const agentSkills = normalizeStringList(agent.skills);
@@ -707,7 +705,6 @@ async function handleTalkToAgent(args, context = {}) {
707
705
  await supabaseInsert('sessions', {
708
706
  id: sessionId,
709
707
  user_id: AMALGM_USER_ID,
710
- computer_id: AMALGM_COMPUTER_ID,
711
708
  container_id: os.hostname() || 'agent-conversation',
712
709
  harness: agent.id,
713
710
  title: taskDescription || 'New Chat',
@@ -779,7 +776,6 @@ async function handleTalkToAgent(args, context = {}) {
779
776
  chatInput: normalizedChatInput,
780
777
  prompt: legacyChatFields.prompt,
781
778
  userId: AMALGM_USER_ID,
782
- computerId: AMALGM_COMPUTER_ID,
783
779
  codeSessionId: sessionId,
784
780
  assistantMessageId,
785
781
  userMessageId,
@@ -912,11 +908,8 @@ async function handleTalkToAgent(args, context = {}) {
912
908
  },
913
909
  },
914
910
  );
915
- const activeAfterTurn = summarizeActiveConversation(sessionId);
916
- const deliveredResponseText = String(activeAfterTurn?.callerResponseText || '').trim();
917
- const finalOutputText = String(outputText || '').trim() ? outputText : deliveredResponseText;
918
911
  const warnings = [];
919
- if (!String(finalOutputText || '').trim()) {
912
+ if (!String(outputText || '').trim()) {
920
913
  warnings.push(
921
914
  metrics?.toolEvents > 0
922
915
  ? 'Agent completed without visible text after tool activity; check the session log or workspace diff before retrying.'
@@ -933,7 +926,7 @@ async function handleTalkToAgent(args, context = {}) {
933
926
  agentName: agent.name,
934
927
  runId,
935
928
  description: taskDescription,
936
- message: finalOutputText,
929
+ message: outputText,
937
930
  timestamp: new Date().toISOString(),
938
931
  stopReason,
939
932
  usage,
@@ -954,7 +947,7 @@ async function handleTalkToAgent(args, context = {}) {
954
947
  });
955
948
 
956
949
  console.log(
957
- `[AmalgmMCP:Agent] ${agent.name} responded (${finalOutputText.length} chars, session: ${sessionId})`,
950
+ `[AmalgmMCP:Agent] ${agent.name} responded (${outputText.length} chars, session: ${sessionId})`,
958
951
  );
959
952
 
960
953
  if (
@@ -982,7 +975,7 @@ async function handleTalkToAgent(args, context = {}) {
982
975
  status: 'completed',
983
976
  isNewConversation,
984
977
  longMessageContext,
985
- outputText: finalOutputText,
978
+ outputText,
986
979
  stopReason,
987
980
  usage,
988
981
  metrics,
@@ -990,9 +983,6 @@ async function handleTalkToAgent(args, context = {}) {
990
983
  recovery:
991
984
  'Use agents_get_conversation with this conversation_id if the caller times out or needs the local transcript.',
992
985
  includeMetrics,
993
- extra: deliveredResponseText && !String(outputText || '').trim()
994
- ? { response_source: 'return_channel' }
995
- : {},
996
986
  }),
997
987
  null,
998
988
  2,
@@ -26,20 +26,17 @@ module.exports = [
26
26
  {
27
27
  name: 'agents_list',
28
28
  description:
29
- 'List all available agents (built-in defaults + custom user-created agents). Use this to discover which agents you can talk to via talk_to_agent.',
29
+ 'List all available agent classes. Use this to discover which agents you can talk to via talk_to_agent.',
30
30
  inputSchema: { type: 'object', properties: {} },
31
31
  async handler() {
32
32
  const all = getAllAgentsWithBuiltins();
33
33
  if (all.length === 0) return textResult('No agents available.');
34
34
  const summary = all
35
35
  .map((a) => {
36
- const tag = a.builtin ? '[built-in]' : '[custom]';
37
- const model = a.builtin
38
- ? getSelectedModel(a.baseHarnessId) || a.baseModelId
39
- : a.baseModelId ||
40
- getSelectedModel(a.baseHarnessId) ||
41
- DEFAULT_SELECTED_MODELS[a.baseHarnessId];
42
- return `- ${a.name} (${a.id}) ${tag}\n ${a.description || 'No description'}\n adapter: ${a.adapter || a.baseHarnessId} | harness: ${a.baseHarnessId} | model: ${model}\n location: ${a.locationName || a.location?.name || 'this computer'} | status: ${a.status || 'unknown'}`;
36
+ const model = a.baseModelId ||
37
+ getSelectedModel(a.baseHarnessId) ||
38
+ DEFAULT_SELECTED_MODELS[a.baseHarnessId];
39
+ return `- ${a.name} (${a.id})\n ${a.description || 'No description'}\n adapter: ${a.adapter || a.baseHarnessId} | harness: ${a.baseHarnessId} | model: ${model}\n location: ${a.locationName || a.location?.name || 'this computer'} | status: ${a.status || 'unknown'}`;
43
40
  })
44
41
  .join('\n\n');
45
42
  return textResult(`Available agents:\n\n${summary}`);
@@ -63,11 +60,9 @@ module.exports = [
63
60
  description: agent.description,
64
61
  adapter: agent.adapter || agent.baseHarnessId,
65
62
  baseHarnessId: agent.baseHarnessId,
66
- baseModelId: agent.builtin
67
- ? getSelectedModel(agent.baseHarnessId) || agent.baseModelId
68
- : agent.baseModelId ||
69
- getSelectedModel(agent.baseHarnessId) ||
70
- DEFAULT_SELECTED_MODELS[agent.baseHarnessId],
63
+ baseModelId: agent.baseModelId ||
64
+ getSelectedModel(agent.baseHarnessId) ||
65
+ DEFAULT_SELECTED_MODELS[agent.baseHarnessId],
71
66
  systemPrompt: agent.systemPrompt ? '(configured)' : '(none)',
72
67
  mcp: {
73
68
  inheritAll: agent.mcp?.inheritAll ?? true,
@@ -10,6 +10,7 @@ const path = require('path');
10
10
  const { spawn } = require('child_process');
11
11
  const { DEFAULT_CWD } = require('../config');
12
12
  const activeMemory = require('../../chat-core/tooling/active-memory');
13
+ const { runtimePort } = require('../../../lib/runtime-manifest');
13
14
  const { syncArtifactRoutesToGateway } = require('./advertise');
14
15
  const {
15
16
  allocatePort,
@@ -88,8 +89,8 @@ function buildEnv(artifact) {
88
89
  AMALGM_ARTIFACT_ID: artifact.id,
89
90
  AMALGM_ARTIFACT_REF: artifact.artifactRef,
90
91
  AMALGM_ARTIFACT_URL: artifact.publicUrl,
91
- AMALGM_CHAT_SERVER_URL: `http://127.0.0.1:${process.env.CHAT_SERVER_PORT || 8084}`,
92
- AMALGM_MCP_URL: `http://127.0.0.1:${process.env.AMALGM_MCP_PORT || 8083}`,
92
+ AMALGM_CHAT_SERVER_URL: `http://127.0.0.1:${runtimePort('chat-server')}`,
93
+ AMALGM_MCP_URL: `http://127.0.0.1:${runtimePort('amalgm-mcp')}`,
93
94
  };
94
95
  }
95
96
 
@@ -14,8 +14,9 @@ const {
14
14
  proxyBaseUrl,
15
15
  readProxyToken,
16
16
  } = require('../proxy-token-store');
17
+ const { runtimePort } = require('../../lib/runtime-manifest');
17
18
 
18
- const PORT = parseInt(process.env.AMALGM_MCP_PORT || '8083', 10);
19
+ const PORT = runtimePort('amalgm-mcp');
19
20
  const MCP_PROTOCOL_VERSION = '2024-11-05';
20
21
 
21
22
  const AMALGM_DIR = process.env.AMALGM_DIR || path.join(os.homedir(), '.amalgm');
@@ -46,7 +47,7 @@ function cleanString(value) {
46
47
  }
47
48
 
48
49
  // Chat server (local Next.js/Electron) — same process tree; no cloud hop.
49
- const CHAT_SERVER_URL = `http://localhost:${process.env.CHAT_SERVER_PORT || 8084}`;
50
+ const CHAT_SERVER_URL = `http://localhost:${runtimePort('chat-server')}`;
50
51
 
51
52
  const SCHEDULER_INTERVAL_MS = 30_000;
52
53
 
@@ -6,8 +6,8 @@
6
6
  * (default: ~/.amalgm/) — same pattern as scripts/credential-adapter.js.
7
7
  *
8
8
  * Ports:
9
- * 8083 this server
10
- * 8084 chat-server (we delegate task/event/agent runs here)
9
+ * this server uses the amalgm-mcp runtime port
10
+ * chat-server uses the chat-server runtime port for task/event/agent runs
11
11
  *
12
12
  * Modules:
13
13
  * config.js env + paths
@@ -7,7 +7,7 @@
7
7
  * - agents/talk.js (talk_to_agent tool)
8
8
  * - email/inbound.js (email replies/new email → agent runs)
9
9
  *
10
- * POSTs to chat-server (:8084) and consumes the SSE stream, accumulating
10
+ * POSTs to chat-server and consumes the SSE stream, accumulating
11
11
  * text from agent_message_chunk events and returning the final output.
12
12
  */
13
13
 
@@ -2,27 +2,11 @@
2
2
 
3
3
  const { currentSeq } = require('./events');
4
4
 
5
- const DEFAULT_RESOURCES = [
6
- 'tasks',
7
- 'event_triggers',
8
- 'agents',
9
- 'artifacts',
10
- 'toolbox',
11
- 'tools',
12
- 'tool_actions',
13
- 'projects',
14
- 'workspaces',
15
- 'memories',
16
- ];
5
+ const DEFAULT_RESOURCES = ['tasks', 'event_triggers', 'agents', 'artifacts', 'toolbox', 'tools', 'tool_actions', 'hooks', 'projects', 'workspaces', 'memories'];
17
6
 
18
7
  function normalizeResources(resources) {
19
- if (!resources) return DEFAULT_RESOURCES;
20
- const values = Array.isArray(resources)
21
- ? resources
22
- : String(resources).split(',');
23
- const clean = values
24
- .map((value) => String(value || '').trim())
25
- .filter(Boolean);
8
+ const values = resources ? String(resources).split(',') : DEFAULT_RESOURCES;
9
+ const clean = values.map((value) => String(value || '').trim()).filter(Boolean);
26
10
  return clean.length > 0 ? Array.from(new Set(clean)) : DEFAULT_RESOURCES;
27
11
  }
28
12
 
@@ -36,18 +20,17 @@ function readResource(resource, cache) {
36
20
  return require('../agents/store').getAllAgentsWithBuiltins();
37
21
  case 'artifacts':
38
22
  return require('../artifacts/store').loadArtifacts().artifacts;
39
- case 'toolbox': {
23
+ case 'toolbox':
40
24
  cache.toolbox ||= require('../toolbox/store').readToolbox();
41
25
  return cache.toolbox;
42
- }
43
- case 'tools': {
26
+ case 'tools':
44
27
  cache.toolbox ||= require('../toolbox/store').readToolbox();
45
28
  return cache.toolbox.tools;
46
- }
47
- case 'tool_actions': {
29
+ case 'tool_actions':
48
30
  cache.toolbox ||= require('../toolbox/store').readToolbox();
49
31
  return cache.toolbox.toolActions;
50
- }
32
+ case 'hooks':
33
+ return require('../agents/hooks').collectNativeHooks();
51
34
  case 'projects':
52
35
  case 'workspaces': {
53
36
  const workspaceStore = require('../workspace/store');
@@ -68,37 +51,18 @@ function buildSnapshot(resourcesInput) {
68
51
  const beforeSeq = currentSeq();
69
52
  const cache = {};
70
53
  const data = {};
71
-
72
54
  for (const resource of resources) {
73
55
  const value = readResource(resource, cache);
74
56
  if (value !== undefined) data[resource] = value;
75
57
  }
76
-
77
58
  const afterSeq = currentSeq();
78
59
  if (beforeSeq === afterSeq) {
79
- return {
80
- seq: afterSeq,
81
- stable: true,
82
- resources: data,
83
- };
60
+ return { seq: afterSeq, stable: true, resources: data };
84
61
  }
85
-
86
- lastUnstable = {
87
- seq: beforeSeq,
88
- stable: false,
89
- resources: data,
90
- };
62
+ lastUnstable = { seq: beforeSeq, stable: false, resources: data };
91
63
  }
92
64
 
93
- return lastUnstable || {
94
- seq: currentSeq(),
95
- stable: true,
96
- resources: {},
97
- };
65
+ return lastUnstable || { seq: currentSeq(), stable: true, resources: {} };
98
66
  }
99
67
 
100
- module.exports = {
101
- DEFAULT_RESOURCES,
102
- buildSnapshot,
103
- normalizeResources,
104
- };
68
+ module.exports = { DEFAULT_RESOURCES, buildSnapshot, normalizeResources };
@@ -8,6 +8,7 @@ const { normalizeClaudeMessage, usageRecordsFromClaudeResult, usageFromClaude }
8
8
  const { recordNativeEvent } = require('../recorder');
9
9
  const { toClaudeMcpServers } = require('../tooling/mcp-bundle');
10
10
  const { bundledClaudeBinary } = require('../tooling/native-binaries');
11
+ const { syncNativeHarnessConfig } = require('../tooling/native-config');
11
12
  const { importPackage } = require('../tooling/package-import');
12
13
  const { composeSystemPrompt } = require('../tooling/system-prompt');
13
14
 
@@ -31,6 +32,7 @@ class ClaudeAdapter {
31
32
  }
32
33
 
33
34
  options(contract, extra = {}) {
35
+ syncNativeHarnessConfig(contract);
34
36
  const systemPrompt = composeSystemPrompt(contract);
35
37
  const pathToClaudeCodeExecutable = process.env.CLAUDE_CODE_BINARY || bundledClaudeBinary();
36
38
  return {
@@ -47,7 +49,7 @@ class ClaudeAdapter {
47
49
  ...(process.env.CHAT_CORE_DEBUG_CLAUDE === '1'
48
50
  ? { debug: true, debugFile: path.join(contract.auth.runtimeHome || process.cwd(), 'claude-debug.log') }
49
51
  : {}),
50
- settingSources: [],
52
+ settingSources: ['user', 'project', 'local'],
51
53
  strictMcpConfig: false,
52
54
  ...extra,
53
55
  };