@marifold/core 0.60.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.
- package/LICENSE +674 -0
- package/dist/agent/AgentEvents.d.ts +106 -0
- package/dist/agent/AgentEvents.d.ts.map +1 -0
- package/dist/agent/AgentEvents.js +3 -0
- package/dist/agent/AgentEvents.js.map +1 -0
- package/dist/agent/AgentHistory.d.ts +27 -0
- package/dist/agent/AgentHistory.d.ts.map +1 -0
- package/dist/agent/AgentHistory.js +42 -0
- package/dist/agent/AgentHistory.js.map +1 -0
- package/dist/agent/AgentRunner.d.ts +149 -0
- package/dist/agent/AgentRunner.d.ts.map +1 -0
- package/dist/agent/AgentRunner.js +727 -0
- package/dist/agent/AgentRunner.js.map +1 -0
- package/dist/agent/ApprovalPolicy.d.ts +71 -0
- package/dist/agent/ApprovalPolicy.d.ts.map +1 -0
- package/dist/agent/ApprovalPolicy.js +29 -0
- package/dist/agent/ApprovalPolicy.js.map +1 -0
- package/dist/agent/ControlBlockTools.d.ts +9 -0
- package/dist/agent/ControlBlockTools.d.ts.map +1 -0
- package/dist/agent/ControlBlockTools.js +49 -0
- package/dist/agent/ControlBlockTools.js.map +1 -0
- package/dist/agent/RunWorkspace.d.ts +92 -0
- package/dist/agent/RunWorkspace.d.ts.map +1 -0
- package/dist/agent/RunWorkspace.js +328 -0
- package/dist/agent/RunWorkspace.js.map +1 -0
- package/dist/agent/ScopedProcess.d.ts +35 -0
- package/dist/agent/ScopedProcess.d.ts.map +1 -0
- package/dist/agent/ScopedProcess.js +280 -0
- package/dist/agent/ScopedProcess.js.map +1 -0
- package/dist/agent/ToolRegistry.d.ts +80 -0
- package/dist/agent/ToolRegistry.d.ts.map +1 -0
- package/dist/agent/ToolRegistry.js +45 -0
- package/dist/agent/ToolRegistry.js.map +1 -0
- package/dist/agent/UserInput.d.ts +59 -0
- package/dist/agent/UserInput.d.ts.map +1 -0
- package/dist/agent/UserInput.js +203 -0
- package/dist/agent/UserInput.js.map +1 -0
- package/dist/agent/tools/AskUserTool.d.ts +71 -0
- package/dist/agent/tools/AskUserTool.d.ts.map +1 -0
- package/dist/agent/tools/AskUserTool.js +70 -0
- package/dist/agent/tools/AskUserTool.js.map +1 -0
- package/dist/agent/tools/DelegateTool.d.ts +49 -0
- package/dist/agent/tools/DelegateTool.d.ts.map +1 -0
- package/dist/agent/tools/DelegateTool.js +62 -0
- package/dist/agent/tools/DelegateTool.js.map +1 -0
- package/dist/agent/tools/InspectAttachmentTool.d.ts +25 -0
- package/dist/agent/tools/InspectAttachmentTool.d.ts.map +1 -0
- package/dist/agent/tools/InspectAttachmentTool.js +129 -0
- package/dist/agent/tools/InspectAttachmentTool.js.map +1 -0
- package/dist/agent/tools/PythonPackageTool.d.ts +26 -0
- package/dist/agent/tools/PythonPackageTool.d.ts.map +1 -0
- package/dist/agent/tools/PythonPackageTool.js +105 -0
- package/dist/agent/tools/PythonPackageTool.js.map +1 -0
- package/dist/agent/tools/ReadFileTool.d.ts +24 -0
- package/dist/agent/tools/ReadFileTool.d.ts.map +1 -0
- package/dist/agent/tools/ReadFileTool.js +112 -0
- package/dist/agent/tools/ReadFileTool.js.map +1 -0
- package/dist/agent/tools/ShellExecTool.d.ts +23 -0
- package/dist/agent/tools/ShellExecTool.d.ts.map +1 -0
- package/dist/agent/tools/ShellExecTool.js +75 -0
- package/dist/agent/tools/ShellExecTool.js.map +1 -0
- package/dist/agent/tools/WebSearchTool.d.ts +26 -0
- package/dist/agent/tools/WebSearchTool.d.ts.map +1 -0
- package/dist/agent/tools/WebSearchTool.js +52 -0
- package/dist/agent/tools/WebSearchTool.js.map +1 -0
- package/dist/agent/tools/WriteFileTool.d.ts +30 -0
- package/dist/agent/tools/WriteFileTool.d.ts.map +1 -0
- package/dist/agent/tools/WriteFileTool.js +155 -0
- package/dist/agent/tools/WriteFileTool.js.map +1 -0
- package/dist/app/AppActionResolver.d.ts +15 -0
- package/dist/app/AppActionResolver.d.ts.map +1 -0
- package/dist/app/AppActionResolver.js +98 -0
- package/dist/app/AppActionResolver.js.map +1 -0
- package/dist/app/AppSchema.d.ts +83 -0
- package/dist/app/AppSchema.d.ts.map +1 -0
- package/dist/app/AppSchema.js +17 -0
- package/dist/app/AppSchema.js.map +1 -0
- package/dist/app/AppStore.d.ts +14 -0
- package/dist/app/AppStore.d.ts.map +1 -0
- package/dist/app/AppStore.js +146 -0
- package/dist/app/AppStore.js.map +1 -0
- package/dist/app/AppValidator.d.ts +10 -0
- package/dist/app/AppValidator.d.ts.map +1 -0
- package/dist/app/AppValidator.js +503 -0
- package/dist/app/AppValidator.js.map +1 -0
- package/dist/app/SkillAppCompiler.d.ts +5 -0
- package/dist/app/SkillAppCompiler.d.ts.map +1 -0
- package/dist/app/SkillAppCompiler.js +632 -0
- package/dist/app/SkillAppCompiler.js.map +1 -0
- package/dist/app/SkillAppDsl.d.ts +93 -0
- package/dist/app/SkillAppDsl.d.ts.map +1 -0
- package/dist/app/SkillAppDsl.js +72 -0
- package/dist/app/SkillAppDsl.js.map +1 -0
- package/dist/app/SkillAppInstanceRegistry.d.ts +25 -0
- package/dist/app/SkillAppInstanceRegistry.d.ts.map +1 -0
- package/dist/app/SkillAppInstanceRegistry.js +218 -0
- package/dist/app/SkillAppInstanceRegistry.js.map +1 -0
- package/dist/app/SkillAppResolver.d.ts +12 -0
- package/dist/app/SkillAppResolver.d.ts.map +1 -0
- package/dist/app/SkillAppResolver.js +89 -0
- package/dist/app/SkillAppResolver.js.map +1 -0
- package/dist/app/SkillAppSchema.d.ts +116 -0
- package/dist/app/SkillAppSchema.d.ts.map +1 -0
- package/dist/app/SkillAppSchema.js +5 -0
- package/dist/app/SkillAppSchema.js.map +1 -0
- package/dist/channels/TelegramBridge.d.ts +103 -0
- package/dist/channels/TelegramBridge.d.ts.map +1 -0
- package/dist/channels/TelegramBridge.js +565 -0
- package/dist/channels/TelegramBridge.js.map +1 -0
- package/dist/channels/respond.d.ts +46 -0
- package/dist/channels/respond.d.ts.map +1 -0
- package/dist/channels/respond.js +41 -0
- package/dist/channels/respond.js.map +1 -0
- package/dist/config/ChatGptTokenRefresh.d.ts +18 -0
- package/dist/config/ChatGptTokenRefresh.d.ts.map +1 -0
- package/dist/config/ChatGptTokenRefresh.js +63 -0
- package/dist/config/ChatGptTokenRefresh.js.map +1 -0
- package/dist/config/ConfigBackup.d.ts +20 -0
- package/dist/config/ConfigBackup.d.ts.map +1 -0
- package/dist/config/ConfigBackup.js +197 -0
- package/dist/config/ConfigBackup.js.map +1 -0
- package/dist/config/ConfigLoader.d.ts +28 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -0
- package/dist/config/ConfigLoader.js +335 -0
- package/dist/config/ConfigLoader.js.map +1 -0
- package/dist/config/ConfigManager.d.ts +55 -0
- package/dist/config/ConfigManager.d.ts.map +1 -0
- package/dist/config/ConfigManager.js +707 -0
- package/dist/config/ConfigManager.js.map +1 -0
- package/dist/config/ConfigSchema.d.ts +231 -0
- package/dist/config/ConfigSchema.d.ts.map +1 -0
- package/dist/config/ConfigSchema.js +15 -0
- package/dist/config/ConfigSchema.js.map +1 -0
- package/dist/config/GitHubCopilotAuth.d.ts +7 -0
- package/dist/config/GitHubCopilotAuth.d.ts.map +1 -0
- package/dist/config/GitHubCopilotAuth.js +40 -0
- package/dist/config/GitHubCopilotAuth.js.map +1 -0
- package/dist/config/MarifoldOpenAICompatProvider.d.ts +39 -0
- package/dist/config/MarifoldOpenAICompatProvider.d.ts.map +1 -0
- package/dist/config/MarifoldOpenAICompatProvider.js +158 -0
- package/dist/config/MarifoldOpenAICompatProvider.js.map +1 -0
- package/dist/config/OpenAICompatUrls.d.ts +7 -0
- package/dist/config/OpenAICompatUrls.d.ts.map +1 -0
- package/dist/config/OpenAICompatUrls.js +47 -0
- package/dist/config/OpenAICompatUrls.js.map +1 -0
- package/dist/config/ProviderFactory.d.ts +12 -0
- package/dist/config/ProviderFactory.d.ts.map +1 -0
- package/dist/config/ProviderFactory.js +61 -0
- package/dist/config/ProviderFactory.js.map +1 -0
- package/dist/config/ProviderInspector.d.ts +46 -0
- package/dist/config/ProviderInspector.d.ts.map +1 -0
- package/dist/config/ProviderInspector.js +356 -0
- package/dist/config/ProviderInspector.js.map +1 -0
- package/dist/config/ProviderRegistry.d.ts +19 -0
- package/dist/config/ProviderRegistry.d.ts.map +1 -0
- package/dist/config/ProviderRegistry.js +301 -0
- package/dist/config/ProviderRegistry.js.map +1 -0
- package/dist/config/XaiTokenRefresh.d.ts +15 -0
- package/dist/config/XaiTokenRefresh.d.ts.map +1 -0
- package/dist/config/XaiTokenRefresh.js +73 -0
- package/dist/config/XaiTokenRefresh.js.map +1 -0
- package/dist/errors/MarifoldError.d.ts +34 -0
- package/dist/errors/MarifoldError.d.ts.map +1 -0
- package/dist/errors/MarifoldError.js +100 -0
- package/dist/errors/MarifoldError.js.map +1 -0
- package/dist/images/ImageOptimizer.d.ts +34 -0
- package/dist/images/ImageOptimizer.d.ts.map +1 -0
- package/dist/images/ImageOptimizer.js +225 -0
- package/dist/images/ImageOptimizer.js.map +1 -0
- package/dist/index.d.ts +96 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +193 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/MemoryControls.d.ts +26 -0
- package/dist/memory/MemoryControls.d.ts.map +1 -0
- package/dist/memory/MemoryControls.js +388 -0
- package/dist/memory/MemoryControls.js.map +1 -0
- package/dist/memory/MemoryStore.d.ts +122 -0
- package/dist/memory/MemoryStore.d.ts.map +1 -0
- package/dist/memory/MemoryStore.js +1096 -0
- package/dist/memory/MemoryStore.js.map +1 -0
- package/dist/profiles/ProfileManager.d.ts +139 -0
- package/dist/profiles/ProfileManager.d.ts.map +1 -0
- package/dist/profiles/ProfileManager.js +475 -0
- package/dist/profiles/ProfileManager.js.map +1 -0
- package/dist/profiles/ProfileResolver.d.ts +18 -0
- package/dist/profiles/ProfileResolver.d.ts.map +1 -0
- package/dist/profiles/ProfileResolver.js +280 -0
- package/dist/profiles/ProfileResolver.js.map +1 -0
- package/dist/runs/RunRegistry.d.ts +140 -0
- package/dist/runs/RunRegistry.d.ts.map +1 -0
- package/dist/runs/RunRegistry.js +432 -0
- package/dist/runs/RunRegistry.js.map +1 -0
- package/dist/runtime/MarifoldRuntime.d.ts +244 -0
- package/dist/runtime/MarifoldRuntime.d.ts.map +1 -0
- package/dist/runtime/MarifoldRuntime.js +1099 -0
- package/dist/runtime/MarifoldRuntime.js.map +1 -0
- package/dist/runtime/MarifoldTypes.d.ts +70 -0
- package/dist/runtime/MarifoldTypes.d.ts.map +1 -0
- package/dist/runtime/MarifoldTypes.js +3 -0
- package/dist/runtime/MarifoldTypes.js.map +1 -0
- package/dist/schedule/ScheduleStore.d.ts +60 -0
- package/dist/schedule/ScheduleStore.d.ts.map +1 -0
- package/dist/schedule/ScheduleStore.js +212 -0
- package/dist/schedule/ScheduleStore.js.map +1 -0
- package/dist/schedule/Scheduler.d.ts +28 -0
- package/dist/schedule/Scheduler.d.ts.map +1 -0
- package/dist/schedule/Scheduler.js +65 -0
- package/dist/schedule/Scheduler.js.map +1 -0
- package/dist/search/DuckDuckGoBackend.d.ts +18 -0
- package/dist/search/DuckDuckGoBackend.d.ts.map +1 -0
- package/dist/search/DuckDuckGoBackend.js +37 -0
- package/dist/search/DuckDuckGoBackend.js.map +1 -0
- package/dist/search/FirecrawlBackend.d.ts +28 -0
- package/dist/search/FirecrawlBackend.d.ts.map +1 -0
- package/dist/search/FirecrawlBackend.js +76 -0
- package/dist/search/FirecrawlBackend.js.map +1 -0
- package/dist/search/SearchBackend.d.ts +18 -0
- package/dist/search/SearchBackend.d.ts.map +1 -0
- package/dist/search/SearchBackend.js +24 -0
- package/dist/search/SearchBackend.js.map +1 -0
- package/dist/search/createSearchBackend.d.ts +16 -0
- package/dist/search/createSearchBackend.d.ts.map +1 -0
- package/dist/search/createSearchBackend.js +30 -0
- package/dist/search/createSearchBackend.js.map +1 -0
- package/dist/sessions/ResponseMetrics.d.ts +17 -0
- package/dist/sessions/ResponseMetrics.d.ts.map +1 -0
- package/dist/sessions/ResponseMetrics.js +3 -0
- package/dist/sessions/ResponseMetrics.js.map +1 -0
- package/dist/sessions/SessionResolver.d.ts +135 -0
- package/dist/sessions/SessionResolver.d.ts.map +1 -0
- package/dist/sessions/SessionResolver.js +1173 -0
- package/dist/sessions/SessionResolver.js.map +1 -0
- package/dist/skill/BuiltInSkillManager.d.ts +11 -0
- package/dist/skill/BuiltInSkillManager.d.ts.map +1 -0
- package/dist/skill/BuiltInSkillManager.js +98 -0
- package/dist/skill/BuiltInSkillManager.js.map +1 -0
- package/dist/skill/SkillInvocation.d.ts +30 -0
- package/dist/skill/SkillInvocation.d.ts.map +1 -0
- package/dist/skill/SkillInvocation.js +135 -0
- package/dist/skill/SkillInvocation.js.map +1 -0
- package/dist/skill/SkillSchema.d.ts +37 -0
- package/dist/skill/SkillSchema.d.ts.map +1 -0
- package/dist/skill/SkillSchema.js +26 -0
- package/dist/skill/SkillSchema.js.map +1 -0
- package/dist/skill/SkillStore.d.ts +41 -0
- package/dist/skill/SkillStore.d.ts.map +1 -0
- package/dist/skill/SkillStore.js +184 -0
- package/dist/skill/SkillStore.js.map +1 -0
- package/dist/skill/SkillTemplater.d.ts +19 -0
- package/dist/skill/SkillTemplater.d.ts.map +1 -0
- package/dist/skill/SkillTemplater.js +36 -0
- package/dist/skill/SkillTemplater.js.map +1 -0
- package/dist/skill/SkillValidator.d.ts +10 -0
- package/dist/skill/SkillValidator.d.ts.map +1 -0
- package/dist/skill/SkillValidator.js +131 -0
- package/dist/skill/SkillValidator.js.map +1 -0
- package/dist/skill/SkillVars.d.ts +10 -0
- package/dist/skill/SkillVars.d.ts.map +1 -0
- package/dist/skill/SkillVars.js +65 -0
- package/dist/skill/SkillVars.js.map +1 -0
- package/dist/skill/index.d.ts +10 -0
- package/dist/skill/index.d.ts.map +1 -0
- package/dist/skill/index.js +22 -0
- package/dist/skill/index.js.map +1 -0
- package/dist/skillapp/SkillAppActionResolver.d.ts +13 -0
- package/dist/skillapp/SkillAppActionResolver.d.ts.map +1 -0
- package/dist/skillapp/SkillAppActionResolver.js +95 -0
- package/dist/skillapp/SkillAppActionResolver.js.map +1 -0
- package/dist/skillapp/SkillAppSchema.d.ts +79 -0
- package/dist/skillapp/SkillAppSchema.d.ts.map +1 -0
- package/dist/skillapp/SkillAppSchema.js +20 -0
- package/dist/skillapp/SkillAppSchema.js.map +1 -0
- package/dist/skillapp/SkillAppStore.d.ts +11 -0
- package/dist/skillapp/SkillAppStore.d.ts.map +1 -0
- package/dist/skillapp/SkillAppStore.js +98 -0
- package/dist/skillapp/SkillAppStore.js.map +1 -0
- package/dist/skillapp/SkillAppValidator.d.ts +10 -0
- package/dist/skillapp/SkillAppValidator.d.ts.map +1 -0
- package/dist/skillapp/SkillAppValidator.js +522 -0
- package/dist/skillapp/SkillAppValidator.js.map +1 -0
- package/dist/tasks/TaskStore.d.ts +110 -0
- package/dist/tasks/TaskStore.d.ts.map +1 -0
- package/dist/tasks/TaskStore.js +371 -0
- package/dist/tasks/TaskStore.js.map +1 -0
- package/dist/util/fetchRetry.d.ts +11 -0
- package/dist/util/fetchRetry.d.ts.map +1 -0
- package/dist/util/fetchRetry.js +48 -0
- package/dist/util/fetchRetry.js.map +1 -0
- package/dist/util/idToken.d.ts +12 -0
- package/dist/util/idToken.d.ts.map +1 -0
- package/dist/util/idToken.js +32 -0
- package/dist/util/idToken.js.map +1 -0
- package/dist/util/proxy.d.ts +18 -0
- package/dist/util/proxy.d.ts.map +1 -0
- package/dist/util/proxy.js +28 -0
- package/dist/util/proxy.js.map +1 -0
- package/dist/workspace/Workspace.d.ts +9 -0
- package/dist/workspace/Workspace.d.ts.map +1 -0
- package/dist/workspace/Workspace.js +55 -0
- package/dist/workspace/Workspace.js.map +1 -0
- package/dist/workspace/WorkspaceInitializer.d.ts +40 -0
- package/dist/workspace/WorkspaceInitializer.d.ts.map +1 -0
- package/dist/workspace/WorkspaceInitializer.js +212 -0
- package/dist/workspace/WorkspaceInitializer.js.map +1 -0
- package/dist/workspace/WorkspacePaths.d.ts +11 -0
- package/dist/workspace/WorkspacePaths.d.ts.map +1 -0
- package/dist/workspace/WorkspacePaths.js +82 -0
- package/dist/workspace/WorkspacePaths.js.map +1 -0
- package/package.json +36 -0
|
@@ -0,0 +1,727 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AgentRunner = void 0;
|
|
37
|
+
const crypto = __importStar(require("crypto"));
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const MemoryControls_1 = require("../memory/MemoryControls");
|
|
42
|
+
const AgentHistory_1 = require("./AgentHistory");
|
|
43
|
+
const ControlBlockTools_1 = require("./ControlBlockTools");
|
|
44
|
+
const RunWorkspace_1 = require("./RunWorkspace");
|
|
45
|
+
const PLAN_SCHEMA = {
|
|
46
|
+
type: 'object',
|
|
47
|
+
properties: {
|
|
48
|
+
title: { type: 'string' },
|
|
49
|
+
steps: { type: 'array', items: { type: 'string' }, maxItems: 5 },
|
|
50
|
+
},
|
|
51
|
+
required: ['title', 'steps'],
|
|
52
|
+
};
|
|
53
|
+
/** Char budget for the injected history window when no profile budget is set. */
|
|
54
|
+
const HISTORY_BUDGET_DEFAULT_CHARS = 16000;
|
|
55
|
+
/**
|
|
56
|
+
* Approval-aware agent loop. Optionally plans, iterates tool calls against the
|
|
57
|
+
* ToolRegistry, and records the final outcome in the ephemeral TaskStore.
|
|
58
|
+
* Observable checks run through ordinary tools inside the loop; there is no
|
|
59
|
+
* separate model self-grading phase. Task state is never promoted into profile
|
|
60
|
+
* memory, and the chat memory pipeline is bypassed entirely: hidden memory
|
|
61
|
+
* control blocks in model output are stripped and their payloads discarded.
|
|
62
|
+
*
|
|
63
|
+
* Events are delivered as an AsyncGenerator so renderers get backpressure and
|
|
64
|
+
* cancellation for free. v0.11 emits one text event per model turn; a future
|
|
65
|
+
* upgrade to SDK streamEvents can add live deltas without changing the event
|
|
66
|
+
* contract.
|
|
67
|
+
*/
|
|
68
|
+
class AgentRunner {
|
|
69
|
+
deps;
|
|
70
|
+
constructor(deps) {
|
|
71
|
+
this.deps = deps;
|
|
72
|
+
}
|
|
73
|
+
async *run(options) {
|
|
74
|
+
const startedAtMs = Date.now();
|
|
75
|
+
const startedAt = new Date(startedAtMs).toISOString();
|
|
76
|
+
const agentConfig = this.deps.agentConfig;
|
|
77
|
+
const settings = this.deps.resolveSettings(options);
|
|
78
|
+
// A skill invocation already supplies its own authoritative instructions;
|
|
79
|
+
// only ordinary agent objectives receive lazily selected built-in guides.
|
|
80
|
+
const builtInInstructions = options.lean
|
|
81
|
+
? []
|
|
82
|
+
: (this.deps.resolveBuiltInInstructions?.(options.objective, settings.profile) ?? []);
|
|
83
|
+
let runOptions = builtInInstructions.length > 0
|
|
84
|
+
? { ...options, instructions: [...builtInInstructions, ...(options.instructions ?? [])] }
|
|
85
|
+
: options;
|
|
86
|
+
if (runOptions.images && this.deps.prepareImages) {
|
|
87
|
+
runOptions = {
|
|
88
|
+
...runOptions,
|
|
89
|
+
images: await this.deps.prepareImages(runOptions.images, runOptions.originalImages !== true),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const { engine: rawEngine, config } = await this.deps.prepareEngine(settings);
|
|
93
|
+
// Tally token usage across every model call (optional plan + loop turns).
|
|
94
|
+
const usage = {};
|
|
95
|
+
const engine = withUsageTally(rawEngine, usage);
|
|
96
|
+
const cwd = options.cwd ?? process.cwd();
|
|
97
|
+
const maxIterations = Math.max(1, options.maxIterations ?? agentConfig.maxIterations);
|
|
98
|
+
const requestedMode = options.toolMode ?? agentConfig.toolMode;
|
|
99
|
+
const task = this.deps.taskStore.create({
|
|
100
|
+
objective: options.objective,
|
|
101
|
+
profile: settings.profile,
|
|
102
|
+
tags: ['agent', ...(options.tags ?? [])],
|
|
103
|
+
});
|
|
104
|
+
yield { type: 'status', taskId: task.id, status: 'running' };
|
|
105
|
+
const workspace = (0, RunWorkspace_1.createRunWorkspace)({
|
|
106
|
+
id: options.executionId ?? task.id,
|
|
107
|
+
cwd,
|
|
108
|
+
trustedFolders: [...agentConfig.trustedFolders, ...(options.trustedFolders ?? [])],
|
|
109
|
+
readOnlyFolders: this.deps.resolveReadOnlyFolders?.(settings.profile),
|
|
110
|
+
files: options.files,
|
|
111
|
+
images: runOptions.images,
|
|
112
|
+
});
|
|
113
|
+
const toolContext = {
|
|
114
|
+
cwd: workspace.cwd,
|
|
115
|
+
trustedFolders: [...agentConfig.trustedFolders, ...(options.trustedFolders ?? [])],
|
|
116
|
+
workspace,
|
|
117
|
+
signal: options.signal,
|
|
118
|
+
outputLimit: agentConfig.toolOutputLimit,
|
|
119
|
+
};
|
|
120
|
+
// Bounded cross-objective memory: inject a window of the recent clean
|
|
121
|
+
// session pairs so a NON-lean task can reference prior turns ("save the
|
|
122
|
+
// above prompt"). Lean/skill runs stay stateless (isolated).
|
|
123
|
+
const recentTurns = !options.lean && options.sessionId && this.deps.loadRecentTurns
|
|
124
|
+
? this.deps.loadRecentTurns(options.sessionId, options.replaceUserTurnIndex)
|
|
125
|
+
: [];
|
|
126
|
+
// Cap to the last N turns when the profile sets session_context_turns — the
|
|
127
|
+
// same turn window chat uses, so the knob means the same thing in both modes.
|
|
128
|
+
// The char budget (≈ the token budget) remains the secondary bound.
|
|
129
|
+
const windowedTurns = settings.sessionContextTurns != null
|
|
130
|
+
? recentTurns.slice(Math.max(0, recentTurns.length - settings.sessionContextTurns))
|
|
131
|
+
: recentTurns;
|
|
132
|
+
const historyContext = (0, AgentHistory_1.buildHistoryContext)(windowedTurns, settings.maxContextTokens ?? HISTORY_BUDGET_DEFAULT_CHARS);
|
|
133
|
+
const state = {
|
|
134
|
+
mode: requestedMode === 'auto' ? 'native' : requestedMode,
|
|
135
|
+
triedNativeFallback: requestedMode !== 'auto',
|
|
136
|
+
exchange: [],
|
|
137
|
+
transcript: [],
|
|
138
|
+
historyContext,
|
|
139
|
+
toolSummaries: [],
|
|
140
|
+
steeringNotes: [],
|
|
141
|
+
activeImages: [],
|
|
142
|
+
};
|
|
143
|
+
let sessionTurnPersisted = false;
|
|
144
|
+
const persistSessionTurn = async (assistantText, outcome) => {
|
|
145
|
+
if (sessionTurnPersisted || !options.sessionId || !this.deps.persistTurn)
|
|
146
|
+
return;
|
|
147
|
+
// A failed regeneration must leave the existing exchange intact. Ordinary
|
|
148
|
+
// append-only runs still keep their submitted prompt and terminal state.
|
|
149
|
+
if (outcome !== 'completed' && options.replaceUserTurnIndex !== undefined)
|
|
150
|
+
return;
|
|
151
|
+
sessionTurnPersisted = true;
|
|
152
|
+
let responseMetrics;
|
|
153
|
+
if (outcome === 'completed') {
|
|
154
|
+
const finishedAtMs = Date.now();
|
|
155
|
+
responseMetrics = {
|
|
156
|
+
mode: 'agent',
|
|
157
|
+
provider: settings.provider,
|
|
158
|
+
model: settings.model,
|
|
159
|
+
think: settings.think,
|
|
160
|
+
startedAt,
|
|
161
|
+
finishedAt: new Date(finishedAtMs).toISOString(),
|
|
162
|
+
latencyMs: Math.max(0, finishedAtMs - startedAtMs),
|
|
163
|
+
...(hasUsage(usage) ? { usage: { ...usage } } : {}),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
await this.deps.persistTurn(options.sessionId, settings.profile, options.userTurn ?? options.objective, assistantText, runOptions.images, options.replaceUserTurnIndex, responseMetrics);
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
if (outcome === 'completed')
|
|
171
|
+
throw error;
|
|
172
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
173
|
+
this.deps.taskStore.appendEvent(task.id, {
|
|
174
|
+
kind: 'note',
|
|
175
|
+
message: `Could not persist the ${outcome} session turn: ${truncate(message, 500)}`,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
try {
|
|
180
|
+
// Phase 1 — plan, only when forced (the TUI's `/steps`, or a weak-model
|
|
181
|
+
// config). Adaptive by default: a separate planning call is overhead for
|
|
182
|
+
// the common single-step task, so the model just reasons inline.
|
|
183
|
+
if (options.forcePlan) {
|
|
184
|
+
const plan = await this.buildPlan(engine, config, settings.profile, runOptions);
|
|
185
|
+
const planned = this.deps.taskStore.update(task.id, {
|
|
186
|
+
title: plan.title,
|
|
187
|
+
plan: plan.steps.map((text, index) => ({
|
|
188
|
+
id: `step_${index + 1}`,
|
|
189
|
+
text,
|
|
190
|
+
status: index === 0 ? 'in_progress' : 'pending',
|
|
191
|
+
})),
|
|
192
|
+
});
|
|
193
|
+
yield { type: 'plan', taskId: task.id, plan: planned.plan };
|
|
194
|
+
}
|
|
195
|
+
// Phase 2 — tool loop
|
|
196
|
+
let finalText;
|
|
197
|
+
let iterations = 0;
|
|
198
|
+
while (iterations < maxIterations) {
|
|
199
|
+
iterations += 1;
|
|
200
|
+
this.assertNotAborted(options.signal);
|
|
201
|
+
for (const note of this.drainSteering(task.id, options, state)) {
|
|
202
|
+
yield { type: 'steering', taskId: task.id, text: note };
|
|
203
|
+
}
|
|
204
|
+
const response = await engine.run(this.loopRequest(config, settings.profile, runOptions, state, workspace), {
|
|
205
|
+
signal: options.signal,
|
|
206
|
+
});
|
|
207
|
+
this.trace({
|
|
208
|
+
kind: 'iteration',
|
|
209
|
+
iteration: iterations,
|
|
210
|
+
mode: state.mode,
|
|
211
|
+
inputTokens: response.usage?.inputTokens,
|
|
212
|
+
outputTokens: response.usage?.outputTokens,
|
|
213
|
+
// Cumulative loop context the model saw this turn (the thing that grows).
|
|
214
|
+
exchangeTurns: state.exchange.length,
|
|
215
|
+
exchangeChars: state.exchange.reduce((n, t) => n + (t.kind === 'tool_result' ? t.content.length : (t.text?.length ?? 0)), 0),
|
|
216
|
+
transcriptChars: state.transcript.reduce((n, t) => n + t.length, 0),
|
|
217
|
+
});
|
|
218
|
+
if (!response.ok) {
|
|
219
|
+
if (this.shouldFallBackToControlBlocks(response, state)) {
|
|
220
|
+
state.mode = 'control-block';
|
|
221
|
+
state.triedNativeFallback = true;
|
|
222
|
+
this.deps.taskStore.appendEvent(task.id, {
|
|
223
|
+
kind: 'decision',
|
|
224
|
+
message: 'Provider rejected native tool calling; switching to control-block tool mode.',
|
|
225
|
+
});
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (response.error?.code === 'REQUEST_ABORTED')
|
|
229
|
+
throw new AbortedError();
|
|
230
|
+
const message = response.error?.message ?? 'Provider call failed.';
|
|
231
|
+
yield { type: 'error', code: response.error?.code ?? 'PROVIDER_ERROR', message };
|
|
232
|
+
await persistSessionTurn(failedSessionOutcome(message), 'failed');
|
|
233
|
+
yield* this.finish(task.id, 'failed', undefined, 'Retry the run once the provider issue is resolved.', usage);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const { text, calls } = this.extractTurn(response, state);
|
|
237
|
+
if (response.reasoning?.summary) {
|
|
238
|
+
yield { type: 'reasoning', summary: response.reasoning.summary };
|
|
239
|
+
}
|
|
240
|
+
if (text)
|
|
241
|
+
yield { type: 'text', text, phase: calls.length > 0 ? 'progress' : 'final' };
|
|
242
|
+
if (calls.length === 0) {
|
|
243
|
+
finalText = text;
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
if (state.mode === 'native') {
|
|
247
|
+
state.exchange.push({
|
|
248
|
+
kind: 'assistant',
|
|
249
|
+
text,
|
|
250
|
+
toolCalls: calls,
|
|
251
|
+
...(response.reasoning ? { reasoning: response.reasoning } : {}),
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
else if (text || calls.length > 0) {
|
|
255
|
+
state.transcript.push(`Assistant reply:\n${text || '(tool calls only)'}`);
|
|
256
|
+
}
|
|
257
|
+
for (const call of calls) {
|
|
258
|
+
this.assertNotAborted(options.signal);
|
|
259
|
+
yield* this.executeCall(task.id, call, options, state, toolContext);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (finalText === undefined) {
|
|
263
|
+
this.deps.taskStore.appendEvent(task.id, {
|
|
264
|
+
kind: 'blocker',
|
|
265
|
+
message: `Iteration cap of ${maxIterations} reached before the objective was completed.`,
|
|
266
|
+
});
|
|
267
|
+
await persistSessionTurn(failedSessionOutcome(`Stopped at the iteration cap of ${maxIterations} before completing the objective.`), 'failed');
|
|
268
|
+
yield* this.finish(task.id, 'failed', 'Stopped at the iteration cap before completing the objective.', 'Re-run with a higher iteration cap or a narrower objective.', usage);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
// Persist a single clean turn pair (objective → final answer) so resuming
|
|
272
|
+
// the session shows the result, not the raw `Objective:`/tool framing.
|
|
273
|
+
await persistSessionTurn(finalText, 'completed');
|
|
274
|
+
// Complete. No verification phase: a separate self-grading model call was
|
|
275
|
+
// non-actionable (a failed grade didn't retry or fix anything) and models
|
|
276
|
+
// self-grade unreliably — so it was pure token overhead. Real checks belong
|
|
277
|
+
// in tools the agent runs inside the loop, not a final self-assessment.
|
|
278
|
+
this.completePlanSteps(task.id, true);
|
|
279
|
+
yield* this.finish(task.id, 'completed', finalText, undefined, usage);
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
if (error instanceof AbortedError || (options.signal?.aborted ?? false)) {
|
|
283
|
+
this.deps.taskStore.appendEvent(task.id, { kind: 'note', message: 'Run cancelled by the user.' });
|
|
284
|
+
await persistSessionTurn('Run cancelled before a final response was produced.', 'cancelled');
|
|
285
|
+
yield* this.finish(task.id, 'cancelled', undefined, 'Resume by starting a new run with the same objective.', usage);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
289
|
+
yield { type: 'error', code: 'AGENT_RUN_ERROR', message };
|
|
290
|
+
await persistSessionTurn(failedSessionOutcome(message), 'failed');
|
|
291
|
+
yield* this.finish(task.id, 'failed', undefined, message, usage);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
async *executeCall(taskId, call, options, state, toolContext) {
|
|
295
|
+
const tool = this.deps.registry.get(call.name);
|
|
296
|
+
if (!tool) {
|
|
297
|
+
const message = `Unknown tool '${call.name}'. Available tools: ${this.deps.registry.list().map(t => t.definition.name).join(', ')}.`;
|
|
298
|
+
yield { type: 'tool_result', callId: call.id, tool: call.name, summary: `unknown tool '${call.name}'`, isError: true };
|
|
299
|
+
this.recordToolResult(taskId, state, call, message, true, `unknown tool '${call.name}'`);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const summary = tool.summarizeCall(call.arguments);
|
|
303
|
+
yield {
|
|
304
|
+
type: 'tool_request',
|
|
305
|
+
call: { id: call.id, tool: call.name, kind: tool.kind, input: call.arguments, summary },
|
|
306
|
+
};
|
|
307
|
+
if (tool.kind === 'interaction') {
|
|
308
|
+
yield* this.requestUserInput(taskId, call, tool, summary, options, state);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const decision = yield* this.resolveApproval(call, tool, summary, options, toolContext);
|
|
312
|
+
if (!decision.approved) {
|
|
313
|
+
const message = `Tool call denied${decision.reason ? `: ${decision.reason}` : '.'}`;
|
|
314
|
+
yield { type: 'tool_result', callId: call.id, tool: call.name, summary: 'denied', isError: true };
|
|
315
|
+
this.deps.taskStore.appendEvent(taskId, { kind: 'decision', message: `Denied ${summary}${decision.reason ? ` (${decision.reason})` : ''}` });
|
|
316
|
+
this.recordToolResult(taskId, state, call, message, true, 'denied', false);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
let content;
|
|
320
|
+
let isError = false;
|
|
321
|
+
let resultSummary = summary;
|
|
322
|
+
try {
|
|
323
|
+
const result = await tool.execute(call.arguments, toolContext);
|
|
324
|
+
content = result.content;
|
|
325
|
+
isError = result.isError ?? false;
|
|
326
|
+
resultSummary = result.summary ?? summary;
|
|
327
|
+
if (!isError && result.images?.length) {
|
|
328
|
+
for (const image of result.images) {
|
|
329
|
+
if (!state.activeImages.includes(image))
|
|
330
|
+
state.activeImages.push(image);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
content = `Tool '${call.name}' failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
336
|
+
isError = true;
|
|
337
|
+
resultSummary = `${summary} failed`;
|
|
338
|
+
}
|
|
339
|
+
yield { type: 'tool_result', callId: call.id, tool: call.name, summary: resultSummary, isError };
|
|
340
|
+
this.recordToolResult(taskId, state, call, content, isError, resultSummary);
|
|
341
|
+
}
|
|
342
|
+
async *requestUserInput(taskId, call, tool, summary, options, state) {
|
|
343
|
+
let request;
|
|
344
|
+
try {
|
|
345
|
+
request = tool.createRequest(call.id, call.arguments);
|
|
346
|
+
}
|
|
347
|
+
catch (error) {
|
|
348
|
+
const content = `Tool '${call.name}' failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
349
|
+
yield { type: 'tool_result', callId: call.id, tool: call.name, summary: `${summary} failed`, isError: true };
|
|
350
|
+
this.recordToolResult(taskId, state, call, content, true, `${summary} failed`);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (options.unattended || !options.userInputHandler) {
|
|
354
|
+
const content = [
|
|
355
|
+
'No interactive user is available for this run.',
|
|
356
|
+
'Continue with a reasonable assumption when that is safe; otherwise explain what information is missing.',
|
|
357
|
+
].join(' ');
|
|
358
|
+
yield { type: 'tool_result', callId: call.id, tool: call.name, summary: 'user input unavailable', isError: true };
|
|
359
|
+
this.recordToolResult(taskId, state, call, content, true, 'user input unavailable');
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
yield { type: 'user_input_request', request };
|
|
363
|
+
const submission = await options.userInputHandler(request);
|
|
364
|
+
if (!submission) {
|
|
365
|
+
const content = 'The clarification request ended without an answer.';
|
|
366
|
+
yield { type: 'tool_result', callId: call.id, tool: call.name, summary: 'no user answer received', isError: true };
|
|
367
|
+
this.recordToolResult(taskId, state, call, content, true, 'no user answer received');
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
try {
|
|
371
|
+
const response = tool.resolveResponse(request, submission);
|
|
372
|
+
const content = tool.formatResponse(response);
|
|
373
|
+
yield { type: 'user_input_response', response };
|
|
374
|
+
yield {
|
|
375
|
+
type: 'tool_result',
|
|
376
|
+
callId: call.id,
|
|
377
|
+
tool: call.name,
|
|
378
|
+
summary: `received ${response.answers.length} user ${response.answers.length === 1 ? 'answer' : 'answers'}`,
|
|
379
|
+
isError: false,
|
|
380
|
+
};
|
|
381
|
+
this.deps.taskStore.appendEvent(taskId, { kind: 'decision', message: content });
|
|
382
|
+
this.recordToolResult(taskId, state, call, content, false, `received ${response.answers.length} user ${response.answers.length === 1 ? 'answer' : 'answers'}`);
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
const content = `Could not accept the user answers: ${error instanceof Error ? error.message : String(error)}`;
|
|
386
|
+
yield { type: 'tool_result', callId: call.id, tool: call.name, summary: 'invalid user answers', isError: true };
|
|
387
|
+
this.recordToolResult(taskId, state, call, content, true, 'invalid user answers');
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
async *resolveApproval(call, tool, summary, options, toolContext) {
|
|
391
|
+
const risk = tool.assessRisk?.(call.arguments, toolContext) ?? { escalate: false };
|
|
392
|
+
if (risk.blocked) {
|
|
393
|
+
const reason = risk.reason ?? 'blocked by the run security policy';
|
|
394
|
+
yield { type: 'approval_decision', requestId: call.id, approved: false, source: 'policy', reason };
|
|
395
|
+
return { approved: false, reason };
|
|
396
|
+
}
|
|
397
|
+
// Tools set `trusted` only for narrow pre-authorized capabilities such as
|
|
398
|
+
// current-run attachments and eligible in-home trusted folders.
|
|
399
|
+
if (risk.trusted) {
|
|
400
|
+
yield { type: 'approval_decision', requestId: call.id, approved: true, source: 'policy' };
|
|
401
|
+
return { approved: true };
|
|
402
|
+
}
|
|
403
|
+
const approval = options.unattended
|
|
404
|
+
? { ...this.deps.agentConfig.approval, ...(this.deps.agentConfig.unattended ?? {}) }
|
|
405
|
+
: this.deps.agentConfig.approval;
|
|
406
|
+
let mode = approval[tool.kind];
|
|
407
|
+
if (risk.escalate && mode === 'allow')
|
|
408
|
+
mode = 'ask';
|
|
409
|
+
if (mode === 'allow') {
|
|
410
|
+
yield { type: 'approval_decision', requestId: call.id, approved: true, source: 'policy' };
|
|
411
|
+
return { approved: true };
|
|
412
|
+
}
|
|
413
|
+
if (mode === 'deny') {
|
|
414
|
+
yield { type: 'approval_decision', requestId: call.id, approved: false, source: 'policy', reason: `${tool.kind} tools are denied by policy` };
|
|
415
|
+
return { approved: false, reason: `${tool.kind} tools are denied by policy` };
|
|
416
|
+
}
|
|
417
|
+
if (!options.approvalHandler) {
|
|
418
|
+
const reason = 'approval required but the run is unattended';
|
|
419
|
+
yield { type: 'approval_decision', requestId: call.id, approved: false, source: 'policy', reason };
|
|
420
|
+
return { approved: false, reason };
|
|
421
|
+
}
|
|
422
|
+
const request = {
|
|
423
|
+
id: call.id,
|
|
424
|
+
tool: call.name,
|
|
425
|
+
kind: tool.kind,
|
|
426
|
+
summary,
|
|
427
|
+
input: call.arguments,
|
|
428
|
+
escalated: risk.escalate,
|
|
429
|
+
...(risk.reason ? { escalationReason: risk.reason } : {}),
|
|
430
|
+
...(risk.targetPath ? { escalatedPath: risk.targetPath } : {}),
|
|
431
|
+
...(risk.persistable === false ? { persistable: false } : {}),
|
|
432
|
+
};
|
|
433
|
+
yield { type: 'approval_request', request };
|
|
434
|
+
const decision = await options.approvalHandler(request);
|
|
435
|
+
yield {
|
|
436
|
+
type: 'approval_decision',
|
|
437
|
+
requestId: call.id,
|
|
438
|
+
approved: decision.approved,
|
|
439
|
+
source: 'user',
|
|
440
|
+
...(decision.reason ? { reason: decision.reason } : {}),
|
|
441
|
+
};
|
|
442
|
+
return decision;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Opt-in diagnostic trace for agent-mode context cost (off by default).
|
|
446
|
+
* Set `MARIFOLD_AGENT_TRACE=1` to append JSONL to ~/.marifold/agent-trace.jsonl,
|
|
447
|
+
* or `MARIFOLD_AGENT_TRACE=<path>` for a custom file. Captures per-iteration
|
|
448
|
+
* input tokens + cumulative loop-context size and per-tool-result sizes, so we
|
|
449
|
+
* can see whether huge results or many small steps drive the growth. Never
|
|
450
|
+
* throws — tracing must not affect a run.
|
|
451
|
+
*/
|
|
452
|
+
trace(record) {
|
|
453
|
+
const target = process.env.MARIFOLD_AGENT_TRACE;
|
|
454
|
+
if (!target)
|
|
455
|
+
return;
|
|
456
|
+
try {
|
|
457
|
+
const file = target === '1' || target === 'true'
|
|
458
|
+
? path.join(os.homedir(), '.marifold', 'agent-trace.jsonl')
|
|
459
|
+
: target;
|
|
460
|
+
fs.appendFileSync(file, JSON.stringify({ ts: new Date().toISOString(), ...record }) + '\n');
|
|
461
|
+
}
|
|
462
|
+
catch {
|
|
463
|
+
// Diagnostics are best-effort; a trace failure must never break a run.
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
recordToolResult(taskId, state, call, content, isError, summary, appendObservation = true) {
|
|
467
|
+
this.trace({ kind: 'tool_result', tool: call.name, chars: content.length, isError });
|
|
468
|
+
if (state.mode === 'native') {
|
|
469
|
+
state.exchange.push({ kind: 'tool_result', toolCallId: call.id, name: call.name, content, isError });
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
state.transcript.push((0, ControlBlockTools_1.formatControlBlockResult)(call, content, isError));
|
|
473
|
+
}
|
|
474
|
+
state.toolSummaries.push(`${isError ? '[error] ' : ''}${summary}`);
|
|
475
|
+
if (appendObservation) {
|
|
476
|
+
this.deps.taskStore.appendEvent(taskId, {
|
|
477
|
+
kind: 'observation',
|
|
478
|
+
message: summary,
|
|
479
|
+
metadata: { tool: call.name, ...(isError ? { error: 'true' } : {}) },
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
/** Drain any `/btw` steering the caller queued and record it for the next
|
|
484
|
+
* turn. Returns the drained notes so the run generator can surface each as a
|
|
485
|
+
* `steering` event to attached clients. */
|
|
486
|
+
drainSteering(taskId, options, state) {
|
|
487
|
+
if (!options.steering)
|
|
488
|
+
return [];
|
|
489
|
+
const drained = [];
|
|
490
|
+
for (const note of options.steering()) {
|
|
491
|
+
const text = note.trim();
|
|
492
|
+
if (!text)
|
|
493
|
+
continue;
|
|
494
|
+
state.steeringNotes.push(text);
|
|
495
|
+
drained.push(text);
|
|
496
|
+
this.deps.taskStore.appendEvent(taskId, { kind: 'note', message: `Steering: ${text}` });
|
|
497
|
+
}
|
|
498
|
+
return drained;
|
|
499
|
+
}
|
|
500
|
+
loopRequest(config, profile, options, state, workspace) {
|
|
501
|
+
const base = {
|
|
502
|
+
config,
|
|
503
|
+
profile,
|
|
504
|
+
prompt: options.lean
|
|
505
|
+
? options.objective
|
|
506
|
+
: `Objective: ${options.objective}\n\nUse tools only when the objective genuinely requires reading or writing files, running commands, searching the web, or delegating. Many objectives — greetings, questions, explanations, drafting text — need no tools at all; for those, answer directly from your own knowledge. Do not invent tool calls. When the objective is complete, reply with a short final answer describing the outcome.`,
|
|
507
|
+
context: this.agentContext(state, workspace, options.instructions, options.lean),
|
|
508
|
+
...(options.sessionId ? { session: { id: options.sessionId, createIfMissing: true } } : {}),
|
|
509
|
+
...(state.activeImages.length > 0 ? { images: state.activeImages } : {}),
|
|
510
|
+
};
|
|
511
|
+
const steering = state.steeringNotes.map(note => `The user added this guidance while you were working — take it into account: ${note}`);
|
|
512
|
+
if (state.mode === 'native') {
|
|
513
|
+
return {
|
|
514
|
+
...base,
|
|
515
|
+
tools: this.deps.registry.definitions(),
|
|
516
|
+
toolExchange: state.exchange,
|
|
517
|
+
...(steering.length > 0 ? { userContext: steering } : {}),
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
return {
|
|
521
|
+
...base,
|
|
522
|
+
userContext: [...state.transcript, ...steering],
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
agentContext(state, workspace, instructions, lean = false) {
|
|
526
|
+
const attachments = workspace.attachments.length > 0
|
|
527
|
+
? [
|
|
528
|
+
'Attachments uploaded for this run:',
|
|
529
|
+
...workspace.attachments.map(attachment => (`- ${attachment.id}: ${attachment.name} (${attachment.mediaType}, ${formatAttachmentBytes(attachment.size)})`)),
|
|
530
|
+
'Attachments are not embedded in this agent prompt. Use inspect_attachment with an attachment ID when their contents matter. You may inspect independent attachments in parallel with other read-only tool calls.',
|
|
531
|
+
].join('\n')
|
|
532
|
+
: 'No attachments were uploaded for this run.';
|
|
533
|
+
const workspaceContext = [
|
|
534
|
+
`Working directory: ${workspace.cwd}. Relative tool paths resolve against it.`,
|
|
535
|
+
`User home: ${workspace.userHome}. In tool paths and shell commands, ~ refers to this directory.`,
|
|
536
|
+
`Isolated run directory: ${workspace.rootDir}. Its internal runtime home is ${workspace.homeDir}.`,
|
|
537
|
+
`${attachments}\nHonor explicit destination paths from the user; otherwise write generated deliverables to ${workspace.outputDir}. Temporary scripts and environments belong in ${workspace.workDir}.`,
|
|
538
|
+
...(this.deps.registry.get('ask_user')?.kind === 'interaction' ? [
|
|
539
|
+
'ask_user is optional. Use it only when essential information is missing and a reasonable assumption could materially change the result. Otherwise proceed. Batch all currently known questions into one call, and call it without other tools in that response.',
|
|
540
|
+
] : []),
|
|
541
|
+
'shell_exec has no network access and can write only the working directory, configured trusted folders, and private run directories even after execution approval. Use write_file for an explicit output path elsewhere. Use python_package_install for approved Python dependencies; it installs only into this run’s disposable uv environment.',
|
|
542
|
+
].join('\n');
|
|
543
|
+
// Lean run (skills): minimal framing — the instructions are authoritative,
|
|
544
|
+
// and we ask for only the final output to avoid plan/preamble/reasoning prose.
|
|
545
|
+
if (lean) {
|
|
546
|
+
const context = [...(instructions ?? [])];
|
|
547
|
+
context.push(`${workspaceContext}\nUse inspect_attachment when the skill depends on an uploaded attachment. Use read_file only if the instructions reference a bundled file (e.g. vars.toml). Resolve required inputs, then reply with ONLY the final output the instructions define — no plan, preamble, reasoning, or commentary.`);
|
|
548
|
+
if (state.mode === 'control-block') {
|
|
549
|
+
context.push((0, ControlBlockTools_1.buildControlBlockInstructions)(this.deps.registry.definitions()));
|
|
550
|
+
}
|
|
551
|
+
return context;
|
|
552
|
+
}
|
|
553
|
+
const context = [
|
|
554
|
+
'You are running as the Marifold agent. Stay focused on the stated objective and keep replies concise.',
|
|
555
|
+
'Prefer answering directly. Reach for a tool only when the objective cannot be completed from your own knowledge — never use a tool just to demonstrate one.',
|
|
556
|
+
'After changing files or producing an observable result, use the narrowest relevant tool for a focused check before claiming success. Report the evidence you actually observed; do not invent results or perform a separate self-grade.',
|
|
557
|
+
workspaceContext,
|
|
558
|
+
];
|
|
559
|
+
// Skill instructions are authoritative for this run — lead with them.
|
|
560
|
+
if (instructions?.length)
|
|
561
|
+
context.unshift(...instructions);
|
|
562
|
+
// Bounded prior-conversation memory (non-lean only) so the objective can
|
|
563
|
+
// reference earlier turns. Placed after framing, before tool instructions.
|
|
564
|
+
if (state.historyContext)
|
|
565
|
+
context.push(state.historyContext);
|
|
566
|
+
if (state.mode === 'control-block') {
|
|
567
|
+
context.push((0, ControlBlockTools_1.buildControlBlockInstructions)(this.deps.registry.definitions()));
|
|
568
|
+
}
|
|
569
|
+
return context;
|
|
570
|
+
}
|
|
571
|
+
extractTurn(response, state) {
|
|
572
|
+
// Memory control blocks are stripped and their payloads discarded — agent
|
|
573
|
+
// runs never write profile memory (see docs/architecture.md).
|
|
574
|
+
const stripped = (0, MemoryControls_1.stripMemoryControls)(response.text ?? '');
|
|
575
|
+
if (state.mode === 'native') {
|
|
576
|
+
return { text: stripped.text.trim(), calls: response.toolCalls ?? [] };
|
|
577
|
+
}
|
|
578
|
+
const parsed = (0, ControlBlockTools_1.parseControlBlockCalls)(stripped.text);
|
|
579
|
+
// Re-id calls so they stay unique across loop iterations.
|
|
580
|
+
const calls = parsed.calls.map(call => ({ ...call, id: `call_${crypto.randomBytes(4).toString('hex')}` }));
|
|
581
|
+
return { text: parsed.visibleText, calls };
|
|
582
|
+
}
|
|
583
|
+
shouldFallBackToControlBlocks(response, state) {
|
|
584
|
+
if (state.mode !== 'native' || state.triedNativeFallback)
|
|
585
|
+
return false;
|
|
586
|
+
if (response.error?.code !== 'PROVIDER_ERROR')
|
|
587
|
+
return false;
|
|
588
|
+
return /tool/i.test(response.error.message);
|
|
589
|
+
}
|
|
590
|
+
async buildPlan(engine, config, profile, options) {
|
|
591
|
+
const fallback = {
|
|
592
|
+
title: options.objective.split(/\r?\n/)[0]?.slice(0, 80) ?? 'Agent task',
|
|
593
|
+
steps: ['Work toward the objective.'],
|
|
594
|
+
};
|
|
595
|
+
const response = await engine.run({
|
|
596
|
+
config,
|
|
597
|
+
profile,
|
|
598
|
+
prompt: `Objective: ${options.objective}\n\nCreate a short execution plan for this objective. Reply with JSON {"title": string, "steps": string[]} using at most 5 short steps.`,
|
|
599
|
+
context: ['You are planning an agent task. Reply with JSON only.'],
|
|
600
|
+
output: { jsonSchema: PLAN_SCHEMA, jsonSchemaName: 'agent_plan' },
|
|
601
|
+
}, { signal: options.signal });
|
|
602
|
+
if (!response.ok) {
|
|
603
|
+
if (response.error?.code === 'REQUEST_ABORTED')
|
|
604
|
+
throw new AbortedError();
|
|
605
|
+
return fallback;
|
|
606
|
+
}
|
|
607
|
+
const parsed = parseJsonObject(response.text ?? '');
|
|
608
|
+
const title = typeof parsed?.title === 'string' && parsed.title.trim() ? parsed.title.trim() : fallback.title;
|
|
609
|
+
const steps = Array.isArray(parsed?.steps)
|
|
610
|
+
? parsed.steps.filter((step) => typeof step === 'string' && step.trim().length > 0).slice(0, 5)
|
|
611
|
+
: [];
|
|
612
|
+
return { title, steps: steps.length > 0 ? steps : fallback.steps };
|
|
613
|
+
}
|
|
614
|
+
completePlanSteps(taskId, passed) {
|
|
615
|
+
if (!passed)
|
|
616
|
+
return;
|
|
617
|
+
const task = this.deps.taskStore.get(taskId);
|
|
618
|
+
if (!task)
|
|
619
|
+
return;
|
|
620
|
+
this.deps.taskStore.update(taskId, {
|
|
621
|
+
plan: task.plan.map(step => ({ id: step.id, text: step.text, status: 'completed' })),
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
async *finish(taskId, status, summary, nextAction, usage) {
|
|
625
|
+
const updated = this.deps.taskStore.update(taskId, {
|
|
626
|
+
status,
|
|
627
|
+
...(summary ? { summary: truncate(summary, 2000) } : {}),
|
|
628
|
+
...(nextAction ? { nextAction: truncate(nextAction, 500) } : {}),
|
|
629
|
+
});
|
|
630
|
+
yield { type: 'status', taskId, status: updated.status };
|
|
631
|
+
yield {
|
|
632
|
+
type: 'done',
|
|
633
|
+
taskId,
|
|
634
|
+
status: updated.status,
|
|
635
|
+
...(updated.summary ? { summary: updated.summary } : {}),
|
|
636
|
+
...(usage && hasUsage(usage) ? { usage } : {}),
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
assertNotAborted(signal) {
|
|
640
|
+
if (signal?.aborted)
|
|
641
|
+
throw new AbortedError();
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
exports.AgentRunner = AgentRunner;
|
|
645
|
+
class AbortedError extends Error {
|
|
646
|
+
constructor() {
|
|
647
|
+
super('Agent run aborted.');
|
|
648
|
+
this.name = 'AbortedError';
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
function parseJsonObject(text) {
|
|
652
|
+
const candidates = [text.trim()];
|
|
653
|
+
const match = text.match(/\{[\s\S]*\}/);
|
|
654
|
+
if (match)
|
|
655
|
+
candidates.push(match[0]);
|
|
656
|
+
for (const candidate of candidates) {
|
|
657
|
+
try {
|
|
658
|
+
const parsed = JSON.parse(candidate);
|
|
659
|
+
if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
660
|
+
return parsed;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
catch {
|
|
664
|
+
// try next candidate
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return undefined;
|
|
668
|
+
}
|
|
669
|
+
function formatAttachmentBytes(bytes) {
|
|
670
|
+
if (bytes === 0)
|
|
671
|
+
return 'remote';
|
|
672
|
+
if (bytes < 1024)
|
|
673
|
+
return `${bytes} B`;
|
|
674
|
+
if (bytes < 1024 * 1024)
|
|
675
|
+
return `${(bytes / 1024).toFixed(1)} KiB`;
|
|
676
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
|
|
677
|
+
}
|
|
678
|
+
function truncate(text, limit) {
|
|
679
|
+
return text.length <= limit ? text : `${text.slice(0, limit - 3)}...`;
|
|
680
|
+
}
|
|
681
|
+
function failedSessionOutcome(message) {
|
|
682
|
+
const detail = truncate(message.trim(), 1000);
|
|
683
|
+
return detail
|
|
684
|
+
? `Run failed before a final response was produced.\n\n${detail}`
|
|
685
|
+
: 'Run failed before a final response was produced.';
|
|
686
|
+
}
|
|
687
|
+
/** Wrap an engine so each model call's token usage accrues into `total`. */
|
|
688
|
+
function withUsageTally(engine, total) {
|
|
689
|
+
return {
|
|
690
|
+
run: async (request, options) => {
|
|
691
|
+
const response = await engine.run(request, options);
|
|
692
|
+
addUsage(total, response.usage);
|
|
693
|
+
return response;
|
|
694
|
+
},
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
function addUsage(total, usage) {
|
|
698
|
+
if (!usage)
|
|
699
|
+
return;
|
|
700
|
+
if (usage.inputTokens != null)
|
|
701
|
+
total.inputTokens = (total.inputTokens ?? 0) + usage.inputTokens;
|
|
702
|
+
if (usage.outputTokens != null)
|
|
703
|
+
total.outputTokens = (total.outputTokens ?? 0) + usage.outputTokens;
|
|
704
|
+
if (usage.cachedInputTokens != null)
|
|
705
|
+
total.cachedInputTokens = (total.cachedInputTokens ?? 0) + usage.cachedInputTokens;
|
|
706
|
+
if (usage.reasoningTokens != null)
|
|
707
|
+
total.reasoningTokens = (total.reasoningTokens ?? 0) + usage.reasoningTokens;
|
|
708
|
+
const turnTotal = usage.totalTokens ?? sumDefined(usage.inputTokens, usage.outputTokens);
|
|
709
|
+
if (turnTotal != null)
|
|
710
|
+
total.totalTokens = (total.totalTokens ?? 0) + turnTotal;
|
|
711
|
+
if (usage.estimatedCostUSD != null)
|
|
712
|
+
total.estimatedCostUSD = (total.estimatedCostUSD ?? 0) + usage.estimatedCostUSD;
|
|
713
|
+
}
|
|
714
|
+
function sumDefined(a, b) {
|
|
715
|
+
if (a == null && b == null)
|
|
716
|
+
return undefined;
|
|
717
|
+
return (a ?? 0) + (b ?? 0);
|
|
718
|
+
}
|
|
719
|
+
function hasUsage(usage) {
|
|
720
|
+
return usage.inputTokens != null
|
|
721
|
+
|| usage.outputTokens != null
|
|
722
|
+
|| usage.totalTokens != null
|
|
723
|
+
|| usage.cachedInputTokens != null
|
|
724
|
+
|| usage.reasoningTokens != null
|
|
725
|
+
|| usage.estimatedCostUSD != null;
|
|
726
|
+
}
|
|
727
|
+
//# sourceMappingURL=AgentRunner.js.map
|