@yeaft/webchat-agent 1.0.377 → 1.0.378
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/local-runtime/version.json +1 -1
- package/package.json +1 -1
- package/yeaft/config.js +2 -0
- package/yeaft/engine.js +237 -21
- package/yeaft/prompts.js +82 -31
- package/yeaft/sessions/project-doc.js +202 -5
- package/yeaft/templates/base.md +4 -74
- package/yeaft/templates/common-rules.md +4 -108
- package/yeaft/templates/core.md +33 -0
- package/yeaft/templates/tool-guidance.md +4 -138
- package/yeaft/tools/activation.js +180 -0
- package/yeaft/tools/discover-tools.js +198 -0
- package/yeaft/tools/index.js +2 -0
- package/yeaft/tools/registry.js +25 -9
- package/yeaft/tools/types.js +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"1.0.
|
|
1
|
+
{"version":"1.0.378"}
|
package/package.json
CHANGED
package/yeaft/config.js
CHANGED
|
@@ -351,6 +351,7 @@ function loadLegacyConfig(dir, overrides) {
|
|
|
351
351
|
proxyUrl: overrides.proxyUrl || env.YEAFT_PROXY_URL || fileConfig.proxyUrl || 'http://localhost:6628',
|
|
352
352
|
baseUrl: overrides.baseUrl || env.YEAFT_BASE_URL || fileConfig.baseUrl || null,
|
|
353
353
|
adapter: overrides.adapter || env.YEAFT_ADAPTER || fileConfig.adapter || null,
|
|
354
|
+
imageApiUrl: overrides.imageApiUrl || env.YEAFT_IMAGE_API_URL || fileConfig.imageApiUrl || null,
|
|
354
355
|
debug: overrides.debug !== undefined ? overrides.debug
|
|
355
356
|
: env.YEAFT_DEBUG !== undefined ? isTruthy(env.YEAFT_DEBUG)
|
|
356
357
|
: fileConfig.debug !== undefined ? fileConfig.debug : DEFAULTS.debug,
|
|
@@ -476,6 +477,7 @@ export function loadConfig(overrides = {}) {
|
|
|
476
477
|
// General settings
|
|
477
478
|
language: overrides.language || jsonConfig.language || DEFAULTS.language,
|
|
478
479
|
debug: overrides.debug !== undefined ? overrides.debug : (jsonConfig.debug ?? DEFAULTS.debug),
|
|
480
|
+
imageApiUrl: overrides.imageApiUrl || jsonConfig.imageApiUrl || null,
|
|
479
481
|
dir,
|
|
480
482
|
|
|
481
483
|
// Token limits. Resolution order:
|
package/yeaft/engine.js
CHANGED
|
@@ -24,7 +24,14 @@ import { buildSystemPrompt, buildWorkerPrompt } from './prompts.js';
|
|
|
24
24
|
import { getRuntimePlatformInfo } from './runtime-platform.js';
|
|
25
25
|
import { LLMContextError, LLMAbortError, LLMAuthError, LLMRateLimitError, LLMServerError, LLMStreamIdleTimeoutError } from './llm/adapter.js';
|
|
26
26
|
import { runMemoryPreflow, buildRelevantScopes, memoryScopeLabel } from './sessions/pre-flow.js';
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
readProjectDoc,
|
|
29
|
+
pickProjectDocFile,
|
|
30
|
+
selectProjectDocContext,
|
|
31
|
+
projectDocPathHintsFromToolCall,
|
|
32
|
+
projectDocWriteScopesNeedingReload,
|
|
33
|
+
DEFAULT_PROJECT_DOC_MAX_BYTES,
|
|
34
|
+
} from './sessions/project-doc.js';
|
|
28
35
|
import { partitionMessages } from './compact/partition.js';
|
|
29
36
|
import { runCompact as runCompactOrchestrator } from './compact/orchestrator.js';
|
|
30
37
|
import { evaluateCompactTriggers } from './compact/triggers.js';
|
|
@@ -45,7 +52,10 @@ import { countTurns } from './turn-utils.js';
|
|
|
45
52
|
import { attachRouterPlan, extractPriorPlan, stripMetaForWire } from './router/continuity.js';
|
|
46
53
|
import { resolveThinking } from './router/thinking.js';
|
|
47
54
|
import { approxTokens, computeBudget } from './memory/budget.js';
|
|
48
|
-
import { COLLAB_TOOL_POLICY, isToolErrorOutput, normalizeToolOutput, truncateToolResultIfNeeded } from './tools/registry.js';
|
|
55
|
+
import { COLLAB_TOOL_POLICY, isToolErrorOutput, localizeVisibleText, normalizeToolOutput, truncateToolResultIfNeeded } from './tools/registry.js';
|
|
56
|
+
import { CONDITIONAL_BUILTIN_TOOL_NAMES, resolveActiveToolNames } from './tools/activation.js';
|
|
57
|
+
import { discoverToolCapabilities } from './tools/discover-tools.js';
|
|
58
|
+
import { agentBelongsToScope, getAgentRegistry } from './tools/agent.js';
|
|
49
59
|
import { extractDisplayImages, stripDisplayImageData } from './image-assets.js';
|
|
50
60
|
import { acknowledgePendingNotifications, formatNotificationsForPrompt, peekPendingNotifications } from './sub-agent/notifications.js';
|
|
51
61
|
import {
|
|
@@ -958,19 +968,22 @@ export class Engine {
|
|
|
958
968
|
}
|
|
959
969
|
|
|
960
970
|
/**
|
|
961
|
-
* Get the
|
|
962
|
-
*
|
|
963
|
-
*
|
|
964
|
-
*
|
|
965
|
-
* always exposed to the LLM.
|
|
971
|
+
* Get the active tool definitions for one provider request. The registry keeps
|
|
972
|
+
* every implementation and compatibility alias loaded; only the active set is
|
|
973
|
+
* serialized into the request. Legacy standalone engines keep exposing their
|
|
974
|
+
* explicitly registered tools because they do not use the built-in registry.
|
|
966
975
|
*
|
|
976
|
+
* @param {string|null} collabToolPolicy
|
|
977
|
+
* @param {Set<string>|null} activeToolNames
|
|
967
978
|
* @returns {import('./llm/adapter.js').UnifiedToolDef[]}
|
|
968
979
|
*/
|
|
969
|
-
#getToolDefs(collabToolPolicy = null) {
|
|
980
|
+
#getToolDefs(collabToolPolicy = null, activeToolNames = null) {
|
|
970
981
|
if (this.#toolRegistry) {
|
|
971
|
-
return this.#toolRegistry.getToolDefs(this.#config?.language || 'en', {
|
|
982
|
+
return this.#toolRegistry.getToolDefs(this.#config?.language || 'en', {
|
|
983
|
+
collabToolPolicy,
|
|
984
|
+
activeToolNames,
|
|
985
|
+
});
|
|
972
986
|
}
|
|
973
|
-
// Legacy path: no mode filtering
|
|
974
987
|
const defs = [];
|
|
975
988
|
for (const [, tool] of this.#tools) {
|
|
976
989
|
defs.push({
|
|
@@ -982,6 +995,14 @@ export class Engine {
|
|
|
982
995
|
return defs;
|
|
983
996
|
}
|
|
984
997
|
|
|
998
|
+
#hasScopedSubAgents({ sessionId, parentVpId, parentThreadId } = {}) {
|
|
999
|
+
const scope = { sessionId, parentVpId, parentThreadId };
|
|
1000
|
+
for (const agent of getAgentRegistry().values()) {
|
|
1001
|
+
if (agentBelongsToScope(agent, scope)) return true;
|
|
1002
|
+
}
|
|
1003
|
+
return false;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
985
1006
|
/**
|
|
986
1007
|
* Load prompt-facing canonical scope content. Every durable memory scope is
|
|
987
1008
|
* query-gated; summary.md remains catalog metadata for Dream triage only.
|
|
@@ -1196,7 +1217,7 @@ export class Engine {
|
|
|
1196
1217
|
* @param {string} [args.explicitSkillName] — leading /skill:<name> command, if present
|
|
1197
1218
|
* @returns {string}
|
|
1198
1219
|
*/
|
|
1199
|
-
#buildSystemPrompt({ prompt, memoryInjection, vpPersona, activeScope, sessionAnnouncement, projectInstruction, projectLabel, workCenterInstructions, projectDoc, taskCtx, activeTasks, explicitSkillName, resolvedSkillContent = null } = {}) {
|
|
1220
|
+
#buildSystemPrompt({ prompt, memoryInjection, vpPersona, activeScope, sessionAnnouncement, projectInstruction, projectLabel, workCenterInstructions, projectDoc, taskCtx, activeTasks, activeToolNames = null, promptNotices = [], explicitSkillName, resolvedSkillContent = null } = {}) {
|
|
1200
1221
|
// Skill selection is normally resolved once by #runQuery so the prompt and
|
|
1201
1222
|
// emitted protocol events describe the exact same skills. Keep the local
|
|
1202
1223
|
// fallback for internal callers that do not need selection events.
|
|
@@ -1210,10 +1231,15 @@ export class Engine {
|
|
|
1210
1231
|
}
|
|
1211
1232
|
}
|
|
1212
1233
|
|
|
1213
|
-
//
|
|
1214
|
-
|
|
1234
|
+
// Use the same active set for the prompt and provider schema. The prompt
|
|
1235
|
+
// only needs these names to select scoped guidance; it does not repeat the
|
|
1236
|
+
// catalogue because the API tool definitions are already authoritative.
|
|
1237
|
+
const registeredToolNames = this.#toolRegistry
|
|
1215
1238
|
? this.#toolRegistry.getToolNames()
|
|
1216
1239
|
: Array.from(this.#tools.keys());
|
|
1240
|
+
const toolNames = activeToolNames instanceof Set
|
|
1241
|
+
? registeredToolNames.filter(name => activeToolNames.has(name))
|
|
1242
|
+
: registeredToolNames;
|
|
1217
1243
|
|
|
1218
1244
|
return buildWorkerPrompt({
|
|
1219
1245
|
language: this.#config.language || 'en',
|
|
@@ -1230,6 +1256,7 @@ export class Engine {
|
|
|
1230
1256
|
runtimePlatform: getRuntimePlatformInfo(),
|
|
1231
1257
|
taskCtx,
|
|
1232
1258
|
activeTasks,
|
|
1259
|
+
promptNotices,
|
|
1233
1260
|
// Worker-shape harness is descriptive metadata for human inspection;
|
|
1234
1261
|
// production prompts skip it to save tokens. Re-enable via env when
|
|
1235
1262
|
// diagnosing prompt structure issues.
|
|
@@ -1336,6 +1363,7 @@ export class Engine {
|
|
|
1336
1363
|
conversationStore: this.#conversationStore,
|
|
1337
1364
|
adapter: this.#adapter,
|
|
1338
1365
|
config: this.#config,
|
|
1366
|
+
discoverTools: vpCtx?.discoverTools,
|
|
1339
1367
|
taskManager: this.#taskManager,
|
|
1340
1368
|
sessionId: vpCtx?.sessionId || this.#sessionId || null,
|
|
1341
1369
|
projectSessionIds: Array.isArray(vpCtx?.projectSessionIds)
|
|
@@ -2352,12 +2380,71 @@ export class Engine {
|
|
|
2352
2380
|
envelope: inboundEnvelope || null,
|
|
2353
2381
|
};
|
|
2354
2382
|
|
|
2355
|
-
const
|
|
2356
|
-
|
|
2383
|
+
const projectDocSource = this.#getProjectDocBlock(workDir);
|
|
2384
|
+
let projectDocLoadedPathHints = [];
|
|
2385
|
+
let projectDocContext = selectProjectDocContext(projectDocSource, {
|
|
2386
|
+
prompt,
|
|
2387
|
+
messages,
|
|
2388
|
+
pathHints: projectDocLoadedPathHints,
|
|
2389
|
+
language: this.#config.language || 'en',
|
|
2390
|
+
});
|
|
2391
|
+
let activeTaskSnapshots = this.#taskManager
|
|
2392
|
+
&& typeof this.#taskManager.listActiveTasks === 'function'
|
|
2393
|
+
? this.#taskManager.listActiveTasks(runtimeSessionId)
|
|
2394
|
+
: [];
|
|
2395
|
+
let activeTasks = this.#taskManager
|
|
2357
2396
|
? this.#taskManager.renderActiveTasksForPrompt(runtimeSessionId, {
|
|
2358
2397
|
language: this.#config.language || 'en',
|
|
2359
2398
|
})
|
|
2360
2399
|
: '';
|
|
2400
|
+
const registeredToolNames = this.#toolRegistry
|
|
2401
|
+
? this.#toolRegistry.getToolNames()
|
|
2402
|
+
: Array.from(this.#tools.keys());
|
|
2403
|
+
const resolveCurrentActiveToolNames = () => this.#toolRegistry
|
|
2404
|
+
? resolveActiveToolNames({
|
|
2405
|
+
toolNames: registeredToolNames,
|
|
2406
|
+
prompt,
|
|
2407
|
+
messages,
|
|
2408
|
+
collabToolPolicy: effectiveCollabToolPolicy,
|
|
2409
|
+
activeTasks: activeTaskSnapshots,
|
|
2410
|
+
subAgentToolsActivated: this.#hasScopedSubAgents({
|
|
2411
|
+
sessionId: runtimeSessionId,
|
|
2412
|
+
parentVpId: queryVpId,
|
|
2413
|
+
parentThreadId: runtimeThreadId,
|
|
2414
|
+
}),
|
|
2415
|
+
imageGenerationConfigured: typeof this.#config?.imageApiUrl === 'string'
|
|
2416
|
+
&& this.#config.imageApiUrl.trim().length > 0,
|
|
2417
|
+
})
|
|
2418
|
+
: null;
|
|
2419
|
+
const discoveredToolNames = new Set();
|
|
2420
|
+
const discoveryTraversals = new Map();
|
|
2421
|
+
const currentDiscoverableTools = () => this.#toolRegistry
|
|
2422
|
+
? this.#toolRegistry.getAllTools()
|
|
2423
|
+
.filter(tool => CONDITIONAL_BUILTIN_TOOL_NAMES.has(tool.name) || tool.name.startsWith('mcp__'))
|
|
2424
|
+
: [];
|
|
2425
|
+
const discoveryDirectorySnapshot = (tools, language) => tools
|
|
2426
|
+
.map(tool => ({
|
|
2427
|
+
name: tool.name,
|
|
2428
|
+
description: localizeVisibleText(tool.description, language, tool.name),
|
|
2429
|
+
parameters: tool.parameters,
|
|
2430
|
+
}));
|
|
2431
|
+
const discoveryDirectoryMatches = (snapshot, liveTools, language) => {
|
|
2432
|
+
const liveSnapshot = discoveryDirectorySnapshot(liveTools, language);
|
|
2433
|
+
if (snapshot.length !== liveSnapshot.length) return false;
|
|
2434
|
+
const byName = new Map(snapshot.map(tool => [tool.name, tool]));
|
|
2435
|
+
return liveSnapshot.every(tool => {
|
|
2436
|
+
const prior = byName.get(tool.name);
|
|
2437
|
+
return prior
|
|
2438
|
+
&& prior.description === tool.description
|
|
2439
|
+
&& JSON.stringify(prior.parameters) === JSON.stringify(tool.parameters);
|
|
2440
|
+
});
|
|
2441
|
+
};
|
|
2442
|
+
const applyDiscoveredTools = (names) => {
|
|
2443
|
+
for (const name of names) {
|
|
2444
|
+
if (this.#toolRegistry?.has(name)) discoveredToolNames.add(name);
|
|
2445
|
+
}
|
|
2446
|
+
};
|
|
2447
|
+
let activeToolNames = resolveCurrentActiveToolNames();
|
|
2361
2448
|
let resolvedSkillContent = '';
|
|
2362
2449
|
let resolvedSkills = [];
|
|
2363
2450
|
let skillResolutionError = null;
|
|
@@ -2385,7 +2472,8 @@ export class Engine {
|
|
|
2385
2472
|
}
|
|
2386
2473
|
}
|
|
2387
2474
|
|
|
2388
|
-
|
|
2475
|
+
let promptNotices = [];
|
|
2476
|
+
const buildCurrentSystemPrompt = () => this.#buildSystemPrompt({
|
|
2389
2477
|
prompt,
|
|
2390
2478
|
memoryInjection,
|
|
2391
2479
|
vpPersona,
|
|
@@ -2394,11 +2482,14 @@ export class Engine {
|
|
|
2394
2482
|
projectInstruction,
|
|
2395
2483
|
projectLabel,
|
|
2396
2484
|
workCenterInstructions,
|
|
2397
|
-
projectDoc,
|
|
2485
|
+
projectDoc: projectDocContext.text,
|
|
2398
2486
|
activeTasks,
|
|
2487
|
+
activeToolNames,
|
|
2488
|
+
promptNotices,
|
|
2399
2489
|
explicitSkillName,
|
|
2400
2490
|
resolvedSkillContent,
|
|
2401
2491
|
});
|
|
2492
|
+
let systemPrompt = buildCurrentSystemPrompt();
|
|
2402
2493
|
|
|
2403
2494
|
// ─── HARD INVARIANT: Compact ≠ Dream (read DESIGN-COMPACT-VS-DREAM.md) ─
|
|
2404
2495
|
// Compact summary (this block) ONLY lands in the messages array head as
|
|
@@ -2598,7 +2689,7 @@ export class Engine {
|
|
|
2598
2689
|
};
|
|
2599
2690
|
}
|
|
2600
2691
|
|
|
2601
|
-
|
|
2692
|
+
let toolDefs = this.#getToolDefs(effectiveCollabToolPolicy, activeToolNames);
|
|
2602
2693
|
let turnNumber = 0;
|
|
2603
2694
|
let continueTurns = 0; // auto-continue counter
|
|
2604
2695
|
let toolLoopTurns = 0; // task-327b: tool-use turns for long-loop auto-bump
|
|
@@ -2775,6 +2866,26 @@ export class Engine {
|
|
|
2775
2866
|
}
|
|
2776
2867
|
}
|
|
2777
2868
|
|
|
2869
|
+
// Tool availability depends on live Session state. A Bash call can start a
|
|
2870
|
+
// background task (or a sub-agent can appear) during the prior loop, so
|
|
2871
|
+
// recompute the schemas and matching guidance before every provider call.
|
|
2872
|
+
activeTaskSnapshots = this.#taskManager
|
|
2873
|
+
&& typeof this.#taskManager.listActiveTasks === 'function'
|
|
2874
|
+
? this.#taskManager.listActiveTasks(runtimeSessionId)
|
|
2875
|
+
: [];
|
|
2876
|
+
activeTasks = this.#taskManager
|
|
2877
|
+
? this.#taskManager.renderActiveTasksForPrompt(runtimeSessionId, {
|
|
2878
|
+
language: this.#config.language || 'en',
|
|
2879
|
+
})
|
|
2880
|
+
: '';
|
|
2881
|
+
activeToolNames = resolveCurrentActiveToolNames();
|
|
2882
|
+
for (const name of [...discoveredToolNames]) {
|
|
2883
|
+
if (this.#toolRegistry?.has(name)) activeToolNames?.add(name);
|
|
2884
|
+
else discoveredToolNames.delete(name);
|
|
2885
|
+
}
|
|
2886
|
+
toolDefs = this.#getToolDefs(effectiveCollabToolPolicy, activeToolNames);
|
|
2887
|
+
systemPrompt = buildCurrentSystemPrompt();
|
|
2888
|
+
|
|
2778
2889
|
try {
|
|
2779
2890
|
// task-327b: resolve effort per-turn so the long-loop auto-bump
|
|
2780
2891
|
// kicks in once toolLoopTurns crosses the threshold.
|
|
@@ -3826,6 +3937,72 @@ export class Engine {
|
|
|
3826
3937
|
setCurrentTodos,
|
|
3827
3938
|
askUser,
|
|
3828
3939
|
workDir,
|
|
3940
|
+
discoverTools: ({ query, cursor, maxResults } = {}) => {
|
|
3941
|
+
if (!this.#toolRegistry) return { query: String(query || ''), tools: [], activated: 0 };
|
|
3942
|
+
const language = this.#config.language || 'en';
|
|
3943
|
+
const queryText = String(query || '');
|
|
3944
|
+
const traversalKey = queryText.trim();
|
|
3945
|
+
const liveTools = currentDiscoverableTools();
|
|
3946
|
+
const requestedCursor = Number(cursor);
|
|
3947
|
+
const hasCursor = cursor != null && cursor !== '' && Number.isInteger(requestedCursor) && requestedCursor > 0;
|
|
3948
|
+
let traversal = hasCursor ? discoveryTraversals.get(traversalKey) : null;
|
|
3949
|
+
if (hasCursor && (!traversal || !discoveryDirectoryMatches(traversal.candidates, liveTools, language))) {
|
|
3950
|
+
discoveryTraversals.delete(traversalKey);
|
|
3951
|
+
return {
|
|
3952
|
+
query: queryText,
|
|
3953
|
+
tools: [],
|
|
3954
|
+
next_cursor: null,
|
|
3955
|
+
total: liveTools.length,
|
|
3956
|
+
omitted_invalid: 0,
|
|
3957
|
+
activated: 0,
|
|
3958
|
+
restart_required: true,
|
|
3959
|
+
message: 'The hidden tool directory changed or no matching traversal exists. Restart discovery without a cursor.',
|
|
3960
|
+
};
|
|
3961
|
+
}
|
|
3962
|
+
if (!hasCursor) {
|
|
3963
|
+
traversal = {
|
|
3964
|
+
candidates: discoveryDirectorySnapshot(liveTools, language),
|
|
3965
|
+
};
|
|
3966
|
+
discoveryTraversals.set(traversalKey, traversal);
|
|
3967
|
+
} else {
|
|
3968
|
+
const pendingTraversal = discoveryTraversals.get(traversalKey);
|
|
3969
|
+
if (!pendingTraversal || pendingTraversal.nextCursor !== requestedCursor) {
|
|
3970
|
+
discoveryTraversals.delete(traversalKey);
|
|
3971
|
+
return {
|
|
3972
|
+
query: queryText,
|
|
3973
|
+
tools: [],
|
|
3974
|
+
next_cursor: null,
|
|
3975
|
+
total: liveTools.length,
|
|
3976
|
+
omitted_invalid: 0,
|
|
3977
|
+
activated: 0,
|
|
3978
|
+
restart_required: true,
|
|
3979
|
+
message: 'The discovery cursor is stale or out of sequence. Restart discovery without a cursor.',
|
|
3980
|
+
};
|
|
3981
|
+
}
|
|
3982
|
+
}
|
|
3983
|
+
const result = discoverToolCapabilities({
|
|
3984
|
+
query: queryText,
|
|
3985
|
+
candidates: traversal.candidates,
|
|
3986
|
+
language,
|
|
3987
|
+
cursor,
|
|
3988
|
+
maxResults,
|
|
3989
|
+
});
|
|
3990
|
+
if (result.restart_required || result.next_cursor == null) discoveryTraversals.delete(traversalKey);
|
|
3991
|
+
else traversal.nextCursor = result.next_cursor;
|
|
3992
|
+
applyDiscoveredTools(result.tools.map(tool => tool.name));
|
|
3993
|
+
return {
|
|
3994
|
+
query: queryText,
|
|
3995
|
+
...result,
|
|
3996
|
+
activated: result.tools.length,
|
|
3997
|
+
message: result.restart_required
|
|
3998
|
+
? (result.message || 'The hidden tool directory changed. Restart discovery without a cursor.')
|
|
3999
|
+
: (result.tools.length > 0
|
|
4000
|
+
? (result.next_cursor == null
|
|
4001
|
+
? 'This discovery page is active on the next model loop; the hidden directory is exhausted.'
|
|
4002
|
+
: 'This discovery page is active on the next model loop; use next_cursor if the target is not listed.')
|
|
4003
|
+
: 'No valid hidden registered tools remain on this directory page.'),
|
|
4004
|
+
};
|
|
4005
|
+
},
|
|
3829
4006
|
currentToolCall: () => currentToolCallForAsyncTask ? { ...currentToolCallForAsyncTask } : null,
|
|
3830
4007
|
requestEndTurn: (reason) => {
|
|
3831
4008
|
// First call wins — preserve the kind/reason of the first tool
|
|
@@ -3921,8 +4098,17 @@ export class Engine {
|
|
|
3921
4098
|
|
|
3922
4099
|
// Resolve tool: prefer ToolRegistry, fallback to legacy #tools Map
|
|
3923
4100
|
const hasTool = this.#toolRegistry
|
|
3924
|
-
? this.#toolRegistry.isAllowed(tc.name, {
|
|
4101
|
+
? this.#toolRegistry.isAllowed(tc.name, {
|
|
4102
|
+
collabToolPolicy: effectiveCollabToolPolicy,
|
|
4103
|
+
activeToolNames,
|
|
4104
|
+
})
|
|
3925
4105
|
: this.#tools.has(tc.name);
|
|
4106
|
+
const toolProjectDocPathHints = projectDocPathHintsFromToolCall(tc.name, tc.input);
|
|
4107
|
+
const readOnlyTool = hasTool ? isReadOnlyTool(this, tc.name, tc.input) : false;
|
|
4108
|
+
const missingProjectDocScopes = hasTool && !readOnlyTool
|
|
4109
|
+
? projectDocWriteScopesNeedingReload(projectDocContext, toolProjectDocPathHints)
|
|
4110
|
+
: new Set();
|
|
4111
|
+
const needsProjectDocReload = missingProjectDocScopes.size > 0;
|
|
3926
4112
|
|
|
3927
4113
|
if (skipped) {
|
|
3928
4114
|
const source = activeToolBatchBarrier.sourceToolName || 'a preceding tool';
|
|
@@ -3945,12 +4131,42 @@ export class Engine {
|
|
|
3945
4131
|
threadId: this.currentThreadId,
|
|
3946
4132
|
};
|
|
3947
4133
|
} else if (!hasTool) {
|
|
3948
|
-
|
|
4134
|
+
const registered = this.#toolRegistry?.has(tc.name) || this.#tools.has(tc.name);
|
|
4135
|
+
output = registered
|
|
4136
|
+
? `Error: tool "${tc.name}" is not active for this request`
|
|
4137
|
+
: `Error: unknown tool "${tc.name}"`;
|
|
3949
4138
|
isError = true;
|
|
3950
4139
|
yield { type: 'tool_end', id: tc.id, name: tc.name, output, isError: true, threadId: this.currentThreadId };
|
|
4140
|
+
} else if (needsProjectDocReload) {
|
|
4141
|
+
projectDocLoadedPathHints = [...new Set([
|
|
4142
|
+
...projectDocLoadedPathHints,
|
|
4143
|
+
...toolProjectDocPathHints,
|
|
4144
|
+
])];
|
|
4145
|
+
const previouslySelectedProjectDocScopes = new Set(projectDocContext.selectedScopes);
|
|
4146
|
+
projectDocContext = selectProjectDocContext(projectDocSource, {
|
|
4147
|
+
prompt,
|
|
4148
|
+
messages,
|
|
4149
|
+
pathHints: projectDocLoadedPathHints,
|
|
4150
|
+
language: this.#config.language || 'en',
|
|
4151
|
+
forcedScopes: [...previouslySelectedProjectDocScopes, ...missingProjectDocScopes],
|
|
4152
|
+
});
|
|
4153
|
+
const zh = String(this.#config?.language || '').toLowerCase().startsWith('zh');
|
|
4154
|
+
const notice = zh
|
|
4155
|
+
? `项目规则已针对路径 ${toolProjectDocPathHints.join(', ')} 重新加载。先复核新载入的规则,再重新提交写操作;本次调用未执行。`
|
|
4156
|
+
: `Project rules were reloaded for ${toolProjectDocPathHints.join(', ')}. Review the newly loaded rules before resubmitting the write; this call was not executed.`;
|
|
4157
|
+
promptNotices = [notice];
|
|
4158
|
+
systemPrompt = buildCurrentSystemPrompt();
|
|
4159
|
+
output = notice;
|
|
4160
|
+
isError = true;
|
|
4161
|
+
toolBatchBarrier = {
|
|
4162
|
+
kind: 'project_rules_reloaded',
|
|
4163
|
+
message: notice,
|
|
4164
|
+
sourceToolCallId: tc.id,
|
|
4165
|
+
sourceToolName: tc.name,
|
|
4166
|
+
};
|
|
4167
|
+
yield { type: 'tool_end', id: tc.id, name: tc.name, output, isError: true, skipped: true, threadId: this.currentThreadId };
|
|
3951
4168
|
} else {
|
|
3952
4169
|
duplicateKey = `${tc.name}\u001f${argsHashOf(tc.input)}`;
|
|
3953
|
-
const readOnlyTool = isReadOnlyTool(this, tc.name, tc.input);
|
|
3954
4170
|
cacheableTool = isCacheableTool(this, tc.name, tc.input);
|
|
3955
4171
|
// The cache is only valid while the workspace has not changed. Clear
|
|
3956
4172
|
// it before every potential mutation, including a tool that later
|
package/yeaft/prompts.js
CHANGED
|
@@ -135,12 +135,10 @@ function extractExactLangSection(content, language) {
|
|
|
135
135
|
/** Loaded templates — read once at module load time. */
|
|
136
136
|
const RAW_TEMPLATES = {
|
|
137
137
|
base: readTemplate('base.md'),
|
|
138
|
-
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
// identity-independent. base.md remains as a back-compat bundle so any
|
|
143
|
-
// external snapshotter / test that reads the file directly keeps working.
|
|
138
|
+
core: readTemplate('core.md'),
|
|
139
|
+
// base.md and the older split fragments remain packaged for external
|
|
140
|
+
// snapshotters and deployment compatibility. Runtime prompt assembly uses
|
|
141
|
+
// core.md plus scoped guidance instead of concatenating those full bundles.
|
|
144
142
|
identityYeaft: readTemplate('identity-yeaft.md', { required: false }),
|
|
145
143
|
commonRules: readTemplate('common-rules.md', { required: false }),
|
|
146
144
|
modeUnified: readTemplate('mode-unified.md'),
|
|
@@ -195,7 +193,6 @@ const PROMPTS = {
|
|
|
195
193
|
identity: 'No VP soul is active for this turn. Participate in the current session with grounded, evidence-based answers and preserve the user\'s context.',
|
|
196
194
|
date: (d) => `Date: ${d}`,
|
|
197
195
|
dream: 'You are in dream mode. Reflect on past conversations and consolidate memories.',
|
|
198
|
-
tools: (names) => `Available tools: ${names}`,
|
|
199
196
|
// DESIGN-PROMPT §3 ④ — current session context block.
|
|
200
197
|
activeScopeHeader: '## Current session context',
|
|
201
198
|
activeScopeSessionIdLabel: 'Session ID',
|
|
@@ -215,13 +212,13 @@ const PROMPTS = {
|
|
|
215
212
|
// AGENTS.md is the cross-tool convention (Codex / OpenAI Codex CLI).
|
|
216
213
|
projectDocHeader: '[Project Doc]',
|
|
217
214
|
projectDocIntro:
|
|
218
|
-
'The
|
|
215
|
+
'The text below is the authoritative project context selected for this turn. It contains the stable core plus any task- or path-scoped sections that the runtime loaded on demand.',
|
|
216
|
+
promptNoticeHeader: '[Runtime Notice]',
|
|
219
217
|
},
|
|
220
218
|
zh: {
|
|
221
219
|
identity: '你正在当前会话中参与协作。保持用户上下文,回答要基于证据;需要工具时使用工具,但不要把自己没有实际执行过的事说成已经执行。',
|
|
222
220
|
date: (d) => `日期:${d}`,
|
|
223
221
|
dream: '你处于梦境模式。回顾过去的对话,整理和巩固记忆。',
|
|
224
|
-
tools: (names) => `可用工具:${names}`,
|
|
225
222
|
// DESIGN-PROMPT §3 ④ — 当前会话上下文。
|
|
226
223
|
activeScopeHeader: '## 当前会话上下文',
|
|
227
224
|
activeScopeSessionIdLabel: '会话 ID',
|
|
@@ -239,7 +236,8 @@ const PROMPTS = {
|
|
|
239
236
|
// 项目文档块:CLAUDE.md / AGENTS.md(与 Codex 通用命名兼容)。
|
|
240
237
|
projectDocHeader: '[项目文档]',
|
|
241
238
|
projectDocIntro:
|
|
242
|
-
'
|
|
239
|
+
'下面是当前 turn 选中的权威项目上下文,包含稳定核心以及 runtime 按任务或路径加载的范围章节。',
|
|
240
|
+
promptNoticeHeader: '[运行时提示]',
|
|
243
241
|
},
|
|
244
242
|
};
|
|
245
243
|
|
|
@@ -309,6 +307,7 @@ export function normalizePromptLanguage(language) {
|
|
|
309
307
|
* projectLabel?: string,
|
|
310
308
|
* workCenterInstructions?: string,
|
|
311
309
|
* projectDoc?: string,
|
|
310
|
+
* promptNotices?: string[],
|
|
312
311
|
* }} params
|
|
313
312
|
* @returns {string}
|
|
314
313
|
*/
|
|
@@ -327,6 +326,7 @@ export function buildSystemPrompt({
|
|
|
327
326
|
projectDoc = '',
|
|
328
327
|
runtimePlatform,
|
|
329
328
|
activeTasks = '',
|
|
329
|
+
promptNotices = [],
|
|
330
330
|
} = {}) {
|
|
331
331
|
// Normalize app locales like `zh-CN` to prompt dictionary/template keys.
|
|
332
332
|
const effectiveLang = normalizePromptLanguage(language);
|
|
@@ -334,24 +334,16 @@ export function buildSystemPrompt({
|
|
|
334
334
|
|
|
335
335
|
const parts = [];
|
|
336
336
|
|
|
337
|
-
// ─── 1. Core
|
|
338
|
-
//
|
|
339
|
-
//
|
|
340
|
-
// there is no persona, fall back to the legacy Yeaft identity bundle.
|
|
337
|
+
// ─── 1. Stable Core ────────────────────────────────────
|
|
338
|
+
// A VP persona supplies the identity layer when present. The compact core
|
|
339
|
+
// rules stay stable across turns and replace the old full base/common bundle.
|
|
341
340
|
const personaBlock = renderVpPersona(vpPersona, lang, effectiveLang);
|
|
342
|
-
if (personaBlock)
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
} else {
|
|
349
|
-
const baseTemplate = getTemplate('base', effectiveLang);
|
|
350
|
-
if (baseTemplate) {
|
|
351
|
-
parts.push(baseTemplate);
|
|
352
|
-
} else {
|
|
353
|
-
parts.push(lang.identity);
|
|
354
|
-
}
|
|
341
|
+
if (personaBlock) parts.push(personaBlock);
|
|
342
|
+
const coreTemplate = getTemplate('core', effectiveLang);
|
|
343
|
+
if (coreTemplate) {
|
|
344
|
+
parts.push(coreTemplate);
|
|
345
|
+
} else if (!personaBlock) {
|
|
346
|
+
parts.push(lang.identity);
|
|
355
347
|
}
|
|
356
348
|
|
|
357
349
|
// ─── 1.4 Project Doc (CLAUDE.md / AGENTS.md from session workDir) ───
|
|
@@ -395,6 +387,13 @@ export function buildSystemPrompt({
|
|
|
395
387
|
parts.push(`${header}\n${intro ? `${intro}\n\n` : ''}${workCenterText}`);
|
|
396
388
|
}
|
|
397
389
|
|
|
390
|
+
const notices = Array.isArray(promptNotices)
|
|
391
|
+
? promptNotices.map(value => typeof value === 'string' ? value.trim() : '').filter(Boolean)
|
|
392
|
+
: [];
|
|
393
|
+
if (notices.length > 0) {
|
|
394
|
+
parts.push(`${lang.promptNoticeHeader || '[Runtime Notice]'}\n${notices.join('\n')}`);
|
|
395
|
+
}
|
|
396
|
+
|
|
398
397
|
// ─── 2. Date Metadata ──────────────────────────────────
|
|
399
398
|
parts.push(lang.date(new Date().toISOString().split('T')[0]));
|
|
400
399
|
|
|
@@ -414,11 +413,10 @@ export function buildSystemPrompt({
|
|
|
414
413
|
}
|
|
415
414
|
|
|
416
415
|
if (toolNames.length > 0) {
|
|
417
|
-
|
|
418
|
-
|
|
416
|
+
const guidance = renderActiveToolGuidance(toolNames, effectiveLang);
|
|
419
417
|
const toolGuidanceTemplate = getTemplate('toolGuidance', effectiveLang);
|
|
420
|
-
if (toolGuidanceTemplate) {
|
|
421
|
-
parts.push(toolGuidanceTemplate);
|
|
418
|
+
if (guidance && toolGuidanceTemplate) {
|
|
419
|
+
parts.push(toolGuidanceTemplate.replace('{{guidance}}', guidance));
|
|
422
420
|
}
|
|
423
421
|
}
|
|
424
422
|
|
|
@@ -457,6 +455,59 @@ export function buildSystemPrompt({
|
|
|
457
455
|
|
|
458
456
|
// ─── helpers ─────────────────────────────────────────────────────
|
|
459
457
|
|
|
458
|
+
const TOOL_GUIDANCE_GROUPS = Object.freeze([
|
|
459
|
+
{
|
|
460
|
+
tools: ['DiscoverTools'],
|
|
461
|
+
en: 'If the visible tools do not clearly cover the request, use `DiscoverTools` with the user goal before concluding that a capability is unavailable. If the target is absent from a page, follow `next_cursor` until found or the hidden directory is exhausted. If `restart_required` is true, restart without a cursor because the registered directory changed.',
|
|
462
|
+
zh: '如果可见工具不能明确覆盖请求,应先按用户目标调用 `DiscoverTools`。若当前页没有目标,应按 `next_cursor` 继续翻页,直到找到或隐藏目录耗尽后,才能判断某项能力不可用。如果 `restart_required` 为 true,说明注册目录已变化,应丢弃游标重新开始。',
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
tools: ['FileRead', 'FileWrite', 'FileEdit', 'Glob', 'Grep', 'ListDir', 'ApplyPatch', 'NotebookEdit'],
|
|
466
|
+
en: 'Read existing files before editing. Use dedicated file/search tools instead of shell search or `sed -i`; make small, reviewable edits and batch independent reads.',
|
|
467
|
+
zh: '编辑前先读现有文件。文件搜索和修改优先使用专用工具,不用 shell 搜索或 `sed -i`;改动保持小而可审查,独立读取应并行发出。',
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
tools: ['Bash'],
|
|
471
|
+
en: 'Use non-interactive, deterministic shell commands, set reasonable timeouts, quote paths with spaces, and do not run destructive operations without authorization.',
|
|
472
|
+
zh: 'Shell 命令保持非交互、确定性并设置合理 timeout;包含空格的路径要引用,未经授权不要执行破坏性操作。',
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
tools: ['StartPlan', 'TodoWrite'],
|
|
476
|
+
en: 'For non-trivial multi-step work, use `StartPlan` before execution and keep the visible `TodoWrite` checklist current; do not stop after planning unless user input genuinely blocks the first step.',
|
|
477
|
+
zh: '非平凡多步骤任务在执行前使用 `StartPlan`,并持续更新可见的 `TodoWrite` checklist;只有用户信息确实阻塞第一步时才在规划后停下。',
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
tools: ['SpawnAgent', 'PromptAgent', 'WaitAgent', 'CloseAgent', 'ListAgents'],
|
|
481
|
+
en: 'Delegate only independent, bounded work. Keep ownership in the parent, avoid polling loops, and close sub-agents after collecting their result.',
|
|
482
|
+
zh: '只委派边界清晰且独立的工作。父级保留任务所有权,不要循环轮询,取得结果后关闭子 Agent。',
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
tools: ['ListTasks', 'ReadTaskLog', 'CancelTask'],
|
|
486
|
+
en: 'Treat background tasks as live execution state, not memory facts. Inspect status or logs before retrying or cancelling work.',
|
|
487
|
+
zh: '后台任务是实时执行状态,不是记忆事实。重试或取消前先检查状态或日志。',
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
tools: ['RouteForward'],
|
|
491
|
+
en: 'Use `RouteForward` for explicit VP-to-VP handoff; writing an @mention in ordinary text does not dispatch another VP.',
|
|
492
|
+
zh: '显式 VP 转交必须使用 `RouteForward`;普通文本中的 @mention 不会调度另一个 VP。',
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
tools: ['CreateWorkItem'],
|
|
496
|
+
en: 'Use `CreateWorkItem` only for goals that need durable cross-turn coordination, recovery, review, waiting, or retry.',
|
|
497
|
+
zh: '只有目标需要跨 turn 持久协调、恢复、评审、等待或重试时才使用 `CreateWorkItem`。',
|
|
498
|
+
},
|
|
499
|
+
]);
|
|
500
|
+
|
|
501
|
+
function renderActiveToolGuidance(toolNames, language) {
|
|
502
|
+
const active = new Set(Array.isArray(toolNames) ? toolNames : []);
|
|
503
|
+
const lines = [];
|
|
504
|
+
for (const group of TOOL_GUIDANCE_GROUPS) {
|
|
505
|
+
if (!group.tools.some(name => active.has(name))) continue;
|
|
506
|
+
lines.push(`- ${language === 'zh' ? group.zh : group.en}`);
|
|
507
|
+
}
|
|
508
|
+
return lines.join('\n');
|
|
509
|
+
}
|
|
510
|
+
|
|
460
511
|
/**
|
|
461
512
|
* Render the VP identity block when the engine is running on behalf of an
|
|
462
513
|
* addressed VP. The `persona` body from role.md is the only soul source;
|