@genesislcap/ai-assistant 15.27.0 → 15.28.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/dist/ai-assistant.api.json +69 -69
- package/dist/ai-assistant.d.ts +25 -27
- package/dist/chat-driver.cjs +13 -14
- package/dist/chat-driver.cjs.map +2 -2
- package/dist/chat-driver.mjs +13 -14
- package/dist/chat-driver.mjs.map +2 -2
- package/dist/custom-elements.json +30 -24
- package/dist/dts/components/ai-driver/ai-driver.d.ts +11 -12
- package/dist/dts/components/ai-driver/ai-driver.d.ts.map +1 -1
- package/dist/dts/components/ai-driver/stub-driver.d.ts +17 -0
- package/dist/dts/components/ai-driver/stub-driver.d.ts.map +1 -0
- package/dist/dts/components/chat-driver/chat-driver.d.ts +10 -11
- package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts +2 -2
- package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
- package/dist/dts/components/settings-modal/settings-modal.template.d.ts.map +1 -1
- package/dist/dts/main/main.d.ts +1 -1
- package/dist/dts/state/ai-assistant-slice.d.ts +5 -1
- package/dist/dts/state/ai-assistant-slice.d.ts.map +1 -1
- package/dist/dts/state/persistence/session-persistence-provider.d.ts +1 -1
- package/dist/dts/state/session-store.d.ts.map +1 -1
- package/dist/dts/utils/resolve-preference-baseline.d.ts.map +1 -1
- package/dist/esm/components/ai-driver/stub-driver.js +16 -0
- package/dist/esm/components/chat-driver/chat-driver.js +11 -12
- package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +5 -5
- package/dist/esm/components/settings-modal/settings-modal.template.js +21 -1
- package/dist/esm/main/main.js +15 -15
- package/dist/esm/main/main.styles.js +1 -1
- package/dist/esm/state/ai-assistant-slice.js +2 -1
- package/dist/esm/state/persistence/session-persister.js +20 -20
- package/dist/esm/utils/resolve-preference-baseline.js +6 -2
- package/docs/migration-GENC-1580.md +108 -58
- package/package.json +16 -16
- package/sandbox/README.md +3 -3
- package/sandbox/sandbox.ts +1 -1
|
@@ -156,7 +156,7 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
156
156
|
* **The inner driver's session, not one of our own.** One session is the invariant: two would
|
|
157
157
|
* let the outer and inner driver disagree about which agent is active, and the inner one would
|
|
158
158
|
* never be activated at all, because this driver installs a *decorated* config through
|
|
159
|
-
* `
|
|
159
|
+
* `installAgent` rather than going via `ChatDriver.activateAgent`. This driver keeps the routing
|
|
160
160
|
* decisions; the session owns the lifecycle.
|
|
161
161
|
*/
|
|
162
162
|
get agentSession() {
|
|
@@ -431,7 +431,7 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
431
431
|
if (this.cancelled)
|
|
432
432
|
break;
|
|
433
433
|
// oxlint-disable-next-line no-await-in-loop
|
|
434
|
-
yield this.
|
|
434
|
+
yield this.installAgent(currentAgent);
|
|
435
435
|
let result;
|
|
436
436
|
if (isHandoff) {
|
|
437
437
|
const contextPrimer = handoffSummary
|
|
@@ -488,7 +488,7 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
488
488
|
break;
|
|
489
489
|
}
|
|
490
490
|
// Pinned agents never hand off — the continuation tool is filtered out in
|
|
491
|
-
//
|
|
491
|
+
// installAgent, but this guards against a model hallucinating a handoff result.
|
|
492
492
|
if (result.reason !== 'agent-handoff' || isFallback(currentAgent) || pinned) {
|
|
493
493
|
break;
|
|
494
494
|
}
|
|
@@ -592,7 +592,7 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
592
592
|
getActiveAgent() {
|
|
593
593
|
return this.agentSession.getActive();
|
|
594
594
|
}
|
|
595
|
-
|
|
595
|
+
installAgent(agent) {
|
|
596
596
|
return __awaiter(this, void 0, void 0, function* () {
|
|
597
597
|
// The transition — handoff record, lifecycle hooks, restored state, flow lock — belongs to
|
|
598
598
|
// the session. What is left below is this driver's own job: what the agent may route to, and
|
|
@@ -634,7 +634,7 @@ export class OrchestratingDriver extends EventTarget {
|
|
|
634
634
|
]);
|
|
635
635
|
}
|
|
636
636
|
this.chatDriver.setProviderHistoryTransform((h) => transformHistoryForAgent(h, agent.name));
|
|
637
|
-
this.chatDriver.
|
|
637
|
+
this.chatDriver.installAgent(agentToApply);
|
|
638
638
|
this.dispatchEvent(new CustomEvent('orchestrating-stop'));
|
|
639
639
|
this.dispatchEvent(new CustomEvent('agent-changed', { detail: agent }));
|
|
640
640
|
});
|
|
@@ -69,7 +69,27 @@ export const timestampModePickerTemplate = (segmentedControlTag, segmentedItemTa
|
|
|
69
69
|
aria-label="Show timestamps"
|
|
70
70
|
:value=${(x) => x.timestampDisplay}
|
|
71
71
|
@change=${(x, c) => {
|
|
72
|
-
|
|
72
|
+
// Only the GROUP's own `change` is authoritative. Each item emits its own `change` from
|
|
73
|
+
// `checkedChanged` as the group checks the incoming one and unchecks the outgoing, and
|
|
74
|
+
// those bubble to this listener carrying whichever mode that item represents — a valid
|
|
75
|
+
// string, and during startup a STALE one, so neither validating the value nor testing
|
|
76
|
+
// `checked` can tell them from a real pick. The group, by contrast, emits from
|
|
77
|
+
// `valueChanged`, which runs only after `changeHandler` has set `value` to the selection.
|
|
78
|
+
//
|
|
79
|
+
// Acting on a stale echo is not cosmetic: it writes a mode nobody chose AND marks
|
|
80
|
+
// preferences user-changed, latching the reconciler for the session — so the host's
|
|
81
|
+
// `chatConfig.ui.showTimestamps` never applies and the slice default wins instead.
|
|
82
|
+
// It fires on FIRST RENDER, before any interaction, which is what made a configured
|
|
83
|
+
// `'always'` unreachable in Create (GENC-1257).
|
|
84
|
+
if (c.event.target !== c.event.currentTarget)
|
|
85
|
+
return;
|
|
86
|
+
const next = c.event.target.value;
|
|
87
|
+
// `hasOwnProperty`, not `in`: `in` walks the prototype chain, so it would accept
|
|
88
|
+
// `'toString'`, `'constructor'` and friends as modes.
|
|
89
|
+
if (typeof next === 'string' &&
|
|
90
|
+
Object.prototype.hasOwnProperty.call(TIMESTAMP_MODE_LABELS, next)) {
|
|
91
|
+
x.timestampDisplay = next;
|
|
92
|
+
}
|
|
73
93
|
}}
|
|
74
94
|
>
|
|
75
95
|
${repeat(() => TIMESTAMP_MODE_OPTIONS, html `
|
package/dist/esm/main/main.js
CHANGED
|
@@ -682,7 +682,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
682
682
|
this.persistPreferences();
|
|
683
683
|
}
|
|
684
684
|
/**
|
|
685
|
-
* When each message shows its timestamp — `'off'`, `'hover'`
|
|
685
|
+
* When each message shows its timestamp — `'off'`, `'hover'` or `'always'` (default).
|
|
686
686
|
*
|
|
687
687
|
* Falls back to `DEFAULT_TIMESTAMP_DISPLAY` rather than `'off'` when there is no
|
|
688
688
|
* store yet: this preference defaults ON, so the pre-store render must match the state
|
|
@@ -1696,7 +1696,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
1696
1696
|
if (agents.length === 1 && !agents[0].fallback) {
|
|
1697
1697
|
// Preset for getters that read activeAgent before the first turn
|
|
1698
1698
|
// (e.g. chatInputDuringExecution). The orchestrator will overwrite
|
|
1699
|
-
// this via `agent-changed` once
|
|
1699
|
+
// this via `agent-changed` once installAgent fires on first sendMessage.
|
|
1700
1700
|
this.activeAgent = agents[0];
|
|
1701
1701
|
}
|
|
1702
1702
|
return new OrchestratingDriver(this.providerRegistry, agents, {
|
|
@@ -2697,11 +2697,11 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
2697
2697
|
* @internal
|
|
2698
2698
|
*/
|
|
2699
2699
|
get contextReserve() {
|
|
2700
|
-
var _a, _b, _c
|
|
2700
|
+
var _a, _b, _c;
|
|
2701
2701
|
const resolved = (_b = (_a = this.driver) === null || _a === void 0 ? void 0 : _a.getContextReserve) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
2702
2702
|
if (resolved)
|
|
2703
2703
|
return resolved;
|
|
2704
|
-
const active = (
|
|
2704
|
+
const active = (_c = this.driver) === null || _c === void 0 ? void 0 : _c.getActiveAgent();
|
|
2705
2705
|
const declared = active === null || active === void 0 ? void 0 : active.contextReserve;
|
|
2706
2706
|
if (declared == null)
|
|
2707
2707
|
return undefined;
|
|
@@ -2945,18 +2945,18 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
2945
2945
|
*/
|
|
2946
2946
|
clearSession() {
|
|
2947
2947
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2948
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j
|
|
2948
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
2949
2949
|
if ((_a = this.driver) === null || _a === void 0 ? void 0 : _a.isBusy())
|
|
2950
2950
|
return; // the menu only opens when idle; defensive
|
|
2951
2951
|
const key = this.getStateKey();
|
|
2952
2952
|
// Capture the live agent + its serialized state BEFORE teardown — the
|
|
2953
2953
|
// machine is stopped below, so this is the host's last chance to learn what
|
|
2954
2954
|
// the agent was working on (so it can tidy up external state if it wants).
|
|
2955
|
-
const liveActive = (
|
|
2956
|
-
const agentName = (
|
|
2955
|
+
const liveActive = (_b = this.driver) === null || _b === void 0 ? void 0 : _b.getActiveAgent();
|
|
2956
|
+
const agentName = (_c = liveActive === null || liveActive === void 0 ? void 0 : liveActive.name) !== null && _c !== void 0 ? _c : (_d = this.activeAgent) === null || _d === void 0 ? void 0 : _d.name;
|
|
2957
2957
|
let agentSnapshot;
|
|
2958
2958
|
try {
|
|
2959
|
-
agentSnapshot = (
|
|
2959
|
+
agentSnapshot = (_e = liveActive === null || liveActive === void 0 ? void 0 : liveActive.serialize) === null || _e === void 0 ? void 0 : _e.call(liveActive);
|
|
2960
2960
|
}
|
|
2961
2961
|
catch (e) {
|
|
2962
2962
|
logger.error('Session clear: agent serialize() failed:', e);
|
|
@@ -2967,7 +2967,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
2967
2967
|
const discarded = this.sessionUsage;
|
|
2968
2968
|
if (this.driver)
|
|
2969
2969
|
this.teardownDriver(key);
|
|
2970
|
-
(
|
|
2970
|
+
(_f = this._sessionRef) === null || _f === void 0 ? void 0 : _f.actions.aiAssistant.resetSession();
|
|
2971
2971
|
// `resetSession` zeroes the store-backed `contextTokens`; the gate's held copy
|
|
2972
2972
|
// is ours to drop. Leaving it would carry a block into a conversation that no
|
|
2973
2973
|
// longer exists — the one thing `contextGate` must never do (GENC-1582).
|
|
@@ -2979,7 +2979,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
2979
2979
|
// Restore-core is owned by the shared per-session persister (GENC-1351 §5.10):
|
|
2980
2980
|
// it drops the snapshot + resets the carry-forward cache. Diagnostics is
|
|
2981
2981
|
// append-only and handled separately below.
|
|
2982
|
-
yield ((
|
|
2982
|
+
yield ((_g = this.persister()) === null || _g === void 0 ? void 0 : _g.clearRestoreCore());
|
|
2983
2983
|
this.confirmingClear = false;
|
|
2984
2984
|
this.composerMenus.closeImmediate();
|
|
2985
2985
|
this.logMeta('session.cleared', { agentName });
|
|
@@ -2988,8 +2988,8 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
2988
2988
|
// forward-capture cursors and flush, so the just-logged `session.cleared`
|
|
2989
2989
|
// event becomes the stream's roll boundary: exactly like a page reload, the
|
|
2990
2990
|
// new conversation begins a fresh section within the same lifetime log (§5.8).
|
|
2991
|
-
resetDiagnosticsCursorsFor((
|
|
2992
|
-
(
|
|
2991
|
+
resetDiagnosticsCursorsFor((_h = this.getStateKey()) !== null && _h !== void 0 ? _h : '');
|
|
2992
|
+
(_j = this.persister()) === null || _j === void 0 ? void 0 : _j.scheduleDiagnostics();
|
|
2993
2993
|
this.dispatchEvent(new CustomEvent(AiAssistantEvent.SessionCleared, {
|
|
2994
2994
|
bubbles: true,
|
|
2995
2995
|
composed: true,
|
|
@@ -3305,11 +3305,11 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
3305
3305
|
* `onActivate` ran, so only its `getDebugSnapshot` closure holds state.
|
|
3306
3306
|
*/
|
|
3307
3307
|
buildMetaSnapshot() {
|
|
3308
|
-
var _a, _b
|
|
3308
|
+
var _a, _b;
|
|
3309
3309
|
return buildMetaSnapshot({
|
|
3310
3310
|
agents: this.agents,
|
|
3311
3311
|
activeAgent: this.activeAgent,
|
|
3312
|
-
liveActiveAgent: (
|
|
3312
|
+
liveActiveAgent: (_a = this.driver) === null || _a === void 0 ? void 0 : _a.getActiveAgent(),
|
|
3313
3313
|
activeFoldStack: this.driver instanceof ChatDriver ? this.driver.getActiveFoldNames() : undefined,
|
|
3314
3314
|
activeModel: this.activeModel,
|
|
3315
3315
|
contextTokens: this.contextTokens,
|
|
@@ -3317,7 +3317,7 @@ let FoundationAiAssistant = FoundationAiAssistant_1 = class FoundationAiAssistan
|
|
|
3317
3317
|
sessionCostUsd: this.sessionCostUsd,
|
|
3318
3318
|
sessionTokensConsumed: this.sessionTokensConsumed,
|
|
3319
3319
|
sessionUsage: this.sessionUsage,
|
|
3320
|
-
debugState: (
|
|
3320
|
+
debugState: (_b = this.debugStateFactory) === null || _b === void 0 ? void 0 : _b.call(this),
|
|
3321
3321
|
host: window.location.host,
|
|
3322
3322
|
});
|
|
3323
3323
|
}
|
|
@@ -399,7 +399,7 @@ const baseStyles = css `
|
|
|
399
399
|
border: 0;
|
|
400
400
|
}
|
|
401
401
|
|
|
402
|
-
/* 'hover'
|
|
402
|
+
/* 'hover'. Space is RESERVED at all times (opacity, not display) so
|
|
403
403
|
revealing the timestamp cannot reflow the bubble under the pointer; only the paint
|
|
404
404
|
changes. The sender line is rarely the widest line in a bubble, so this costs no
|
|
405
405
|
width except on very short messages. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createSlice } from '@genesislcap/foundation-redux';
|
|
2
|
+
import { DEFAULT_TIMESTAMP_DISPLAY } from '../utils/resolve-preference-baseline';
|
|
2
3
|
import { emptyUsage, sumUsage } from '../utils/sum-usage';
|
|
3
4
|
/**
|
|
4
5
|
* Build a fresh default session state. A factory (not just a shared const) so
|
|
@@ -13,7 +14,7 @@ export function createDefaultSessionState() {
|
|
|
13
14
|
showThinkingSteps: false,
|
|
14
15
|
showNarration: false,
|
|
15
16
|
showAgentSwitchIndicator: false,
|
|
16
|
-
timestampDisplay:
|
|
17
|
+
timestampDisplay: DEFAULT_TIMESTAMP_DISPLAY,
|
|
17
18
|
enabledAnimations: [],
|
|
18
19
|
suggestionsState: { status: 'idle' },
|
|
19
20
|
contextTokens: undefined,
|
|
@@ -270,7 +270,7 @@ export class SessionPersister {
|
|
|
270
270
|
*/
|
|
271
271
|
persistSession() {
|
|
272
272
|
return __awaiter(this, void 0, void 0, function* () {
|
|
273
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l
|
|
273
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
274
274
|
const { provider, enabled } = this.deps.getPersistence();
|
|
275
275
|
if (!enabled || !provider)
|
|
276
276
|
return;
|
|
@@ -288,7 +288,7 @@ export class SessionPersister {
|
|
|
288
288
|
// mints fresh configs (the showcase) would otherwise hand us a never-activated
|
|
289
289
|
// closure whose `serialize()` returns undefined.
|
|
290
290
|
const liveSnapshots = {};
|
|
291
|
-
const liveActive =
|
|
291
|
+
const liveActive = driver === null || driver === void 0 ? void 0 : driver.getActiveAgent();
|
|
292
292
|
if (liveActive === null || liveActive === void 0 ? void 0 : liveActive.serialize) {
|
|
293
293
|
try {
|
|
294
294
|
const snap = liveActive.serialize();
|
|
@@ -344,7 +344,7 @@ export class SessionPersister {
|
|
|
344
344
|
sessionKey: key,
|
|
345
345
|
host: window.location.host,
|
|
346
346
|
messages: messages.slice(0, Math.min(boundary, messages.length)),
|
|
347
|
-
activeAgentName: (
|
|
347
|
+
activeAgentName: (_a = s.activeAgent) === null || _a === void 0 ? void 0 : _a.name,
|
|
348
348
|
pinnedAgentName: s.pinnedAgentName,
|
|
349
349
|
flowOwnerAgentName: null,
|
|
350
350
|
sessionCostUsd: s.sessionUsage.costUsd,
|
|
@@ -365,7 +365,7 @@ export class SessionPersister {
|
|
|
365
365
|
return;
|
|
366
366
|
}
|
|
367
367
|
this.logMeta('persistence.suppressed', {
|
|
368
|
-
agent: (
|
|
368
|
+
agent: (_b = liveActive === null || liveActive === void 0 ? void 0 : liveActive.name) !== null && _b !== void 0 ? _b : null,
|
|
369
369
|
isFlowOwner: s.flowOwnerAgentName === (liveActive === null || liveActive === void 0 ? void 0 : liveActive.name),
|
|
370
370
|
frozeAtMessages: frozen.messages.length,
|
|
371
371
|
custom: !!(liveActive === null || liveActive === void 0 ? void 0 : liveActive.notResumableMessage),
|
|
@@ -382,7 +382,7 @@ export class SessionPersister {
|
|
|
382
382
|
// event was added to the resumable branch — so a session that broke while an agent was
|
|
383
383
|
// mid-flow still reported nothing at all, which is precisely the blind spot the
|
|
384
384
|
// user-facing warning exists to cover.
|
|
385
|
-
this.recordSaveOutcome(false, (
|
|
385
|
+
this.recordSaveOutcome(false, (_c = e === null || e === void 0 ? void 0 : e.message) !== null && _c !== void 0 ? _c : String(e));
|
|
386
386
|
// Deliberately NOT reporting from here. This is the non-resumable branch and a reload is
|
|
387
387
|
// the modal's only action, so advising one now would destroy the in-flight work the
|
|
388
388
|
// snapshot could not carry. `recordSaveOutcome` has already set `_brokenSince`, so the
|
|
@@ -395,11 +395,11 @@ export class SessionPersister {
|
|
|
395
395
|
this.logMeta('persistence.save-failed', {
|
|
396
396
|
messages: frozen.messages.length,
|
|
397
397
|
agents: [],
|
|
398
|
-
active: (
|
|
398
|
+
active: (_d = liveActive === null || liveActive === void 0 ? void 0 : liveActive.name) !== null && _d !== void 0 ? _d : null,
|
|
399
399
|
hasSerialize: !!(liveActive === null || liveActive === void 0 ? void 0 : liveActive.serialize),
|
|
400
400
|
frozen: true,
|
|
401
|
-
error: (
|
|
402
|
-
message: (
|
|
401
|
+
error: (_e = e === null || e === void 0 ? void 0 : e.name) !== null && _e !== void 0 ? _e : typeof e,
|
|
402
|
+
message: (_f = e === null || e === void 0 ? void 0 : e.message) !== null && _f !== void 0 ? _f : String(e),
|
|
403
403
|
});
|
|
404
404
|
logger.error('Session save (interrupted marker) failed:', e);
|
|
405
405
|
}
|
|
@@ -411,7 +411,7 @@ export class SessionPersister {
|
|
|
411
411
|
sessionKey: key,
|
|
412
412
|
host: window.location.host,
|
|
413
413
|
messages: [...messages],
|
|
414
|
-
activeAgentName: (
|
|
414
|
+
activeAgentName: (_g = s.activeAgent) === null || _g === void 0 ? void 0 : _g.name,
|
|
415
415
|
pinnedAgentName: s.pinnedAgentName,
|
|
416
416
|
flowOwnerAgentName: s.flowOwnerAgentName,
|
|
417
417
|
sessionCostUsd: s.sessionUsage.costUsd,
|
|
@@ -435,7 +435,7 @@ export class SessionPersister {
|
|
|
435
435
|
const captured = {
|
|
436
436
|
messages: messages.length,
|
|
437
437
|
agents: Object.keys(agentSnapshots),
|
|
438
|
-
active: (
|
|
438
|
+
active: (_h = liveActive === null || liveActive === void 0 ? void 0 : liveActive.name) !== null && _h !== void 0 ? _h : null,
|
|
439
439
|
hasSerialize: !!(liveActive === null || liveActive === void 0 ? void 0 : liveActive.serialize),
|
|
440
440
|
agentSnapshots,
|
|
441
441
|
};
|
|
@@ -454,7 +454,7 @@ export class SessionPersister {
|
|
|
454
454
|
// means the body never reached the wire at all, because a provider builds its
|
|
455
455
|
// request body inside the `fetch` init, so a throw while serialising creates no
|
|
456
456
|
// request and leaves no server-side trace.
|
|
457
|
-
this.recordSaveOutcome(false, (
|
|
457
|
+
this.recordSaveOutcome(false, (_j = e === null || e === void 0 ? void 0 : e.message) !== null && _j !== void 0 ? _j : String(e));
|
|
458
458
|
// Report straight away rather than waiting for the next attempt: we are on the resumable
|
|
459
459
|
// path, so a reload is safe NOW, and a one-cycle delay is a second of extra exposure for
|
|
460
460
|
// no benefit.
|
|
@@ -471,8 +471,8 @@ export class SessionPersister {
|
|
|
471
471
|
agents: captured.agents,
|
|
472
472
|
active: captured.active,
|
|
473
473
|
hasSerialize: captured.hasSerialize,
|
|
474
|
-
error: (
|
|
475
|
-
message: (
|
|
474
|
+
error: (_k = e === null || e === void 0 ? void 0 : e.name) !== null && _k !== void 0 ? _k : typeof e,
|
|
475
|
+
message: (_l = e === null || e === void 0 ? void 0 : e.message) !== null && _l !== void 0 ? _l : String(e),
|
|
476
476
|
});
|
|
477
477
|
logger.error('Session save failed:', e);
|
|
478
478
|
}
|
|
@@ -487,7 +487,7 @@ export class SessionPersister {
|
|
|
487
487
|
*/
|
|
488
488
|
restore() {
|
|
489
489
|
return __awaiter(this, void 0, void 0, function* () {
|
|
490
|
-
var _a, _b, _c, _d
|
|
490
|
+
var _a, _b, _c, _d;
|
|
491
491
|
const { provider } = this.deps.getPersistence();
|
|
492
492
|
if (!provider)
|
|
493
493
|
return;
|
|
@@ -536,8 +536,8 @@ export class SessionPersister {
|
|
|
536
536
|
// Prime each stateful agent's persisted machine/state image FIRST — before
|
|
537
537
|
// seeding Redux or driver history — so the image is waiting on the driver no
|
|
538
538
|
// matter when the agent re-activates. The flow-owner lock self-heals on
|
|
539
|
-
// activation (OrchestratingDriver.
|
|
540
|
-
|
|
539
|
+
// activation (OrchestratingDriver.installAgent re-sets it).
|
|
540
|
+
driver === null || driver === void 0 ? void 0 : driver.primeRestoredAgentState(snapshot.agentSnapshots);
|
|
541
541
|
// Seed the carry-forward cache so the immediate post-restore autosave (fired
|
|
542
542
|
// by the history change below, before any agent re-activates) preserves the
|
|
543
543
|
// machine snapshot instead of overwriting it with an empty one (GENC-1351).
|
|
@@ -550,7 +550,7 @@ export class SessionPersister {
|
|
|
550
550
|
// actually came back (meta events reset on reload, so this is the only window
|
|
551
551
|
// onto the saved→loaded handoff).
|
|
552
552
|
this.logMeta('persistence.restored', {
|
|
553
|
-
agents: Object.keys((
|
|
553
|
+
agents: Object.keys((_a = snapshot.agentSnapshots) !== null && _a !== void 0 ? _a : {}),
|
|
554
554
|
agentSnapshots: snapshot.agentSnapshots,
|
|
555
555
|
pinnedAgentName: snapshot.pinnedAgentName,
|
|
556
556
|
flowOwnerAgentName: snapshot.flowOwnerAgentName,
|
|
@@ -618,7 +618,7 @@ export class SessionPersister {
|
|
|
618
618
|
}
|
|
619
619
|
const messages = [...trimmed, ...trailers];
|
|
620
620
|
ref.actions.aiAssistant.loadSession(toLoadSessionPayload(Object.assign(Object.assign({}, snapshot), { messages })));
|
|
621
|
-
(
|
|
621
|
+
(_b = driver === null || driver === void 0 ? void 0 : driver.loadHistory) === null || _b === void 0 ? void 0 : _b.call(driver, [...messages]);
|
|
622
622
|
// Interrupted flow (§5.9): pre-fill the composer with the prompt that started
|
|
623
623
|
// the flow so "send it again" is one keystroke, and record it for observability.
|
|
624
624
|
if (snapshot.interrupted) {
|
|
@@ -641,8 +641,8 @@ export class SessionPersister {
|
|
|
641
641
|
// driver's routing agrees with the restored (picker-locked) store state.
|
|
642
642
|
const orchestrator = asOrchestrator(driver);
|
|
643
643
|
if (orchestrator) {
|
|
644
|
-
orchestrator.setPinnedAgent((
|
|
645
|
-
orchestrator.setFlowOwner((
|
|
644
|
+
orchestrator.setPinnedAgent((_c = snapshot.pinnedAgentName) !== null && _c !== void 0 ? _c : null);
|
|
645
|
+
orchestrator.setFlowOwner((_d = snapshot.flowOwnerAgentName) !== null && _d !== void 0 ? _d : null);
|
|
646
646
|
}
|
|
647
647
|
logger.debug('Session restored', { key, messages: messages.length });
|
|
648
648
|
}
|
|
@@ -20,7 +20,7 @@ export function preferenceUnchanged(current, value) {
|
|
|
20
20
|
return current === value;
|
|
21
21
|
}
|
|
22
22
|
/** The mode used when neither the host config nor stored prefs name a valid one. */
|
|
23
|
-
export const DEFAULT_TIMESTAMP_DISPLAY = '
|
|
23
|
+
export const DEFAULT_TIMESTAMP_DISPLAY = 'always';
|
|
24
24
|
/**
|
|
25
25
|
* Every valid {@link TimestampDisplay}, for narrowing untrusted input.
|
|
26
26
|
*
|
|
@@ -39,8 +39,12 @@ const TIMESTAMP_DISPLAY_MODES = {
|
|
|
39
39
|
*
|
|
40
40
|
* Both inputs need this: the config field is typed but comes from plain JS hosts, and the
|
|
41
41
|
* stored field is a bare `string` read back out of a persistence blob.
|
|
42
|
+
*
|
|
43
|
+
* Membership is tested with `hasOwnProperty` rather than `in`, which walks the prototype
|
|
44
|
+
* chain — `in` would admit `'constructor'`, `'toString'` and every other `Object.prototype`
|
|
45
|
+
* key as a valid mode, which is exactly the junk this is here to reject.
|
|
42
46
|
*/
|
|
43
|
-
const asTimestampDisplay = (value) => typeof value === 'string' && value
|
|
47
|
+
const asTimestampDisplay = (value) => typeof value === 'string' && Object.prototype.hasOwnProperty.call(TIMESTAMP_DISPLAY_MODES, value)
|
|
44
48
|
? value
|
|
45
49
|
: undefined;
|
|
46
50
|
/**
|
|
@@ -1,47 +1,26 @@
|
|
|
1
1
|
# Migration Guide — GENC-1580 (agent lifecycle leaves the orchestrator)
|
|
2
2
|
|
|
3
|
-
> ## ⚠️
|
|
3
|
+
> ## ⚠️ §4 only is still unshipped
|
|
4
4
|
>
|
|
5
|
-
>
|
|
6
|
-
>
|
|
7
|
-
> as the last PR of GENC-1580 — the rename cannot be caught by a compiler in a JavaScript caller,
|
|
8
|
-
> so it is worth scheduling rather than absorbing alongside a refactor.
|
|
5
|
+
> Everything in this guide is now true **except §4** (`pinned-changed`), which carries its own
|
|
6
|
+
> notice. The breaking rename landed in this release.
|
|
9
7
|
>
|
|
10
|
-
> **
|
|
8
|
+
> **Self-check, whatever version you are on:** if `ChatDriver.installAgent` exists, you are on the
|
|
9
|
+
> breaking release and §1–§3 and §5 apply. If only `applyAgent` exists, you are on an earlier one
|
|
10
|
+
> and should not touch your calls.
|
|
11
11
|
>
|
|
12
|
-
>
|
|
13
|
-
>
|
|
14
|
-
>
|
|
15
|
-
> | `ChatDriver.agentSession` | ✓ — available (`@internal`) |
|
|
16
|
-
> | `getActiveAgent` / `primeRestoredAgentState` implemented by both drivers | ✓ — and `setPinnedAgent` / `setFlowOwner` are on the interface now, all four **optional** |
|
|
17
|
-
> | `ChatDriver.installAgent` | ✗ — **still `ChatDriver.applyAgent`, unchanged** |
|
|
18
|
-
> | `pinned-changed` is dispatched | ✗ — still dead wiring (§7.2 of `code-health.md`) |
|
|
12
|
+
> **What breaks:** `ChatDriver.applyAgent` is gone (§1), and four `AiDriver` members are now
|
|
13
|
+
> required rather than optional (§3). Both are compile-time breaks in TypeScript; §1 is **not**
|
|
14
|
+
> caught by a compiler in a JavaScript caller, which is why it shipped alone.
|
|
19
15
|
>
|
|
20
|
-
> **
|
|
21
|
-
>
|
|
22
|
-
>
|
|
16
|
+
> **This ships as a MINOR version, not a major.** The AI surface is `@beta` and has no external
|
|
17
|
+
> consumers — the only callers are migrated by hand alongside the release. So a `^15` range will
|
|
18
|
+
> pick this up: if you call `ChatDriver` directly or implement `AiDriver`, read §1 and §3 before
|
|
19
|
+
> upgrading rather than relying on the version number to warn you.
|
|
23
20
|
>
|
|
24
|
-
> **
|
|
25
|
-
>
|
|
26
|
-
>
|
|
27
|
-
>
|
|
28
|
-
> GENC-1580 ships in more than one PR. The first delivered an internal decomposition of `main.ts`
|
|
29
|
-
> only — five feature controllers, no public API change of any kind. **The agent-lifecycle work
|
|
30
|
-
> described here comes in a later PR under the same ticket**, and this notice is removed in the
|
|
31
|
-
> same change that makes the guide true.
|
|
32
|
-
>
|
|
33
|
-
> ---
|
|
34
|
-
>
|
|
35
|
-
> When it does land: **config-compatible — no changes required to `chatConfig`, `agents`, or
|
|
36
|
-
> `persistence`.** No element property, attribute, event or `::part()` changes. Nothing about which
|
|
37
|
-
> driver runs your agents changes, so **no runtime behaviour changes**.
|
|
38
|
-
>
|
|
39
|
-
> You will need to read this if you call `ChatDriver` directly, or if you implement `AiDriver`
|
|
40
|
-
> yourself. If you only configure the element, there is nothing here for you.
|
|
41
|
-
>
|
|
42
|
-
> All surfaces below are `@beta` unless marked otherwise.
|
|
43
|
-
|
|
44
|
-
---
|
|
21
|
+
> **Config-compatible** — no changes to `chatConfig`, `agents` or `persistence`, no element
|
|
22
|
+
> property, attribute, event or `::part()` changes, and **no runtime behaviour changes**. You only
|
|
23
|
+
> need this guide if you call `ChatDriver` directly or implement `AiDriver` yourself.
|
|
45
24
|
|
|
46
25
|
## Why
|
|
47
26
|
|
|
@@ -67,8 +46,9 @@ longer depends on which driver you got.
|
|
|
67
46
|
|
|
68
47
|
### 1. `ChatDriver.applyAgent` is renamed to `installAgent`
|
|
69
48
|
|
|
70
|
-
|
|
71
|
-
|
|
49
|
+
`applyAgent` was public `@beta` and appeared in the published type rollup, so this is a
|
|
50
|
+
compile-time break for a TypeScript caller — and a silent one for a JavaScript caller, which is
|
|
51
|
+
why it shipped in a release of its own.
|
|
72
52
|
|
|
73
53
|
```ts
|
|
74
54
|
// before
|
|
@@ -97,6 +77,10 @@ name `installAgent` says what it does, so the next reader has to choose delibera
|
|
|
97
77
|
state to restore, `activateAgent`. If you are re-applying a per-turn tool surface on an agent
|
|
98
78
|
already activated, `installAgent`.
|
|
99
79
|
|
|
80
|
+
`OrchestratingDriver`'s own private `applyAgent` was renamed to match. It was never public, so
|
|
81
|
+
there is nothing to do — but the two are related, and leaving them differently named would have
|
|
82
|
+
been worse than the original footgun.
|
|
83
|
+
|
|
100
84
|
### 2. `ChatDriver.agentSession` is exposed
|
|
101
85
|
|
|
102
86
|
```ts
|
|
@@ -123,27 +107,90 @@ which is the coupling that used to span two classes.
|
|
|
123
107
|
|
|
124
108
|
| Member | Was | Now |
|
|
125
109
|
| --- | --- | --- |
|
|
126
|
-
| `getActiveAgent
|
|
127
|
-
| `setPinnedAgent
|
|
128
|
-
| `setFlowOwner
|
|
129
|
-
| `primeRestoredAgentState
|
|
110
|
+
| `getActiveAgent()` | `OrchestratingDriver` only, optional | both, **required** |
|
|
111
|
+
| `setPinnedAgent(name)` | not on the interface | both, **required** |
|
|
112
|
+
| `setFlowOwner(name)` | not on the interface | both, **required** |
|
|
113
|
+
| `primeRestoredAgentState(snapshots)` | `OrchestratingDriver` only, optional | both, **required** |
|
|
114
|
+
|
|
115
|
+
**If you only call these, there is nothing to do** — and you can now drop your guards, because the
|
|
116
|
+
interface finally promises what both shipped drivers have always provided:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
// before
|
|
120
|
+
const reserve = driver.getActiveAgent?.()?.contextReserve;
|
|
130
121
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
on yet.
|
|
122
|
+
// after
|
|
123
|
+
const reserve = driver.getActiveAgent()?.contextReserve;
|
|
124
|
+
```
|
|
135
125
|
|
|
136
|
-
|
|
137
|
-
|
|
126
|
+
**If you implement `AiDriver` yourself, this is the break.** Add the four methods. Each has a
|
|
127
|
+
sensible inert form if your driver has nothing to route between — record the value and return it,
|
|
128
|
+
or no-op:
|
|
138
129
|
|
|
139
130
|
```ts
|
|
140
|
-
|
|
141
|
-
|
|
131
|
+
import type { AgentConfig } from '@genesislcap/ai-assistant';
|
|
132
|
+
|
|
133
|
+
class MyDriver implements AiDriver {
|
|
134
|
+
// …your existing members — `AiDriver` extends `EventTarget` and has 11 required
|
|
135
|
+
// members in total, so this fragment alone will not compile…
|
|
136
|
+
|
|
137
|
+
private active?: AgentConfig;
|
|
138
|
+
|
|
139
|
+
getActiveAgent() {
|
|
140
|
+
return this.active;
|
|
141
|
+
}
|
|
142
|
+
setPinnedAgent(_name: string | null) {}
|
|
143
|
+
setFlowOwner(_name: string | null) {}
|
|
144
|
+
primeRestoredAgentState(_snapshots: Record<string, unknown>) {}
|
|
145
|
+
}
|
|
142
146
|
```
|
|
143
147
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
148
|
+
<details>
|
|
149
|
+
<summary>A complete inert implementation, if you want one to start from</summary>
|
|
150
|
+
|
|
151
|
+
`AiDriver` extends `EventTarget` and has 11 required members. This compiles as written — the four
|
|
152
|
+
above plus the seven that were already required:
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
import type { ChatAttachment, ChatDriverResult, ChatMessage } from '@genesislcap/foundation-ai';
|
|
156
|
+
import type { AgentConfig, AiDriver } from '@genesislcap/ai-assistant';
|
|
157
|
+
|
|
158
|
+
class MyDriver extends EventTarget implements AiDriver {
|
|
159
|
+
private active?: AgentConfig;
|
|
160
|
+
|
|
161
|
+
// the four this release promotes
|
|
162
|
+
getActiveAgent() {
|
|
163
|
+
return this.active;
|
|
164
|
+
}
|
|
165
|
+
setPinnedAgent(_name: string | null) {}
|
|
166
|
+
setFlowOwner(_name: string | null) {}
|
|
167
|
+
primeRestoredAgentState(_snapshots: Record<string, unknown>) {}
|
|
168
|
+
|
|
169
|
+
// the seven that were already required
|
|
170
|
+
async sendMessage(_input: string, _attachments?: ChatAttachment[]): Promise<ChatDriverResult> {
|
|
171
|
+
return { reason: 'done' };
|
|
172
|
+
}
|
|
173
|
+
async continueFromHistory(_primer?: ChatMessage[]): Promise<ChatDriverResult> {
|
|
174
|
+
return { reason: 'done' };
|
|
175
|
+
}
|
|
176
|
+
resolveInteraction(_interactionId: string, _result: unknown) {}
|
|
177
|
+
loadHistory(_messages: ChatMessage[]) {}
|
|
178
|
+
isBusy() {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
async getSuggestions(): Promise<string[]> {
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
getActiveProviderName() {
|
|
185
|
+
return 'my-provider';
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
</details>
|
|
191
|
+
|
|
192
|
+
Fourteen of `AiDriver`'s 25 members remain optional — compaction, diagnostics, interactions,
|
|
193
|
+
`cancel`/`dispose`. Feature-detect those as before; this release changes nothing about them.
|
|
147
194
|
|
|
148
195
|
### 4. `pinned-changed` — still not dispatched, still nothing to do
|
|
149
196
|
|
|
@@ -182,8 +229,9 @@ behaviour on the session-restore path and deserves its own release to be judged
|
|
|
182
229
|
a release.
|
|
183
230
|
- `OrchestratingDriver`'s routing: classification, handoffs, the handoff cap, terminality rules,
|
|
184
231
|
the continuation tool, and per-agent history masking.
|
|
185
|
-
- `OrchestratingDriver
|
|
186
|
-
name
|
|
232
|
+
- `OrchestratingDriver`'s installer — private before and after, so its rename to `installAgent`
|
|
233
|
+
(§1) is invisible to you; it also shrank, but neither its name nor its signature is part of any
|
|
234
|
+
contract.
|
|
187
235
|
- Session persistence and restore, including the shape written to the persistence provider.
|
|
188
236
|
|
|
189
237
|
## Known remaining: sub-agent lifecycle
|
|
@@ -208,8 +256,10 @@ This release also splits the two largest modules. None of it is consumer-visible
|
|
|
208
256
|
package's entry points (`.`, `./config`, `./chat-driver`, `./react`), but if you deep-import from
|
|
209
257
|
`dist/esm/...` — which is unsupported — paths have moved:
|
|
210
258
|
|
|
211
|
-
- `chat-driver.ts` sheds its turn-failure taxonomy, interaction handling
|
|
212
|
-
|
|
259
|
+
- `chat-driver.ts` sheds its turn-failure taxonomy (`turn-failures.ts`), interaction handling
|
|
260
|
+
(`interaction-controller.ts`), turn recording and diagnostics (`turn-recorder.ts`), fold
|
|
261
|
+
machinery (`fold-controller.ts`) and its constants (`chat-driver.constants.ts`) into sibling
|
|
262
|
+
modules. Turn setup and provider resolution were considered and **not** extracted.
|
|
213
263
|
- `main.ts` sheds the settings modal, composer controls, cost ledger, budget latching, context gate
|
|
214
264
|
and diagnostics assembly into controllers.
|
|
215
265
|
|