@marifold/core 0.64.1 → 0.66.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 (77) hide show
  1. package/dist/agent/AgentRunner.d.ts +7 -0
  2. package/dist/agent/AgentRunner.d.ts.map +1 -1
  3. package/dist/agent/AgentRunner.js +27 -1
  4. package/dist/agent/AgentRunner.js.map +1 -1
  5. package/dist/agent/RunWorkspace.d.ts +10 -0
  6. package/dist/agent/RunWorkspace.d.ts.map +1 -1
  7. package/dist/agent/RunWorkspace.js +19 -1
  8. package/dist/agent/RunWorkspace.js.map +1 -1
  9. package/dist/agent/tools/ReadFileTool.d.ts +7 -0
  10. package/dist/agent/tools/ReadFileTool.d.ts.map +1 -1
  11. package/dist/agent/tools/ReadFileTool.js +24 -2
  12. package/dist/agent/tools/ReadFileTool.js.map +1 -1
  13. package/dist/app/AppStore.d.ts +11 -1
  14. package/dist/app/AppStore.d.ts.map +1 -1
  15. package/dist/app/AppStore.js +105 -16
  16. package/dist/app/AppStore.js.map +1 -1
  17. package/dist/app/SkillAppCompiler.d.ts.map +1 -1
  18. package/dist/app/SkillAppCompiler.js +314 -44
  19. package/dist/app/SkillAppCompiler.js.map +1 -1
  20. package/dist/app/SkillAppDsl.d.ts +60 -4
  21. package/dist/app/SkillAppDsl.d.ts.map +1 -1
  22. package/dist/app/SkillAppDsl.js +44 -0
  23. package/dist/app/SkillAppDsl.js.map +1 -1
  24. package/dist/app/SkillAppInstanceRegistry.d.ts +3 -2
  25. package/dist/app/SkillAppInstanceRegistry.d.ts.map +1 -1
  26. package/dist/app/SkillAppInstanceRegistry.js +191 -11
  27. package/dist/app/SkillAppInstanceRegistry.js.map +1 -1
  28. package/dist/app/SkillAppResolver.d.ts +5 -2
  29. package/dist/app/SkillAppResolver.d.ts.map +1 -1
  30. package/dist/app/SkillAppResolver.js +66 -15
  31. package/dist/app/SkillAppResolver.js.map +1 -1
  32. package/dist/app/SkillAppSchema.d.ts +84 -8
  33. package/dist/app/SkillAppSchema.d.ts.map +1 -1
  34. package/dist/app/SkillAppSchema.js +2 -1
  35. package/dist/app/SkillAppSchema.js.map +1 -1
  36. package/dist/config/ConfigLoader.d.ts.map +1 -1
  37. package/dist/config/ConfigLoader.js +17 -3
  38. package/dist/config/ConfigLoader.js.map +1 -1
  39. package/dist/config/ConfigManager.d.ts.map +1 -1
  40. package/dist/config/ConfigManager.js +13 -2
  41. package/dist/config/ConfigManager.js.map +1 -1
  42. package/dist/config/ConfigSchema.d.ts +8 -2
  43. package/dist/config/ConfigSchema.d.ts.map +1 -1
  44. package/dist/config/ConfigSchema.js.map +1 -1
  45. package/dist/config/MarifoldOpenAICompatProvider.d.ts +1 -1
  46. package/dist/config/MarifoldOpenAICompatProvider.d.ts.map +1 -1
  47. package/dist/config/MarifoldOpenAICompatProvider.js +8 -6
  48. package/dist/config/MarifoldOpenAICompatProvider.js.map +1 -1
  49. package/dist/config/ProviderFactory.d.ts +6 -4
  50. package/dist/config/ProviderFactory.d.ts.map +1 -1
  51. package/dist/config/ProviderFactory.js +41 -6
  52. package/dist/config/ProviderFactory.js.map +1 -1
  53. package/dist/index.d.ts +8 -6
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +14 -3
  56. package/dist/index.js.map +1 -1
  57. package/dist/runtime/MarifoldRuntime.d.ts +17 -9
  58. package/dist/runtime/MarifoldRuntime.d.ts.map +1 -1
  59. package/dist/runtime/MarifoldRuntime.js +270 -59
  60. package/dist/runtime/MarifoldRuntime.js.map +1 -1
  61. package/dist/runtime/MarifoldTypes.d.ts +1 -1
  62. package/dist/runtime/MarifoldTypes.d.ts.map +1 -1
  63. package/dist/runtime/NativeWebSearch.d.ts +9 -0
  64. package/dist/runtime/NativeWebSearch.d.ts.map +1 -0
  65. package/dist/runtime/NativeWebSearch.js +17 -0
  66. package/dist/runtime/NativeWebSearch.js.map +1 -0
  67. package/dist/search/OllamaSearchBackend.d.ts +22 -0
  68. package/dist/search/OllamaSearchBackend.d.ts.map +1 -0
  69. package/dist/search/OllamaSearchBackend.js +64 -0
  70. package/dist/search/OllamaSearchBackend.js.map +1 -0
  71. package/dist/search/createSearchBackend.d.ts +4 -3
  72. package/dist/search/createSearchBackend.d.ts.map +1 -1
  73. package/dist/search/createSearchBackend.js +13 -3
  74. package/dist/search/createSearchBackend.js.map +1 -1
  75. package/dist/skill/SkillStore.d.ts +2 -2
  76. package/dist/skill/SkillStore.js +2 -2
  77. package/package.json +1 -1
@@ -81,6 +81,7 @@ const SkillAppResolver_1 = require("../app/SkillAppResolver");
81
81
  const SkillAppInstanceRegistry_1 = require("../app/SkillAppInstanceRegistry");
82
82
  const TaskStore_1 = require("../tasks/TaskStore");
83
83
  const WorkspacePaths_1 = require("../workspace/WorkspacePaths");
84
+ const NativeWebSearch_1 = require("./NativeWebSearch");
84
85
  // Older OpenAI-compatible gateways still take a raw `{think}` body option.
85
86
  // Priest 2.8 owns neutral reasoning for Ollama, Anthropic, and Responses.
86
87
  const LEGACY_THINK_PROVIDER_NAMES = new Set(['bailian', 'alibaba_cloud']);
@@ -97,6 +98,7 @@ class MarifoldRuntime {
97
98
  memoryStore;
98
99
  taskStore;
99
100
  searchBackend;
101
+ searchBackendOverridden;
100
102
  scheduleStore;
101
103
  constructor(options) {
102
104
  this.options = options;
@@ -107,6 +109,7 @@ class MarifoldRuntime {
107
109
  this.providerFactory = new ProviderFactory_1.ProviderFactory(config, configPath);
108
110
  this.memoryStore = new MemoryStore_1.MemoryStore(config.paths.profilesDir);
109
111
  this.taskStore = new TaskStore_1.TaskStore(config.paths.tasksDir);
112
+ this.searchBackendOverridden = options.searchBackend !== undefined;
110
113
  this.searchBackend = options.searchBackend
111
114
  ?? (0, createSearchBackend_1.createSearchBackend)((0, ConfigSchema_1.resolveWebSearchConfig)(config.webSearch));
112
115
  this.scheduleStore = new ScheduleStore_1.ScheduleStore(config.paths.schedulesDir ?? (0, WorkspacePaths_1.defaultSchedulesDir)());
@@ -138,10 +141,14 @@ class MarifoldRuntime {
138
141
  const engine = this.createEngine(settings.provider, Boolean(request.sessionId) && !replacing && !isolated, request.profileContext !== false);
139
142
  const memoryOn = this.memoryEnabled(settings.profile, request.memories);
140
143
  const memory = this.memoryForRequest(settings.profile, request.memories, request.prompt, settings.think);
141
- const webSearchMode = this.resolveWebSearchMode(settings, request.chatTools !== false);
142
- const chatTools = this.chatTools(request, webSearchMode);
143
- const priestRequest = {
144
- config: this.toPriestConfig(settings, webSearchMode === 'native'),
144
+ const searchResolution = this.resolveWebSearch(settings, request.chatTools !== false);
145
+ let webSearchMode = searchResolution.mode;
146
+ let chatTools = this.chatTools(request, webSearchMode);
147
+ const nativeFallbackAvailable = webSearchMode === 'native'
148
+ && this.fallbackWebSearchAvailable(settings, request.chatTools !== false);
149
+ let nativeFallbackAttempted = false;
150
+ const buildPriestRequest = () => ({
151
+ config: this.toPriestConfig(settings, webSearchMode === 'native' ? searchResolution.nativeStrategy : 'none'),
145
152
  profile: settings.profile,
146
153
  prompt: request.prompt,
147
154
  session: request.sessionId && !replacing && !isolated
@@ -155,10 +162,11 @@ class MarifoldRuntime {
155
162
  memory,
156
163
  images: preparedImages.images.length > 0 ? preparedImages.images : undefined,
157
164
  userContext: request.userContext,
158
- providerTools: this.providerToolsFor(webSearchMode),
159
- };
165
+ providerTools: this.providerToolsFor(webSearchMode, searchResolution.nativeStrategy),
166
+ });
167
+ let priestRequest = buildPriestRequest();
160
168
  const exchange = [];
161
- const maxIterations = chatTools ? CHAT_TOOL_MAX_ITERATIONS : 1;
169
+ const maxIterations = chatTools || nativeFallbackAvailable ? CHAT_TOOL_MAX_ITERATIONS : 1;
162
170
  let response;
163
171
  let aggregateUsage;
164
172
  // Keep the non-streaming CLI/service path at parity with stream(): a model
@@ -172,6 +180,18 @@ class MarifoldRuntime {
172
180
  ...(exchange.length > 0 ? { toolExchange: exchange } : {}),
173
181
  }, request.signal ? { signal: request.signal } : undefined);
174
182
  aggregateUsage = sumUsage(aggregateUsage, response.usage);
183
+ if (!response.ok
184
+ && webSearchMode === 'native'
185
+ && nativeFallbackAvailable
186
+ && !nativeFallbackAttempted
187
+ && (0, NativeWebSearch_1.isNativeWebSearchCapabilityError)(response.error)) {
188
+ nativeFallbackAttempted = true;
189
+ webSearchMode = 'fallback';
190
+ chatTools = this.chatTools(request, webSearchMode);
191
+ priestRequest = buildPriestRequest();
192
+ iteration -= 1;
193
+ continue;
194
+ }
175
195
  if (!response.ok || !chatTools || !response.toolCalls?.length)
176
196
  break;
177
197
  exchange.push({
@@ -241,10 +261,14 @@ class MarifoldRuntime {
241
261
  const engine = this.createEngine(settings.provider, enginePersistsSession, request.profileContext !== false);
242
262
  const memoryOn = this.memoryEnabled(settings.profile, request.memories);
243
263
  const memory = this.memoryForRequest(settings.profile, request.memories, request.prompt, settings.think);
244
- const webSearchMode = this.resolveWebSearchMode(settings, request.chatTools !== false);
245
- const chatTools = this.chatTools(request, webSearchMode);
246
- const baseRequest = {
247
- config: this.toPriestConfig(settings, webSearchMode === 'native'),
264
+ const searchResolution = this.resolveWebSearch(settings, request.chatTools !== false);
265
+ let webSearchMode = searchResolution.mode;
266
+ let chatTools = this.chatTools(request, webSearchMode);
267
+ const nativeFallbackAvailable = webSearchMode === 'native'
268
+ && this.fallbackWebSearchAvailable(settings, request.chatTools !== false);
269
+ let nativeFallbackAttempted = false;
270
+ const buildBaseRequest = () => ({
271
+ config: this.toPriestConfig(settings, webSearchMode === 'native' ? searchResolution.nativeStrategy : 'none'),
248
272
  profile: settings.profile,
249
273
  prompt: request.prompt,
250
274
  session: request.sessionId && !replacing && !isolated
@@ -258,19 +282,21 @@ class MarifoldRuntime {
258
282
  memory,
259
283
  images: preparedImages.images.length > 0 ? preparedImages.images : undefined,
260
284
  userContext: request.userContext,
261
- providerTools: this.providerToolsFor(webSearchMode),
262
- };
285
+ providerTools: this.providerToolsFor(webSearchMode, searchResolution.nativeStrategy),
286
+ });
287
+ let baseRequest = buildBaseRequest();
263
288
  // Caller-executed fallback/read loop. Provider-hosted search is carried
264
289
  // separately and does not enter Marifold's tool exchange.
265
290
  // Intermediate tool-call turns are turn-local; the engine persists the
266
291
  // session only on the loop's final response, and memory payloads are
267
292
  // applied only from that final response.
268
293
  const exchange = [];
269
- const maxIterations = chatTools ? CHAT_TOOL_MAX_ITERATIONS : 1;
294
+ const maxIterations = chatTools || nativeFallbackAvailable ? CHAT_TOOL_MAX_ITERATIONS : 1;
270
295
  for (let iteration = 0; iteration < maxIterations; iteration += 1) {
271
296
  const stripper = new MemoryControls_1.MemoryControlStripper();
272
297
  const visibleParts = [];
273
298
  let done;
299
+ let providerOutputStarted = false;
274
300
  const lastIteration = iteration === maxIterations - 1;
275
301
  for await (const event of engine.streamEvents({
276
302
  ...baseRequest,
@@ -278,6 +304,7 @@ class MarifoldRuntime {
278
304
  ...(exchange.length > 0 ? { toolExchange: exchange } : {}),
279
305
  }, request.signal ? { signal: request.signal } : undefined)) {
280
306
  if (event.type === 'text_delta') {
307
+ providerOutputStarted = true;
281
308
  const visible = stripper.feed(event.text);
282
309
  if (visible) {
283
310
  visibleParts.push(visible);
@@ -285,8 +312,14 @@ class MarifoldRuntime {
285
312
  }
286
313
  }
287
314
  else if (event.type === 'reasoning_summary_delta') {
315
+ providerOutputStarted = true;
288
316
  onReasoningSummary?.(event.text);
289
317
  }
318
+ else if (event.type === 'tool_call_start'
319
+ || event.type === 'tool_call_delta'
320
+ || event.type === 'tool_call_end') {
321
+ providerOutputStarted = true;
322
+ }
290
323
  else if (event.type === 'done') {
291
324
  done = event.response;
292
325
  }
@@ -297,6 +330,18 @@ class MarifoldRuntime {
297
330
  yield tail;
298
331
  }
299
332
  aggregateUsage = sumUsage(aggregateUsage, done?.usage);
333
+ if (!providerOutputStarted
334
+ && webSearchMode === 'native'
335
+ && nativeFallbackAvailable
336
+ && !nativeFallbackAttempted
337
+ && (0, NativeWebSearch_1.isNativeWebSearchCapabilityError)(done?.error)) {
338
+ nativeFallbackAttempted = true;
339
+ webSearchMode = 'fallback';
340
+ chatTools = this.chatTools(request, webSearchMode);
341
+ baseRequest = buildBaseRequest();
342
+ iteration -= 1;
343
+ continue;
344
+ }
300
345
  if (done?.error) {
301
346
  this.discardFailedNewSession(request.sessionId, sessionWasMissing);
302
347
  throw MarifoldError_1.MarifoldError.providerError(done.error.message, settings.provider, settings.model, done.error.code);
@@ -355,7 +400,7 @@ class MarifoldRuntime {
355
400
  }
356
401
  }
357
402
  }
358
- /** Run the web search backend directly (the /search chat command). */
403
+ /** Run the selected web-search backend directly for non-chat integrations. */
359
404
  async searchWeb(query, maxResults) {
360
405
  const config = (0, ConfigSchema_1.resolveWebSearchConfig)(this.options.loadedConfig.config.webSearch);
361
406
  const results = await this.searchBackend.search(query, maxResults ?? config.maxResults);
@@ -495,6 +540,9 @@ class MarifoldRuntime {
495
540
  * validation as the CLI's `config set` (see ConfigManager.setValue). */
496
541
  setConfigValue(key, value) {
497
542
  new ConfigManager_1.ConfigManager(this.options.loadedConfig).setValue(key, value);
543
+ if (key.startsWith('web_search.') && !this.searchBackendOverridden) {
544
+ this.searchBackend = (0, createSearchBackend_1.createSearchBackend)((0, ConfigSchema_1.resolveWebSearchConfig)(this.options.loadedConfig.config.webSearch));
545
+ }
498
546
  }
499
547
  /** Read a config value by dotted key (CLI `config get`). */
500
548
  getConfigValue(key) {
@@ -623,23 +671,28 @@ class MarifoldRuntime {
623
671
  toolMode: override.toolMode ?? global.toolMode,
624
672
  };
625
673
  }
626
- createAgentRunner(profile, registry) {
674
+ createAgentRunner(profile, registry, agentConfigOverride, runtimeOptions = {}) {
627
675
  return new AgentRunner_1.AgentRunner({
628
676
  taskStore: this.taskStore,
629
677
  registry: registry ?? this.createDefaultToolRegistry(profile),
630
- agentConfig: this.resolveAgentConfigForProfile(profile),
678
+ agentConfig: agentConfigOverride ?? this.resolveAgentConfigForProfile(profile),
631
679
  resolveSettings: request => this.resolveSettings(request),
632
680
  prepareEngine: async (settings) => {
633
681
  await this.refreshProviderCredentialsIfNeeded(settings.provider);
634
- const webSearchMode = this.resolveWebSearchMode(settings);
682
+ const searchResolution = runtimeOptions.webSearch === false
683
+ ? { mode: 'unavailable', nativeStrategy: 'none' }
684
+ : this.resolveWebSearch(settings);
685
+ const webSearchMode = searchResolution.mode;
635
686
  return {
636
687
  // No engine-level session store: priest would otherwise persist the
637
688
  // raw per-iteration `Objective:`/tool framing (and duplicates). The
638
689
  // runner instead persists one clean turn pair via `persistTurn` below.
639
690
  engine: this.createEngine(settings.provider, false),
640
- config: this.toPriestConfig(settings, webSearchMode === 'native'),
691
+ config: this.toPriestConfig(settings, searchResolution.nativeStrategy),
641
692
  webSearchMode,
642
- providerTools: this.providerToolsFor(webSearchMode),
693
+ webSearchFallbackAvailable: webSearchMode === 'native'
694
+ && this.fallbackWebSearchAvailable(settings),
695
+ providerTools: this.providerToolsFor(webSearchMode, searchResolution.nativeStrategy),
643
696
  };
644
697
  },
645
698
  prepareImages: async (images, optimize) => (await (0, ImageOptimizer_1.prepareImageInputs)(images, { optimize })).images,
@@ -671,12 +724,16 @@ class MarifoldRuntime {
671
724
  })];
672
725
  },
673
726
  resolveReadOnlyFolders: resolvedProfile => {
727
+ if (runtimeOptions.readOnlyFolders)
728
+ return runtimeOptions.readOnlyFolders;
674
729
  const { config } = this.options.loadedConfig;
675
730
  return [
676
731
  path.join(config.paths.profilesDir, resolvedProfile, 'skills'),
677
732
  config.paths.skillsDir ?? (0, WorkspacePaths_1.defaultSkillsDir)(),
678
733
  ];
679
734
  },
735
+ resolveReadOnlyFiles: () => runtimeOptions.readOnlyFiles ?? [],
736
+ allowExternalReadOnlyFolders: runtimeOptions.allowExternalReadOnlyFolders,
680
737
  });
681
738
  }
682
739
  createDefaultToolRegistry(profile) {
@@ -759,7 +816,12 @@ class MarifoldRuntime {
759
816
  }
760
817
  createAppStore() {
761
818
  const { config } = this.options.loadedConfig;
762
- return new AppStore_1.AppStore(config.paths.appsDir ?? (0, WorkspacePaths_1.defaultAppsDir)());
819
+ return new AppStore_1.AppStore(config.paths.appsDir ?? (0, WorkspacePaths_1.defaultAppsDir)(), {
820
+ resolveProfileSkill: (profile, skillName) => {
821
+ this.profileResolver.load(profile);
822
+ return this.createSkillStore(profile).get(skillName);
823
+ },
824
+ });
763
825
  }
764
826
  listApps() {
765
827
  return this.createAppStore().list();
@@ -767,36 +829,82 @@ class MarifoldRuntime {
767
829
  getApp(name) {
768
830
  return this.createAppStore().get(name);
769
831
  }
770
- /** Execute one profile-free v1 operation and normalize provider output to
771
- * the Skill's declared result contract. No session, memory, tools, profile
772
- * context, or transcript is created. */
773
- async runSkillAppOperation(appName, operationName, state, signal) {
832
+ /** Execute one statically compiled SkillApp operation. v1 operations remain
833
+ * profile-free; v2 profile operations load the referenced profile and may
834
+ * receive read-only memory plus instance-local history. */
835
+ async runSkillAppOperation(appName, operationName, state, signal, history, attachments) {
774
836
  const startedAt = Date.now();
775
837
  try {
776
838
  const store = this.createAppStore();
777
839
  const definition = store.require(appName);
778
840
  const operation = (0, SkillAppResolver_1.resolveSkillAppOperation)(store, definition, operationName, state);
779
- await this.refreshProviderCredentialsIfNeeded(operation.model.provider);
780
- const settings = {
781
- profile: `skillapp-${appName}`,
782
- provider: operation.model.provider,
783
- model: operation.model.model,
784
- think: operation.model.think,
785
- mode: 'chat',
786
- };
787
- const response = await this.createEngine(settings.provider, false, false).run({
788
- config: this.toPriestConfig(settings),
789
- profile: settings.profile,
790
- prompt: operation.prompt,
791
- context: operation.instructions,
792
- }, signal ? { signal } : undefined);
793
- if (response.error) {
794
- throw MarifoldError_1.MarifoldError.providerError(response.error.message, settings.provider, settings.model, response.error.code);
841
+ let settings;
842
+ let text;
843
+ let usage;
844
+ if (operation.profile) {
845
+ settings = this.resolveSettings({
846
+ profile: operation.profile.profile,
847
+ ...(operation.profile.provider ? { provider: operation.profile.provider } : {}),
848
+ ...(operation.profile.model ? { model: operation.profile.model } : {}),
849
+ ...(operation.profile.think !== undefined ? { think: operation.profile.think } : {}),
850
+ });
851
+ const memory = this.memoryForRequest(settings.profile, operation.profile.memory, operation.prompt, settings.think);
852
+ const historyContext = operation.profile.history && history?.length
853
+ ? (0, AgentHistory_1.buildHistoryContext)(history, 16_000)
854
+ : undefined;
855
+ const instructions = [
856
+ ...operation.instructions,
857
+ ...(historyContext ? [historyContext] : []),
858
+ ];
859
+ if ((operation.mode ?? settings.mode) === 'agent') {
860
+ const run = await this.runProfileSkillAppAgent(appName, operation.operationName, settings, operation.prompt, instructions, memory, operation.skillDirectory, definition.permissions ?? [], attachments, signal);
861
+ text = run.text;
862
+ usage = run.usage;
863
+ }
864
+ else {
865
+ if (attachments?.some(attachment => attachment.kind === 'file')) {
866
+ throw MarifoldError_1.MarifoldError.appInvalid(`SkillApp operation '${operationName}' needs Agent mode to inspect non-image attachments.`);
867
+ }
868
+ await this.refreshProviderCredentialsIfNeeded(settings.provider);
869
+ const response = await this.createEngine(settings.provider, false).run({
870
+ config: this.toPriestConfig(settings),
871
+ profile: settings.profile,
872
+ prompt: operation.prompt,
873
+ context: instructions,
874
+ ...(memory.length > 0 ? { memory } : {}),
875
+ ...(attachments?.some(attachment => attachment.kind === 'image') ? {
876
+ images: attachments
877
+ .filter(attachment => attachment.kind === 'image')
878
+ .map(attachment => ({ data: attachment.data, mediaType: attachment.mediaType })),
879
+ } : {}),
880
+ }, signal ? { signal } : undefined);
881
+ text = requireSkillAppResponseText(response, settings);
882
+ usage = response.usage;
883
+ }
795
884
  }
796
- if (response.text === undefined) {
797
- throw MarifoldError_1.MarifoldError.providerError(`Provider '${settings.provider}' returned no text for model '${settings.model}'.`, settings.provider, settings.model, 'EMPTY_RESPONSE');
885
+ else {
886
+ if (!operation.model) {
887
+ throw MarifoldError_1.MarifoldError.appInvalid(`SkillApp operation '${operationName}' has no model.`);
888
+ }
889
+ settings = {
890
+ profile: `skillapp-${appName}`,
891
+ provider: operation.model.provider,
892
+ model: operation.model.model,
893
+ think: operation.model.think,
894
+ mode: 'chat',
895
+ };
896
+ await this.refreshProviderCredentialsIfNeeded(settings.provider);
897
+ const response = await this.createEngine(settings.provider, false, false).run({
898
+ config: this.toPriestConfig(settings),
899
+ profile: settings.profile,
900
+ prompt: operation.prompt,
901
+ context: operation.instructions,
902
+ }, signal ? { signal } : undefined);
903
+ text = requireSkillAppResponseText(response, settings);
904
+ usage = response.usage;
798
905
  }
799
- const text = operation.result.trim ? response.text.trim() : response.text;
906
+ text = (0, MemoryControls_1.stripMemoryControls)(text).text;
907
+ text = operation.result.trim ? text.trim() : text;
800
908
  return {
801
909
  status: 'ok',
802
910
  data: { text },
@@ -804,7 +912,7 @@ class MarifoldRuntime {
804
912
  engine: settings.provider,
805
913
  model: settings.model,
806
914
  durationMs: Date.now() - startedAt,
807
- ...(response.usage ? { usage: response.usage } : {}),
915
+ ...(usage ? { usage } : {}),
808
916
  },
809
917
  };
810
918
  }
@@ -823,6 +931,81 @@ class MarifoldRuntime {
823
931
  createSkillAppInstanceRegistry() {
824
932
  return new SkillAppInstanceRegistry_1.SkillAppInstanceRegistry(this);
825
933
  }
934
+ async runProfileSkillAppAgent(appName, operationName, settings, prompt, instructions, memory, skillDirectory, permissions = [], attachments = [], signal) {
935
+ const registry = new ToolRegistry_1.ToolRegistry();
936
+ registry.register(new InspectAttachmentTool_1.InspectAttachmentTool());
937
+ registry.register(new ReadAttachmentTool_1.ReadAttachmentTool());
938
+ registry.register(new SearchAttachmentTool_1.SearchAttachmentTool());
939
+ registry.register(new ReadFileTool_1.ReadFileTool({ strictWorkspace: true }));
940
+ const base = this.resolveAgentConfigForProfile(settings.profile);
941
+ const isolatedConfig = {
942
+ ...base,
943
+ approval: {
944
+ read: 'allow',
945
+ write: 'deny',
946
+ shell: 'deny',
947
+ network: 'deny',
948
+ delegate: 'deny',
949
+ },
950
+ trustedFolders: [],
951
+ };
952
+ const runner = this.createAgentRunner(settings.profile, registry, isolatedConfig, {
953
+ webSearch: false,
954
+ readOnlyFolders: [
955
+ ...(skillDirectory ? [skillDirectory] : []),
956
+ ...permissions.filter(permission => permission.kind === 'folder').map(permission => permission.path),
957
+ ],
958
+ readOnlyFiles: permissions
959
+ .filter(permission => permission.kind === 'file')
960
+ .map(permission => permission.path),
961
+ allowExternalReadOnlyFolders: true,
962
+ });
963
+ const images = attachments
964
+ .filter(attachment => attachment.kind === 'image')
965
+ .map(attachment => ({ data: attachment.data, mediaType: attachment.mediaType }));
966
+ const files = attachments
967
+ .filter(attachment => attachment.kind === 'file')
968
+ .map(attachment => ({
969
+ name: attachment.name,
970
+ mediaType: attachment.mediaType,
971
+ data: attachment.data,
972
+ ...(attachment.inspectionText !== undefined ? { inspectionText: attachment.inspectionText } : {}),
973
+ }));
974
+ let finalText;
975
+ let failure;
976
+ let status;
977
+ let usage;
978
+ for await (const event of runner.run({
979
+ objective: prompt,
980
+ profile: settings.profile,
981
+ provider: settings.provider,
982
+ model: settings.model,
983
+ think: settings.think,
984
+ lean: true,
985
+ instructions,
986
+ ...(images.length > 0 || files.length > 0 ? {
987
+ images,
988
+ files,
989
+ } : {}),
990
+ ...(memory.length > 0 ? { memory } : {}),
991
+ cwd: (0, WorkspacePaths_1.marifoldHome)(),
992
+ tags: ['skillapp', appName, operationName],
993
+ signal,
994
+ })) {
995
+ if (event.type === 'text' && event.phase === 'final')
996
+ finalText = event.text;
997
+ if (event.type === 'error')
998
+ failure = { code: event.code, message: event.message };
999
+ if (event.type === 'done') {
1000
+ status = event.status;
1001
+ usage = event.usage;
1002
+ }
1003
+ }
1004
+ if (status !== 'completed' || finalText === undefined) {
1005
+ throw MarifoldError_1.MarifoldError.providerError(failure?.message ?? `Profile Skill operation '${operationName}' did not produce a final response.`, settings.provider, settings.model, failure?.code ?? 'APP_SKILL_FAILED');
1006
+ }
1007
+ return { text: finalText, ...(usage ? { usage } : {}) };
1008
+ }
826
1009
  createSchedule(input) {
827
1010
  return this.scheduleStore.create(input);
828
1011
  }
@@ -1048,7 +1231,7 @@ class MarifoldRuntime {
1048
1231
  },
1049
1232
  };
1050
1233
  }
1051
- toPriestConfig(settings, nativeWebSearch = false) {
1234
+ toPriestConfig(settings, nativeWebSearch = 'none') {
1052
1235
  const { config } = this.options.loadedConfig;
1053
1236
  const provider = config.providers[settings.provider];
1054
1237
  const neutralReasoning = this.supportsNeutralReasoning(settings.provider, settings.model);
@@ -1058,8 +1241,12 @@ class MarifoldRuntime {
1058
1241
  // Compatibility bridge for Priest 3.0.x. Priest 3.1 reads providerTools
1059
1242
  // directly; Marifold's Responses wrapper consumes and removes this marker
1060
1243
  // when an older engine does not forward that additive request field.
1061
- if (nativeWebSearch)
1244
+ if (nativeWebSearch === 'responses-tool') {
1062
1245
  providerOptions[NATIVE_WEB_SEARCH_COMPAT_OPTION] = true;
1246
+ }
1247
+ else if (nativeWebSearch === 'chat-option') {
1248
+ providerOptions['enable_search'] = true;
1249
+ }
1063
1250
  return {
1064
1251
  provider: settings.provider,
1065
1252
  model: settings.model,
@@ -1108,20 +1295,35 @@ class MarifoldRuntime {
1108
1295
  thinking,
1109
1296
  });
1110
1297
  }
1111
- resolveWebSearchMode(settings, modelToolsEnabled = true) {
1112
- if (!modelToolsEnabled)
1113
- return 'unavailable';
1298
+ resolveWebSearch(settings, modelToolsEnabled = true) {
1299
+ if (!modelToolsEnabled) {
1300
+ return { mode: 'unavailable', nativeStrategy: 'none' };
1301
+ }
1114
1302
  const approval = this.resolveAgentConfigForProfile(settings.profile).approval;
1115
- if (approval.network === 'deny')
1116
- return 'unavailable';
1117
- if (this.providerFactory.supportsNativeWebSearch(settings.provider))
1118
- return 'native';
1119
- return (0, ConfigSchema_1.resolveWebSearchConfig)(this.options.loadedConfig.config.webSearch).enabled
1120
- ? 'fallback'
1121
- : 'unavailable';
1303
+ if (approval.network === 'deny') {
1304
+ return { mode: 'unavailable', nativeStrategy: 'none' };
1305
+ }
1306
+ const nativeStrategy = this.providerFactory.nativeWebSearchStrategy(settings.provider, settings.model);
1307
+ if (nativeStrategy !== 'none') {
1308
+ return { mode: 'native', nativeStrategy };
1309
+ }
1310
+ return {
1311
+ mode: this.fallbackWebSearchAvailable(settings, modelToolsEnabled) ? 'fallback' : 'unavailable',
1312
+ nativeStrategy: 'none',
1313
+ };
1122
1314
  }
1123
- providerToolsFor(mode) {
1124
- return mode === 'native' ? [{ type: 'web_search' }] : undefined;
1315
+ fallbackWebSearchAvailable(settings, modelToolsEnabled = true) {
1316
+ if (!modelToolsEnabled) {
1317
+ return false;
1318
+ }
1319
+ const approval = this.resolveAgentConfigForProfile(settings.profile).approval;
1320
+ return approval.network !== 'deny'
1321
+ && (0, ConfigSchema_1.resolveWebSearchConfig)(this.options.loadedConfig.config.webSearch).enabled;
1322
+ }
1323
+ providerToolsFor(mode, nativeStrategy) {
1324
+ return mode === 'native' && nativeStrategy === 'responses-tool'
1325
+ ? [{ type: 'web_search' }]
1326
+ : undefined;
1125
1327
  }
1126
1328
  runtimeContext(memory, prompt, memoryOn, webSearchMode) {
1127
1329
  const context = ['Running inside Marifold.'];
@@ -1171,6 +1373,15 @@ class MarifoldRuntime {
1171
1373
  }
1172
1374
  }
1173
1375
  exports.MarifoldRuntime = MarifoldRuntime;
1376
+ function requireSkillAppResponseText(response, settings) {
1377
+ if (response.error) {
1378
+ throw MarifoldError_1.MarifoldError.providerError(response.error.message, settings.provider, settings.model, response.error.code);
1379
+ }
1380
+ if (response.text === undefined) {
1381
+ throw MarifoldError_1.MarifoldError.providerError(`Provider '${settings.provider}' returned no text for model '${settings.model}'.`, settings.provider, settings.model, 'EMPTY_RESPONSE');
1382
+ }
1383
+ return response.text;
1384
+ }
1174
1385
  function completedResponseMetrics(mode, settings, startedAt, startedAtMs, usage) {
1175
1386
  const finishedAtMs = Date.now();
1176
1387
  return {