maka-agent 0.2.0-dev.77.20261009 → 0.2.0-dev.78.20261010
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.zh-CN.md +2 -2
- package/dist/pi-tui-pickers.js +5 -2
- package/dist/pi-tui-runner.js +211 -33
- package/dist/runtime-host-session-driver.js +91 -13
- package/dist/session-catalog-limits.js +22 -0
- package/dist/session-driver.js +10 -0
- package/dist/tui-copy-catalog.js +15 -0
- package/native/runtime-host-windows-task-launcher/prebuilds/win32-x64/maka-runtime-host-task-launcher.exe +0 -0
- package/node_modules/@maka/core/dist/bot-chat-settings.js +3 -0
- package/node_modules/@maka/core/dist/bot-events.js +1 -8
- package/node_modules/@maka/core/dist/local-memory.js +38 -1
- package/node_modules/@maka/core/dist/scheduled-task.js +14 -15
- package/node_modules/@maka/core/dist/session.js +3 -0
- package/node_modules/@maka/core/dist/text-sanitize.js +8 -3
- package/node_modules/@maka/runtime/dist/ai-sdk-backend.js +11 -0
- package/node_modules/@maka/runtime/dist/ai-sdk-turn.js +13 -5
- package/node_modules/@maka/runtime/dist/bots/proxied-fetch.js +29 -1
- package/node_modules/@maka/runtime/dist/file-stable-write.js +15 -3
- package/node_modules/@maka/runtime/dist/model-adapter.js +20 -1
- package/node_modules/@maka/runtime/dist/network/proxy-test.js +28 -1
- package/node_modules/@maka/runtime/dist/network/scoped-fetch-transport.js +13 -2
- package/node_modules/@maka/runtime/dist/runtime-kernel.js +92 -5
- package/node_modules/@maka/runtime/dist/sandbox-boundary-tool.js +12 -0
- package/node_modules/@maka/runtime/dist/session-manager.js +31 -5
- package/node_modules/@maka/runtime/dist/tool-availability.js +22 -16
- package/node_modules/@maka/runtime/dist/tool-call-snapshot.js +142 -0
- package/node_modules/@maka/runtime/dist/tool-runtime.js +25 -83
- package/node_modules/@maka/runtime/dist/workers/filesystem-worker.mjs +12 -1
- package/node_modules/@maka/runtime-host/dist/client/connect-or-spawn.js +30 -6
- package/node_modules/@maka/runtime-host/dist/client/index.js +1 -1
- package/node_modules/@maka/runtime-host/dist/protocol/index.js +2 -1
- package/node_modules/@maka/runtime-host/dist/protocol/message.js +1 -13
- package/node_modules/@maka/runtime-host/dist/protocol/session-continuity.js +1 -13
- package/node_modules/@maka/runtime-host/dist/protocol/turn.js +1 -13
- package/node_modules/@maka/runtime-host/dist/protocol/workhub-coordination.js +4 -7
- package/node_modules/@maka/runtime-host/dist/server/archive-retention-coordinator.js +81 -105
- package/node_modules/@maka/runtime-host/dist/server/execution-composition.js +17 -43
- package/node_modules/@maka/runtime-host/dist/server/host-maintenance-guards.js +95 -0
- package/node_modules/@maka/runtime-host/dist/server/hosted-execution-tool-profile.js +18 -8
- package/node_modules/@maka/runtime-host/dist/server/interactive-run-composer.js +32 -3
- package/node_modules/@maka/runtime-host/dist/server/project-catalog-coordinator.js +2 -0
- package/node_modules/@maka/runtime-host/dist/server/root-turn-coordinator.js +68 -3
- package/node_modules/@maka/runtime-host/dist/server/skill-catalog-coordinator.js +2 -0
- package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-action-gate.js +19 -24
- package/node_modules/@maka/runtime-host/dist/server/workhub-coordination-coordinator.js +74 -19
- package/node_modules/@maka/runtime-host/dist/server/workhub-result-coordinator.js +1 -1
- package/node_modules/@maka/runtime-host/dist/server/workhub-result-runtime.js +3 -98
- package/node_modules/@maka/runtime-host/dist/server/workhub-target-execution-authority.js +14 -126
- package/node_modules/@maka/storage/dist/git-entry.js +142 -5
- package/package.json +1 -1
package/README.zh-CN.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
>
|
|
1
|
+
> **开发快照**:此 Nightly 不是 Apache Release,不用于生产环境;其版本只对应一个精确的 `apache/maka` commit。
|
|
2
2
|
|
|
3
3
|
<!--
|
|
4
4
|
Licensed to the Apache Software Foundation (ASF) under one
|
|
@@ -35,7 +35,7 @@ release commit 中的 [DISCLAIMER-WIP](https://github.com/apache/maka/blob/main/
|
|
|
35
35
|
[Maka podling 状态页](https://incubator.apache.org/projects/maka.html)。本段仅为中文说明,
|
|
36
36
|
英文免责声明以随包发布的 `DISCLAIMER-WIP` 为准。
|
|
37
37
|
|
|
38
|
-
> **Beta
|
|
38
|
+
> **Beta**:CLI 仍在积极开发中,稳定版发布前,命令和本地数据格式可能发生变化。
|
|
39
39
|
|
|
40
40
|
## 环境要求
|
|
41
41
|
|
package/dist/pi-tui-pickers.js
CHANGED
|
@@ -569,6 +569,7 @@ export class SessionSearchOverlay {
|
|
|
569
569
|
list;
|
|
570
570
|
selectedValue;
|
|
571
571
|
scopeLabel;
|
|
572
|
+
emptyText;
|
|
572
573
|
notice;
|
|
573
574
|
constructor(tui, input) {
|
|
574
575
|
this.tui = tui;
|
|
@@ -577,15 +578,17 @@ export class SessionSearchOverlay {
|
|
|
577
578
|
this.choices = input.choices;
|
|
578
579
|
this.filtered = input.choices;
|
|
579
580
|
this.scopeLabel = input.scopeLabel;
|
|
581
|
+
this.emptyText = input.emptyText;
|
|
580
582
|
this.notice = input.notice;
|
|
581
583
|
this.list = this.buildList();
|
|
582
584
|
this.searchEditor = new Editor(tui, editorTheme(), { paddingX: 0 });
|
|
583
585
|
this.searchEditor.onChange = (text) => this.applyQuery(text);
|
|
584
586
|
}
|
|
585
|
-
updateChoices(choices, scopeLabel, notice) {
|
|
587
|
+
updateChoices(choices, scopeLabel, notice, emptyText) {
|
|
586
588
|
this.choices = choices;
|
|
587
589
|
this.scopeLabel = scopeLabel;
|
|
588
590
|
this.notice = notice;
|
|
591
|
+
this.emptyText = emptyText;
|
|
589
592
|
if (this.input.onQuery) {
|
|
590
593
|
this.filtered = choices;
|
|
591
594
|
this.list = this.buildList();
|
|
@@ -659,7 +662,7 @@ export class SessionSearchOverlay {
|
|
|
659
662
|
padLine('', safeWidth),
|
|
660
663
|
...(this.notice ? [padLine(ansi.dim(this.notice), safeWidth)] : []),
|
|
661
664
|
...(this.filtered.length === 0
|
|
662
|
-
? [padLine(ansi.dim(this.
|
|
665
|
+
? [padLine(ansi.dim(this.emptyText ?? this.copy.noMatchingSessions), safeWidth)]
|
|
663
666
|
: this.list.render(safeWidth).map((line) => formatPickerItemLine(line, safeWidth))),
|
|
664
667
|
padLine(ansi.accent('-'.repeat(safeWidth)), safeWidth),
|
|
665
668
|
];
|
package/dist/pi-tui-runner.js
CHANGED
|
@@ -31,7 +31,8 @@ import { AUTO_RECAP_DISPLAY_LIMIT_BYTES, shouldAutoRecap } from './session-recap
|
|
|
31
31
|
import { MakaSkillHighlightEditor } from './skill-highlight-editor.js';
|
|
32
32
|
import { parseGraphCommand } from '@maka/core/graph-command';
|
|
33
33
|
import { parseSwarmCommand } from '@maka/core/swarm-command';
|
|
34
|
-
import { inspectSessionResumeAvailability, } from './session-driver.js';
|
|
34
|
+
import { inspectSessionResumeAvailability, MakaSessionCatalogIncompleteError, } from './session-driver.js';
|
|
35
|
+
import { SESSION_CATALOG_MAX_SCAN_SESSIONS } from './session-catalog-limits.js';
|
|
35
36
|
import { SafeBoundaryResumeParkedError } from './runtime-host-session-driver.js';
|
|
36
37
|
import { appendExpansionCollapseConfirmation, appendTurnFailureToTranscript, appendUserCommandToTranscript, appendUserPrompt, applyMakaSessionEventToTranscript, applyShellRunUpdateToTranscript, createMakaPiTranscriptState, hasRunningUserCommand, activeSandboxBoundaryRequest, activeFormRequest, activeUserQuestionRequest, applyExpansionDefaultToAll, completePendingInteraction, applyShellRunViewUpdateToTranscript, EXPANSION_COLLAPSE_CONFIRM_WINDOW_MS, hasExpandedEntriesAboveViewport, permissionModeLabel, retireCancelledTransientMessages, replaceTranscriptWithStoredMessages, hydrateToolsWithStoredMessages, submitCompactToTranscript, toggleAllThinkingExpansion, toggleAllToolExpansion, } from './pi-transcript.js';
|
|
37
38
|
import { FormInteractionOverlay } from './pi-tui-form-interaction.js';
|
|
@@ -169,6 +170,7 @@ function sessionConnectionIdentityNotice(session, identities, locale) {
|
|
|
169
170
|
}
|
|
170
171
|
return undefined;
|
|
171
172
|
}
|
|
173
|
+
const SESSION_RESUME_AVAILABILITY_CONCURRENCY = 8;
|
|
172
174
|
export async function runMakaPiTui(input) {
|
|
173
175
|
const locale = input.locale ?? 'en';
|
|
174
176
|
const pickerCopy = getTuiPickerCopy(locale);
|
|
@@ -1578,25 +1580,34 @@ export async function runMakaPiTui(input) {
|
|
|
1578
1580
|
sessionId !== input.driver.getSessionId() &&
|
|
1579
1581
|
(sessionId === pair.parentSessionId || sessionId === pair.sideSessionId)) {
|
|
1580
1582
|
await toggleSideConversation();
|
|
1581
|
-
return;
|
|
1583
|
+
return true;
|
|
1582
1584
|
}
|
|
1583
1585
|
const leavesPair = pair !== undefined && sessionId !== pair.parentSessionId && sessionId !== pair.sideSessionId;
|
|
1584
1586
|
if (!turnRunning) {
|
|
1587
|
+
let switched = false;
|
|
1585
1588
|
await runControl(async () => {
|
|
1586
1589
|
await switchSession(sessionId);
|
|
1590
|
+
switched = true;
|
|
1587
1591
|
if (leavesPair)
|
|
1588
1592
|
await discardCurrentSidePair();
|
|
1589
1593
|
});
|
|
1590
|
-
return;
|
|
1594
|
+
return switched;
|
|
1591
1595
|
}
|
|
1592
1596
|
// One detach at a time (#3380): a second mid-turn switch while the first
|
|
1593
1597
|
// is still handing the view over would clear `detaching` early, reopen
|
|
1594
1598
|
// the interrupt window, and double-apply the adoption.
|
|
1595
1599
|
if (detaching)
|
|
1596
|
-
return;
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
+
return false;
|
|
1601
|
+
try {
|
|
1602
|
+
await switchAwayMidTurn(sessionId);
|
|
1603
|
+
if (leavesPair)
|
|
1604
|
+
await discardCurrentSidePair();
|
|
1605
|
+
return true;
|
|
1606
|
+
}
|
|
1607
|
+
catch (error) {
|
|
1608
|
+
reportError(error);
|
|
1609
|
+
return false;
|
|
1610
|
+
}
|
|
1600
1611
|
};
|
|
1601
1612
|
const openSideConversation = async (prompt) => {
|
|
1602
1613
|
if (sideConversation) {
|
|
@@ -2460,14 +2471,50 @@ export async function runMakaPiTui(input) {
|
|
|
2460
2471
|
onChange: requestRender,
|
|
2461
2472
|
});
|
|
2462
2473
|
};
|
|
2474
|
+
let sessionListPromise;
|
|
2475
|
+
let activeResumeAvailabilityChecks = 0;
|
|
2476
|
+
const queuedResumeAvailabilityChecks = [];
|
|
2477
|
+
const runResumeAvailabilityCheck = async (task) => {
|
|
2478
|
+
if (activeResumeAvailabilityChecks >= SESSION_RESUME_AVAILABILITY_CONCURRENCY) {
|
|
2479
|
+
await new Promise((resolve) => queuedResumeAvailabilityChecks.push(resolve));
|
|
2480
|
+
}
|
|
2481
|
+
activeResumeAvailabilityChecks += 1;
|
|
2482
|
+
try {
|
|
2483
|
+
return await task();
|
|
2484
|
+
}
|
|
2485
|
+
finally {
|
|
2486
|
+
activeResumeAvailabilityChecks -= 1;
|
|
2487
|
+
queuedResumeAvailabilityChecks.shift()?.();
|
|
2488
|
+
}
|
|
2489
|
+
};
|
|
2490
|
+
const listSessions = (options = {}) => {
|
|
2491
|
+
if (sessionListPromise) {
|
|
2492
|
+
if (sessionListPromise.limit === options.limit && sessionListPromise.cwd === options.cwd) {
|
|
2493
|
+
return sessionListPromise.promise;
|
|
2494
|
+
}
|
|
2495
|
+
return sessionListPromise.promise.then(() => listSessions(options), () => listSessions(options));
|
|
2496
|
+
}
|
|
2497
|
+
{
|
|
2498
|
+
const promise = input.driver.listSessions(options).finally(() => {
|
|
2499
|
+
if (sessionListPromise?.promise === promise)
|
|
2500
|
+
sessionListPromise = undefined;
|
|
2501
|
+
});
|
|
2502
|
+
sessionListPromise = { limit: options.limit, cwd: options.cwd, promise };
|
|
2503
|
+
}
|
|
2504
|
+
return sessionListPromise.promise;
|
|
2505
|
+
};
|
|
2463
2506
|
const resumeSession = async () => {
|
|
2507
|
+
if (!input.driver.getSessionId()) {
|
|
2508
|
+
await showSessionList({ onlyResumable: true });
|
|
2509
|
+
return;
|
|
2510
|
+
}
|
|
2464
2511
|
if (!input.driver.resumeLatest) {
|
|
2465
|
-
throw new Error(
|
|
2512
|
+
throw new Error(pickerCopy.resumeUnavailableNotice);
|
|
2466
2513
|
}
|
|
2467
2514
|
state.entries.push({
|
|
2468
2515
|
kind: 'notice',
|
|
2469
2516
|
level: 'info',
|
|
2470
|
-
text:
|
|
2517
|
+
text: pickerCopy.resumeStartingNotice,
|
|
2471
2518
|
});
|
|
2472
2519
|
requestRender();
|
|
2473
2520
|
try {
|
|
@@ -2784,26 +2831,97 @@ export async function runMakaPiTui(input) {
|
|
|
2784
2831
|
label: externalSourceLabel(adapterId),
|
|
2785
2832
|
})), (item) => void showExternalSessionPage(item.value, initialScope), { minPrimaryColumnWidth: 20, maxPrimaryColumnWidth: 40 });
|
|
2786
2833
|
};
|
|
2787
|
-
const
|
|
2788
|
-
const
|
|
2789
|
-
|
|
2790
|
-
|
|
2834
|
+
const showSessionCatalogIncompleteNotice = () => {
|
|
2835
|
+
const text = pickerCopy.resumeCatalogIncompleteNotice;
|
|
2836
|
+
state.entries.push({ kind: 'notice', level: 'info', text });
|
|
2837
|
+
requestRender();
|
|
2838
|
+
};
|
|
2839
|
+
const readSessionCatalog = async (sessionOptions = {}, onIncomplete) => {
|
|
2840
|
+
try {
|
|
2841
|
+
return await listSessions(sessionOptions);
|
|
2842
|
+
}
|
|
2843
|
+
catch (error) {
|
|
2844
|
+
if (error instanceof MakaSessionCatalogIncompleteError) {
|
|
2845
|
+
onIncomplete?.();
|
|
2846
|
+
return [...error.sessions];
|
|
2847
|
+
}
|
|
2848
|
+
throw error;
|
|
2849
|
+
}
|
|
2850
|
+
};
|
|
2851
|
+
const showSessionList = async (options = {}) => {
|
|
2852
|
+
let catalogIncompleteNoticeShown = false;
|
|
2853
|
+
const readPickerSessionCatalog = (sessionOptions) => readSessionCatalog(sessionOptions, () => {
|
|
2854
|
+
if (catalogIncompleteNoticeShown)
|
|
2855
|
+
return;
|
|
2856
|
+
catalogIncompleteNoticeShown = true;
|
|
2857
|
+
showSessionCatalogIncompleteNotice();
|
|
2858
|
+
});
|
|
2859
|
+
let sessions = await readPickerSessionCatalog(options.onlyResumable ? { limit: SESSION_CATALOG_MAX_SCAN_SESSIONS, cwd } : {});
|
|
2860
|
+
let sessionTree = projectRevisionLinkedSessionTree(sessions, input.driver.getSessionId() ?? undefined);
|
|
2861
|
+
let projectedSessions = flattenLinkedSessionTree(sessionTree.roots, sessionTree.childrenByParentId);
|
|
2862
|
+
const resumeCandidateCheckFailures = new Set();
|
|
2863
|
+
const checkSessionAvailability = async (session) => {
|
|
2864
|
+
try {
|
|
2865
|
+
const result = await runResumeAvailabilityCheck(async () => {
|
|
2866
|
+
if (!session.cwd) {
|
|
2867
|
+
return [session.id, { available: false, reason: 'Missing working directory' }];
|
|
2868
|
+
}
|
|
2869
|
+
if (options.onlyResumable) {
|
|
2870
|
+
if (!input.driver.getSessionResumeCandidateAvailability) {
|
|
2871
|
+
resumeCandidateCheckFailures.add(session.id);
|
|
2872
|
+
return [
|
|
2873
|
+
session.id,
|
|
2874
|
+
{
|
|
2875
|
+
available: false,
|
|
2876
|
+
reason: pickerCopy.resumeUnavailableNotice,
|
|
2877
|
+
},
|
|
2878
|
+
];
|
|
2879
|
+
}
|
|
2880
|
+
return [
|
|
2881
|
+
session.id,
|
|
2882
|
+
await input.driver.getSessionResumeCandidateAvailability(session),
|
|
2883
|
+
];
|
|
2884
|
+
}
|
|
2885
|
+
const availability = (await input.driver.getSessionResumeAvailability?.(session)) ??
|
|
2886
|
+
(await inspectSessionResumeAvailability(session));
|
|
2887
|
+
return [session.id, availability];
|
|
2888
|
+
});
|
|
2889
|
+
if (options.onlyResumable && input.driver.getSessionResumeCandidateAvailability) {
|
|
2890
|
+
resumeCandidateCheckFailures.delete(session.id);
|
|
2891
|
+
}
|
|
2892
|
+
return result;
|
|
2893
|
+
}
|
|
2894
|
+
catch (error) {
|
|
2895
|
+
if (options.onlyResumable)
|
|
2896
|
+
resumeCandidateCheckFailures.add(session.id);
|
|
2897
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
2898
|
+
return [session.id, { available: false, reason: detail }];
|
|
2899
|
+
}
|
|
2900
|
+
};
|
|
2901
|
+
// Recovery starts in the current workspace even for Host profiles whose
|
|
2902
|
+
// normal /session picker starts in All. The user can still opt into the
|
|
2903
|
+
// full catalog with the scope toggle after the current rows are ready.
|
|
2904
|
+
let pickerScope = options.onlyResumable ? 'current' : sessionListScope;
|
|
2905
|
+
const sessionsToCheck = (pickerScope === 'current' ? sessions.filter((session) => session.cwd === cwd) : sessions).slice(0, SESSION_CATALOG_MAX_SCAN_SESSIONS);
|
|
2791
2906
|
// Maka-session availability and Host source discovery are independent I/O; run
|
|
2792
2907
|
// them concurrently so the picker's open latency is the slower of the two,
|
|
2793
2908
|
// not their sum.
|
|
2794
2909
|
const [availabilityEntries, externalSourceQuery] = await Promise.all([
|
|
2795
|
-
Promise.all(
|
|
2796
|
-
|
|
2797
|
-
session.id,
|
|
2798
|
-
(await input.driver.getSessionResumeAvailability?.(session)) ??
|
|
2799
|
-
(await inspectSessionResumeAvailability(session)),
|
|
2800
|
-
];
|
|
2801
|
-
})),
|
|
2802
|
-
input.externalSessions && !turnRunning
|
|
2910
|
+
Promise.all(sessionsToCheck.map(checkSessionAvailability)),
|
|
2911
|
+
!options.onlyResumable && input.externalSessions && !turnRunning
|
|
2803
2912
|
? input.externalSessions.listSources().then((adapterIds) => ({ adapterIds }), () => ({ error: true }))
|
|
2804
2913
|
: Promise.resolve({ adapterIds: [] }),
|
|
2805
2914
|
]);
|
|
2806
2915
|
const availability = new Map(availabilityEntries);
|
|
2916
|
+
const ensureAvailabilityForScope = async (scope) => {
|
|
2917
|
+
const scopeSessions = (scope === 'current' ? sessions.filter((session) => session.cwd === cwd) : sessions).slice(0, SESSION_CATALOG_MAX_SCAN_SESSIONS);
|
|
2918
|
+
const missingSessions = scopeSessions.filter((session) => !availability.has(session.id));
|
|
2919
|
+
if (missingSessions.length === 0)
|
|
2920
|
+
return;
|
|
2921
|
+
for (const [sessionId, sessionAvailability] of await Promise.all(missingSessions.map(checkSessionAvailability))) {
|
|
2922
|
+
availability.set(sessionId, sessionAvailability);
|
|
2923
|
+
}
|
|
2924
|
+
};
|
|
2807
2925
|
if ('error' in externalSourceQuery) {
|
|
2808
2926
|
state.entries.push({
|
|
2809
2927
|
kind: 'notice',
|
|
@@ -2814,14 +2932,22 @@ export async function runMakaPiTui(input) {
|
|
|
2814
2932
|
let overlay;
|
|
2815
2933
|
let sessionSearch;
|
|
2816
2934
|
const renderScope = () => {
|
|
2817
|
-
const visibleSessions =
|
|
2935
|
+
const visibleSessions = pickerScope === 'current'
|
|
2818
2936
|
? projectedSessions.filter(({ session }) => session.cwd === cwd)
|
|
2819
2937
|
: projectedSessions;
|
|
2820
|
-
const
|
|
2938
|
+
const selectableSessions = options.onlyResumable
|
|
2939
|
+
? visibleSessions.filter(({ session }) => availability.get(session.id)?.available === true)
|
|
2940
|
+
: visibleSessions;
|
|
2941
|
+
const emptyText = options.onlyResumable &&
|
|
2942
|
+
visibleSessions.length > 0 &&
|
|
2943
|
+
visibleSessions.every(({ session }) => resumeCandidateCheckFailures.has(session.id))
|
|
2944
|
+
? pickerCopy.resumeCandidateCheckFailed
|
|
2945
|
+
: undefined;
|
|
2946
|
+
const choices = selectableSessions.map(({ session, depth }) => {
|
|
2821
2947
|
const state = availability.get(session.id);
|
|
2822
2948
|
const statusBadge = sessionStatusBadge(session, locale);
|
|
2823
2949
|
const statusDetail = statusBadge ? ` · ${statusBadge}` : '';
|
|
2824
|
-
const location =
|
|
2950
|
+
const location = pickerScope === 'all' && session.cwd ? ` ${session.cwd}` : '';
|
|
2825
2951
|
const childDetail = session.subagentRuntime
|
|
2826
2952
|
? ` subagent:${session.subagentRuntime.profile}`
|
|
2827
2953
|
: '';
|
|
@@ -2846,7 +2972,9 @@ export async function runMakaPiTui(input) {
|
|
|
2846
2972
|
.toLocaleLowerCase(),
|
|
2847
2973
|
};
|
|
2848
2974
|
});
|
|
2849
|
-
if (
|
|
2975
|
+
if (!options.onlyResumable &&
|
|
2976
|
+
'adapterIds' in externalSourceQuery &&
|
|
2977
|
+
externalSourceQuery.adapterIds.length > 0) {
|
|
2850
2978
|
choices.push({
|
|
2851
2979
|
item: {
|
|
2852
2980
|
value: 'external:import',
|
|
@@ -2866,16 +2994,20 @@ export async function runMakaPiTui(input) {
|
|
|
2866
2994
|
showExternalSourcePicker(externalSourceQuery.adapterIds);
|
|
2867
2995
|
return;
|
|
2868
2996
|
}
|
|
2869
|
-
|
|
2997
|
+
const selectedSession = sessions.find((session) => session.id === item.value);
|
|
2998
|
+
if (selectedSession && !selectedSession.cwd) {
|
|
2870
2999
|
return;
|
|
3000
|
+
}
|
|
2871
3001
|
closeOverlay();
|
|
2872
|
-
void
|
|
3002
|
+
void (async () => {
|
|
3003
|
+
const switched = await goToSession(item.value);
|
|
3004
|
+
if (switched && options.onlyResumable)
|
|
3005
|
+
await runControl(resumeSession);
|
|
3006
|
+
})().catch(reportError);
|
|
2873
3007
|
};
|
|
2874
|
-
const scopeLabel =
|
|
2875
|
-
? pickerCopy.sessionScopeCurrent
|
|
2876
|
-
: pickerCopy.sessionScopeAll;
|
|
3008
|
+
const scopeLabel = pickerScope === 'current' ? pickerCopy.sessionScopeCurrent : pickerCopy.sessionScopeAll;
|
|
2877
3009
|
if (sessionSearch) {
|
|
2878
|
-
sessionSearch.updateChoices(choices, scopeLabel);
|
|
3010
|
+
sessionSearch.updateChoices(choices, scopeLabel, undefined, emptyText);
|
|
2879
3011
|
sessionSearch.invalidate();
|
|
2880
3012
|
return;
|
|
2881
3013
|
}
|
|
@@ -2883,11 +3015,28 @@ export async function runMakaPiTui(input) {
|
|
|
2883
3015
|
locale,
|
|
2884
3016
|
choices,
|
|
2885
3017
|
scopeLabel,
|
|
3018
|
+
emptyText,
|
|
2886
3019
|
onSelect,
|
|
2887
3020
|
onCancel: closeOverlay,
|
|
2888
3021
|
onToggleScope: () => {
|
|
2889
|
-
|
|
2890
|
-
|
|
3022
|
+
const nextScope = pickerScope === 'current' ? 'all' : 'current';
|
|
3023
|
+
void (async () => {
|
|
3024
|
+
if (options.onlyResumable) {
|
|
3025
|
+
sessions = await readPickerSessionCatalog(nextScope === 'current'
|
|
3026
|
+
? { limit: SESSION_CATALOG_MAX_SCAN_SESSIONS, cwd }
|
|
3027
|
+
: { limit: SESSION_CATALOG_MAX_SCAN_SESSIONS });
|
|
3028
|
+
sessionTree = projectRevisionLinkedSessionTree(sessions, input.driver.getSessionId() ?? undefined);
|
|
3029
|
+
projectedSessions = flattenLinkedSessionTree(sessionTree.roots, sessionTree.childrenByParentId);
|
|
3030
|
+
}
|
|
3031
|
+
await ensureAvailabilityForScope(nextScope);
|
|
3032
|
+
})()
|
|
3033
|
+
.then(() => {
|
|
3034
|
+
pickerScope = nextScope;
|
|
3035
|
+
if (!options.onlyResumable)
|
|
3036
|
+
sessionListScope = nextScope;
|
|
3037
|
+
renderScope();
|
|
3038
|
+
})
|
|
3039
|
+
.catch(reportError);
|
|
2891
3040
|
},
|
|
2892
3041
|
});
|
|
2893
3042
|
sessionPickerOverlayOpen = true;
|
|
@@ -2895,6 +3044,34 @@ export async function runMakaPiTui(input) {
|
|
|
2895
3044
|
};
|
|
2896
3045
|
renderScope();
|
|
2897
3046
|
};
|
|
3047
|
+
const announceResumeAvailability = async () => {
|
|
3048
|
+
const sessionId = input.driver.getSessionId();
|
|
3049
|
+
try {
|
|
3050
|
+
if (!input.driver.getSessionResumeCandidateAvailability)
|
|
3051
|
+
return;
|
|
3052
|
+
const sessions = sessionId
|
|
3053
|
+
? undefined
|
|
3054
|
+
: await readSessionCatalog({ limit: SESSION_CATALOG_MAX_SCAN_SESSIONS, cwd });
|
|
3055
|
+
const session = sessionId
|
|
3056
|
+
? ((await input.driver.getSessionSummary?.(sessionId)) ??
|
|
3057
|
+
(await readSessionCatalog()).find((candidate) => candidate.id === sessionId))
|
|
3058
|
+
: sessions?.find((candidate) => candidate.cwd === cwd);
|
|
3059
|
+
if (!session)
|
|
3060
|
+
return;
|
|
3061
|
+
const availability = await runResumeAvailabilityCheck(() => input.driver.getSessionResumeCandidateAvailability(session));
|
|
3062
|
+
if (availability.available) {
|
|
3063
|
+
state.entries.push({
|
|
3064
|
+
kind: 'notice',
|
|
3065
|
+
level: 'info',
|
|
3066
|
+
text: pickerCopy.resumeAvailabilityNotice,
|
|
3067
|
+
});
|
|
3068
|
+
requestRender();
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
catch {
|
|
3072
|
+
// Resume discovery is advisory and must never prevent the TUI from starting.
|
|
3073
|
+
}
|
|
3074
|
+
};
|
|
2898
3075
|
const showRewindPicker = async () => {
|
|
2899
3076
|
const targets = await input.driver.listRewindTargets();
|
|
2900
3077
|
if (targets.length === 0) {
|
|
@@ -4283,6 +4460,7 @@ export async function runMakaPiTui(input) {
|
|
|
4283
4460
|
terminal.write(ENABLE_FOCUS_REPORTING);
|
|
4284
4461
|
if (input.firstRun)
|
|
4285
4462
|
void showSetupWizard();
|
|
4463
|
+
setTimeout(() => void announceResumeAvailability(), 0);
|
|
4286
4464
|
}
|
|
4287
4465
|
catch (error) {
|
|
4288
4466
|
beginClose(error instanceof Error ? error : new Error(String(error)));
|
|
@@ -28,14 +28,19 @@ import { mergeShellRunUpdate } from '@maka/core/shell-run-result';
|
|
|
28
28
|
import { isActiveShellRunStatus } from '@maka/core/shell-run';
|
|
29
29
|
import { executionBoundaryDisplayMode } from '@maka/core/sandbox-boundary';
|
|
30
30
|
import { isRuntimeHostTerminalTurn as isTerminalTurn } from '@maka/runtime-host/adapter';
|
|
31
|
-
import { projectSessionCatalogSummary, readRuntimeHostResources, readRuntimeHostSessions, RuntimeHostOperationError, RuntimeHostRequestInterruptedError, } from '@maka/runtime-host/client';
|
|
31
|
+
import { projectSessionCatalogSummary, readRuntimeHostSessionCatalogPage, readRuntimeHostResources, readRuntimeHostSessions, RuntimeHostSessionCatalogRevisionChangedError, RuntimeHostOperationError, RuntimeHostRequestInterruptedError, } from '@maka/runtime-host/client';
|
|
32
32
|
import { SESSION_TRANSCRIPT_BOOTSTRAP_MAX_BYTES, } from '@maka/runtime-host/protocol';
|
|
33
33
|
import { RuntimeHostSessionChannel } from './runtime-host-session-channel.js';
|
|
34
|
+
import { SESSION_CATALOG_MAX_SCAN_PAGES } from './session-catalog-limits.js';
|
|
34
35
|
import { getRuntimeHostSession, requireRuntimeHostSessionProjection as requireSession, updateRuntimeHostSession, } from './runtime-host-session-update.js';
|
|
35
|
-
import { inspectSessionResumeAvailability, skillInvocationBlockedMessage, } from './session-driver.js';
|
|
36
|
+
import { MakaSessionCatalogIncompleteError, inspectSessionResumeAvailability, skillInvocationBlockedMessage, } from './session-driver.js';
|
|
36
37
|
import { cwdRank, firstLine, inspectGitCwdChanges, resolveMoveCwd, } from './session-driver-policy.js';
|
|
37
38
|
const decodeStoredMessage = (value) => decodePersistedStoredMessage(markPersisted(value));
|
|
38
39
|
const MAX_CATALOG_ATTEMPTS = 3;
|
|
40
|
+
// Sparse cwd or visibility matches must not turn a bounded lookup into a full Host scan.
|
|
41
|
+
const MAX_SESSION_CATALOG_READ_ATTEMPTS = 8;
|
|
42
|
+
const SESSION_CATALOG_READ_RETRY_BASE_DELAY_MS = 8;
|
|
43
|
+
const SESSION_CATALOG_READ_RETRY_MAX_DELAY_MS = 64;
|
|
39
44
|
/**
|
|
40
45
|
* The host declined to start a safe-boundary continuation and explained why.
|
|
41
46
|
* `reason` is the durable park reason from the `turn.resume` protocol, so
|
|
@@ -162,14 +167,16 @@ class RuntimeHostMakaSessionDriverImpl {
|
|
|
162
167
|
const session = await this.#createSession(input.name ?? DEFAULT_SESSION_NAME);
|
|
163
168
|
return projectSessionCatalogSummary(session);
|
|
164
169
|
}
|
|
165
|
-
async listSessions() {
|
|
166
|
-
const sessions =
|
|
167
|
-
.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
+
async listSessions(options = {}) {
|
|
171
|
+
const sessions = options.cwd !== undefined || options.limit !== undefined
|
|
172
|
+
? await this.#readBoundedSessionCatalog(options.limit, options.cwd)
|
|
173
|
+
: (await readRuntimeHostSessions(this.#connection))
|
|
174
|
+
.flatMap(representableSession)
|
|
175
|
+
.filter((session) => !isSideConversationSession(session.labels));
|
|
176
|
+
const summaries = sessions.map(projectSessionCatalogSummary);
|
|
170
177
|
if (this.#executionLocation.kind === 'host')
|
|
171
|
-
return
|
|
172
|
-
return
|
|
178
|
+
return summaries;
|
|
179
|
+
return summaries
|
|
173
180
|
.map((session, index) => ({ session, index }))
|
|
174
181
|
.sort((left, right) => {
|
|
175
182
|
const cwdDelta = cwdRank(left.session, this.#workspace.hostCwd) -
|
|
@@ -178,9 +185,80 @@ class RuntimeHostMakaSessionDriverImpl {
|
|
|
178
185
|
})
|
|
179
186
|
.map(({ session }) => session);
|
|
180
187
|
}
|
|
181
|
-
|
|
188
|
+
async getSessionSummary(sessionId) {
|
|
189
|
+
const projection = await getRuntimeHostSession(this.#connection, sessionId);
|
|
190
|
+
if (!projection)
|
|
191
|
+
return undefined;
|
|
192
|
+
const [session] = representableSession(projection);
|
|
193
|
+
return session ? projectSessionCatalogSummary(session) : undefined;
|
|
194
|
+
}
|
|
195
|
+
async #readBoundedSessionCatalog(limit, cwd) {
|
|
196
|
+
if (limit !== undefined && (!Number.isSafeInteger(limit) || limit < 0)) {
|
|
197
|
+
throw new Error(`Session catalog limit must be a non-negative safe integer: ${limit}`);
|
|
198
|
+
}
|
|
199
|
+
if (limit === 0)
|
|
200
|
+
return [];
|
|
201
|
+
for (let attempt = 0; attempt < MAX_SESSION_CATALOG_READ_ATTEMPTS; attempt += 1) {
|
|
202
|
+
try {
|
|
203
|
+
const sessions = [];
|
|
204
|
+
const cursors = new Set();
|
|
205
|
+
let cursor;
|
|
206
|
+
let pagesRead = 0;
|
|
207
|
+
while (pagesRead < SESSION_CATALOG_MAX_SCAN_PAGES) {
|
|
208
|
+
const page = await readRuntimeHostSessionCatalogPage(this.#connection, cursor);
|
|
209
|
+
pagesRead += 1;
|
|
210
|
+
for (const item of page.sessions) {
|
|
211
|
+
for (const session of representableSession(item)) {
|
|
212
|
+
if ((cwd === undefined || session.workspace.hostCwd === cwd) &&
|
|
213
|
+
!isSideConversationSession(session.labels)) {
|
|
214
|
+
sessions.push(session);
|
|
215
|
+
if (limit !== undefined && sessions.length >= limit)
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (limit !== undefined && sessions.length >= limit)
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
if (!page.nextCursor) {
|
|
223
|
+
cursor = undefined;
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
cursor = page.nextCursor;
|
|
227
|
+
if (limit !== undefined && sessions.length >= limit) {
|
|
228
|
+
throw new MakaSessionCatalogIncompleteError(pagesRead, sessions.map(projectSessionCatalogSummary));
|
|
229
|
+
}
|
|
230
|
+
if (cursors.has(cursor.cursor)) {
|
|
231
|
+
throw new Error('Runtime Host Session catalog returned a repeated cursor');
|
|
232
|
+
}
|
|
233
|
+
cursors.add(cursor.cursor);
|
|
234
|
+
}
|
|
235
|
+
if (cursor !== undefined && pagesRead === SESSION_CATALOG_MAX_SCAN_PAGES) {
|
|
236
|
+
throw new MakaSessionCatalogIncompleteError(pagesRead, sessions.map(projectSessionCatalogSummary));
|
|
237
|
+
}
|
|
238
|
+
return sessions;
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
if (!(error instanceof RuntimeHostSessionCatalogRevisionChangedError) ||
|
|
242
|
+
attempt + 1 === MAX_SESSION_CATALOG_READ_ATTEMPTS) {
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(SESSION_CATALOG_READ_RETRY_BASE_DELAY_MS * 2 ** attempt, SESSION_CATALOG_READ_RETRY_MAX_DELAY_MS)));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
throw new Error('Runtime Host Session catalog could not be read consistently');
|
|
249
|
+
}
|
|
250
|
+
async getSessionResumeAvailability(session) {
|
|
182
251
|
return inspectRuntimeHostSessionResumeAvailability(session, this.#executionLocation);
|
|
183
252
|
}
|
|
253
|
+
async getSessionResumeCandidateAvailability(session) {
|
|
254
|
+
const availability = await inspectRuntimeHostSessionResumeAvailability(session, this.#executionLocation);
|
|
255
|
+
if (!availability.available)
|
|
256
|
+
return availability;
|
|
257
|
+
const plan = await this.#request('turn.resume.query', { sessionId: session.id });
|
|
258
|
+
return plan.disposition === 'ready'
|
|
259
|
+
? { available: true }
|
|
260
|
+
: { available: false, reason: plan.reason };
|
|
261
|
+
}
|
|
184
262
|
async preparePrompt(prompt, options = {}) {
|
|
185
263
|
const sessionId = await this.#ensureSession();
|
|
186
264
|
const sessionGeneration = this.#sessionGeneration;
|
|
@@ -1496,9 +1574,9 @@ function inspectRuntimeHostSessionResumeAvailability(summary, location) {
|
|
|
1496
1574
|
if (!summary.cwd) {
|
|
1497
1575
|
return Promise.resolve({ available: false, reason: 'Missing working directory' });
|
|
1498
1576
|
}
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1577
|
+
if (location.kind !== 'host')
|
|
1578
|
+
return inspectSessionResumeAvailability(summary);
|
|
1579
|
+
return Promise.resolve({ available: true });
|
|
1502
1580
|
}
|
|
1503
1581
|
async function assertSessionResumeAvailable(summary, location) {
|
|
1504
1582
|
const availability = await inspectRuntimeHostSessionResumeAvailability(summary, location);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Licensed to the Apache Software Foundation (ASF) under one
|
|
3
|
+
* or more contributor license agreements. See the NOTICE file
|
|
4
|
+
* distributed with this work for additional information
|
|
5
|
+
* regarding copyright ownership. The ASF licenses this file
|
|
6
|
+
* to you under the Apache License, Version 2.0 (the
|
|
7
|
+
* "License"); you may not use this file except in compliance
|
|
8
|
+
* with the License. You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing,
|
|
13
|
+
* software distributed under the License is distributed on an
|
|
14
|
+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
15
|
+
* KIND, either express or implied. See the License for the
|
|
16
|
+
* specific language governing permissions and limitations
|
|
17
|
+
* under the License.
|
|
18
|
+
*/
|
|
19
|
+
import { SESSION_CATALOG_PAGE_MAX_ITEMS } from '@maka/runtime-host/protocol';
|
|
20
|
+
// Sparse cwd or visibility matches must not turn a bounded lookup into a full Host scan.
|
|
21
|
+
export const SESSION_CATALOG_MAX_SCAN_PAGES = 8;
|
|
22
|
+
export const SESSION_CATALOG_MAX_SCAN_SESSIONS = SESSION_CATALOG_MAX_SCAN_PAGES * SESSION_CATALOG_PAGE_MAX_ITEMS;
|
package/dist/session-driver.js
CHANGED
|
@@ -31,6 +31,16 @@ export function skillInvocationBlockedMessage(skillInvocation) {
|
|
|
31
31
|
? `Could not resolve the Skill this Turn asked for: ${reasons.join(', ')}`
|
|
32
32
|
: 'Explicit Skill invocation could not be resolved';
|
|
33
33
|
}
|
|
34
|
+
export class MakaSessionCatalogIncompleteError extends Error {
|
|
35
|
+
scannedPages;
|
|
36
|
+
sessions;
|
|
37
|
+
constructor(scannedPages, sessions = []) {
|
|
38
|
+
super(`Session catalog results may be incomplete after scanning ${scannedPages} pages.`);
|
|
39
|
+
this.scannedPages = scannedPages;
|
|
40
|
+
this.sessions = sessions;
|
|
41
|
+
this.name = 'MakaSessionCatalogIncompleteError';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
34
44
|
export async function inspectSessionResumeAvailability(session) {
|
|
35
45
|
if (!session.cwd)
|
|
36
46
|
return { available: false, reason: 'Missing working directory' };
|
package/dist/tui-copy-catalog.js
CHANGED
|
@@ -638,6 +638,11 @@ export const TUI_COPY_RESOURCES = {
|
|
|
638
638
|
sessionScopeAll: 'All',
|
|
639
639
|
sessionSearchHint: 'Search name / id / path / model / connection · Tab scope · ↑↓ select · Enter open · Esc close',
|
|
640
640
|
noMatchingSessions: 'No matching sessions',
|
|
641
|
+
resumeCandidateCheckFailed: 'Could not check whether any sessions can be resumed.',
|
|
642
|
+
resumeAvailabilityNotice: 'An interrupted run is available — use /resume to continue from a safe boundary.',
|
|
643
|
+
resumeStartingNotice: 'Resuming from the latest safe boundary…',
|
|
644
|
+
resumeUnavailableNotice: 'Safe-boundary resume is unavailable on this runtime.',
|
|
645
|
+
resumeCatalogIncompleteNotice: 'Session discovery stopped before the full catalog was checked. Showing the sessions found so far.',
|
|
641
646
|
selectPickerHint: '↑↓ select · Enter confirm · Esc close',
|
|
642
647
|
providerConfigured: 'configured',
|
|
643
648
|
addAccount: 'add account',
|
|
@@ -758,6 +763,11 @@ export const TUI_COPY_RESOURCES = {
|
|
|
758
763
|
sessionScopeAll: '全部',
|
|
759
764
|
sessionSearchHint: '搜索名称 / ID / 路径 / 模型 / 连接 · Tab 切换范围 · ↑↓ 选择 · Enter 打开 · Esc 关闭',
|
|
760
765
|
noMatchingSessions: '没有匹配的会话',
|
|
766
|
+
resumeCandidateCheckFailed: '无法检查是否有可恢复的会话。',
|
|
767
|
+
resumeAvailabilityNotice: '有可恢复的中断会话,可使用 /resume 从安全边界继续。',
|
|
768
|
+
resumeStartingNotice: '正在从最新的安全边界恢复……',
|
|
769
|
+
resumeUnavailableNotice: '当前运行环境不支持安全边界恢复。',
|
|
770
|
+
resumeCatalogIncompleteNotice: '会话目录尚未检查完整。当前显示已找到的会话。',
|
|
761
771
|
selectPickerHint: '↑↓ 选择 · Enter 确认 · Esc 关闭',
|
|
762
772
|
providerConfigured: '已设置',
|
|
763
773
|
addAccount: '添加账号',
|
|
@@ -877,6 +887,11 @@ export const TUI_COPY_RESOURCES = {
|
|
|
877
887
|
sessionScopeAll: '全部',
|
|
878
888
|
sessionSearchHint: '搜尋名稱 / ID / 路徑 / 模型 / 連線 · Tab 切換範圍 · ↑↓ 選取 · Enter 開啟 · Esc 關閉',
|
|
879
889
|
noMatchingSessions: '沒有符合的會話',
|
|
890
|
+
resumeCandidateCheckFailed: '無法檢查是否有可恢復的會話。',
|
|
891
|
+
resumeAvailabilityNotice: '有可恢復的中斷會話,可使用 /resume 從安全邊界繼續。',
|
|
892
|
+
resumeStartingNotice: '正在從最新的安全邊界恢復……',
|
|
893
|
+
resumeUnavailableNotice: '目前執行環境不支援安全邊界恢復。',
|
|
894
|
+
resumeCatalogIncompleteNotice: '會話目錄尚未檢查完整。目前顯示已找到的會話。',
|
|
880
895
|
selectPickerHint: '↑↓ 選擇 · Enter 確認 · Esc 關閉',
|
|
881
896
|
providerConfigured: '已設定',
|
|
882
897
|
addAccount: '新增帳號',
|
|
Binary file
|
|
@@ -61,12 +61,14 @@ export function createDefaultBotChannel(provider) {
|
|
|
61
61
|
}
|
|
62
62
|
export function createDefaultBotChatSettings() {
|
|
63
63
|
return {
|
|
64
|
+
messageHandling: 'task',
|
|
64
65
|
channels: Object.fromEntries(BOT_PROVIDERS.map((provider) => [provider, createDefaultBotChannel(provider)])),
|
|
65
66
|
};
|
|
66
67
|
}
|
|
67
68
|
export function mergeBotChatSettings(current, patch) {
|
|
68
69
|
return {
|
|
69
70
|
...current,
|
|
71
|
+
...(patch?.messageHandling ? { messageHandling: patch.messageHandling } : {}),
|
|
70
72
|
channels: {
|
|
71
73
|
...current.channels,
|
|
72
74
|
...Object.fromEntries(Object.entries(patch?.channels ?? {}).map(([provider, channelPatch]) => {
|
|
@@ -93,6 +95,7 @@ export function mergeBotChatSettings(current, patch) {
|
|
|
93
95
|
}
|
|
94
96
|
export function normalizeBotChatSettings(settings, rawSettings) {
|
|
95
97
|
return {
|
|
98
|
+
messageHandling: rawSettings?.messageHandling === 'workhub' ? 'workhub' : 'task',
|
|
96
99
|
channels: Object.fromEntries(BOT_PROVIDERS.map((provider) => {
|
|
97
100
|
const rawChannel = rawSettings?.channels?.[provider];
|
|
98
101
|
return [provider, normalizeBotChannel(provider, settings.channels[provider], rawChannel)];
|