agent-world 0.13.0 → 0.15.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/README.md +90 -17
- package/dist/cli/commands.d.ts +7 -1
- package/dist/cli/commands.js +27 -10
- package/dist/cli/hitl.d.ts +4 -1
- package/dist/cli/hitl.js +55 -20
- package/dist/cli/index.js +249 -97
- package/dist/cli/system-events.d.ts +27 -0
- package/dist/cli/system-events.js +63 -0
- package/dist/core/activity-tracker.d.ts +26 -0
- package/dist/core/activity-tracker.d.ts.map +1 -1
- package/dist/core/activity-tracker.js +21 -4
- package/dist/core/activity-tracker.js.map +1 -1
- package/dist/core/anthropic-direct.d.ts +2 -0
- package/dist/core/anthropic-direct.d.ts.map +1 -1
- package/dist/core/anthropic-direct.js +43 -1
- package/dist/core/anthropic-direct.js.map +1 -1
- package/dist/core/chat-constants.d.ts +12 -0
- package/dist/core/chat-constants.d.ts.map +1 -1
- package/dist/core/chat-constants.js +5 -0
- package/dist/core/chat-constants.js.map +1 -1
- package/dist/core/create-agent-tool.d.ts +5 -0
- package/dist/core/create-agent-tool.d.ts.map +1 -1
- package/dist/core/create-agent-tool.js +57 -34
- package/dist/core/create-agent-tool.js.map +1 -1
- package/dist/core/events/index.d.ts +5 -2
- package/dist/core/events/index.d.ts.map +1 -1
- package/dist/core/events/index.js +5 -2
- package/dist/core/events/index.js.map +1 -1
- package/dist/core/events/memory-manager.d.ts +26 -1
- package/dist/core/events/memory-manager.d.ts.map +1 -1
- package/dist/core/events/memory-manager.js +877 -72
- package/dist/core/events/memory-manager.js.map +1 -1
- package/dist/core/events/orchestrator.d.ts +8 -0
- package/dist/core/events/orchestrator.d.ts.map +1 -1
- package/dist/core/events/orchestrator.js +203 -36
- package/dist/core/events/orchestrator.js.map +1 -1
- package/dist/core/events/persistence.d.ts +21 -14
- package/dist/core/events/persistence.d.ts.map +1 -1
- package/dist/core/events/persistence.js +100 -35
- package/dist/core/events/persistence.js.map +1 -1
- package/dist/core/events/publishers.d.ts +13 -7
- package/dist/core/events/publishers.d.ts.map +1 -1
- package/dist/core/events/publishers.js +53 -37
- package/dist/core/events/publishers.js.map +1 -1
- package/dist/core/events/subscribers.d.ts +17 -14
- package/dist/core/events/subscribers.d.ts.map +1 -1
- package/dist/core/events/subscribers.js +61 -148
- package/dist/core/events/subscribers.js.map +1 -1
- package/dist/core/events/title-scheduler.d.ts +27 -0
- package/dist/core/events/title-scheduler.d.ts.map +1 -0
- package/dist/core/events/title-scheduler.js +135 -0
- package/dist/core/events/title-scheduler.js.map +1 -0
- package/dist/core/events/tool-bridge-logging.d.ts +4 -1
- package/dist/core/events/tool-bridge-logging.d.ts.map +1 -1
- package/dist/core/events/tool-bridge-logging.js +112 -13
- package/dist/core/events/tool-bridge-logging.js.map +1 -1
- package/dist/core/events-metadata.d.ts.map +1 -1
- package/dist/core/events-metadata.js +8 -4
- package/dist/core/events-metadata.js.map +1 -1
- package/dist/core/export.d.ts +1 -1
- package/dist/core/export.d.ts.map +1 -1
- package/dist/core/export.js +2 -15
- package/dist/core/export.js.map +1 -1
- package/dist/core/feature-path-logging.d.ts +50 -0
- package/dist/core/feature-path-logging.d.ts.map +1 -0
- package/dist/core/feature-path-logging.js +130 -0
- package/dist/core/feature-path-logging.js.map +1 -0
- package/dist/core/file-tools.d.ts +57 -1
- package/dist/core/file-tools.d.ts.map +1 -1
- package/dist/core/file-tools.js +329 -29
- package/dist/core/file-tools.js.map +1 -1
- package/dist/core/google-direct.d.ts +6 -1
- package/dist/core/google-direct.d.ts.map +1 -1
- package/dist/core/google-direct.js +76 -7
- package/dist/core/google-direct.js.map +1 -1
- package/dist/core/heartbeat.d.ts +34 -0
- package/dist/core/heartbeat.d.ts.map +1 -0
- package/dist/core/heartbeat.js +153 -0
- package/dist/core/heartbeat.js.map +1 -0
- package/dist/core/hitl-tool.d.ts +6 -12
- package/dist/core/hitl-tool.d.ts.map +1 -1
- package/dist/core/hitl-tool.js +66 -88
- package/dist/core/hitl-tool.js.map +1 -1
- package/dist/core/hitl.d.ts +61 -4
- package/dist/core/hitl.d.ts.map +1 -1
- package/dist/core/hitl.js +324 -60
- package/dist/core/hitl.js.map +1 -1
- package/dist/core/index.d.ts +11 -7
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +10 -6
- package/dist/core/index.js.map +1 -1
- package/dist/core/llm-manager.d.ts +15 -0
- package/dist/core/llm-manager.d.ts.map +1 -1
- package/dist/core/llm-manager.js +325 -40
- package/dist/core/llm-manager.js.map +1 -1
- package/dist/core/load-skill-tool.d.ts +36 -3
- package/dist/core/load-skill-tool.d.ts.map +1 -1
- package/dist/core/load-skill-tool.js +807 -93
- package/dist/core/load-skill-tool.js.map +1 -1
- package/dist/core/logger.d.ts +14 -0
- package/dist/core/logger.d.ts.map +1 -1
- package/dist/core/logger.js +15 -0
- package/dist/core/logger.js.map +1 -1
- package/dist/core/managers.d.ts +18 -50
- package/dist/core/managers.d.ts.map +1 -1
- package/dist/core/managers.js +340 -502
- package/dist/core/managers.js.map +1 -1
- package/dist/core/mcp-server-registry.d.ts +16 -1
- package/dist/core/mcp-server-registry.d.ts.map +1 -1
- package/dist/core/mcp-server-registry.js +162 -12
- package/dist/core/mcp-server-registry.js.map +1 -1
- package/dist/core/message-cutoff.d.ts +29 -0
- package/dist/core/message-cutoff.d.ts.map +1 -0
- package/dist/core/message-cutoff.js +63 -0
- package/dist/core/message-cutoff.js.map +1 -0
- package/dist/core/message-edit-manager.d.ts +54 -0
- package/dist/core/message-edit-manager.d.ts.map +1 -0
- package/dist/core/message-edit-manager.js +602 -0
- package/dist/core/message-edit-manager.js.map +1 -0
- package/dist/core/message-prep.d.ts +2 -0
- package/dist/core/message-prep.d.ts.map +1 -1
- package/dist/core/message-prep.js +39 -12
- package/dist/core/message-prep.js.map +1 -1
- package/dist/core/message-processing-control.d.ts +1 -0
- package/dist/core/message-processing-control.d.ts.map +1 -1
- package/dist/core/message-processing-control.js +23 -6
- package/dist/core/message-processing-control.js.map +1 -1
- package/dist/core/openai-direct.d.ts +9 -3
- package/dist/core/openai-direct.d.ts.map +1 -1
- package/dist/core/openai-direct.js +267 -33
- package/dist/core/openai-direct.js.map +1 -1
- package/dist/core/optional-tracers/opik-runtime.d.ts +32 -0
- package/dist/core/optional-tracers/opik-runtime.d.ts.map +1 -0
- package/dist/core/optional-tracers/opik-runtime.js +141 -0
- package/dist/core/optional-tracers/opik-runtime.js.map +1 -0
- package/dist/core/queue-manager.d.ts +84 -0
- package/dist/core/queue-manager.d.ts.map +1 -0
- package/dist/core/queue-manager.js +814 -0
- package/dist/core/queue-manager.js.map +1 -0
- package/dist/core/reasoning-controls.d.ts +30 -0
- package/dist/core/reasoning-controls.d.ts.map +1 -0
- package/dist/core/reasoning-controls.js +118 -0
- package/dist/core/reasoning-controls.js.map +1 -0
- package/dist/core/reliability-config.d.ts +82 -0
- package/dist/core/reliability-config.d.ts.map +1 -0
- package/dist/core/reliability-config.js +106 -0
- package/dist/core/reliability-config.js.map +1 -0
- package/dist/core/reliability-runtime.d.ts +53 -0
- package/dist/core/reliability-runtime.d.ts.map +1 -0
- package/dist/core/reliability-runtime.js +92 -0
- package/dist/core/reliability-runtime.js.map +1 -0
- package/dist/core/security/guardrails.d.ts +21 -0
- package/dist/core/security/guardrails.d.ts.map +1 -0
- package/dist/core/security/guardrails.js +111 -0
- package/dist/core/security/guardrails.js.map +1 -0
- package/dist/core/send-message-tool.d.ts +79 -0
- package/dist/core/send-message-tool.d.ts.map +1 -0
- package/dist/core/send-message-tool.js +222 -0
- package/dist/core/send-message-tool.js.map +1 -0
- package/dist/core/shell-cmd-tool.d.ts +82 -1
- package/dist/core/shell-cmd-tool.d.ts.map +1 -1
- package/dist/core/shell-cmd-tool.js +854 -42
- package/dist/core/shell-cmd-tool.js.map +1 -1
- package/dist/core/skill-registry.d.ts +2 -0
- package/dist/core/skill-registry.d.ts.map +1 -1
- package/dist/core/skill-registry.js +52 -2
- package/dist/core/skill-registry.js.map +1 -1
- package/dist/core/storage/eventStorage/fileEventStorage.d.ts +5 -0
- package/dist/core/storage/eventStorage/fileEventStorage.d.ts.map +1 -1
- package/dist/core/storage/eventStorage/fileEventStorage.js +61 -0
- package/dist/core/storage/eventStorage/fileEventStorage.js.map +1 -1
- package/dist/core/storage/eventStorage/memoryEventStorage.d.ts +5 -0
- package/dist/core/storage/eventStorage/memoryEventStorage.d.ts.map +1 -1
- package/dist/core/storage/eventStorage/memoryEventStorage.js +34 -0
- package/dist/core/storage/eventStorage/memoryEventStorage.js.map +1 -1
- package/dist/core/storage/eventStorage/sqliteEventStorage.d.ts +1 -0
- package/dist/core/storage/eventStorage/sqliteEventStorage.d.ts.map +1 -1
- package/dist/core/storage/eventStorage/sqliteEventStorage.js +19 -2
- package/dist/core/storage/eventStorage/sqliteEventStorage.js.map +1 -1
- package/dist/core/storage/eventStorage/types.d.ts +6 -0
- package/dist/core/storage/eventStorage/types.d.ts.map +1 -1
- package/dist/core/storage/eventStorage/types.js +1 -0
- package/dist/core/storage/eventStorage/types.js.map +1 -1
- package/dist/core/storage/eventStorage/validation.d.ts.map +1 -1
- package/dist/core/storage/eventStorage/validation.js +2 -1
- package/dist/core/storage/eventStorage/validation.js.map +1 -1
- package/dist/core/storage/github-world-import.d.ts +84 -0
- package/dist/core/storage/github-world-import.d.ts.map +1 -0
- package/dist/core/storage/github-world-import.js +365 -0
- package/dist/core/storage/github-world-import.js.map +1 -0
- package/dist/core/storage/memory-storage.d.ts +19 -8
- package/dist/core/storage/memory-storage.d.ts.map +1 -1
- package/dist/core/storage/memory-storage.js +147 -49
- package/dist/core/storage/memory-storage.js.map +1 -1
- package/dist/core/storage/queue-storage.d.ts +1 -0
- package/dist/core/storage/queue-storage.d.ts.map +1 -1
- package/dist/core/storage/queue-storage.js +3 -2
- package/dist/core/storage/queue-storage.js.map +1 -1
- package/dist/core/storage/sqlite-storage.d.ts +14 -9
- package/dist/core/storage/sqlite-storage.d.ts.map +1 -1
- package/dist/core/storage/sqlite-storage.js +131 -154
- package/dist/core/storage/sqlite-storage.js.map +1 -1
- package/dist/core/storage/storage-factory.d.ts +3 -0
- package/dist/core/storage/storage-factory.d.ts.map +1 -1
- package/dist/core/storage/storage-factory.js +175 -89
- package/dist/core/storage/storage-factory.js.map +1 -1
- package/dist/core/storage/world-storage.d.ts +1 -1
- package/dist/core/storage/world-storage.d.ts.map +1 -1
- package/dist/core/storage/world-storage.js +5 -1
- package/dist/core/storage/world-storage.js.map +1 -1
- package/dist/core/storage-init.d.ts +11 -0
- package/dist/core/storage-init.d.ts.map +1 -0
- package/dist/core/storage-init.js +122 -0
- package/dist/core/storage-init.js.map +1 -0
- package/dist/core/subscription.d.ts +8 -1
- package/dist/core/subscription.d.ts.map +1 -1
- package/dist/core/subscription.js +130 -23
- package/dist/core/subscription.js.map +1 -1
- package/dist/core/tool-approval.d.ts +45 -0
- package/dist/core/tool-approval.d.ts.map +1 -0
- package/dist/core/tool-approval.js +223 -0
- package/dist/core/tool-approval.js.map +1 -0
- package/dist/core/tool-execution-envelope.d.ts +87 -0
- package/dist/core/tool-execution-envelope.d.ts.map +1 -0
- package/dist/core/tool-execution-envelope.js +168 -0
- package/dist/core/tool-execution-envelope.js.map +1 -0
- package/dist/core/tool-utils.d.ts +7 -2
- package/dist/core/tool-utils.d.ts.map +1 -1
- package/dist/core/tool-utils.js +81 -17
- package/dist/core/tool-utils.js.map +1 -1
- package/dist/core/types.d.ts +67 -19
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +3 -0
- package/dist/core/types.js.map +1 -1
- package/dist/core/utils.d.ts +7 -0
- package/dist/core/utils.d.ts.map +1 -1
- package/dist/core/utils.js +71 -21
- package/dist/core/utils.js.map +1 -1
- package/dist/core/web-fetch-tool.d.ts +72 -0
- package/dist/core/web-fetch-tool.d.ts.map +1 -0
- package/dist/core/web-fetch-tool.js +491 -0
- package/dist/core/web-fetch-tool.js.map +1 -0
- package/dist/core/world-registry.d.ts +84 -0
- package/dist/core/world-registry.d.ts.map +1 -0
- package/dist/core/world-registry.js +247 -0
- package/dist/core/world-registry.js.map +1 -0
- package/dist/public/assets/index-Be-1xtV-.js +104 -0
- package/dist/public/assets/index-tsDdiXDU.css +1 -0
- package/dist/public/index.html +2 -2
- package/dist/public/mcp-sandbox-proxy.html +148 -0
- package/dist/server/api.js +260 -18
- package/dist/server/error-response.d.ts +27 -0
- package/dist/server/error-response.js +77 -0
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +6 -2
- package/dist/server/sse-handler.d.ts +11 -1
- package/dist/server/sse-handler.js +194 -34
- package/migrations/0015_add_message_queue.sql +36 -0
- package/migrations/0016_add_world_heartbeat.sql +13 -0
- package/migrations/0017_add_title_provenance.sql +7 -0
- package/package.json +31 -10
- package/dist/public/assets/index-BW41BxMy.css +0 -1
- package/dist/public/assets/index-kO6UJFwK.js +0 -96
|
@@ -18,9 +18,19 @@
|
|
|
18
18
|
* - Keeps payload format deterministic for stable downstream parsing
|
|
19
19
|
*
|
|
20
20
|
* Recent Changes:
|
|
21
|
-
* - 2026-
|
|
22
|
-
* - 2026-
|
|
23
|
-
* - 2026-
|
|
21
|
+
* - 2026-03-12: Added `read` permission level guard to script execution — instructions load normally but all script execution steps are blocked with an inline note when toolPermission is 'read'.
|
|
22
|
+
* - 2026-03-06: Removed `world.currentChatId` fallback from interactive approval/result scoping; interactive `load_skill` now requires explicit `context.chatId`.
|
|
23
|
+
* - 2026-03-01: Removed minimal-check mode branch so `load_skill` always runs script/reference preflight consistently and keeps script-root execution guidance available.
|
|
24
|
+
* - 2026-03-01: Relaxed execution-directive narration requirements to avoid mandatory pre-tool plan text and reduce token overhead.
|
|
25
|
+
* - 2026-03-01: Added skill-description thread-through and acknowledgment-first execution directive requirements after successful `load_skill`, including unconditional pre-execution plan narration.
|
|
26
|
+
* - 2026-02-28: Updated `<execution_directive>` to require concise tool-use intent text before tool calls and to allow mixed text + tool-call turns when supported by the provider.
|
|
27
|
+
* - 2026-02-27: Run-scoped `load_skill` cache now stores only successful outcomes so declined/error/not-found paths remain retryable in the same run.
|
|
28
|
+
* - 2026-02-27: Added run-scoped `load_skill` result caching keyed by latest user-turn marker so repeated same-skill calls are auto-suppressed across assistant/tool hops.
|
|
29
|
+
* - 2026-02-27: Replaced `[load_skill:hitl]` console logs with structured category logger events (`load_skill.hitl`).
|
|
30
|
+
* - 2026-02-27: Added same-turn `yes_once` approval reuse + in-flight approval dedupe so repeated/concurrent `load_skill` calls for the same skill do not spam duplicate HITL prompts.
|
|
31
|
+
* - 2026-02-25: Added persisted synthetic HITL tool-call/response messages for `load_skill` approval so frontends can reconstruct prompts from memory without relying on transient event timing.
|
|
32
|
+
* - 2026-02-24: Surface non-zero script exits as informational output instead of blocking errors (scripts may be CLI tools requiring args).
|
|
33
|
+
* - 2026-02-24: Execute skill scripts with cwd set to trusted working directory when provided, otherwise use the skill root.
|
|
24
34
|
* - 2026-02-14: Strip SKILL.md YAML front matter from injected `<instructions>` content.
|
|
25
35
|
* - 2026-02-14: Omit `<active_resources>` from `load_skill` payloads when no instruction-referenced scripts are present.
|
|
26
36
|
* - 2026-02-14: Added skill-level HITL gating so `load_skill` requires approval even when no script references are present.
|
|
@@ -29,14 +39,25 @@
|
|
|
29
39
|
import { promises as fs } from 'fs';
|
|
30
40
|
import * as path from 'path';
|
|
31
41
|
import { getSkill, getSkillSourcePath, getSkillSourceScope, waitForInitialSkillSync, } from './skill-registry.js';
|
|
42
|
+
import { createStorageWithWrappers } from './storage/storage-factory.js';
|
|
32
43
|
import { executeShellCommand, formatResultForLLM, validateShellCommandScope, } from './shell-cmd-tool.js';
|
|
44
|
+
import { buildToolArtifactPreviewUrl, createArtifactToolPreview, createTextToolPreview, createUrlToolPreview, guessMediaTypeFromPath, parseToolExecutionEnvelopeContent, serializeToolExecutionEnvelope, } from './tool-execution-envelope.js';
|
|
45
|
+
import { createCategoryLogger } from './logger.js';
|
|
33
46
|
import { parseSkillIdListFromEnv } from './skill-settings.js';
|
|
34
|
-
import {
|
|
47
|
+
import { generateId, getEnvValueFromText } from './utils.js';
|
|
48
|
+
import { requestToolApproval } from './tool-approval.js';
|
|
35
49
|
const APPROVAL_OPTION_YES_ONCE = 'yes_once';
|
|
36
50
|
const APPROVAL_OPTION_YES_IN_SESSION = 'yes_in_session';
|
|
37
51
|
const APPROVAL_OPTION_NO = 'no';
|
|
38
52
|
const SCRIPT_TIMEOUT_MS = 120_000;
|
|
53
|
+
const LOAD_SKILL_RUN_RESULT_CACHE_LIMIT = 256;
|
|
39
54
|
const skillSessionApprovals = new Set();
|
|
55
|
+
const skillTurnApprovals = new Set();
|
|
56
|
+
const inFlightSkillApprovals = new Map();
|
|
57
|
+
const loadSkillRunResultCache = new Map();
|
|
58
|
+
const inFlightLoadSkillRunResults = new Map();
|
|
59
|
+
const loggerLoadSkillHitl = createCategoryLogger('load_skill.hitl');
|
|
60
|
+
const LOAD_SKILL_PREVIEW_SCRIPT_OUTPUT_CHARS = 800;
|
|
40
61
|
class SkillScriptExecutionError extends Error {
|
|
41
62
|
constructor(message) {
|
|
42
63
|
super(message);
|
|
@@ -92,6 +113,13 @@ function buildDisabledBySettingsResult(skillId) {
|
|
|
92
113
|
'</skill_context>',
|
|
93
114
|
].join('\n');
|
|
94
115
|
}
|
|
116
|
+
function truncatePreviewText(text, maxChars = LOAD_SKILL_PREVIEW_SCRIPT_OUTPUT_CHARS) {
|
|
117
|
+
const normalized = String(text || '').trim();
|
|
118
|
+
if (normalized.length <= maxChars) {
|
|
119
|
+
return normalized;
|
|
120
|
+
}
|
|
121
|
+
return `${normalized.slice(0, maxChars)}\n...[truncated ${normalized.length - maxChars} chars]`;
|
|
122
|
+
}
|
|
95
123
|
function isSkillEnabledBySettings(skillId) {
|
|
96
124
|
const includeGlobalSkills = String(process.env.AGENT_WORLD_ENABLE_GLOBAL_SKILLS ?? 'true').toLowerCase() !== 'false';
|
|
97
125
|
const includeProjectSkills = String(process.env.AGENT_WORLD_ENABLE_PROJECT_SKILLS ?? 'true').toLowerCase() !== 'false';
|
|
@@ -110,14 +138,33 @@ function isSkillEnabledBySettings(skillId) {
|
|
|
110
138
|
function normalizeScriptPath(scriptPath) {
|
|
111
139
|
return scriptPath.replace(/\\/g, '/').replace(/^\.\//, '').trim();
|
|
112
140
|
}
|
|
141
|
+
function normalizeComparablePath(targetPath) {
|
|
142
|
+
const resolvedPath = path.resolve(targetPath).replace(/\\/g, '/');
|
|
143
|
+
return process.platform === 'win32'
|
|
144
|
+
? resolvedPath.replace(/\/+/g, '/').replace(/\/$/, '')
|
|
145
|
+
: resolvedPath.replace(/\/+/g, '/').replace(/\/$/, '') || '/';
|
|
146
|
+
}
|
|
147
|
+
function normalizeAbsoluteLocalPath(targetPath) {
|
|
148
|
+
return normalizeComparablePath(targetPath);
|
|
149
|
+
}
|
|
150
|
+
function toRootRelativePath(rootPath, targetPath) {
|
|
151
|
+
const normalizedRoot = normalizeComparablePath(rootPath);
|
|
152
|
+
const normalizedTarget = normalizeComparablePath(targetPath);
|
|
153
|
+
if (normalizedTarget === normalizedRoot) {
|
|
154
|
+
return '';
|
|
155
|
+
}
|
|
156
|
+
if (normalizedTarget.startsWith(`${normalizedRoot}/`)) {
|
|
157
|
+
return normalizedTarget.slice(normalizedRoot.length + 1);
|
|
158
|
+
}
|
|
159
|
+
return normalizedTarget.replace(/^\/+/, '');
|
|
160
|
+
}
|
|
113
161
|
function stripYamlFrontMatter(markdown) {
|
|
114
162
|
const frontMatterPattern = /^\uFEFF?---\s*\r?\n[\s\S]*?\r?\n---\s*(?:\r?\n|$)/;
|
|
115
163
|
return markdown.replace(frontMatterPattern, '');
|
|
116
164
|
}
|
|
117
165
|
function isPathWithinRoot(skillRoot, targetPath) {
|
|
118
|
-
const
|
|
119
|
-
const
|
|
120
|
-
const normalizedTarget = normalize(targetPath);
|
|
166
|
+
const normalizedRoot = normalizeComparablePath(skillRoot);
|
|
167
|
+
const normalizedTarget = normalizeComparablePath(targetPath);
|
|
121
168
|
return normalizedTarget === normalizedRoot || normalizedTarget.startsWith(`${normalizedRoot}/`);
|
|
122
169
|
}
|
|
123
170
|
function extractReferencedScriptPaths(markdown) {
|
|
@@ -160,14 +207,14 @@ async function collectReferenceFiles(skillRoot, markdown) {
|
|
|
160
207
|
if (linkedPath.startsWith('#')) {
|
|
161
208
|
continue;
|
|
162
209
|
}
|
|
163
|
-
const absolutePath = path.resolve(skillRoot, linkedPath);
|
|
210
|
+
const absolutePath = normalizeAbsoluteLocalPath(path.resolve(skillRoot, linkedPath));
|
|
164
211
|
if (!isPathWithinRoot(skillRoot, absolutePath)) {
|
|
165
212
|
continue;
|
|
166
213
|
}
|
|
167
214
|
try {
|
|
168
215
|
const stat = await fs.stat(absolutePath);
|
|
169
216
|
if (stat.isFile()) {
|
|
170
|
-
collected.add(
|
|
217
|
+
collected.add(toRootRelativePath(skillRoot, absolutePath));
|
|
171
218
|
}
|
|
172
219
|
}
|
|
173
220
|
catch {
|
|
@@ -197,12 +244,12 @@ async function collectReferenceFiles(skillRoot, markdown) {
|
|
|
197
244
|
}
|
|
198
245
|
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
199
246
|
for (const entry of entries) {
|
|
200
|
-
const absolutePath = path.join(currentPath, entry.name);
|
|
247
|
+
const absolutePath = normalizeAbsoluteLocalPath(path.join(currentPath, entry.name));
|
|
201
248
|
if (entry.isDirectory()) {
|
|
202
249
|
queue.push(absolutePath);
|
|
203
250
|
}
|
|
204
251
|
else if (entry.isFile()) {
|
|
205
|
-
collected.add(
|
|
252
|
+
collected.add(toRootRelativePath(skillRoot, absolutePath));
|
|
206
253
|
}
|
|
207
254
|
}
|
|
208
255
|
}
|
|
@@ -230,54 +277,442 @@ function formatReferenceFilesBlock(referenceFiles) {
|
|
|
230
277
|
function createSessionApprovalKey(worldId, chatId, skillId) {
|
|
231
278
|
return `${worldId}::${chatId ?? 'global'}::${skillId}`;
|
|
232
279
|
}
|
|
280
|
+
function getExplicitContextChatId(context) {
|
|
281
|
+
const chatId = typeof context?.chatId === 'string' ? context.chatId.trim() : '';
|
|
282
|
+
return chatId || null;
|
|
283
|
+
}
|
|
284
|
+
function getCurrentTurnMarker(context) {
|
|
285
|
+
const chatId = getExplicitContextChatId(context);
|
|
286
|
+
const messages = Array.isArray(context?.messages) ? context.messages : [];
|
|
287
|
+
if (!chatId || messages.length === 0) {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
291
|
+
const message = messages[index];
|
|
292
|
+
if (message?.role !== 'user') {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
const messageChatId = message?.chatId ? String(message.chatId).trim() : null;
|
|
296
|
+
if (messageChatId && messageChatId !== chatId) {
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
const messageId = String(message?.messageId || '').trim();
|
|
300
|
+
if (messageId) {
|
|
301
|
+
return `msg:${messageId}`;
|
|
302
|
+
}
|
|
303
|
+
const createdAt = message?.createdAt ? new Date(message.createdAt) : null;
|
|
304
|
+
if (createdAt && Number.isFinite(createdAt.valueOf())) {
|
|
305
|
+
return `ts:${createdAt.toISOString()}`;
|
|
306
|
+
}
|
|
307
|
+
const content = String(message?.content || '').trim();
|
|
308
|
+
if (content) {
|
|
309
|
+
return `content:${content.slice(0, 80)}`;
|
|
310
|
+
}
|
|
311
|
+
return `idx:${index}`;
|
|
312
|
+
}
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
function createTurnApprovalKey(worldId, chatId, skillId, turnMarker) {
|
|
316
|
+
return `${worldId}::${chatId ?? 'global'}::${skillId}::turn::${turnMarker}`;
|
|
317
|
+
}
|
|
318
|
+
function createRunResultKey(worldId, chatId, skillId, turnMarker) {
|
|
319
|
+
return `${worldId}::${chatId ?? 'global'}::${skillId}::run::${turnMarker}`;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Reconstruct skill approval caches from persisted message history.
|
|
323
|
+
* Called during chat restore so that `yes_in_session` and `yes_once` grants
|
|
324
|
+
* survive app restarts without re-prompting the user.
|
|
325
|
+
*
|
|
326
|
+
* Scans `role: 'tool'` messages whose JSON content contains `skillId` and
|
|
327
|
+
* `optionId` fields (written by `persistLoadSkillApprovalResolutionMessage`).
|
|
328
|
+
*
|
|
329
|
+
* - `yes_in_session` grants are restored unconditionally (session-scoped).
|
|
330
|
+
* - `yes_once` grants are restored only when they belong to the current turn
|
|
331
|
+
* (i.e. appear after the last `role: 'user'` message in the history).
|
|
332
|
+
*/
|
|
333
|
+
export function reconstructSkillApprovalsFromMessages(worldId, chatId, messages) {
|
|
334
|
+
if (!worldId || !Array.isArray(messages) || messages.length === 0) {
|
|
335
|
+
return 0;
|
|
336
|
+
}
|
|
337
|
+
// Find the index of the last user message for this chat (turn boundary).
|
|
338
|
+
let lastUserMessageIndex = -1;
|
|
339
|
+
let turnMarker = null;
|
|
340
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
341
|
+
const msg = messages[i];
|
|
342
|
+
if (msg?.role !== 'user')
|
|
343
|
+
continue;
|
|
344
|
+
const msgChatId = msg?.chatId ? String(msg.chatId).trim() : null;
|
|
345
|
+
if (msgChatId && msgChatId !== (chatId ?? 'global'))
|
|
346
|
+
continue;
|
|
347
|
+
lastUserMessageIndex = i;
|
|
348
|
+
// Derive turn marker using same precedence as getCurrentTurnMarker.
|
|
349
|
+
const messageId = String(msg?.messageId || '').trim();
|
|
350
|
+
if (messageId) {
|
|
351
|
+
turnMarker = `msg:${messageId}`;
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
const createdAt = msg?.createdAt ? new Date(msg.createdAt) : null;
|
|
355
|
+
if (createdAt && Number.isFinite(createdAt.valueOf())) {
|
|
356
|
+
turnMarker = `ts:${createdAt.toISOString()}`;
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
const content = String(msg?.content || '').trim();
|
|
360
|
+
if (content) {
|
|
361
|
+
turnMarker = `content:${content.slice(0, 80)}`;
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
turnMarker = `idx:${i}`;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
let restored = 0;
|
|
371
|
+
for (let i = 0; i < messages.length; i += 1) {
|
|
372
|
+
const msg = messages[i];
|
|
373
|
+
if (msg?.role !== 'tool')
|
|
374
|
+
continue;
|
|
375
|
+
let payload = null;
|
|
376
|
+
try {
|
|
377
|
+
const content = typeof msg.content === 'string' ? msg.content : null;
|
|
378
|
+
if (content)
|
|
379
|
+
payload = JSON.parse(content);
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
if (!payload
|
|
385
|
+
|| typeof payload.requestId !== 'string'
|
|
386
|
+
|| !payload.requestId.includes('load_skill_approval')
|
|
387
|
+
|| typeof payload.skillId !== 'string'
|
|
388
|
+
|| typeof payload.optionId !== 'string') {
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
const { skillId, optionId } = payload;
|
|
392
|
+
if (optionId === APPROVAL_OPTION_YES_IN_SESSION) {
|
|
393
|
+
skillSessionApprovals.add(createSessionApprovalKey(worldId, chatId, skillId));
|
|
394
|
+
restored += 1;
|
|
395
|
+
}
|
|
396
|
+
else if (optionId === APPROVAL_OPTION_YES_ONCE && turnMarker && i > lastUserMessageIndex) {
|
|
397
|
+
skillTurnApprovals.add(createTurnApprovalKey(worldId, chatId, skillId, turnMarker));
|
|
398
|
+
restored += 1;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (restored > 0) {
|
|
402
|
+
loggerLoadSkillHitl.debug('Reconstructed skill approvals from message history', {
|
|
403
|
+
worldId,
|
|
404
|
+
chatId: chatId || null,
|
|
405
|
+
restored,
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
return restored;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Clear cached skill approvals and run results scoped to a specific chat.
|
|
412
|
+
* Must be called when messages are removed (e.g. edit+resubmit) so that
|
|
413
|
+
* HITL approval prompts fire again for the reprocessed message.
|
|
414
|
+
*/
|
|
415
|
+
export function clearChatSkillApprovals(worldId, chatId) {
|
|
416
|
+
const chatToken = chatId ?? 'global';
|
|
417
|
+
const prefix = `${worldId}::${chatToken}::`;
|
|
418
|
+
for (const key of skillSessionApprovals) {
|
|
419
|
+
if (key.startsWith(prefix)) {
|
|
420
|
+
skillSessionApprovals.delete(key);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
for (const key of skillTurnApprovals) {
|
|
424
|
+
if (key.startsWith(prefix)) {
|
|
425
|
+
skillTurnApprovals.delete(key);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
for (const key of inFlightSkillApprovals.keys()) {
|
|
429
|
+
if (key.includes(prefix)) {
|
|
430
|
+
inFlightSkillApprovals.delete(key);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
for (const key of loadSkillRunResultCache.keys()) {
|
|
434
|
+
if (key.startsWith(prefix)) {
|
|
435
|
+
loadSkillRunResultCache.delete(key);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
for (const key of inFlightLoadSkillRunResults.keys()) {
|
|
439
|
+
if (key.startsWith(prefix)) {
|
|
440
|
+
inFlightLoadSkillRunResults.delete(key);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function getRunScopedLoadSkillResultKey(skillId, context) {
|
|
445
|
+
const worldId = String(context?.world?.id || '').trim();
|
|
446
|
+
if (!worldId) {
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
const chatId = getExplicitContextChatId(context);
|
|
450
|
+
if (!chatId) {
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
const turnMarker = getCurrentTurnMarker(context);
|
|
454
|
+
if (!turnMarker) {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
return createRunResultKey(worldId, chatId, skillId, turnMarker);
|
|
458
|
+
}
|
|
459
|
+
function rememberRunScopedLoadSkillResult(cacheKey, result) {
|
|
460
|
+
if (loadSkillRunResultCache.has(cacheKey)) {
|
|
461
|
+
loadSkillRunResultCache.delete(cacheKey);
|
|
462
|
+
}
|
|
463
|
+
loadSkillRunResultCache.set(cacheKey, result);
|
|
464
|
+
while (loadSkillRunResultCache.size > LOAD_SKILL_RUN_RESULT_CACHE_LIMIT) {
|
|
465
|
+
const oldestKey = loadSkillRunResultCache.keys().next().value;
|
|
466
|
+
if (!oldestKey) {
|
|
467
|
+
break;
|
|
468
|
+
}
|
|
469
|
+
loadSkillRunResultCache.delete(oldestKey);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
function getLoadSkillApprovalRequestId(context, skillId) {
|
|
473
|
+
const parentToolCallId = String(context?.toolCallId || '').trim();
|
|
474
|
+
if (parentToolCallId) {
|
|
475
|
+
return `${parentToolCallId}::load_skill_approval`;
|
|
476
|
+
}
|
|
477
|
+
const normalizedSkillId = String(skillId || '').trim() || 'skill';
|
|
478
|
+
return `load_skill_approval::${normalizedSkillId}::${generateId()}`;
|
|
479
|
+
}
|
|
480
|
+
async function persistAgentMemoryIfAvailable(context) {
|
|
481
|
+
const worldId = String(context?.world?.id || '').trim();
|
|
482
|
+
const agentName = String(context?.agentName || '').trim();
|
|
483
|
+
const messages = Array.isArray(context?.messages) ? context.messages : null;
|
|
484
|
+
const world = context?.world;
|
|
485
|
+
if (!worldId || !agentName || !messages || !world?.agents || typeof world.agents.get !== 'function') {
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
const agent = world.agents.get(agentName);
|
|
489
|
+
if (!agent) {
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
const storage = await createStorageWithWrappers();
|
|
493
|
+
await storage.saveAgent(worldId, agent);
|
|
494
|
+
}
|
|
495
|
+
async function persistLoadSkillApprovalPromptMessage(options) {
|
|
496
|
+
const messages = Array.isArray(options.context?.messages) ? options.context.messages : null;
|
|
497
|
+
if (!messages) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
const existing = messages.some((message) => message?.role === 'assistant'
|
|
501
|
+
&& Array.isArray(message?.tool_calls)
|
|
502
|
+
&& message.tool_calls.some((toolCall) => String(toolCall?.id || '').trim() === options.requestId));
|
|
503
|
+
if (existing) {
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
const question = `Skill "${options.skillId}" requested execution.${options.scriptPaths.length > 0 ? ` Referenced scripts:\n${options.scriptPaths.map((scriptPath) => `- ${scriptPath}`).join('\n')}` : ''}\n\nApprove applying this skill now?`;
|
|
507
|
+
const toolArguments = {
|
|
508
|
+
question,
|
|
509
|
+
options: [
|
|
510
|
+
{ id: APPROVAL_OPTION_YES_ONCE, label: 'Yes once' },
|
|
511
|
+
{ id: APPROVAL_OPTION_YES_IN_SESSION, label: 'Yes in this session' },
|
|
512
|
+
{ id: APPROVAL_OPTION_NO, label: 'No' },
|
|
513
|
+
],
|
|
514
|
+
defaultOptionId: APPROVAL_OPTION_NO,
|
|
515
|
+
defaultOption: 'No',
|
|
516
|
+
metadata: {
|
|
517
|
+
tool: 'human_intervention_request',
|
|
518
|
+
toolCallId: options.requestId,
|
|
519
|
+
source: 'load_skill',
|
|
520
|
+
skillId: options.skillId,
|
|
521
|
+
scriptPaths: options.scriptPaths,
|
|
522
|
+
},
|
|
523
|
+
};
|
|
524
|
+
const chatId = getExplicitContextChatId(options.context);
|
|
525
|
+
if (!chatId) {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
const agentName = String(options.context?.agentName || '').trim() || 'assistant';
|
|
529
|
+
messages.push({
|
|
530
|
+
role: 'assistant',
|
|
531
|
+
content: `Calling tool: human_intervention_request (skill_id: "${options.skillId}")`,
|
|
532
|
+
tool_calls: [{
|
|
533
|
+
id: options.requestId,
|
|
534
|
+
type: 'function',
|
|
535
|
+
function: {
|
|
536
|
+
name: 'human_intervention_request',
|
|
537
|
+
arguments: JSON.stringify(toolArguments),
|
|
538
|
+
},
|
|
539
|
+
}],
|
|
540
|
+
sender: agentName,
|
|
541
|
+
createdAt: new Date(),
|
|
542
|
+
chatId,
|
|
543
|
+
messageId: generateId(),
|
|
544
|
+
replyToMessageId: options.context?.toolCallId,
|
|
545
|
+
agentId: agentName,
|
|
546
|
+
});
|
|
547
|
+
await persistAgentMemoryIfAvailable(options.context);
|
|
548
|
+
loggerLoadSkillHitl.debug('Persisted load_skill approval tool-call message', {
|
|
549
|
+
chatId: chatId || null,
|
|
550
|
+
agentName,
|
|
551
|
+
requestId: options.requestId,
|
|
552
|
+
skillId: options.skillId,
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
async function persistLoadSkillApprovalResolutionMessage(options) {
|
|
556
|
+
const messages = Array.isArray(options.context?.messages) ? options.context.messages : null;
|
|
557
|
+
if (!messages) {
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
const existing = messages.some((message) => message?.role === 'tool'
|
|
561
|
+
&& String(message?.tool_call_id || '').trim() === options.requestId);
|
|
562
|
+
if (existing) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
const chatId = getExplicitContextChatId(options.context);
|
|
566
|
+
if (!chatId) {
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
const agentName = String(options.context?.agentName || '').trim() || 'assistant';
|
|
570
|
+
const payload = {
|
|
571
|
+
requestId: options.resolution.requestId,
|
|
572
|
+
optionId: options.resolution.optionId,
|
|
573
|
+
source: options.resolution.source,
|
|
574
|
+
skillId: options.skillId,
|
|
575
|
+
};
|
|
576
|
+
messages.push({
|
|
577
|
+
role: 'tool',
|
|
578
|
+
content: JSON.stringify(payload),
|
|
579
|
+
tool_call_id: options.requestId,
|
|
580
|
+
sender: agentName,
|
|
581
|
+
createdAt: new Date(),
|
|
582
|
+
chatId,
|
|
583
|
+
messageId: generateId(),
|
|
584
|
+
agentId: agentName,
|
|
585
|
+
});
|
|
586
|
+
await persistAgentMemoryIfAvailable(options.context);
|
|
587
|
+
loggerLoadSkillHitl.debug('Persisted load_skill approval tool-result message', {
|
|
588
|
+
chatId: chatId || null,
|
|
589
|
+
agentName,
|
|
590
|
+
requestId: options.requestId,
|
|
591
|
+
optionId: options.resolution.optionId,
|
|
592
|
+
});
|
|
593
|
+
}
|
|
233
594
|
async function requestSkillExecutionApproval(options) {
|
|
234
|
-
const
|
|
235
|
-
const
|
|
236
|
-
|
|
595
|
+
const worldContext = options.context?.world;
|
|
596
|
+
const worldId = String(worldContext?.id || '').trim();
|
|
597
|
+
const chatId = getExplicitContextChatId(options.context);
|
|
598
|
+
const requestId = getLoadSkillApprovalRequestId(options.context, options.skillId);
|
|
599
|
+
if (!worldId || !worldContext) {
|
|
237
600
|
return true;
|
|
238
601
|
}
|
|
602
|
+
if (!chatId) {
|
|
603
|
+
return false;
|
|
604
|
+
}
|
|
239
605
|
const sessionApprovalKey = createSessionApprovalKey(worldId, chatId, options.skillId);
|
|
606
|
+
const turnMarker = getCurrentTurnMarker(options.context);
|
|
607
|
+
const turnApprovalKey = turnMarker
|
|
608
|
+
? createTurnApprovalKey(worldId, chatId, options.skillId, turnMarker)
|
|
609
|
+
: null;
|
|
240
610
|
if (skillSessionApprovals.has(sessionApprovalKey)) {
|
|
241
611
|
return true;
|
|
242
612
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
613
|
+
if (turnApprovalKey && skillTurnApprovals.has(turnApprovalKey)) {
|
|
614
|
+
return true;
|
|
615
|
+
}
|
|
616
|
+
const inFlightApprovalKey = turnApprovalKey
|
|
617
|
+
? `turn::${turnApprovalKey}`
|
|
618
|
+
: `session::${sessionApprovalKey}`;
|
|
619
|
+
const existingInFlightApproval = inFlightSkillApprovals.get(inFlightApprovalKey);
|
|
620
|
+
if (existingInFlightApproval) {
|
|
621
|
+
return await existingInFlightApproval;
|
|
622
|
+
}
|
|
623
|
+
const approvalPromise = (async () => {
|
|
624
|
+
const scriptSummary = options.scriptPaths.length > 0
|
|
625
|
+
? `The skill references local scripts:\n${options.scriptPaths.map((scriptPath) => `- ${scriptPath}`).join('\n')}`
|
|
626
|
+
: 'No instruction-referenced local scripts were detected for this skill.';
|
|
627
|
+
await persistLoadSkillApprovalPromptMessage({
|
|
628
|
+
context: options.context,
|
|
629
|
+
requestId,
|
|
630
|
+
skillId: options.skillId,
|
|
631
|
+
scriptPaths: options.scriptPaths,
|
|
632
|
+
});
|
|
633
|
+
const approvalResult = await requestToolApproval({
|
|
634
|
+
world: worldContext,
|
|
635
|
+
requestId,
|
|
636
|
+
title: `Run skill ${options.skillId}?`,
|
|
637
|
+
message: [
|
|
638
|
+
`Skill "${options.skillId}" requested execution.`,
|
|
639
|
+
scriptSummary,
|
|
640
|
+
'Approve applying this skill now?',
|
|
641
|
+
].join('\n\n'),
|
|
642
|
+
chatId,
|
|
643
|
+
defaultOptionId: APPROVAL_OPTION_NO,
|
|
644
|
+
options: [
|
|
645
|
+
{ id: APPROVAL_OPTION_YES_ONCE, label: 'Yes once', description: 'Allow this skill for this call only.' },
|
|
646
|
+
{
|
|
647
|
+
id: APPROVAL_OPTION_YES_IN_SESSION,
|
|
648
|
+
label: 'Yes in this session',
|
|
649
|
+
description: 'Allow this skill for the current chat session.',
|
|
650
|
+
},
|
|
651
|
+
{ id: APPROVAL_OPTION_NO, label: 'No', description: 'Do not apply this skill now.' },
|
|
652
|
+
],
|
|
653
|
+
approvedOptionIds: [APPROVAL_OPTION_YES_ONCE, APPROVAL_OPTION_YES_IN_SESSION],
|
|
654
|
+
metadata: {
|
|
655
|
+
tool: 'human_intervention_request',
|
|
656
|
+
toolCallId: requestId,
|
|
657
|
+
source: 'load_skill',
|
|
658
|
+
skillId: options.skillId,
|
|
659
|
+
scriptPaths: options.scriptPaths,
|
|
261
660
|
},
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
661
|
+
agentName: options.context?.agentName ?? null,
|
|
662
|
+
});
|
|
663
|
+
const approval = {
|
|
664
|
+
requestId,
|
|
665
|
+
worldId,
|
|
666
|
+
chatId,
|
|
667
|
+
optionId: approvalResult.optionId,
|
|
668
|
+
source: approvalResult.source,
|
|
669
|
+
};
|
|
670
|
+
await persistLoadSkillApprovalResolutionMessage({
|
|
671
|
+
context: options.context,
|
|
672
|
+
requestId,
|
|
673
|
+
resolution: approval,
|
|
674
|
+
skillId: options.skillId,
|
|
675
|
+
});
|
|
676
|
+
if (!approvalResult.approved) {
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
if (approval.optionId === APPROVAL_OPTION_YES_IN_SESSION) {
|
|
680
|
+
skillSessionApprovals.add(sessionApprovalKey);
|
|
681
|
+
if (turnApprovalKey) {
|
|
682
|
+
skillTurnApprovals.add(turnApprovalKey);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
else if (approval.optionId === APPROVAL_OPTION_YES_ONCE && turnApprovalKey) {
|
|
686
|
+
skillTurnApprovals.add(turnApprovalKey);
|
|
687
|
+
}
|
|
688
|
+
return true;
|
|
689
|
+
})();
|
|
690
|
+
inFlightSkillApprovals.set(inFlightApprovalKey, approvalPromise);
|
|
691
|
+
try {
|
|
692
|
+
return await approvalPromise;
|
|
268
693
|
}
|
|
269
|
-
|
|
270
|
-
|
|
694
|
+
finally {
|
|
695
|
+
const currentInFlightApproval = inFlightSkillApprovals.get(inFlightApprovalKey);
|
|
696
|
+
if (currentInFlightApproval === approvalPromise) {
|
|
697
|
+
inFlightSkillApprovals.delete(inFlightApprovalKey);
|
|
698
|
+
}
|
|
271
699
|
}
|
|
272
|
-
return true;
|
|
273
700
|
}
|
|
274
701
|
async function executeSkillScripts(options) {
|
|
275
702
|
const scriptPaths = options.scriptPaths;
|
|
276
703
|
if (scriptPaths.length === 0) {
|
|
277
704
|
return [];
|
|
278
705
|
}
|
|
706
|
+
// Check world-level tool permission: 'read' blocks all script execution steps.
|
|
707
|
+
const toolPermission = getEnvValueFromText(options.context?.world?.variables, 'tool_permission') ?? 'auto';
|
|
708
|
+
if (toolPermission === 'read') {
|
|
709
|
+
return scriptPaths.map((scriptPath) => ({
|
|
710
|
+
source: scriptPath,
|
|
711
|
+
output: 'Script execution is blocked by the current permission level (read).',
|
|
712
|
+
}));
|
|
713
|
+
}
|
|
279
714
|
const worldId = String(options.context?.world?.id || '').trim();
|
|
280
|
-
const chatId = options.context
|
|
715
|
+
const chatId = getExplicitContextChatId(options.context);
|
|
281
716
|
const executionDirectory = options.context?.workingDirectory || options.skillRoot;
|
|
282
717
|
if (!worldId || !options.context?.world) {
|
|
283
718
|
return [{
|
|
@@ -329,13 +764,6 @@ async function executeSkillScripts(options) {
|
|
|
329
764
|
continue;
|
|
330
765
|
}
|
|
331
766
|
const absoluteCommandSpec = resolveScriptCommand(normalizedAbsoluteScriptPath);
|
|
332
|
-
if (!isPathWithinRoot(executionDirectory, normalizedAbsoluteScriptPath)) {
|
|
333
|
-
scriptOutputs.push({
|
|
334
|
-
source: relativeScriptPath,
|
|
335
|
-
output: `Script path rejected: "${relativeScriptPath}" resolves outside execution working directory "${executionDirectory}".`,
|
|
336
|
-
});
|
|
337
|
-
continue;
|
|
338
|
-
}
|
|
339
767
|
const executionResult = await executeShellCommand(absoluteCommandSpec.command, absoluteCommandSpec.parameters, executionDirectory, {
|
|
340
768
|
timeout: SCRIPT_TIMEOUT_MS,
|
|
341
769
|
abortSignal: options.context?.abortSignal,
|
|
@@ -346,8 +774,17 @@ async function executeSkillScripts(options) {
|
|
|
346
774
|
if (executionResult.exitCode !== 0 || executionResult.error) {
|
|
347
775
|
const exitCode = executionResult.exitCode === null ? 'unknown' : String(executionResult.exitCode);
|
|
348
776
|
const stderr = executionResult.stderr.trim();
|
|
349
|
-
const
|
|
350
|
-
|
|
777
|
+
const stdoutPreview = executionResult.stdout.trim();
|
|
778
|
+
const detail = [
|
|
779
|
+
`exit code ${exitCode}`,
|
|
780
|
+
stderr ? `stderr: ${stderr}` : '',
|
|
781
|
+
stdoutPreview ? `stdout: ${stdoutPreview}` : '',
|
|
782
|
+
].filter(Boolean).join(' | ');
|
|
783
|
+
scriptOutputs.push({
|
|
784
|
+
source: relativeScriptPath,
|
|
785
|
+
output: `Script exited with ${detail}`,
|
|
786
|
+
});
|
|
787
|
+
continue;
|
|
351
788
|
}
|
|
352
789
|
scriptOutputs.push({
|
|
353
790
|
source: relativeScriptPath,
|
|
@@ -356,10 +793,161 @@ async function executeSkillScripts(options) {
|
|
|
356
793
|
}
|
|
357
794
|
return scriptOutputs;
|
|
358
795
|
}
|
|
796
|
+
function isYouTubeUrl(value) {
|
|
797
|
+
return /https?:\/\/(?:www\.)?(?:youtube\.com|youtu\.be)\//i.test(String(value || ''));
|
|
798
|
+
}
|
|
799
|
+
function extractUrlsFromText(text) {
|
|
800
|
+
const matches = String(text || '').match(/https?:\/\/[^\s)>\]}]+/gi) || [];
|
|
801
|
+
return [...new Set(matches.map((match) => match.replace(/[),.;!?]+$/g, '')))];
|
|
802
|
+
}
|
|
803
|
+
function extractArtifactPathCandidates(text) {
|
|
804
|
+
const matches = String(text || '').match(/(?:\/[^\s"'`<>]+|\.[/\\][^\s"'`<>]+|[A-Za-z0-9_.-]+\.(?:svg|png|jpg|jpeg|gif|webp|mp3|wav|ogg|m4a|mp4|webm|mov|md|txt))/gi) || [];
|
|
805
|
+
return [...new Set(matches)];
|
|
806
|
+
}
|
|
807
|
+
async function resolvePreviewArtifactFromCandidate(candidate, roots, options = {}) {
|
|
808
|
+
const normalizedCandidate = String(candidate || '').trim();
|
|
809
|
+
if (!normalizedCandidate) {
|
|
810
|
+
return null;
|
|
811
|
+
}
|
|
812
|
+
for (const root of roots) {
|
|
813
|
+
const absolutePath = path.isAbsolute(normalizedCandidate)
|
|
814
|
+
? normalizeAbsoluteLocalPath(normalizedCandidate)
|
|
815
|
+
: normalizeAbsoluteLocalPath(path.resolve(root, normalizedCandidate));
|
|
816
|
+
const isAllowedPath = roots.some((allowedRoot) => isPathWithinRoot(allowedRoot, absolutePath));
|
|
817
|
+
if (!isAllowedPath) {
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
try {
|
|
821
|
+
const stat = await fs.stat(absolutePath);
|
|
822
|
+
if (!stat.isFile()) {
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
const mediaType = guessMediaTypeFromPath(absolutePath);
|
|
826
|
+
return createArtifactToolPreview({
|
|
827
|
+
path: absolutePath,
|
|
828
|
+
bytes: stat.size,
|
|
829
|
+
media_type: mediaType,
|
|
830
|
+
display_name: path.basename(absolutePath),
|
|
831
|
+
url: buildToolArtifactPreviewUrl({ path: absolutePath, worldId: options.worldId }),
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
catch {
|
|
835
|
+
continue;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
return null;
|
|
839
|
+
}
|
|
840
|
+
async function collectLoadSkillPreviewArtifacts(options) {
|
|
841
|
+
const previews = [];
|
|
842
|
+
const seen = new Set();
|
|
843
|
+
const roots = [
|
|
844
|
+
options.skillRoot,
|
|
845
|
+
...(options.context?.workingDirectory ? [options.context.workingDirectory] : []),
|
|
846
|
+
];
|
|
847
|
+
for (const referenceFile of options.referenceFiles) {
|
|
848
|
+
const absolutePath = path.isAbsolute(referenceFile)
|
|
849
|
+
? normalizeAbsoluteLocalPath(referenceFile)
|
|
850
|
+
: normalizeAbsoluteLocalPath(path.resolve(options.skillRoot, referenceFile));
|
|
851
|
+
try {
|
|
852
|
+
const stat = await fs.stat(absolutePath);
|
|
853
|
+
if (!stat.isFile()) {
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
const key = `path:${absolutePath}`;
|
|
857
|
+
if (seen.has(key)) {
|
|
858
|
+
continue;
|
|
859
|
+
}
|
|
860
|
+
seen.add(key);
|
|
861
|
+
previews.push(createArtifactToolPreview({
|
|
862
|
+
path: absolutePath,
|
|
863
|
+
bytes: stat.size,
|
|
864
|
+
media_type: guessMediaTypeFromPath(absolutePath),
|
|
865
|
+
display_name: path.basename(referenceFile),
|
|
866
|
+
title: referenceFile,
|
|
867
|
+
url: buildToolArtifactPreviewUrl({
|
|
868
|
+
path: absolutePath,
|
|
869
|
+
worldId: typeof options.context?.world?.id === 'string' ? options.context.world.id : undefined,
|
|
870
|
+
}),
|
|
871
|
+
}));
|
|
872
|
+
}
|
|
873
|
+
catch {
|
|
874
|
+
continue;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
for (const output of options.scriptOutputs) {
|
|
878
|
+
for (const url of extractUrlsFromText(output.output)) {
|
|
879
|
+
const key = `url:${url}`;
|
|
880
|
+
if (seen.has(key)) {
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
seen.add(key);
|
|
884
|
+
previews.push(createUrlToolPreview(url, {
|
|
885
|
+
renderer: isYouTubeUrl(url) ? 'youtube' : undefined,
|
|
886
|
+
text: output.source,
|
|
887
|
+
title: output.source,
|
|
888
|
+
}));
|
|
889
|
+
}
|
|
890
|
+
for (const candidate of extractArtifactPathCandidates(output.output)) {
|
|
891
|
+
const preview = await resolvePreviewArtifactFromCandidate(candidate, roots, {
|
|
892
|
+
worldId: typeof options.context?.world?.id === 'string' ? options.context.world.id : undefined,
|
|
893
|
+
});
|
|
894
|
+
if (!preview) {
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
if (!('artifact' in preview)) {
|
|
898
|
+
continue;
|
|
899
|
+
}
|
|
900
|
+
const key = `artifact:${JSON.stringify(preview.artifact)}`;
|
|
901
|
+
if (seen.has(key)) {
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
seen.add(key);
|
|
905
|
+
previews.push(preview);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return previews;
|
|
909
|
+
}
|
|
910
|
+
async function buildLoadSkillSuccessPreview(options) {
|
|
911
|
+
const summaryLines = [
|
|
912
|
+
`Loaded skill \`${options.skillId}\`.`,
|
|
913
|
+
'',
|
|
914
|
+
`${options.skillDescription}`,
|
|
915
|
+
'',
|
|
916
|
+
`Referenced scripts: ${options.scriptPaths.length > 0 ? options.scriptPaths.join(', ') : '(none)'}`,
|
|
917
|
+
`Reference files: ${options.referenceFiles.length > 0 ? options.referenceFiles.join(', ') : '(none)'}`,
|
|
918
|
+
];
|
|
919
|
+
if (options.scriptOutputs.length > 0) {
|
|
920
|
+
summaryLines.push('');
|
|
921
|
+
summaryLines.push('Script outputs:');
|
|
922
|
+
for (const scriptOutput of options.scriptOutputs) {
|
|
923
|
+
summaryLines.push(`- ${scriptOutput.source}: ${truncatePreviewText(scriptOutput.output)}`);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
const previews = [
|
|
927
|
+
createTextToolPreview(summaryLines.join('\n'), {
|
|
928
|
+
markdown: true,
|
|
929
|
+
title: `load_skill ${options.skillId}`,
|
|
930
|
+
}),
|
|
931
|
+
];
|
|
932
|
+
previews.push(...await collectLoadSkillPreviewArtifacts(options));
|
|
933
|
+
return previews;
|
|
934
|
+
}
|
|
935
|
+
function wrapLoadSkillToolResult(options) {
|
|
936
|
+
return serializeToolExecutionEnvelope({
|
|
937
|
+
__type: 'tool_execution_envelope',
|
|
938
|
+
version: 1,
|
|
939
|
+
tool: 'load_skill',
|
|
940
|
+
...(options.toolCallId ? { tool_call_id: options.toolCallId } : {}),
|
|
941
|
+
status: /<error>/i.test(options.result) ? 'failed' : 'completed',
|
|
942
|
+
preview: options.preview,
|
|
943
|
+
result: options.result,
|
|
944
|
+
});
|
|
945
|
+
}
|
|
359
946
|
function buildSuccessResult(options) {
|
|
360
|
-
const { skillId, skillName, markdown, scriptOutputs, referenceFiles } = options;
|
|
947
|
+
const { skillId, skillName, skillDescription, skillRoot, markdown, scriptOutputs, referenceFiles, scriptPaths, } = options;
|
|
361
948
|
const escapedSkillId = escapeXmlText(skillId);
|
|
362
949
|
const escapedSkillName = escapeXmlText(skillName);
|
|
950
|
+
const escapedSkillDescription = escapeXmlText(skillDescription);
|
|
363
951
|
const hasActiveResources = scriptOutputs.length > 0;
|
|
364
952
|
const scriptBlocks = scriptOutputs.flatMap((scriptOutput) => ([
|
|
365
953
|
` <script_output source="${escapeXmlText(scriptOutput.source)}">`,
|
|
@@ -379,14 +967,21 @@ function buildSuccessResult(options) {
|
|
|
379
967
|
'',
|
|
380
968
|
]
|
|
381
969
|
: [];
|
|
970
|
+
const hasReferencedScripts = scriptPaths.length > 0;
|
|
382
971
|
const executionDirective = [
|
|
383
972
|
' <execution_directive>',
|
|
384
973
|
` You are now operating under the specialized ${escapedSkillName} protocol.`,
|
|
385
|
-
|
|
974
|
+
` Skill purpose: ${escapedSkillDescription}`,
|
|
975
|
+
' 1. Acknowledge which skill was loaded and apply it directly to the user request.',
|
|
976
|
+
' 2. Prioritize the logic in <instructions> over generic behavior.',
|
|
386
977
|
hasActiveResources
|
|
387
|
-
? '
|
|
388
|
-
: '
|
|
389
|
-
'
|
|
978
|
+
? ' 3. Use the data in <active_resources> to complete the user\'s specific request.'
|
|
979
|
+
: ' 3. Use the skill instructions to complete the user\'s specific request.',
|
|
980
|
+
' 4. Execute required steps directly; avoid unnecessary planning narration unless the user explicitly asks for a plan.',
|
|
981
|
+
' 5. Keep tool-related assistant text concise and result-focused.',
|
|
982
|
+
...(hasReferencedScripts
|
|
983
|
+
? [` 6. Scripts referenced in <instructions> are located at skill root: ${escapeXmlText(skillRoot)}. When invoking them via shell commands, construct the absolute path (e.g., ${escapeXmlText(skillRoot)}/scripts/example.py) since they may not be accessible via relative paths from the project directory.`]
|
|
984
|
+
: []),
|
|
390
985
|
' </execution_directive>',
|
|
391
986
|
];
|
|
392
987
|
return [
|
|
@@ -402,7 +997,7 @@ function buildSuccessResult(options) {
|
|
|
402
997
|
}
|
|
403
998
|
export function createLoadSkillToolDefinition() {
|
|
404
999
|
return {
|
|
405
|
-
description: 'Load full SKILL.md instructions by skill_id from the skill registry. Use this when a request matches a listed skill.',
|
|
1000
|
+
description: 'Load full SKILL.md instructions by skill_id from the skill registry. Use this when a request matches a listed skill. After loading, apply the skill instructions directly to the user request.',
|
|
406
1001
|
parameters: {
|
|
407
1002
|
type: 'object',
|
|
408
1003
|
properties: {
|
|
@@ -416,51 +1011,170 @@ export function createLoadSkillToolDefinition() {
|
|
|
416
1011
|
},
|
|
417
1012
|
execute: async (args, _sequenceId, _parentToolCall, context) => {
|
|
418
1013
|
await waitForInitialSkillSync();
|
|
1014
|
+
const persistToolEnvelope = context?.persistToolEnvelope === true;
|
|
1015
|
+
const toolCallId = typeof context?.toolCallId === 'string' ? context.toolCallId : undefined;
|
|
419
1016
|
const requestedSkillId = typeof args?.skill_id === 'string' ? args.skill_id.trim() : '';
|
|
420
1017
|
if (!requestedSkillId) {
|
|
421
|
-
|
|
1018
|
+
const result = buildReadErrorResult('', 'Missing required parameter: skill_id');
|
|
1019
|
+
return persistToolEnvelope
|
|
1020
|
+
? wrapLoadSkillToolResult({
|
|
1021
|
+
result,
|
|
1022
|
+
preview: createTextToolPreview('Missing required parameter: skill_id'),
|
|
1023
|
+
toolCallId,
|
|
1024
|
+
})
|
|
1025
|
+
: result;
|
|
422
1026
|
}
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
1027
|
+
const runScopedResultKey = getRunScopedLoadSkillResultKey(requestedSkillId, context);
|
|
1028
|
+
if (runScopedResultKey) {
|
|
1029
|
+
const cachedResult = loadSkillRunResultCache.get(runScopedResultKey);
|
|
1030
|
+
if (cachedResult !== undefined) {
|
|
1031
|
+
loggerLoadSkillHitl.debug('Returning cached run-scoped load_skill result', {
|
|
1032
|
+
skillId: requestedSkillId,
|
|
1033
|
+
runScopedResultKey,
|
|
1034
|
+
});
|
|
1035
|
+
return cachedResult;
|
|
1036
|
+
}
|
|
1037
|
+
const inFlightResult = inFlightLoadSkillRunResults.get(runScopedResultKey);
|
|
1038
|
+
if (inFlightResult) {
|
|
1039
|
+
return await inFlightResult;
|
|
1040
|
+
}
|
|
427
1041
|
}
|
|
428
|
-
|
|
429
|
-
|
|
1042
|
+
const computeResult = async () => {
|
|
1043
|
+
const entry = getSkill(requestedSkillId);
|
|
1044
|
+
const sourcePath = getSkillSourcePath(requestedSkillId);
|
|
1045
|
+
if (!entry || !sourcePath) {
|
|
1046
|
+
const result = buildNotFoundResult(requestedSkillId);
|
|
1047
|
+
return {
|
|
1048
|
+
result,
|
|
1049
|
+
cacheableForRun: false,
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
if (!isSkillEnabledBySettings(requestedSkillId)) {
|
|
1053
|
+
const result = buildDisabledBySettingsResult(requestedSkillId);
|
|
1054
|
+
return {
|
|
1055
|
+
result,
|
|
1056
|
+
cacheableForRun: false,
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
try {
|
|
1060
|
+
const markdown = await fs.readFile(sourcePath, 'utf8');
|
|
1061
|
+
const instructionsMarkdown = stripYamlFrontMatter(markdown);
|
|
1062
|
+
const skillRoot = path.dirname(sourcePath);
|
|
1063
|
+
const scriptPaths = extractReferencedScriptPaths(instructionsMarkdown);
|
|
1064
|
+
const toolPermission = getEnvValueFromText(context?.world?.variables, 'tool_permission') ?? 'auto';
|
|
1065
|
+
if (context?.world && !getExplicitContextChatId(context)) {
|
|
1066
|
+
const result = buildReadErrorResult(requestedSkillId, 'Interactive load_skill execution requires an explicit chatId.');
|
|
1067
|
+
return {
|
|
1068
|
+
result,
|
|
1069
|
+
cacheableForRun: false,
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
if (toolPermission === 'ask') {
|
|
1073
|
+
const isApproved = await requestSkillExecutionApproval({
|
|
1074
|
+
skillId: requestedSkillId,
|
|
1075
|
+
scriptPaths,
|
|
1076
|
+
context,
|
|
1077
|
+
});
|
|
1078
|
+
if (!isApproved) {
|
|
1079
|
+
const result = buildDeclinedResult(requestedSkillId);
|
|
1080
|
+
return {
|
|
1081
|
+
result,
|
|
1082
|
+
cacheableForRun: false,
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
const scriptOutputs = await executeSkillScripts({
|
|
1087
|
+
scriptPaths,
|
|
1088
|
+
skillRoot,
|
|
1089
|
+
context,
|
|
1090
|
+
});
|
|
1091
|
+
const referenceFiles = await collectReferenceFiles(skillRoot, instructionsMarkdown);
|
|
1092
|
+
const result = buildSuccessResult({
|
|
1093
|
+
skillId: requestedSkillId,
|
|
1094
|
+
skillName: entry.skill_id,
|
|
1095
|
+
skillDescription: entry.description?.trim() || entry.skill_id,
|
|
1096
|
+
skillRoot,
|
|
1097
|
+
markdown: instructionsMarkdown,
|
|
1098
|
+
scriptOutputs,
|
|
1099
|
+
referenceFiles,
|
|
1100
|
+
scriptPaths,
|
|
1101
|
+
});
|
|
1102
|
+
if (!persistToolEnvelope) {
|
|
1103
|
+
return {
|
|
1104
|
+
result,
|
|
1105
|
+
cacheableForRun: true,
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
const preview = await buildLoadSkillSuccessPreview({
|
|
1109
|
+
skillId: requestedSkillId,
|
|
1110
|
+
skillDescription: entry.description?.trim() || entry.skill_id,
|
|
1111
|
+
skillRoot,
|
|
1112
|
+
scriptOutputs,
|
|
1113
|
+
referenceFiles,
|
|
1114
|
+
scriptPaths,
|
|
1115
|
+
context,
|
|
1116
|
+
});
|
|
1117
|
+
return {
|
|
1118
|
+
result: wrapLoadSkillToolResult({
|
|
1119
|
+
result,
|
|
1120
|
+
preview,
|
|
1121
|
+
toolCallId,
|
|
1122
|
+
}),
|
|
1123
|
+
cacheableForRun: true,
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
catch (error) {
|
|
1127
|
+
if (error instanceof SkillScriptExecutionError) {
|
|
1128
|
+
throw error;
|
|
1129
|
+
}
|
|
1130
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1131
|
+
const result = buildReadErrorResult(requestedSkillId, message);
|
|
1132
|
+
return {
|
|
1133
|
+
result: persistToolEnvelope
|
|
1134
|
+
? wrapLoadSkillToolResult({
|
|
1135
|
+
result,
|
|
1136
|
+
preview: createTextToolPreview(message),
|
|
1137
|
+
toolCallId,
|
|
1138
|
+
})
|
|
1139
|
+
: result,
|
|
1140
|
+
cacheableForRun: false,
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
};
|
|
1144
|
+
if (!runScopedResultKey) {
|
|
1145
|
+
const outcome = await computeResult();
|
|
1146
|
+
if (persistToolEnvelope && !parseToolExecutionEnvelopeContent(outcome.result)) {
|
|
1147
|
+
return wrapLoadSkillToolResult({
|
|
1148
|
+
result: outcome.result,
|
|
1149
|
+
preview: createTextToolPreview(truncatePreviewText(outcome.result, 1600)),
|
|
1150
|
+
toolCallId,
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
return outcome.result;
|
|
430
1154
|
}
|
|
1155
|
+
const runScopedPromise = computeResult().then((outcome) => {
|
|
1156
|
+
if (outcome.cacheableForRun) {
|
|
1157
|
+
rememberRunScopedLoadSkillResult(runScopedResultKey, outcome.result);
|
|
1158
|
+
}
|
|
1159
|
+
return outcome.result;
|
|
1160
|
+
});
|
|
1161
|
+
inFlightLoadSkillRunResults.set(runScopedResultKey, runScopedPromise);
|
|
431
1162
|
try {
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
context,
|
|
440
|
-
});
|
|
441
|
-
if (!isApproved) {
|
|
442
|
-
return buildDeclinedResult(requestedSkillId);
|
|
1163
|
+
const result = await runScopedPromise;
|
|
1164
|
+
if (persistToolEnvelope && !parseToolExecutionEnvelopeContent(result)) {
|
|
1165
|
+
return wrapLoadSkillToolResult({
|
|
1166
|
+
result,
|
|
1167
|
+
preview: createTextToolPreview(truncatePreviewText(result, 1600)),
|
|
1168
|
+
toolCallId,
|
|
1169
|
+
});
|
|
443
1170
|
}
|
|
444
|
-
|
|
445
|
-
scriptPaths,
|
|
446
|
-
skillRoot,
|
|
447
|
-
context,
|
|
448
|
-
});
|
|
449
|
-
const referenceFiles = await collectReferenceFiles(skillRoot, instructionsMarkdown);
|
|
450
|
-
return buildSuccessResult({
|
|
451
|
-
skillId: requestedSkillId,
|
|
452
|
-
skillName: entry.skill_id,
|
|
453
|
-
markdown: instructionsMarkdown,
|
|
454
|
-
scriptOutputs,
|
|
455
|
-
referenceFiles,
|
|
456
|
-
});
|
|
1171
|
+
return result;
|
|
457
1172
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
1173
|
+
finally {
|
|
1174
|
+
const inFlightResult = inFlightLoadSkillRunResults.get(runScopedResultKey);
|
|
1175
|
+
if (inFlightResult === runScopedPromise) {
|
|
1176
|
+
inFlightLoadSkillRunResults.delete(runScopedResultKey);
|
|
461
1177
|
}
|
|
462
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
463
|
-
return buildReadErrorResult(requestedSkillId, message);
|
|
464
1178
|
}
|
|
465
1179
|
},
|
|
466
1180
|
};
|