@yeaft/webchat-agent 1.0.513 → 1.0.515
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/connection/message-router.js +4 -1
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +107 -86
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +2 -2
- package/local-runtime/web/style.bundle.css +1 -1
- package/local-runtime/web/style.bundle.css.gz +0 -0
- package/package.json +1 -1
- package/yeaft/conversation/persist.js +89 -0
- package/yeaft/engine.js +34 -45
- package/yeaft/prompts.js +27 -320
- package/yeaft/sessions/index.js +1 -0
- package/yeaft/sessions/session-crud.js +64 -2
- package/yeaft/sub-agent/execution-control.js +66 -6
- package/yeaft/sub-agent/liveness.js +9 -1
- package/yeaft/sub-agent/runner.js +48 -39
- package/yeaft/sub-agent/spawned-prompt.js +3 -2
- package/yeaft/sub-agent/tool-access.js +138 -0
- package/yeaft/tasks/manager.js +0 -56
- package/yeaft/templates/common-rules.md +2 -2
- package/yeaft/templates/personas/reviewer.md +5 -2
- package/yeaft/tools/activation.js +2 -0
- package/yeaft/tools/agent.js +30 -14
- package/yeaft/tools/file-read.js +4 -5
- package/yeaft/tools/git-read.js +266 -0
- package/yeaft/tools/index.js +4 -0
- package/yeaft/tools/update-agent.js +79 -0
- package/yeaft/web-bridge.js +26 -30
- package/yeaft/templates/tool-guidance.md +0 -11
|
Binary file
|
package/package.json
CHANGED
|
@@ -827,6 +827,20 @@ class SegmentStore {
|
|
|
827
827
|
: out.sort(compareMessagesBySeq);
|
|
828
828
|
}
|
|
829
829
|
|
|
830
|
+
/**
|
|
831
|
+
* Read physical rows without applying reflection tombstones. Session cloning
|
|
832
|
+
* needs the complete durable transcript, including rows hidden by folding.
|
|
833
|
+
*/
|
|
834
|
+
readAllRaw({ includeCold = false } = {}) {
|
|
835
|
+
if (!this.hasData()) return [];
|
|
836
|
+
const idx = this.loadIndex();
|
|
837
|
+
return (idx.segments || [])
|
|
838
|
+
.slice()
|
|
839
|
+
.sort((a, b) => (a.firstSeq || 0) - (b.firstSeq || 0))
|
|
840
|
+
.flatMap(segment => this.#readSegment(segment.file, { includeCold }))
|
|
841
|
+
.sort(compareMessagesBySeq);
|
|
842
|
+
}
|
|
843
|
+
|
|
830
844
|
*scan({ beforeSeq = Infinity, afterSeq = -Infinity, desc = false, includeCold = false, scanStats = null } = {}) {
|
|
831
845
|
if (!this.hasData()) return;
|
|
832
846
|
const idx = this.loadIndex();
|
|
@@ -1615,6 +1629,81 @@ export class ConversationStore {
|
|
|
1615
1629
|
return this.loadRecentBySession(sessionId, Infinity);
|
|
1616
1630
|
}
|
|
1617
1631
|
|
|
1632
|
+
/**
|
|
1633
|
+
* Copy the complete durable transcript to a new Session identity.
|
|
1634
|
+
*
|
|
1635
|
+
* Unlike visible history readers, this includes cold rows, internal rows,
|
|
1636
|
+
* reflections, and the rows hidden by reflection tombstones. New persisted
|
|
1637
|
+
* message ids are allocated in original order. References that point to a
|
|
1638
|
+
* copied persisted message are remapped; external/client/tool identities are
|
|
1639
|
+
* intentionally preserved.
|
|
1640
|
+
*
|
|
1641
|
+
* @returns {{ copiedCount: number, idMap: Map<string, string> }}
|
|
1642
|
+
*/
|
|
1643
|
+
copySession(sourceSessionId, targetSessionId) {
|
|
1644
|
+
if (!sourceSessionId || !targetSessionId || sourceSessionId === targetSessionId) {
|
|
1645
|
+
return { copiedCount: 0, idMap: new Map() };
|
|
1646
|
+
}
|
|
1647
|
+
const primary = this.#segmentStoreForConversationDir(this.#sessionConversationDir(sourceSessionId));
|
|
1648
|
+
const segmentedRows = primary.readAllRaw({ includeCold: true });
|
|
1649
|
+
const legacyRows = this.#sessionFileEntries('all', sourceSessionId)
|
|
1650
|
+
.map(entry => {
|
|
1651
|
+
try { return this.readMessageFile(entry.path); } catch (err) {
|
|
1652
|
+
if (isPermissionError(err)) return null;
|
|
1653
|
+
throw err;
|
|
1654
|
+
}
|
|
1655
|
+
})
|
|
1656
|
+
.filter(Boolean);
|
|
1657
|
+
// Migration can temporarily leave the same durable row in both the legacy
|
|
1658
|
+
// markdown layout and the segment store. Preserve legacy-only rows, but let
|
|
1659
|
+
// the canonical segment copy win when both contain the same persisted id.
|
|
1660
|
+
const rowsById = new Map();
|
|
1661
|
+
for (const row of [...legacyRows, ...segmentedRows]) {
|
|
1662
|
+
if (row?.sessionId !== sourceSessionId || typeof row.id !== 'string' || !row.id) continue;
|
|
1663
|
+
rowsById.set(row.id, row);
|
|
1664
|
+
}
|
|
1665
|
+
const rows = [...rowsById.values()].sort(compareMessagesBySeq);
|
|
1666
|
+
if (rows.length === 0) return { copiedCount: 0, idMap: new Map() };
|
|
1667
|
+
|
|
1668
|
+
const firstSeq = this.#getNextSeq();
|
|
1669
|
+
const idMap = new Map(rows.map((row, index) => [
|
|
1670
|
+
row.id,
|
|
1671
|
+
`m${String(firstSeq + index).padStart(4, '0')}`,
|
|
1672
|
+
]));
|
|
1673
|
+
const remapId = id => idMap.get(id) || id;
|
|
1674
|
+
const copies = rows.map(row => {
|
|
1675
|
+
const copy = { ...row, sessionId: targetSessionId };
|
|
1676
|
+
delete copy.id;
|
|
1677
|
+
if (idMap.has(row.causalRootId)) copy.causalRootId = remapId(row.causalRootId);
|
|
1678
|
+
if (Array.isArray(row.foldedMessageIds)) {
|
|
1679
|
+
copy.foldedMessageIds = row.foldedMessageIds.map(remapId);
|
|
1680
|
+
}
|
|
1681
|
+
if (Array.isArray(row.sourceMessageIds)) {
|
|
1682
|
+
copy.sourceMessageIds = row.sourceMessageIds.map(remapId);
|
|
1683
|
+
}
|
|
1684
|
+
if (row.cold === true) delete copy.cold;
|
|
1685
|
+
return copy;
|
|
1686
|
+
});
|
|
1687
|
+
const written = this.appendBatch(copies);
|
|
1688
|
+
for (let index = 0; index < written.length; index += 1) {
|
|
1689
|
+
if (rows[index].cold === true) this.moveToCold(written[index].id);
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
// append() intentionally treats permission failures as best-effort for live
|
|
1693
|
+
// chat. A Session copy cannot: reporting success with a partial transcript
|
|
1694
|
+
// would make the new Session irrecoverably incomplete. Verify the target's
|
|
1695
|
+
// physical rows before the higher-level CRUD operation commits the clone.
|
|
1696
|
+
const target = this.#segmentStoreForConversationDir(this.#sessionConversationDir(targetSessionId));
|
|
1697
|
+
const persistedIds = new Set(target.readAllRaw({ includeCold: true }).map(row => row.id));
|
|
1698
|
+
const expectedIds = [...idMap.values()];
|
|
1699
|
+
if (written.length !== rows.length
|
|
1700
|
+
|| persistedIds.size !== expectedIds.length
|
|
1701
|
+
|| expectedIds.some(id => !persistedIds.has(id))) {
|
|
1702
|
+
throw new Error(`Session transcript copy incomplete: expected ${rows.length}, persisted ${persistedIds.size}`);
|
|
1703
|
+
}
|
|
1704
|
+
return { copiedCount: written.length, idMap };
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1618
1707
|
/**
|
|
1619
1708
|
* VP-scoped view of Session history, used to build a pair-safe provider
|
|
1620
1709
|
* snapshot for one VP. It operates on what the VP can actually see, not
|
package/yeaft/engine.js
CHANGED
|
@@ -1250,8 +1250,8 @@ export class Engine {
|
|
|
1250
1250
|
}
|
|
1251
1251
|
|
|
1252
1252
|
/**
|
|
1253
|
-
* Build the system prompt with the AMS-rendered Memory block,
|
|
1254
|
-
*
|
|
1253
|
+
* Build the system prompt with the AMS-rendered Memory block, routing
|
|
1254
|
+
* metadata, and skill content. The legacy multi-path
|
|
1255
1255
|
* Memory injection (FTS-formatted + AMS snapshot + Layer-A summaries +
|
|
1256
1256
|
* userProfile + coreMemory) was retired in DESIGN-PROMPT v1; callers
|
|
1257
1257
|
* now thread a single `memoryInjection` string composed upstream from
|
|
@@ -1260,8 +1260,7 @@ export class Engine {
|
|
|
1260
1260
|
* Routes through `buildWorkerPrompt`, which:
|
|
1261
1261
|
* - Lays in the persona-as-identity block (or Yeaft identity fallback)
|
|
1262
1262
|
* - Adds the Memory section (passed in as `memoryInjection`)
|
|
1263
|
-
* - Adds
|
|
1264
|
-
* - Forwards optional `taskCtx` for the legacy task-context sub-block
|
|
1263
|
+
* - Adds multi-VP routing metadata from `activeScope` when applicable
|
|
1265
1264
|
*
|
|
1266
1265
|
* @param {object} args
|
|
1267
1266
|
* @param {string} args.prompt — user prompt (for skill relevance matching)
|
|
@@ -1273,11 +1272,10 @@ export class Engine {
|
|
|
1273
1272
|
* @param {string} [args.projectLabel] — current Project name and id for prompt attribution
|
|
1274
1273
|
* @param {string} [args.workCenterInstructions] — frozen Agent-level Work Center policy
|
|
1275
1274
|
* @param {string} [args.projectDoc] — resolved CLAUDE.md / AGENTS.md text (already truncated)
|
|
1276
|
-
* @param {object} [args.taskCtx] — legacy task-context sub-block (optional)
|
|
1277
1275
|
* @param {string} [args.explicitSkillName] — leading /skill:<name> command, if present
|
|
1278
1276
|
* @returns {string}
|
|
1279
1277
|
*/
|
|
1280
|
-
#buildSystemPrompt({ prompt, memoryInjection, vpPersona, activeScope, sessionAnnouncement, projectInstruction, projectLabel, workCenterInstructions, projectDoc,
|
|
1278
|
+
#buildSystemPrompt({ prompt, memoryInjection, vpPersona, activeScope, sessionAnnouncement, projectInstruction, projectLabel, workCenterInstructions, projectDoc, promptNotices = [], explicitSkillName, resolvedSkillContent = null } = {}) {
|
|
1281
1279
|
// #runQuery resolves Skill selection at each provider-request boundary so
|
|
1282
1280
|
// a live Plugin policy change cannot leave stale content in the next prompt.
|
|
1283
1281
|
// Keep the local fallback for internal callers that do not need selection
|
|
@@ -1292,21 +1290,8 @@ export class Engine {
|
|
|
1292
1290
|
}
|
|
1293
1291
|
}
|
|
1294
1292
|
|
|
1295
|
-
// Prompt guidance must describe the same canonical capability
|
|
1296
|
-
// intersection that reaches provider schemas and execution.
|
|
1297
|
-
const registeredToolNames = this.#toolRegistry
|
|
1298
|
-
? this.#toolRegistry.getToolNames({
|
|
1299
|
-
plugins: this.#config?.plugins,
|
|
1300
|
-
collabToolPolicy,
|
|
1301
|
-
})
|
|
1302
|
-
: Array.from(this.#tools.keys());
|
|
1303
|
-
const toolNames = activeToolNames instanceof Set
|
|
1304
|
-
? registeredToolNames.filter(name => activeToolNames.has(name))
|
|
1305
|
-
: registeredToolNames;
|
|
1306
|
-
|
|
1307
1293
|
return buildWorkerPrompt({
|
|
1308
1294
|
language: this.#config.language || 'en',
|
|
1309
|
-
toolNames,
|
|
1310
1295
|
memoryInjection,
|
|
1311
1296
|
skillContent,
|
|
1312
1297
|
vpPersona,
|
|
@@ -1317,8 +1302,6 @@ export class Engine {
|
|
|
1317
1302
|
workCenterInstructions,
|
|
1318
1303
|
projectDoc,
|
|
1319
1304
|
runtimePlatform: getRuntimePlatformInfo(),
|
|
1320
|
-
taskCtx,
|
|
1321
|
-
activeTasks,
|
|
1322
1305
|
promptNotices,
|
|
1323
1306
|
// Worker-shape harness is descriptive metadata for human inspection;
|
|
1324
1307
|
// production prompts skip it to save tokens. Re-enable via env when
|
|
@@ -2306,18 +2289,13 @@ export class Engine {
|
|
|
2306
2289
|
}))
|
|
2307
2290
|
: [];
|
|
2308
2291
|
|
|
2309
|
-
// ───
|
|
2310
|
-
//
|
|
2311
|
-
//
|
|
2312
|
-
//
|
|
2313
|
-
const activeSessionTopics = topicScopesForResident
|
|
2314
|
-
.map(scope => scope.replace(/^sessions\/[^/]+\/topic\//, ''));
|
|
2292
|
+
// ─── Multi-VP routing scope ──────────────────────────────────
|
|
2293
|
+
// Only identity and peer membership affect the routing contract rendered in
|
|
2294
|
+
// the system prompt. Session bookkeeping and inferred topics remain in their
|
|
2295
|
+
// owning runtime/memory paths instead of being mirrored into prompt metadata.
|
|
2315
2296
|
const activeScope = {
|
|
2316
|
-
|
|
2317
|
-
sessionMember: ownVpIdForAms || '',
|
|
2297
|
+
vpId: ownVpIdForAms || '',
|
|
2318
2298
|
sessionMembers: Array.isArray(sessionMembers) ? sessionMembers : [],
|
|
2319
|
-
sessionTopics: activeSessionTopics,
|
|
2320
|
-
envelope: inboundEnvelope || null,
|
|
2321
2299
|
};
|
|
2322
2300
|
|
|
2323
2301
|
let projectDocSource = this.#getProjectDocBlock(workDir);
|
|
@@ -2332,11 +2310,6 @@ export class Engine {
|
|
|
2332
2310
|
&& typeof this.#taskManager.listActiveTasks === 'function'
|
|
2333
2311
|
? this.#taskManager.listActiveTasks(runtimeSessionId)
|
|
2334
2312
|
: [];
|
|
2335
|
-
let activeTasks = this.#taskManager
|
|
2336
|
-
? this.#taskManager.renderActiveTasksForPrompt(runtimeSessionId, {
|
|
2337
|
-
language: this.#config.language || 'en',
|
|
2338
|
-
})
|
|
2339
|
-
: '';
|
|
2340
2313
|
const registeredToolNames = this.#toolRegistry
|
|
2341
2314
|
? this.#toolRegistry.getToolNames({
|
|
2342
2315
|
collabToolPolicy: effectiveCollabToolPolicy,
|
|
@@ -2407,9 +2380,6 @@ export class Engine {
|
|
|
2407
2380
|
projectLabel,
|
|
2408
2381
|
workCenterInstructions,
|
|
2409
2382
|
projectDoc: projectDocContext.text,
|
|
2410
|
-
activeTasks,
|
|
2411
|
-
collabToolPolicy: effectiveCollabToolPolicy,
|
|
2412
|
-
activeToolNames,
|
|
2413
2383
|
promptNotices,
|
|
2414
2384
|
explicitSkillName,
|
|
2415
2385
|
resolvedSkillContent,
|
|
@@ -2825,17 +2795,18 @@ export class Engine {
|
|
|
2825
2795
|
&& typeof this.#taskManager.listActiveTasks === 'function'
|
|
2826
2796
|
? this.#taskManager.listActiveTasks(runtimeSessionId)
|
|
2827
2797
|
: [];
|
|
2828
|
-
activeTasks = this.#taskManager
|
|
2829
|
-
? this.#taskManager.renderActiveTasksForPrompt(runtimeSessionId, {
|
|
2830
|
-
language: this.#config.language || 'en',
|
|
2831
|
-
})
|
|
2832
|
-
: '';
|
|
2833
2798
|
activeToolNames = resolveCurrentActiveToolNames();
|
|
2834
2799
|
for (const name of [...discoveredToolNames]) {
|
|
2835
2800
|
if (this.#toolRegistry?.has(name)) activeToolNames?.add(name);
|
|
2836
2801
|
else discoveredToolNames.delete(name);
|
|
2837
2802
|
}
|
|
2838
2803
|
toolDefs = this.#getToolDefs(effectiveCollabToolPolicy, activeToolNames);
|
|
2804
|
+
// Only a child registry supplies this policy. Budget exhaustion closes
|
|
2805
|
+
// investigation, not the evidence-bearing conversation: reserve one
|
|
2806
|
+
// tool-free response for a useful handoff, under the existing signal.
|
|
2807
|
+
const executionPolicy = isSubAgent
|
|
2808
|
+
? this.#toolRegistry?.prepareProviderRequest?.() : null;
|
|
2809
|
+
if (executionPolicy?.finalize) toolDefs = [];
|
|
2839
2810
|
({ resolvedSkillContent, resolvedSkills, skillResolutionError } = resolveSkillPromptState({
|
|
2840
2811
|
skillManager: this.#skillManager,
|
|
2841
2812
|
prompt,
|
|
@@ -2853,6 +2824,7 @@ export class Engine {
|
|
|
2853
2824
|
reportedSkillNames = currentSkillNames;
|
|
2854
2825
|
reportedSkillError = skillResolutionError;
|
|
2855
2826
|
systemPrompt = buildCurrentSystemPrompt();
|
|
2827
|
+
if (executionPolicy?.prompt) systemPrompt += `\n\n${executionPolicy.prompt}`;
|
|
2856
2828
|
|
|
2857
2829
|
try {
|
|
2858
2830
|
// Resolve effort per provider request so a saved Session effort takes
|
|
@@ -2966,6 +2938,7 @@ export class Engine {
|
|
|
2966
2938
|
const requestMaxOutputTokens = Math.max(1, Math.min(
|
|
2967
2939
|
requestConfig.maxOutputTokens || resolveMaxOutputTokens(currentModel, requestConfig),
|
|
2968
2940
|
resolveMaxOutputTokens(currentModel, requestConfig),
|
|
2941
|
+
executionPolicy?.maxOutputTokens || Infinity,
|
|
2969
2942
|
));
|
|
2970
2943
|
const toolSchemaTokens = toolDefs.length > 0
|
|
2971
2944
|
? approxTokens(JSON.stringify(toolDefs)) : 0;
|
|
@@ -3059,7 +3032,12 @@ export class Engine {
|
|
|
3059
3032
|
retryLifecycle.pendingContinuation = null;
|
|
3060
3033
|
continuationCommitted = true;
|
|
3061
3034
|
};
|
|
3035
|
+
let childDispatchReserved = false;
|
|
3062
3036
|
const commitDispatch = () => {
|
|
3037
|
+
if (isSubAgent && !childDispatchReserved) {
|
|
3038
|
+
this.#toolRegistry?.reserveProviderRequest?.({ reporting: !!executionPolicy?.finalize });
|
|
3039
|
+
childDispatchReserved = true;
|
|
3040
|
+
}
|
|
3063
3041
|
if (!activeProviderRequest && typeof prepareProviderRequest === 'function') {
|
|
3064
3042
|
activeProviderRequest = prepareProviderRequest({
|
|
3065
3043
|
turnNumber,
|
|
@@ -3166,6 +3144,9 @@ export class Engine {
|
|
|
3166
3144
|
}
|
|
3167
3145
|
break;
|
|
3168
3146
|
case 'tool_call':
|
|
3147
|
+
// No dispatch or orphan protocol rows during the reserved report,
|
|
3148
|
+
// even if an adapter ignores the absence of tool definitions.
|
|
3149
|
+
if (executionPolicy?.finalize) break;
|
|
3169
3150
|
if (toolCalls.length === 0) {
|
|
3170
3151
|
traceRequest('llm.first_tool_call', {
|
|
3171
3152
|
durationMs: perfNowMs() - requestPerfStart,
|
|
@@ -3409,7 +3390,7 @@ export class Engine {
|
|
|
3409
3390
|
// the caller. Replaying that request would publish a duplicate call and
|
|
3410
3391
|
// leave ambiguous execution ownership, so only pre-tool failures are
|
|
3411
3392
|
// eligible for transparent retry or model fallback.
|
|
3412
|
-
const canReplayProviderRequest = toolCalls.length === 0;
|
|
3393
|
+
const canReplayProviderRequest = toolCalls.length === 0 && !executionPolicy?.finalize;
|
|
3413
3394
|
if (earlyIsContextOverflow && canReplayProviderRequest
|
|
3414
3395
|
&& contextOverflowRecoveryAttempts < 3) {
|
|
3415
3396
|
contextOverflowRecoveryAttempts += 1;
|
|
@@ -3808,6 +3789,14 @@ export class Engine {
|
|
|
3808
3789
|
conversationMessages.push(assistantMsg);
|
|
3809
3790
|
fullResponseText += responseText;
|
|
3810
3791
|
|
|
3792
|
+
// The reporting allowance is exactly one provider response, not another
|
|
3793
|
+
// investigation loop. Ignore unsolicited calls even from a noncompliant
|
|
3794
|
+
// adapter, and never auto-continue a truncated reporting response.
|
|
3795
|
+
if (executionPolicy?.finalize) {
|
|
3796
|
+
yield { type: 'turn_end', turnNumber, stopReason: 'budget_report', threadId, terminal: true };
|
|
3797
|
+
break;
|
|
3798
|
+
}
|
|
3799
|
+
|
|
3811
3800
|
// ─── Handle max_tokens → auto-continue ────────────
|
|
3812
3801
|
// A suppressed call leaves a synthetic reminder as the latest user
|
|
3813
3802
|
// message. Some models answer it with an empty end_turn. Continue exactly
|