@undefineds.co/linx 0.3.7 → 0.3.13
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 +17 -0
- package/dist/generated/version.js +2 -2
- package/dist/generated/version.js.map +1 -1
- package/dist/index.js +16 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/auto-mode/pod-approval.js +216 -2
- package/dist/lib/auto-mode/pod-approval.js.map +1 -1
- package/dist/lib/linx-status-line.js +335 -0
- package/dist/lib/linx-status-line.js.map +1 -0
- package/dist/lib/linx-tui-contract.js +3 -3
- package/dist/lib/linx-tui-contract.js.map +1 -1
- package/dist/lib/models.js +2 -2
- package/dist/lib/models.js.map +1 -1
- package/dist/lib/pi-adapter/auth.js +68 -0
- package/dist/lib/pi-adapter/auth.js.map +1 -0
- package/dist/lib/pi-adapter/interactive.js +326 -231
- package/dist/lib/pi-adapter/interactive.js.map +1 -1
- package/dist/lib/pi-adapter/pod-mirror.js +52 -2
- package/dist/lib/pi-adapter/pod-mirror.js.map +1 -1
- package/dist/lib/pi-adapter/pod-tools.js +140 -0
- package/dist/lib/pi-adapter/pod-tools.js.map +1 -0
- package/dist/lib/pi-adapter/runtime.js +14 -4
- package/dist/lib/pi-adapter/runtime.js.map +1 -1
- package/dist/lib/pi-adapter/stream.js +24 -1
- package/dist/lib/pi-adapter/stream.js.map +1 -1
- package/dist/lib/status-line-command.js +108 -0
- package/dist/lib/status-line-command.js.map +1 -0
- package/dist/lib/symphony/pod-projection.js +15 -7
- package/dist/lib/symphony/pod-projection.js.map +1 -1
- package/dist/lib/symphony-command.js +20 -21
- package/dist/lib/symphony-command.js.map +1 -1
- package/dist/skills/symphony/SKILL.md +69 -10
- package/dist/skills/xpod-cli/SKILL.md +70 -0
- package/package.json +9 -3
- package/vendor/agent-runtime/dist/client-inbox-subscription.d.ts +56 -0
- package/vendor/agent-runtime/dist/client-inbox-subscription.js +93 -0
- package/vendor/agent-runtime/dist/index.d.ts +1 -0
- package/vendor/agent-runtime/dist/index.js +1 -0
- package/vendor/agent-runtime/dist/reconciler.d.ts +60 -1
- package/vendor/agent-runtime/dist/reconciler.js +148 -4
- package/vendor/agent-runtime/dist/thread-reconciler-controller.d.ts +2 -1
- package/vendor/agent-runtime/dist/thread-reconciler-controller.js +4 -0
|
@@ -13,9 +13,10 @@ import { installPodStatusOutputFilter } from './pod-status-output.js';
|
|
|
13
13
|
import { createPodBackedExtensionUiContext } from './pod-approval.js';
|
|
14
14
|
import { DEFAULT_SECRETARY_CHAT_ID, secretaryChatUri, secretaryThreadUri } from './pod-mirror-mapping.js';
|
|
15
15
|
import { getSecretaryAutoInputController } from './auto-input-controller.js';
|
|
16
|
-
import { createSymphonyIdeaRecord, listSymphonyIssues, listSymphonySessions,
|
|
17
|
-
import { listOpenSymphonyIssuesFromPod, listRecentSymphonyReportsFromPod, listRunningSymphonyWorkersFromPod, mirrorSymphonyProjectionJsonLdFromPod, persistSymphonyIdeaToPod,
|
|
16
|
+
import { createSymphonyIdeaRecord, listSymphonyIssues, listSymphonySessions, } from '../symphony/archive.js';
|
|
17
|
+
import { listOpenSymphonyIssuesFromPod, listRecentSymphonyReportsFromPod, listRunningSymphonyWorkersFromPod, mirrorSymphonyProjectionJsonLdFromPod, persistSymphonyIdeaToPod, persistSymphonyControlStateToPod, } from '../symphony/pod-projection.js';
|
|
18
18
|
import { getSessionControlManager, installSessionControlRuntimeEventBridge, } from './session-control.js';
|
|
19
|
+
import { buildLinxFooterStatusLine, calculateSessionUsage, DEFAULT_STATUS_LINE_TOKENS, formatTokenCount, LINX_STATUS_LINE_TOKEN_NAMES, parseLinxStatusLineColorArg, parseLinxStatusLineTokenArgs, readLinxStatusLineConfig, resetLinxStatusLineConfig, writeLinxStatusLineConfigPatch, } from '../linx-status-line.js';
|
|
19
20
|
let footerPatched = false;
|
|
20
21
|
let assistantMessagePatched = false;
|
|
21
22
|
let linxResumeOutputStyleRestore = null;
|
|
@@ -27,6 +28,21 @@ const BACKEND_OWNED_SLASH_COMMANDS = new Set([
|
|
|
27
28
|
]);
|
|
28
29
|
const SYMPHONY_STATUS_POD_TIMEOUT_MS = 1_200;
|
|
29
30
|
const DEFAULT_SYMPHONY_WORKER_SUPERVISOR_INTERVAL_MS = 10 * 60 * 1000;
|
|
31
|
+
const CODEX_STYLE_STATUS_LINE_TOKENS = [
|
|
32
|
+
'model-with-reasoning',
|
|
33
|
+
'git-branch',
|
|
34
|
+
'context-remaining',
|
|
35
|
+
'total-input-tokens',
|
|
36
|
+
'total-output-tokens',
|
|
37
|
+
'current-dir',
|
|
38
|
+
];
|
|
39
|
+
const COMPACT_STATUS_LINE_TOKENS = [
|
|
40
|
+
'model-with-reasoning',
|
|
41
|
+
'context-remaining',
|
|
42
|
+
'current-dir',
|
|
43
|
+
];
|
|
44
|
+
/** Module-level reference to interactive for footer mode state (set during bootstrap). */
|
|
45
|
+
let _linxFooterInteractive = null;
|
|
30
46
|
export function bootstrapLinxInteractiveMode(runtime, options = {}) {
|
|
31
47
|
installLinxResumeOutputStyle();
|
|
32
48
|
patchPiFooter();
|
|
@@ -37,6 +53,7 @@ export function bootstrapLinxInteractiveMode(runtime, options = {}) {
|
|
|
37
53
|
interactive.runtime = runtime;
|
|
38
54
|
interactive.__autoEnabled = runtime?.autoEnabled === true;
|
|
39
55
|
interactive.__linxSymphonyModeEnabled = runtime?.symphonyEnabled === true;
|
|
56
|
+
_linxFooterInteractive = interactive;
|
|
40
57
|
if (options.onSymphonyControlChange) {
|
|
41
58
|
;
|
|
42
59
|
interactive.__linxOnSymphonyControlChange = options.onSymphonyControlChange;
|
|
@@ -124,7 +141,7 @@ export function installLinxRestoredAutoStartup(interactive, runtime, sessionCont
|
|
|
124
141
|
controller.start({ scheduleImmediately: true });
|
|
125
142
|
interactive.showStatus?.([
|
|
126
143
|
'Auto restored from the previous session.',
|
|
127
|
-
'
|
|
144
|
+
'auto · Ctrl+C or /auto off to hand control back',
|
|
128
145
|
].join('\n'));
|
|
129
146
|
interactive.ui?.requestRender?.();
|
|
130
147
|
}
|
|
@@ -528,6 +545,15 @@ function parseLinxGlobalCommand(input) {
|
|
|
528
545
|
if (input.startsWith('/ai connect ')) {
|
|
529
546
|
return { action: 'ai-connect', ...parseInteractiveAiConnectArgs(input.slice('/ai connect'.length).trim()) };
|
|
530
547
|
}
|
|
548
|
+
if (input === '/statusline' || input === '/status-line') {
|
|
549
|
+
return { action: 'statusline', args: [] };
|
|
550
|
+
}
|
|
551
|
+
if (input.startsWith('/statusline ') || input.startsWith('/status-line ')) {
|
|
552
|
+
const body = input.startsWith('/statusline ')
|
|
553
|
+
? input.slice('/statusline'.length).trim()
|
|
554
|
+
: input.slice('/status-line'.length).trim();
|
|
555
|
+
return { action: 'statusline', args: splitInteractiveCommandArgs(body) };
|
|
556
|
+
}
|
|
531
557
|
if (input === '/rewind') {
|
|
532
558
|
return { action: 'rewind-select' };
|
|
533
559
|
}
|
|
@@ -649,6 +675,10 @@ async function handleLinxGlobalCommand(interactive, runtime, command) {
|
|
|
649
675
|
await handleInteractiveAiConnectCommand(interactive, runtime, command);
|
|
650
676
|
return;
|
|
651
677
|
}
|
|
678
|
+
if (command.action === 'statusline') {
|
|
679
|
+
await handleInteractiveStatusLineCommand(interactive, command.args);
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
652
682
|
if (command.action === 'rewind-select') {
|
|
653
683
|
await handleInteractiveRewindSelector(interactive, runtime);
|
|
654
684
|
return;
|
|
@@ -659,6 +689,130 @@ async function handleLinxGlobalCommand(interactive, runtime, command) {
|
|
|
659
689
|
}
|
|
660
690
|
await changeInteractiveCwd(interactive, runtime, command.target);
|
|
661
691
|
}
|
|
692
|
+
async function handleInteractiveStatusLineCommand(interactive, args) {
|
|
693
|
+
if (args.length > 0) {
|
|
694
|
+
handleInteractiveStatusLineArgs(interactive, args);
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
const summary = formatInteractiveStatusLineSummary();
|
|
698
|
+
if (typeof interactive.showExtensionSelector !== 'function') {
|
|
699
|
+
interactive.showStatus?.(`${summary} · Use /statusline set <tokens...>, /statusline tokens, /statusline colors <on|off>, or /statusline reset.`);
|
|
700
|
+
interactive.ui?.requestRender?.();
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
const choice = await interactive.showExtensionSelector(`Status line\n${summary}`, [
|
|
704
|
+
'Use Codex-style preset',
|
|
705
|
+
'Use compact preset',
|
|
706
|
+
'Configure tokens manually',
|
|
707
|
+
'Toggle colors',
|
|
708
|
+
'Show available tokens',
|
|
709
|
+
'Reset to default',
|
|
710
|
+
]);
|
|
711
|
+
if (choice === 'Use Codex-style preset') {
|
|
712
|
+
writeInteractiveStatusLineConfig(interactive, {
|
|
713
|
+
statusLine: CODEX_STYLE_STATUS_LINE_TOKENS,
|
|
714
|
+
message: 'Status line set to Codex-style preset.',
|
|
715
|
+
});
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (choice === 'Use compact preset') {
|
|
719
|
+
writeInteractiveStatusLineConfig(interactive, {
|
|
720
|
+
statusLine: COMPACT_STATUS_LINE_TOKENS,
|
|
721
|
+
message: 'Status line set to compact preset.',
|
|
722
|
+
});
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
if (choice === 'Toggle colors') {
|
|
726
|
+
const current = readLinxStatusLineConfig();
|
|
727
|
+
writeInteractiveStatusLineConfig(interactive, {
|
|
728
|
+
statusLineUseColors: !current.useColors,
|
|
729
|
+
message: `Status line colors ${current.useColors ? 'disabled' : 'enabled'}.`,
|
|
730
|
+
});
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
if (choice === 'Show available tokens') {
|
|
734
|
+
showInteractiveStatusLineTokens(interactive);
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
if (choice === 'Reset to default') {
|
|
738
|
+
resetLinxStatusLineConfig();
|
|
739
|
+
finishInteractiveStatusLineUpdate(interactive, `Status line reset to default: ${DEFAULT_STATUS_LINE_TOKENS.join(', ')}`);
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
if (choice === 'Configure tokens manually') {
|
|
743
|
+
await promptInteractiveStatusLineTokens(interactive);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
function handleInteractiveStatusLineArgs(interactive, args) {
|
|
747
|
+
const action = args[0]?.toLowerCase();
|
|
748
|
+
if (action === 'tokens' || action === 'list') {
|
|
749
|
+
showInteractiveStatusLineTokens(interactive);
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
if (action === 'reset') {
|
|
753
|
+
resetLinxStatusLineConfig();
|
|
754
|
+
finishInteractiveStatusLineUpdate(interactive, `Status line reset to default: ${DEFAULT_STATUS_LINE_TOKENS.join(', ')}`);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
if (action === 'colors' || action === 'color') {
|
|
758
|
+
const value = parseLinxStatusLineColorArg(args[1]);
|
|
759
|
+
if (value === undefined) {
|
|
760
|
+
interactive.showError?.('Usage: /statusline colors <on|off>');
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
writeInteractiveStatusLineConfig(interactive, {
|
|
764
|
+
statusLineUseColors: value,
|
|
765
|
+
message: `Status line colors ${value ? 'enabled' : 'disabled'}.`,
|
|
766
|
+
});
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
const tokenArgs = action === 'set' ? args.slice(1) : args;
|
|
770
|
+
try {
|
|
771
|
+
const tokens = parseLinxStatusLineTokenArgs(tokenArgs);
|
|
772
|
+
writeInteractiveStatusLineConfig(interactive, {
|
|
773
|
+
statusLine: tokens,
|
|
774
|
+
message: `Status line updated: ${tokens.join(', ')}`,
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
catch (error) {
|
|
778
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
779
|
+
interactive.showError?.(`${message}. Use /statusline tokens to list valid tokens.`);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
async function promptInteractiveStatusLineTokens(interactive) {
|
|
783
|
+
if (typeof interactive.showExtensionInput !== 'function') {
|
|
784
|
+
interactive.showStatus?.(`Use /statusline set ${CODEX_STYLE_STATUS_LINE_TOKENS.join(' ')}`);
|
|
785
|
+
interactive.ui?.requestRender?.();
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
const value = await interactive.showExtensionInput('Status line tokens', CODEX_STYLE_STATUS_LINE_TOKENS.join(' '));
|
|
789
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
790
|
+
interactive.showStatus?.('Status line unchanged.');
|
|
791
|
+
interactive.ui?.requestRender?.();
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
handleInteractiveStatusLineArgs(interactive, ['set', ...splitInteractiveCommandArgs(value)]);
|
|
795
|
+
}
|
|
796
|
+
function writeInteractiveStatusLineConfig(interactive, patch) {
|
|
797
|
+
writeLinxStatusLineConfigPatch({
|
|
798
|
+
...(patch.statusLine ? { statusLine: patch.statusLine } : {}),
|
|
799
|
+
...(patch.statusLineUseColors !== undefined ? { statusLineUseColors: patch.statusLineUseColors } : {}),
|
|
800
|
+
});
|
|
801
|
+
finishInteractiveStatusLineUpdate(interactive, patch.message);
|
|
802
|
+
}
|
|
803
|
+
function finishInteractiveStatusLineUpdate(interactive, message) {
|
|
804
|
+
interactive.footer?.invalidate?.();
|
|
805
|
+
interactive.showStatus?.(message);
|
|
806
|
+
interactive.ui?.requestRender?.();
|
|
807
|
+
}
|
|
808
|
+
function showInteractiveStatusLineTokens(interactive) {
|
|
809
|
+
interactive.showStatus?.(`Status line tokens: ${LINX_STATUS_LINE_TOKEN_NAMES.join(', ')}`);
|
|
810
|
+
interactive.ui?.requestRender?.();
|
|
811
|
+
}
|
|
812
|
+
function formatInteractiveStatusLineSummary() {
|
|
813
|
+
const config = readLinxStatusLineConfig();
|
|
814
|
+
return `Current: ${config.tokens.join(', ')} · colors ${config.useColors ? 'on' : 'off'} · source ${config.tokenSource}`;
|
|
815
|
+
}
|
|
662
816
|
async function handleInteractiveRewindSelector(interactive, runtime) {
|
|
663
817
|
const session = resolveInteractiveSession(interactive, runtime);
|
|
664
818
|
const sessionManager = resolveInteractiveSessionManager(interactive, runtime);
|
|
@@ -720,6 +874,7 @@ async function handleInteractiveRewindTurnsCommand(interactive, runtime, turns)
|
|
|
720
874
|
}
|
|
721
875
|
const cleanResult = materializeCleanRewindSession(sessionManager, result.targetLeafId, previousState);
|
|
722
876
|
syncAgentStateFromSessionManager(session, sessionManager);
|
|
877
|
+
refreshInteractiveTranscriptFromSessionManager(interactive);
|
|
723
878
|
await syncRewindProjection(interactive, runtime, {
|
|
724
879
|
previousState,
|
|
725
880
|
cleanResult,
|
|
@@ -748,6 +903,7 @@ async function rewindSessionManagerBeforeUserEntry(interactive, runtime, session
|
|
|
748
903
|
moveSessionManagerLeaf(sessionManager, targetLeafId);
|
|
749
904
|
const cleanResult = materializeCleanRewindSession(sessionManager, targetLeafId, previousState);
|
|
750
905
|
syncAgentStateFromSessionManager(session, sessionManager);
|
|
906
|
+
refreshInteractiveTranscriptFromSessionManager(interactive);
|
|
751
907
|
await syncRewindProjection(interactive, runtime, {
|
|
752
908
|
previousState,
|
|
753
909
|
cleanResult,
|
|
@@ -987,6 +1143,22 @@ function syncAgentStateFromSessionManager(session, sessionManager) {
|
|
|
987
1143
|
session.agent.state.messages = context.messages;
|
|
988
1144
|
}
|
|
989
1145
|
}
|
|
1146
|
+
function refreshInteractiveTranscriptFromSessionManager(interactive) {
|
|
1147
|
+
try {
|
|
1148
|
+
if (typeof interactive?.rebuildChatFromMessages === 'function') {
|
|
1149
|
+
interactive.rebuildChatFromMessages();
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
if (typeof interactive?.renderInitialMessages === 'function') {
|
|
1153
|
+
interactive.chatContainer?.clear?.();
|
|
1154
|
+
interactive.renderInitialMessages();
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
catch (error) {
|
|
1158
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1159
|
+
interactive?.showWarning?.(`Rewind transcript refresh failed: ${message}`);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
990
1162
|
function collectRewindUserMessages(_session, sessionManager) {
|
|
991
1163
|
return getActiveSessionBranch(sessionManager)
|
|
992
1164
|
.filter((entry) => entry?.type === 'message' && entry.message?.role === 'user')
|
|
@@ -1190,7 +1362,7 @@ function formatAutoModeChangeStatus(enabled) {
|
|
|
1190
1362
|
'Auto is on.',
|
|
1191
1363
|
'Auto on: Secretary drives the current session input loop.',
|
|
1192
1364
|
'What changed: backend prompts and blocked approval/input requests go to Secretary first; Secretary answers in-policy and asks you only when blocked.',
|
|
1193
|
-
'User-visible state: the input bar shows
|
|
1365
|
+
'User-visible state: the input bar shows auto; Ctrl+C or /auto off hands control back to you.',
|
|
1194
1366
|
'Backend approval policy is unchanged.',
|
|
1195
1367
|
].join('\n')
|
|
1196
1368
|
: [
|
|
@@ -1270,18 +1442,8 @@ export function installSymphonyCommand(interactive) {
|
|
|
1270
1442
|
return;
|
|
1271
1443
|
}
|
|
1272
1444
|
if (this.__linxSymphonyModeEnabled && shouldProjectSymphonyInput(input)) {
|
|
1273
|
-
const source = await resolveSymphonySourceContext(this);
|
|
1274
|
-
const idea = await captureSymphonyIdeaIfNeeded(input, source);
|
|
1275
1445
|
getSessionControlManager(this, this.runtime).recordUserMessage({ text: input });
|
|
1276
|
-
|
|
1277
|
-
await dispatchSymphonyWorkerFromInteractive(this, input, source);
|
|
1278
|
-
return;
|
|
1279
|
-
}
|
|
1280
|
-
await originalSubmit(buildSymphonyDelegationPrompt(input, {
|
|
1281
|
-
persistentMode: true,
|
|
1282
|
-
...(source ? { source } : {}),
|
|
1283
|
-
...(idea ? { idea } : {}),
|
|
1284
|
-
}));
|
|
1446
|
+
await originalSubmit(renderSymphonySecretaryProjection(input));
|
|
1285
1447
|
return;
|
|
1286
1448
|
}
|
|
1287
1449
|
await originalSubmit(text);
|
|
@@ -1347,6 +1509,17 @@ const LINX_INTERACTIVE_SLASH_COMMANDS = [
|
|
|
1347
1509
|
name: 'rewind',
|
|
1348
1510
|
description: 'select a user message and rewind the active branch before it',
|
|
1349
1511
|
},
|
|
1512
|
+
{
|
|
1513
|
+
name: 'statusline',
|
|
1514
|
+
argumentHint: 'set|colors|tokens|reset',
|
|
1515
|
+
description: 'configure which items appear in the status line',
|
|
1516
|
+
getArgumentCompletions: (prefix) => completeStaticArguments(prefix, [
|
|
1517
|
+
{ value: 'set', description: 'Set status line tokens' },
|
|
1518
|
+
{ value: 'colors', description: 'Enable or disable status line colors' },
|
|
1519
|
+
{ value: 'tokens', description: 'List available status line tokens' },
|
|
1520
|
+
{ value: 'reset', description: 'Restore default status line tokens' },
|
|
1521
|
+
]),
|
|
1522
|
+
},
|
|
1350
1523
|
{
|
|
1351
1524
|
name: 'ai',
|
|
1352
1525
|
argumentHint: 'connect <provider>',
|
|
@@ -1356,11 +1529,11 @@ const LINX_INTERACTIVE_SLASH_COMMANDS = [
|
|
|
1356
1529
|
{
|
|
1357
1530
|
name: 'symphony',
|
|
1358
1531
|
argumentHint: 'on|off|status',
|
|
1359
|
-
description: '
|
|
1532
|
+
description: 'turn Secretary task handoff on/off, or show status',
|
|
1360
1533
|
getArgumentCompletions: (prefix) => completeStaticArguments(prefix, [
|
|
1361
|
-
{ value: 'on', description: '
|
|
1362
|
-
{ value: 'off', description: '
|
|
1363
|
-
{ value: 'status', description: 'Show Symphony
|
|
1534
|
+
{ value: 'on', description: 'Secretary can plan and hand off larger tasks' },
|
|
1535
|
+
{ value: 'off', description: 'Return to direct chat' },
|
|
1536
|
+
{ value: 'status', description: 'Show whether Symphony task handoff is enabled' },
|
|
1364
1537
|
]),
|
|
1365
1538
|
},
|
|
1366
1539
|
];
|
|
@@ -1472,19 +1645,8 @@ async function handleSymphonyCommand(interactive, command) {
|
|
|
1472
1645
|
}
|
|
1473
1646
|
function formatSymphonyModeChangeStatus(enabled) {
|
|
1474
1647
|
return enabled
|
|
1475
|
-
?
|
|
1476
|
-
|
|
1477
|
-
'What changed: following normal messages enter the Secretary control lane before worker/backend routing.',
|
|
1478
|
-
'Skills: issue triage, existing Issue lookup, create/update/ask decision, task split, worker dispatch, status/report tracking.',
|
|
1479
|
-
'Ordinary chat stays ordinary Message; only trackable work becomes Issue/Task/Delivery/Session.',
|
|
1480
|
-
'Use /symphony status to inspect workers, /symphony off to chat with the current worker/backend peer.',
|
|
1481
|
-
].join('\n')
|
|
1482
|
-
: [
|
|
1483
|
-
'Symphony off: you are now chatting with the current worker/backend peer.',
|
|
1484
|
-
'What changed: following messages bypass Secretary Symphony triage and dispatch.',
|
|
1485
|
-
'Current Symphony dispatches started from this TUI were cancelled; archived workers remain inspectable with /symphony status.',
|
|
1486
|
-
'Use /symphony on to chat with Secretary again.',
|
|
1487
|
-
].join('\n');
|
|
1648
|
+
? 'Symphony is on. I will keep ordinary chat ordinary and only plan or hand off real work.'
|
|
1649
|
+
: 'Symphony is off. Back to direct chat. Active handoffs from this window were stopped.';
|
|
1488
1650
|
}
|
|
1489
1651
|
function formatSymphonyUsage(input) {
|
|
1490
1652
|
return [
|
|
@@ -1498,13 +1660,21 @@ function shouldProjectSymphonyInput(input) {
|
|
|
1498
1660
|
&& !input.startsWith('/')
|
|
1499
1661
|
&& !input.startsWith('!');
|
|
1500
1662
|
}
|
|
1501
|
-
function
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1663
|
+
function renderSymphonySecretaryProjection(input) {
|
|
1664
|
+
return [
|
|
1665
|
+
'# AI Secretary Symphony request',
|
|
1666
|
+
'',
|
|
1667
|
+
'Symphony is on: the user is chatting with Secretary, not directly with the worker/backend peer.',
|
|
1668
|
+
'Treat the user message below as a Secretary-facing product message.',
|
|
1669
|
+
'Decide whether it is ordinary chat, an Idea, a change to existing work, or delegable work.',
|
|
1670
|
+
'Default response style: reply like normal chat.',
|
|
1671
|
+
'Do not print internal Symphony binding, Issue/Task routing, worker selection, or report-style sections unless a visible state change or blocker must be surfaced.',
|
|
1672
|
+
'If the message is ordinary chat or early exploration, answer directly and do not explain that it was not delegated.',
|
|
1673
|
+
'If real delegation is needed, summarize the visible handoff result briefly after updating control state.',
|
|
1674
|
+
'',
|
|
1675
|
+
'User message:',
|
|
1676
|
+
input,
|
|
1677
|
+
].join('\n');
|
|
1508
1678
|
}
|
|
1509
1679
|
async function dispatchSymphonyWorkerFromInteractive(interactive, objective, source) {
|
|
1510
1680
|
const backend = resolveSymphonyWorkerBackend(interactive, objective);
|
|
@@ -1522,18 +1692,9 @@ async function dispatchSymphonyWorkerFromInteractive(interactive, objective, sou
|
|
|
1522
1692
|
const controller = new AbortController();
|
|
1523
1693
|
const controllers = getInteractiveSymphonyDispatchControllers(interactive);
|
|
1524
1694
|
controllers.add(controller);
|
|
1525
|
-
interactive.showStatus?.(
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
`Worker credentials: ${workerCredentialSource}`,
|
|
1529
|
-
...(agentRuntime ? [`Control runtime: ${formatSymphonyControlRuntime(agentRuntime)}`] : []),
|
|
1530
|
-
...(workerModel ? [`Worker model: ${workerModel}`] : []),
|
|
1531
|
-
workerGoalMode
|
|
1532
|
-
? `Worker goal: on · supervisor interval=${formatSymphonySupervisorInterval(workerSupervisorIntervalMs)}`
|
|
1533
|
-
: 'Worker goal: off',
|
|
1534
|
-
'Status: creating Issue / Task / Delivery and starting a quiet worker session.',
|
|
1535
|
-
'Use /symphony status to inspect running workers and reports.',
|
|
1536
|
-
].join('\n'));
|
|
1695
|
+
interactive.showStatus?.(`Symphony handoff started: ${backend}${workerModel ? ` · ${workerModel}` : ''}`
|
|
1696
|
+
+ `${workerGoalMode ? ` · supervised every ${formatSymphonySupervisorInterval(workerSupervisorIntervalMs)}` : ''}.`
|
|
1697
|
+
+ ' Use /symphony status for details.');
|
|
1537
1698
|
interactive.ui?.requestRender?.();
|
|
1538
1699
|
const run = typeof interactive.__linxRunSymphony === 'function'
|
|
1539
1700
|
? interactive.__linxRunSymphony
|
|
@@ -1624,8 +1785,8 @@ function createInteractiveSymphonyRuntime(interactive) {
|
|
|
1624
1785
|
return {
|
|
1625
1786
|
runAutoMode,
|
|
1626
1787
|
listAutoModeSessions: listArchivedAutoModeSessions,
|
|
1627
|
-
|
|
1628
|
-
return
|
|
1788
|
+
persistSymphonyControlStateToPod(plan, options) {
|
|
1789
|
+
return persistSymphonyControlStateToPod(plan, {
|
|
1629
1790
|
...options,
|
|
1630
1791
|
runtime: projectionRuntime,
|
|
1631
1792
|
});
|
|
@@ -1817,13 +1978,9 @@ function formatSymphonyDispatchResult(plan) {
|
|
|
1817
1978
|
const delivery = worker?.delivery ?? plan.delivery;
|
|
1818
1979
|
const lines = [
|
|
1819
1980
|
plan.issue.status === 'resolved' && delivery.status === 'completed'
|
|
1820
|
-
?
|
|
1821
|
-
:
|
|
1822
|
-
|
|
1823
|
-
`Task: ${formatSymphonyResourceTail(worker?.task ?? plan.task) ?? worker?.task ?? plan.task}`,
|
|
1824
|
-
`Delivery: ${delivery.status}${delivery.autoModeSessionId ? ` · runtime=${delivery.autoModeSessionId}` : ''}`,
|
|
1825
|
-
`Worker session: ${session.status}${session.autoModeSessionId ? ` · runtime=${session.autoModeSessionId}` : ''}`,
|
|
1826
|
-
'Use /symphony status to inspect the Pod-projected worker report.',
|
|
1981
|
+
? `Symphony handoff completed: ${plan.issue.title}.`
|
|
1982
|
+
: `Symphony handoff recorded: ${plan.issue.title}.`,
|
|
1983
|
+
'Use /symphony status for details.',
|
|
1827
1984
|
];
|
|
1828
1985
|
if (session.error) {
|
|
1829
1986
|
lines.push(`Error: ${session.error}`);
|
|
@@ -1847,10 +2004,9 @@ async function captureSymphonyIdeaIfNeeded(input, source) {
|
|
|
1847
2004
|
...(affectedArea ? { affectedArea } : {}),
|
|
1848
2005
|
};
|
|
1849
2006
|
const idea = createSymphonyIdeaRecord(captureInput);
|
|
1850
|
-
const persisted = await persistSymphonyIdeaToPod(idea)
|
|
1851
|
-
.catch(() => null);
|
|
2007
|
+
const persisted = await persistSymphonyIdeaToPod(idea);
|
|
1852
2008
|
if (!persisted) {
|
|
1853
|
-
|
|
2009
|
+
throw new Error('No active Pod session; Symphony Idea records must be written to Pod in LinX runtime.');
|
|
1854
2010
|
}
|
|
1855
2011
|
return {
|
|
1856
2012
|
uri: idea.uri,
|
|
@@ -1859,7 +2015,10 @@ async function captureSymphonyIdeaIfNeeded(input, source) {
|
|
|
1859
2015
|
commitment: idea.commitment,
|
|
1860
2016
|
};
|
|
1861
2017
|
}
|
|
1862
|
-
catch {
|
|
2018
|
+
catch (error) {
|
|
2019
|
+
process.emitWarning(error instanceof Error
|
|
2020
|
+
? new Error(`LinX Symphony Idea Pod write failed: ${error.message}`)
|
|
2021
|
+
: new Error(`LinX Symphony Idea Pod write failed: ${String(error)}`));
|
|
1863
2022
|
return undefined;
|
|
1864
2023
|
}
|
|
1865
2024
|
}
|
|
@@ -1881,51 +2040,6 @@ function inferSymphonyIdeaAffectedArea(input) {
|
|
|
1881
2040
|
}
|
|
1882
2041
|
return undefined;
|
|
1883
2042
|
}
|
|
1884
|
-
function buildSymphonyDelegationPrompt(objective, options) {
|
|
1885
|
-
const modeLine = options.persistentMode
|
|
1886
|
-
? 'Symphony is on: the user is chatting with Secretary in this LinX TUI session.'
|
|
1887
|
-
: 'This is a chat-driven Symphony request from the LinX TUI.';
|
|
1888
|
-
const sourceLines = options.source
|
|
1889
|
-
? [
|
|
1890
|
-
'',
|
|
1891
|
-
'Source conversation resources:',
|
|
1892
|
-
`Chat: ${options.source.chat}`,
|
|
1893
|
-
`Thread: ${options.source.thread}`,
|
|
1894
|
-
...(options.source.sessionId ? [`Runtime session: ${options.source.sessionId}`] : []),
|
|
1895
|
-
]
|
|
1896
|
-
: [];
|
|
1897
|
-
const ideaLines = options.idea
|
|
1898
|
-
? [
|
|
1899
|
-
'',
|
|
1900
|
-
'Captured Idea:',
|
|
1901
|
-
`Idea: ${options.idea.uri}`,
|
|
1902
|
-
`Summary: ${options.idea.summary}`,
|
|
1903
|
-
`Commitment: ${options.idea.commitment}`,
|
|
1904
|
-
`Status: ${options.idea.status}`,
|
|
1905
|
-
]
|
|
1906
|
-
: [];
|
|
1907
|
-
return [
|
|
1908
|
-
'AI Secretary Symphony request.',
|
|
1909
|
-
modeLine,
|
|
1910
|
-
...sourceLines,
|
|
1911
|
-
...ideaLines,
|
|
1912
|
-
'',
|
|
1913
|
-
'User objective:',
|
|
1914
|
-
objective.trim(),
|
|
1915
|
-
'',
|
|
1916
|
-
'Act as AI Secretary with Symphony skills enabled: issue triage, existing issue lookup, create/update/ask decision, task split, worker dispatch, and status/report tracking.',
|
|
1917
|
-
'Decide whether this objective should be delegated to backend workers through Symphony.',
|
|
1918
|
-
'Do not create an Issue for ordinary chat. Create or update an Issue only when the objective is a trackable work item.',
|
|
1919
|
-
'If this is an uncommitted fragment, keep or merge it as an Idea first; do not dispatch a worker until promotion gates are met.',
|
|
1920
|
-
'Before creating a new Issue, compare against existing open Issues. Update the existing Issue when it is clearly the same work item, and ask the user only when new-vs-existing is ambiguous.',
|
|
1921
|
-
'Every delegation must target a Chat resource. Use a personal AI contact chat when assigning to one worker, or a group chat when the work belongs in a shared room.',
|
|
1922
|
-
'Use the Source conversation resources only as provenance unless they are also the correct target chat.',
|
|
1923
|
-
'If delegation is appropriate, create or update the normal LinX work context, derive issue/task acceptance criteria from the objective and source context, and project the task to the selected backend worker.',
|
|
1924
|
-
'Ask the user only when acceptance, authority, credentials, or target selection cannot be safely inferred.',
|
|
1925
|
-
'If delegation is not appropriate, explain the reason and continue in this conversation.',
|
|
1926
|
-
'Keep the user-facing answer concise and show the next observable step.',
|
|
1927
|
-
].join('\n');
|
|
1928
|
-
}
|
|
1929
2043
|
async function formatSymphonyStatus(interactive) {
|
|
1930
2044
|
const enabled = interactive.__linxSymphonyModeEnabled === true;
|
|
1931
2045
|
const [source, workersRead, issuesRead, reportsRead] = await Promise.all([
|
|
@@ -1937,32 +2051,29 @@ async function formatSymphonyStatus(interactive) {
|
|
|
1937
2051
|
const workers = workersRead.items;
|
|
1938
2052
|
const issues = issuesRead.items;
|
|
1939
2053
|
const reports = reportsRead.items;
|
|
1940
|
-
const
|
|
2054
|
+
const controlStateErrors = Array.from(new Set([
|
|
1941
2055
|
workersRead.error,
|
|
1942
2056
|
issuesRead.error,
|
|
1943
2057
|
reportsRead.error,
|
|
1944
2058
|
].filter((item) => Boolean(item))));
|
|
1945
|
-
const
|
|
2059
|
+
const controlStateSources = new Set([workersRead.source, issuesRead.source, reportsRead.source]);
|
|
1946
2060
|
const lines = [
|
|
1947
2061
|
`Symphony is ${enabled ? 'on' : 'off'}.`,
|
|
1948
2062
|
`Current chat peer: ${enabled ? 'Secretary' : 'worker/backend peer'}.`,
|
|
1949
2063
|
`Open issues: ${issues.length}`,
|
|
1950
2064
|
`Running workers: ${workers.length}`,
|
|
1951
2065
|
`Recent reports: ${reports.length}`,
|
|
1952
|
-
|
|
1953
|
-
? `Pod
|
|
1954
|
-
:
|
|
1955
|
-
? 'Pod
|
|
1956
|
-
: 'Pod
|
|
2066
|
+
controlStateErrors.length > 0
|
|
2067
|
+
? `Pod control state: unavailable (${formatSymphonyStatusError(controlStateErrors[0])})`
|
|
2068
|
+
: controlStateSources.has('pod')
|
|
2069
|
+
? 'Pod control state: active.'
|
|
2070
|
+
: 'Pod control state: portable local archive mode.',
|
|
1957
2071
|
'Skills: issue triage, existing issue lookup, create/update/ask decision, task split, worker dispatch, status/report tracking.',
|
|
1958
2072
|
'Delegation target: AI Secretary must choose a Chat resource before dispatch.',
|
|
1959
2073
|
'Allowed targets: personal AI contact chat or group chat.',
|
|
1960
2074
|
'Thread role: concrete work timeline under the selected Chat.',
|
|
1961
2075
|
'Session role: backend runtime lifecycle only.',
|
|
1962
2076
|
];
|
|
1963
|
-
if (projectionErrors.length > 0) {
|
|
1964
|
-
lines.push('Fallback: showing local Symphony archive while Pod projection is unavailable.');
|
|
1965
|
-
}
|
|
1966
2077
|
for (const issue of issues.slice(0, 5)) {
|
|
1967
2078
|
lines.push(` - ${formatSymphonyIssueStatus(issue)}`);
|
|
1968
2079
|
}
|
|
@@ -2017,19 +2128,24 @@ async function withSymphonyStatusTimeout(interactive, label, task) {
|
|
|
2017
2128
|
}
|
|
2018
2129
|
}
|
|
2019
2130
|
async function listOpenSymphonyIssues(interactive) {
|
|
2020
|
-
const
|
|
2021
|
-
let projectionError;
|
|
2131
|
+
const controlRuntime = interactive?.__linxSymphonyPodProjectionRuntime;
|
|
2022
2132
|
try {
|
|
2023
|
-
if (
|
|
2024
|
-
const podIssues = await withSymphonyStatusTimeout(interactive, 'Symphony Pod issue status', listOpenSymphonyIssuesFromPod({ runtime:
|
|
2133
|
+
if (controlRuntime?.issueResource) {
|
|
2134
|
+
const podIssues = await withSymphonyStatusTimeout(interactive, 'Symphony Pod issue status', listOpenSymphonyIssuesFromPod({ runtime: controlRuntime }));
|
|
2025
2135
|
if (podIssues) {
|
|
2026
2136
|
return { items: podIssues, source: 'pod' };
|
|
2027
2137
|
}
|
|
2028
2138
|
}
|
|
2029
2139
|
}
|
|
2030
2140
|
catch (error) {
|
|
2031
|
-
|
|
2032
|
-
|
|
2141
|
+
return { items: [], source: 'none', error: error instanceof Error ? error.message : String(error) };
|
|
2142
|
+
}
|
|
2143
|
+
if (controlRuntime?.issueResource) {
|
|
2144
|
+
return {
|
|
2145
|
+
items: [],
|
|
2146
|
+
source: 'none',
|
|
2147
|
+
error: 'No active Pod session; Symphony control-plane state is Pod-authoritative.',
|
|
2148
|
+
};
|
|
2033
2149
|
}
|
|
2034
2150
|
try {
|
|
2035
2151
|
const issues = typeof interactive?.__linxListSymphonyIssues === 'function'
|
|
@@ -2038,26 +2154,31 @@ async function listOpenSymphonyIssues(interactive) {
|
|
|
2038
2154
|
return {
|
|
2039
2155
|
items: issues.filter((issue) => issue.status !== 'closed' && issue.status !== 'resolved'),
|
|
2040
2156
|
source: 'local',
|
|
2041
|
-
...(projectionError ? { error: projectionError } : {}),
|
|
2042
2157
|
};
|
|
2043
2158
|
}
|
|
2044
2159
|
catch {
|
|
2045
|
-
return { items: [], source: 'none'
|
|
2160
|
+
return { items: [], source: 'none' };
|
|
2046
2161
|
}
|
|
2047
2162
|
}
|
|
2048
2163
|
async function listRunningSymphonyWorkers(interactive) {
|
|
2049
|
-
const
|
|
2050
|
-
let projectionError;
|
|
2164
|
+
const controlRuntime = interactive?.__linxSymphonyPodProjectionRuntime;
|
|
2051
2165
|
try {
|
|
2052
|
-
if (
|
|
2053
|
-
const podWorkers = await withSymphonyStatusTimeout(interactive, 'Symphony Pod worker status', listRunningSymphonyWorkersFromPod({ runtime:
|
|
2166
|
+
if (controlRuntime?.sessionResource) {
|
|
2167
|
+
const podWorkers = await withSymphonyStatusTimeout(interactive, 'Symphony Pod worker status', listRunningSymphonyWorkersFromPod({ runtime: controlRuntime }));
|
|
2054
2168
|
if (podWorkers) {
|
|
2055
2169
|
return { items: podWorkers, source: 'pod' };
|
|
2056
2170
|
}
|
|
2057
2171
|
}
|
|
2058
2172
|
}
|
|
2059
2173
|
catch (error) {
|
|
2060
|
-
|
|
2174
|
+
return { items: [], source: 'none', error: error instanceof Error ? error.message : String(error) };
|
|
2175
|
+
}
|
|
2176
|
+
if (controlRuntime?.sessionResource) {
|
|
2177
|
+
return {
|
|
2178
|
+
items: [],
|
|
2179
|
+
source: 'none',
|
|
2180
|
+
error: 'No active Pod session; Symphony control-plane state is Pod-authoritative.',
|
|
2181
|
+
};
|
|
2061
2182
|
}
|
|
2062
2183
|
try {
|
|
2063
2184
|
if (typeof interactive?.__linxListSymphonySessions === 'function') {
|
|
@@ -2065,27 +2186,24 @@ async function listRunningSymphonyWorkers(interactive) {
|
|
|
2065
2186
|
return {
|
|
2066
2187
|
items: sessions.filter((session) => session.status === 'running'),
|
|
2067
2188
|
source: 'local',
|
|
2068
|
-
...(projectionError ? { error: projectionError } : {}),
|
|
2069
2189
|
};
|
|
2070
2190
|
}
|
|
2071
2191
|
return {
|
|
2072
2192
|
items: listSymphonySessions()
|
|
2073
2193
|
.filter((session) => session.status === 'running'),
|
|
2074
2194
|
source: 'local',
|
|
2075
|
-
...(projectionError ? { error: projectionError } : {}),
|
|
2076
2195
|
};
|
|
2077
2196
|
}
|
|
2078
2197
|
catch {
|
|
2079
|
-
return { items: [], source: 'none'
|
|
2198
|
+
return { items: [], source: 'none' };
|
|
2080
2199
|
}
|
|
2081
2200
|
}
|
|
2082
2201
|
async function listRecentSymphonyReports(interactive) {
|
|
2083
|
-
const
|
|
2084
|
-
let projectionError;
|
|
2202
|
+
const controlRuntime = interactive?.__linxSymphonyPodProjectionRuntime;
|
|
2085
2203
|
try {
|
|
2086
|
-
if (
|
|
2204
|
+
if (controlRuntime?.deliveryResource) {
|
|
2087
2205
|
const podReports = await withSymphonyStatusTimeout(interactive, 'Symphony Pod report status', listRecentSymphonyReportsFromPod({
|
|
2088
|
-
runtime:
|
|
2206
|
+
runtime: controlRuntime,
|
|
2089
2207
|
limit: 5,
|
|
2090
2208
|
}));
|
|
2091
2209
|
if (podReports) {
|
|
@@ -2094,8 +2212,14 @@ async function listRecentSymphonyReports(interactive) {
|
|
|
2094
2212
|
}
|
|
2095
2213
|
}
|
|
2096
2214
|
catch (error) {
|
|
2097
|
-
|
|
2098
|
-
|
|
2215
|
+
return { items: [], source: 'none', error: error instanceof Error ? error.message : String(error) };
|
|
2216
|
+
}
|
|
2217
|
+
if (controlRuntime?.deliveryResource) {
|
|
2218
|
+
return {
|
|
2219
|
+
items: [],
|
|
2220
|
+
source: 'none',
|
|
2221
|
+
error: 'No active Pod session; Symphony control-plane state is Pod-authoritative.',
|
|
2222
|
+
};
|
|
2099
2223
|
}
|
|
2100
2224
|
try {
|
|
2101
2225
|
const sessions = typeof interactive?.__linxListSymphonySessions === 'function'
|
|
@@ -2106,11 +2230,10 @@ async function listRecentSymphonyReports(interactive) {
|
|
|
2106
2230
|
.filter((session) => session.status === 'completed' || session.status === 'failed')
|
|
2107
2231
|
.slice(0, 5),
|
|
2108
2232
|
source: 'local',
|
|
2109
|
-
...(projectionError ? { error: projectionError } : {}),
|
|
2110
2233
|
};
|
|
2111
2234
|
}
|
|
2112
2235
|
catch {
|
|
2113
|
-
return { items: [], source: 'none'
|
|
2236
|
+
return { items: [], source: 'none' };
|
|
2114
2237
|
}
|
|
2115
2238
|
}
|
|
2116
2239
|
function formatSymphonyWorkerStatus(session) {
|
|
@@ -2433,19 +2556,37 @@ export function installLinxEscapeInterrupt(interactive) {
|
|
|
2433
2556
|
let currentOnEscape = isLinxEscapeInterruptWrapper(initialOnEscape)
|
|
2434
2557
|
? undefined
|
|
2435
2558
|
: initialOnEscape;
|
|
2559
|
+
let lastIdleEscapeTime = 0;
|
|
2436
2560
|
const linxEscapeInterrupt = function linxEscapeInterrupt() {
|
|
2437
2561
|
const session = interactive?.session;
|
|
2438
2562
|
if (handBackAutoControlOnInterrupt(interactive)) {
|
|
2563
|
+
lastIdleEscapeTime = 0;
|
|
2439
2564
|
return;
|
|
2440
2565
|
}
|
|
2441
2566
|
if (session?.isBashRunning && typeof session.abortBash === 'function') {
|
|
2567
|
+
lastIdleEscapeTime = 0;
|
|
2442
2568
|
void session.abortBash();
|
|
2443
2569
|
return;
|
|
2444
2570
|
}
|
|
2445
2571
|
if (isLinxSessionRunning(interactive) && typeof session?.abort === 'function') {
|
|
2572
|
+
lastIdleEscapeTime = 0;
|
|
2446
2573
|
void session.abort();
|
|
2447
2574
|
return;
|
|
2448
2575
|
}
|
|
2576
|
+
if (shouldHandleLinxIdleDoubleEscape(interactive)) {
|
|
2577
|
+
const now = Date.now();
|
|
2578
|
+
if (now - lastIdleEscapeTime < 500) {
|
|
2579
|
+
lastIdleEscapeTime = 0;
|
|
2580
|
+
void openInteractiveRewindFromEscape(interactive);
|
|
2581
|
+
}
|
|
2582
|
+
else {
|
|
2583
|
+
lastIdleEscapeTime = now;
|
|
2584
|
+
interactive?.showStatus?.('Press Escape again to rewind this session.');
|
|
2585
|
+
interactive?.ui?.requestRender?.();
|
|
2586
|
+
}
|
|
2587
|
+
return;
|
|
2588
|
+
}
|
|
2589
|
+
lastIdleEscapeTime = 0;
|
|
2449
2590
|
currentOnEscape?.call(editor);
|
|
2450
2591
|
};
|
|
2451
2592
|
Object.defineProperty(linxEscapeInterrupt, '__linxEscapeInterruptWrapper', {
|
|
@@ -2466,6 +2607,21 @@ export function installLinxEscapeInterrupt(interactive) {
|
|
|
2466
2607
|
installLinxClearInterrupt(interactive, editor);
|
|
2467
2608
|
editor.__linxEscapeInterruptInstalled = true;
|
|
2468
2609
|
}
|
|
2610
|
+
function shouldHandleLinxIdleDoubleEscape(interactive) {
|
|
2611
|
+
if (typeof interactive?.editor?.getText !== 'function') {
|
|
2612
|
+
return false;
|
|
2613
|
+
}
|
|
2614
|
+
const text = String(interactive.editor.getText() ?? '');
|
|
2615
|
+
return text.trim().length === 0;
|
|
2616
|
+
}
|
|
2617
|
+
async function openInteractiveRewindFromEscape(interactive) {
|
|
2618
|
+
try {
|
|
2619
|
+
await handleInteractiveRewindSelector(interactive, interactive?.runtime);
|
|
2620
|
+
}
|
|
2621
|
+
catch (error) {
|
|
2622
|
+
interactive?.showError?.(error instanceof Error ? error.message : String(error));
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2469
2625
|
function isLinxEscapeInterruptWrapper(value) {
|
|
2470
2626
|
return typeof value === 'function'
|
|
2471
2627
|
&& value.__linxEscapeInterruptWrapper === true;
|
|
@@ -2711,12 +2867,38 @@ function patchPiFooter() {
|
|
|
2711
2867
|
if (Array.isArray(lines) && lines.length > 1 && typeof lines[1] === 'string') {
|
|
2712
2868
|
const session = this.session;
|
|
2713
2869
|
const autoCompactEnabled = this.autoCompactEnabled !== false;
|
|
2714
|
-
|
|
2870
|
+
const footerData = this.footerData;
|
|
2871
|
+
const modePrefix = buildLinxFooterModePrefix();
|
|
2872
|
+
const modeLen = visibleWidth(modePrefix);
|
|
2873
|
+
const bulletLen = modeLen > 0 ? 3 : 0;
|
|
2874
|
+
const statusWidth = Math.max(0, width - modeLen - bulletLen);
|
|
2875
|
+
lines[1] = buildLinxFooterStatusLine({
|
|
2876
|
+
session,
|
|
2877
|
+
width: statusWidth,
|
|
2878
|
+
autoCompactEnabled,
|
|
2879
|
+
footerData: footerData,
|
|
2880
|
+
});
|
|
2881
|
+
if (modePrefix) {
|
|
2882
|
+
lines[1] = modePrefix + ' • ' + lines[1];
|
|
2883
|
+
}
|
|
2715
2884
|
}
|
|
2716
2885
|
return lines;
|
|
2717
2886
|
};
|
|
2718
2887
|
footerPatched = true;
|
|
2719
2888
|
}
|
|
2889
|
+
function buildLinxFooterModePrefix() {
|
|
2890
|
+
if (!_linxFooterInteractive)
|
|
2891
|
+
return '';
|
|
2892
|
+
const autoOn = _linxFooterInteractive.__autoEnabled === true;
|
|
2893
|
+
const symphonyOn = _linxFooterInteractive.__linxSymphonyModeEnabled === true;
|
|
2894
|
+
if (!autoOn && !symphonyOn)
|
|
2895
|
+
return '';
|
|
2896
|
+
if (autoOn && symphonyOn)
|
|
2897
|
+
return 'Symphony · Auto';
|
|
2898
|
+
if (autoOn)
|
|
2899
|
+
return 'Auto';
|
|
2900
|
+
return 'Symphony';
|
|
2901
|
+
}
|
|
2720
2902
|
export function patchPiAssistantMessageRendering() {
|
|
2721
2903
|
if (assistantMessagePatched) {
|
|
2722
2904
|
return;
|
|
@@ -2747,91 +2929,4 @@ function isLinxHiddenAssistantContentPart(part) {
|
|
|
2747
2929
|
function isRecord(value) {
|
|
2748
2930
|
return typeof value === 'object' && value !== null;
|
|
2749
2931
|
}
|
|
2750
|
-
function buildLinxFooterStatusLine(session, width, autoCompactEnabled) {
|
|
2751
|
-
const usage = calculateSessionUsage(session);
|
|
2752
|
-
const state = session?.state ?? {};
|
|
2753
|
-
const model = state.model ?? {};
|
|
2754
|
-
const parts = [];
|
|
2755
|
-
if (usage.input > 0) {
|
|
2756
|
-
parts.push(`↑${formatTokenCount(usage.input)}`);
|
|
2757
|
-
}
|
|
2758
|
-
if (usage.output > 0) {
|
|
2759
|
-
parts.push(`↓${formatTokenCount(usage.output)}`);
|
|
2760
|
-
}
|
|
2761
|
-
parts.push(formatContextUsage(session, model, autoCompactEnabled));
|
|
2762
|
-
if (usage.cacheRate !== null) {
|
|
2763
|
-
parts.push(`cache ${usage.cacheRate}%`);
|
|
2764
|
-
}
|
|
2765
|
-
parts.push(typeof model.id === 'string' && model.id ? model.id : 'no-model');
|
|
2766
|
-
if (model.reasoning) {
|
|
2767
|
-
const thinkingLevel = typeof state.thinkingLevel === 'string' && state.thinkingLevel
|
|
2768
|
-
? state.thinkingLevel
|
|
2769
|
-
: 'off';
|
|
2770
|
-
parts.push(thinkingLevel === 'off' ? 'thinking off' : thinkingLevel);
|
|
2771
|
-
}
|
|
2772
|
-
return fitFooterLine(parts.join(' • '), width);
|
|
2773
|
-
}
|
|
2774
|
-
function fitFooterLine(line, width) {
|
|
2775
|
-
const truncated = truncateToWidth(line, width);
|
|
2776
|
-
const visible = visibleWidth(truncated);
|
|
2777
|
-
const padded = visible < width ? `${truncated}${' '.repeat(width - visible)}` : truncated;
|
|
2778
|
-
return `\x1b[2m${padded}\x1b[22m`;
|
|
2779
|
-
}
|
|
2780
|
-
function calculateSessionUsage(session) {
|
|
2781
|
-
const entries = session?.sessionManager?.getEntries?.();
|
|
2782
|
-
if (!Array.isArray(entries)) {
|
|
2783
|
-
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cacheRate: null };
|
|
2784
|
-
}
|
|
2785
|
-
let input = 0;
|
|
2786
|
-
let output = 0;
|
|
2787
|
-
let cacheRead = 0;
|
|
2788
|
-
let cacheWrite = 0;
|
|
2789
|
-
for (const entry of entries) {
|
|
2790
|
-
const message = entry?.type === 'message' ? entry.message : undefined;
|
|
2791
|
-
if (message?.role !== 'assistant' || !message.usage) {
|
|
2792
|
-
continue;
|
|
2793
|
-
}
|
|
2794
|
-
input += safeTokenCount(message.usage.input);
|
|
2795
|
-
output += safeTokenCount(message.usage.output);
|
|
2796
|
-
cacheRead += safeTokenCount(message.usage.cacheRead);
|
|
2797
|
-
cacheWrite += safeTokenCount(message.usage.cacheWrite);
|
|
2798
|
-
}
|
|
2799
|
-
const totalPromptTokens = input + cacheRead + cacheWrite;
|
|
2800
|
-
if (totalPromptTokens <= 0) {
|
|
2801
|
-
return { input, output, cacheRead, cacheWrite, cacheRate: null };
|
|
2802
|
-
}
|
|
2803
|
-
return {
|
|
2804
|
-
input,
|
|
2805
|
-
output,
|
|
2806
|
-
cacheRead,
|
|
2807
|
-
cacheWrite,
|
|
2808
|
-
cacheRate: Math.round((cacheRead / totalPromptTokens) * 100),
|
|
2809
|
-
};
|
|
2810
|
-
}
|
|
2811
|
-
function formatContextUsage(session, model, autoCompactEnabled) {
|
|
2812
|
-
const contextUsage = session?.getContextUsage?.();
|
|
2813
|
-
const contextWindow = safeTokenCount(contextUsage?.contextWindow) || safeTokenCount(model.contextWindow);
|
|
2814
|
-
const percent = typeof contextUsage?.percent === 'number' && Number.isFinite(contextUsage.percent)
|
|
2815
|
-
? `${contextUsage.percent.toFixed(1)}%`
|
|
2816
|
-
: '?';
|
|
2817
|
-
return `${percent}/${formatTokenCount(contextWindow)}${autoCompactEnabled ? ' (auto)' : ''}`;
|
|
2818
|
-
}
|
|
2819
|
-
function formatTokenCount(count) {
|
|
2820
|
-
if (count < 1000) {
|
|
2821
|
-
return count.toString();
|
|
2822
|
-
}
|
|
2823
|
-
if (count < 10000) {
|
|
2824
|
-
return `${(count / 1000).toFixed(1)}k`;
|
|
2825
|
-
}
|
|
2826
|
-
if (count < 1000000) {
|
|
2827
|
-
return `${Math.round(count / 1000)}k`;
|
|
2828
|
-
}
|
|
2829
|
-
if (count < 10000000) {
|
|
2830
|
-
return `${(count / 1000000).toFixed(1)}M`;
|
|
2831
|
-
}
|
|
2832
|
-
return `${Math.round(count / 1000000)}M`;
|
|
2833
|
-
}
|
|
2834
|
-
function safeTokenCount(value) {
|
|
2835
|
-
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : 0;
|
|
2836
|
-
}
|
|
2837
2932
|
//# sourceMappingURL=interactive.js.map
|