mixdog 0.9.151 → 0.9.152
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 +110 -68
- package/package.json +3 -2
- package/scripts/tool-stress.mjs +0 -2
- package/src/defaults/skills/setup/SKILL.md +18 -27
- package/src/headless-exec.mjs +3 -10
- package/src/headless-exec.test.mjs +2 -7
- package/src/runtime/agent/orchestrator/config.mjs +6 -66
- package/src/runtime/agent/orchestrator/context/collect-skills.test.mjs +153 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +49 -45
- package/src/runtime/agent/orchestrator/mcp/client.mjs +242 -15
- package/src/runtime/agent/orchestrator/mcp/features.test.mjs +66 -0
- package/src/runtime/agent/orchestrator/mcp/security.test.mjs +36 -20
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +54 -33
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +3 -8
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +10 -2
- package/src/runtime/agent/orchestrator/providers/lib/provider-replay.mjs +50 -0
- package/src/runtime/agent/orchestrator/providers/lib/sse-framing.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +3 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +8 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +25 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +35 -4
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +22 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +0 -2
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +4 -7
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/provider-replay.test.mjs +235 -0
- package/src/runtime/agent/orchestrator/providers/sse-framing.test.mjs +719 -0
- package/src/runtime/agent/orchestrator/providers/stream-json-pool.mjs +399 -39
- package/src/runtime/agent/orchestrator/providers/stream-json-worker.mjs +26 -0
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +14 -4
- package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +24 -1
- package/src/runtime/agent/orchestrator/session/compaction-read-reset.test.mjs +161 -0
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +14 -4
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +12 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +31 -18
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/message-sanitize.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +6 -1
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +556 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.test.mjs +457 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +105 -186
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +115 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +23 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool-cwd-env.test.mjs +14 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -10
- package/src/runtime/agent/orchestrator/tools/builtin/find-search-budget.test.mjs +30 -14
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +15 -4
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +73 -15
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +76 -2
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +24 -683
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +64 -72
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-runner.mjs +2 -5
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.mjs +75 -0
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.test.mjs +24 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +12 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +24 -5
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.test.mjs +44 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +12 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +35 -21
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +28 -13
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.test.mjs +54 -0
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +11 -11
- package/src/runtime/browser-bridge/client.mjs +103 -0
- package/src/runtime/browser-bridge/tool-defs.mjs +42 -0
- package/src/runtime/computer-bridge/client.mjs +101 -0
- package/src/runtime/computer-bridge/tool-defs.mjs +53 -0
- package/src/runtime/media/store.mjs +18 -8
- package/src/runtime/media/store.test.mjs +14 -0
- package/src/runtime/memory/index.mjs +1 -1
- package/src/runtime/memory/lib/transcript-ingest.mjs +8 -6
- package/src/runtime/memory/lib/transcript-ingest.test.mjs +52 -0
- package/src/runtime/shared/child-spawn-gate.mjs +17 -0
- package/src/runtime/shared/child-spawn-remote.mjs +69 -7
- package/src/runtime/shared/child-spawn-remote.test.mjs +124 -0
- package/src/runtime/shared/pristine-execution-contract.json +2 -1
- package/src/runtime/shared/provider-api-key.mjs +1 -0
- package/src/runtime/shared/session-runtime-health.mjs +91 -0
- package/src/runtime/shared/session-runtime-health.test.mjs +53 -0
- package/src/runtime/shared/skill-document.mjs +86 -0
- package/src/runtime/shared/skill-document.test.mjs +81 -0
- package/src/runtime/shared/tool-surface.mjs +18 -0
- package/src/runtime/shared/turn-snapshot-store.mjs +1 -0
- package/src/runtime/shared/turn-snapshot.mjs +32 -9
- package/src/runtime/shared/user-cwd.mjs +33 -14
- package/src/runtime/shared/user-cwd.test.mjs +43 -0
- package/src/session-runtime/cwd-plugins.mjs +26 -26
- package/src/session-runtime/cwd-plugins.test.mjs +75 -0
- package/src/session-runtime/cwd-tool-routing.test.mjs +5 -0
- package/src/session-runtime/global-extensions.mjs +26 -0
- package/src/session-runtime/global-extensions.test.mjs +28 -0
- package/src/session-runtime/goal-runtime.mjs +721 -0
- package/src/session-runtime/goal-runtime.test.mjs +108 -0
- package/src/session-runtime/lifecycle-api.mjs +18 -4
- package/src/session-runtime/lifecycle-api.test.mjs +21 -0
- package/src/session-runtime/mcp-glue.mjs +49 -47
- package/src/session-runtime/plugin-mcp-standard.test.mjs +177 -0
- package/src/session-runtime/plugin-mcp.mjs +96 -22
- package/src/session-runtime/prewarm.mjs +1 -10
- package/src/session-runtime/resource-api-global.test.mjs +69 -0
- package/src/session-runtime/resource-api.mjs +173 -58
- package/src/session-runtime/runtime-core.mjs +148 -20
- package/src/session-runtime/runtime-tunables.mjs +6 -8
- package/src/session-runtime/session-turn-api.mjs +22 -11
- package/src/session-runtime/skills-api.mjs +79 -25
- package/src/session-runtime/skills-api.test.mjs +99 -0
- package/src/session-runtime/tool-catalog-schema.mjs +0 -5
- package/src/session-runtime/tool-catalog.mjs +4 -13
- package/src/session-runtime/tool-policy-surface.test.mjs +33 -11
- package/src/session-runtime/tool-surface.mjs +12 -0
- package/src/standalone/agent-dispatch-broker.mjs +5 -9
- package/src/standalone/agent-tool/notify.mjs +6 -8
- package/src/standalone/agent-tool/notify.test.mjs +37 -0
- package/src/standalone/daemon.mjs +31 -15
- package/src/standalone/hook-bus/config.mjs +1 -0
- package/src/standalone/plugin-admin.mjs +17 -0
- package/src/standalone/plugin-admin.test.mjs +7 -0
- package/src/standalone/provider-admin.mjs +1 -0
- package/src/standalone/session-protocol.mjs +5 -0
- package/src/standalone/session-runtime-agent-control-client.mjs +133 -0
- package/src/standalone/session-runtime-agent-control-client.test.mjs +71 -0
- package/src/standalone/session-runtime-dispatch-cancel.test.mjs +139 -0
- package/src/standalone/session-runtime-host-factory.mjs +17 -0
- package/src/standalone/session-runtime-host-factory.test.mjs +31 -0
- package/src/standalone/session-runtime-host-health.test.mjs +5 -0
- package/src/standalone/session-runtime-host.mjs +696 -58
- package/src/standalone/session-runtime-inline-host.mjs +351 -0
- package/src/standalone/session-runtime-inline-host.test.mjs +83 -0
- package/src/standalone/session-runtime-provider-cooldown.mjs +78 -0
- package/src/standalone/session-runtime-provider-cooldown.test.mjs +80 -0
- package/src/standalone/session-runtime-shard-host.test.mjs +569 -0
- package/src/standalone/session-runtime-shard-router.mjs +149 -0
- package/src/standalone/session-runtime-shard-router.test.mjs +101 -0
- package/src/standalone/session-runtime-worker.mjs +476 -18
- package/src/standalone/session-service.mjs +81 -6
- package/src/tui/app/app-view.jsx +1 -0
- package/src/tui/app/core-memory-picker.mjs +2 -0
- package/src/tui/app/extension-pickers.mjs +1 -1
- package/src/tui/app/maintenance-pickers.mjs +3 -3
- package/src/tui/app/model-options.mjs +2 -0
- package/src/tui/app/model-picker.mjs +14 -4
- package/src/tui/app/panel-handoff.test.mjs +311 -0
- package/src/tui/app/project-picker.mjs +1 -0
- package/src/tui/app/route-pickers.mjs +21 -6
- package/src/tui/app/settings-picker.mjs +26 -3
- package/src/tui/app/slash-commands.mjs +1 -0
- package/src/tui/app/slash-dispatch.mjs +73 -22
- package/src/tui/app/theme-effort-pickers.mjs +7 -2
- package/src/tui/components/Picker.jsx +10 -1
- package/src/tui/dist/index.mjs +163 -44
- package/src/tui/session/context-state.mjs +1 -0
- package/src/tui/session/goal-continuation.mjs +110 -0
- package/src/tui/session/goal-continuation.test.mjs +84 -0
- package/src/tui/session/queue-helpers.mjs +5 -2
- package/src/tui/session/session-action-surface.test.mjs +108 -0
- package/src/tui/session/session-api-ext.mjs +39 -8
- package/src/tui/session/session-api.mjs +58 -0
- package/src/tui/session/session-flow.mjs +6 -0
- package/src/tui/session/turn.mjs +1 -0
- package/src/tui/session-local.mjs +15 -1
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +0 -301
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
import { createGoalContinuation } from './goal-continuation.mjs';
|
|
5
|
+
|
|
6
|
+
const settleImmediate = () => new Promise((resolve) => setImmediate(resolve));
|
|
7
|
+
|
|
8
|
+
test('idle Goal schedules one hidden continuation and user input cancels it', async () => {
|
|
9
|
+
const goal = { id: 'goal-1', status: 'active', objective: 'Finish it' };
|
|
10
|
+
const state = { busy: false, commandBusy: false, sessionId: 'sess_goal', goal };
|
|
11
|
+
const pending = [];
|
|
12
|
+
let listener = null;
|
|
13
|
+
let archived = 0;
|
|
14
|
+
const controller = createGoalContinuation({
|
|
15
|
+
runtime: {
|
|
16
|
+
id: 'sess_goal',
|
|
17
|
+
goalStatus: () => goal,
|
|
18
|
+
goalContinuation: () => ({ run: true, reason: 'idle', goal, prompt: 'hidden continuation' }),
|
|
19
|
+
onGoalStatusChange: (next) => {
|
|
20
|
+
listener = next;
|
|
21
|
+
return () => { listener = null; };
|
|
22
|
+
},
|
|
23
|
+
archiveCompletedGoalOnUserInput: async () => { archived += 1; },
|
|
24
|
+
},
|
|
25
|
+
flags: { disposed: false, pendingSessionReset: false },
|
|
26
|
+
getState: () => state,
|
|
27
|
+
set: (patch) => Object.assign(state, patch),
|
|
28
|
+
getPending: () => pending,
|
|
29
|
+
enqueue: (text, options) => {
|
|
30
|
+
pending.push({ content: text, ...options });
|
|
31
|
+
return true;
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
try {
|
|
35
|
+
assert.equal(controller.scheduleGoalContinuation(), true);
|
|
36
|
+
controller.scheduleGoalContinuation();
|
|
37
|
+
await settleImmediate();
|
|
38
|
+
assert.equal(pending.length, 1);
|
|
39
|
+
assert.equal(pending[0].mode, 'goal-continuation');
|
|
40
|
+
assert.equal(pending[0].priority, 'later');
|
|
41
|
+
assert.equal(controller.shouldRunGoalContinuation(pending[0]), true);
|
|
42
|
+
|
|
43
|
+
controller.archiveCompletedGoalOnUserInput();
|
|
44
|
+
assert.equal(pending.length, 0);
|
|
45
|
+
await settleImmediate();
|
|
46
|
+
assert.equal(archived, 1);
|
|
47
|
+
|
|
48
|
+
listener?.({ sessionId: 'sess_goal', goal: { ...goal, status: 'paused' } });
|
|
49
|
+
await settleImmediate();
|
|
50
|
+
assert.equal(pending.length, 0);
|
|
51
|
+
} finally {
|
|
52
|
+
controller.disposeGoalContinuation();
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('busy or agent-parked Goal does not enqueue a continuation', async () => {
|
|
57
|
+
const goal = { id: 'goal-2', status: 'active' };
|
|
58
|
+
const state = { busy: true, commandBusy: false, sessionId: 'sess_goal_busy', goal };
|
|
59
|
+
const pending = [];
|
|
60
|
+
const controller = createGoalContinuation({
|
|
61
|
+
runtime: {
|
|
62
|
+
id: 'sess_goal_busy',
|
|
63
|
+
goalStatus: () => goal,
|
|
64
|
+
goalContinuation: () => ({ run: false, reason: 'agent-running', goal }),
|
|
65
|
+
onGoalStatusChange: () => () => {},
|
|
66
|
+
},
|
|
67
|
+
flags: { disposed: false, pendingSessionReset: false },
|
|
68
|
+
getState: () => state,
|
|
69
|
+
set: (patch) => Object.assign(state, patch),
|
|
70
|
+
getPending: () => pending,
|
|
71
|
+
enqueue: (text, options) => pending.push({ content: text, ...options }),
|
|
72
|
+
});
|
|
73
|
+
try {
|
|
74
|
+
controller.scheduleGoalContinuation();
|
|
75
|
+
await settleImmediate();
|
|
76
|
+
assert.equal(pending.length, 0);
|
|
77
|
+
state.busy = false;
|
|
78
|
+
controller.onGoalTurnSettled('done');
|
|
79
|
+
await settleImmediate();
|
|
80
|
+
assert.equal(pending.length, 0);
|
|
81
|
+
} finally {
|
|
82
|
+
controller.disposeGoalContinuation();
|
|
83
|
+
}
|
|
84
|
+
});
|
|
@@ -36,7 +36,10 @@ export function defaultQueuePriority(mode) {
|
|
|
36
36
|
|
|
37
37
|
export function isQueuedEntryEditable(entry) {
|
|
38
38
|
const mode = entry?.mode || 'prompt';
|
|
39
|
-
return mode !== 'task-notification'
|
|
39
|
+
return mode !== 'task-notification'
|
|
40
|
+
&& mode !== 'pending-resume'
|
|
41
|
+
&& mode !== 'goal-continuation'
|
|
42
|
+
&& entry?.isMeta !== true;
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
export function isQueuedEntryVisible(entry) {
|
|
@@ -44,7 +47,7 @@ export function isQueuedEntryVisible(entry) {
|
|
|
44
47
|
// task completions stay in the internal pending queue, but should never look
|
|
45
48
|
// like commands typed by the user while they wait to be drained.
|
|
46
49
|
const mode = entry?.mode || 'prompt';
|
|
47
|
-
if (mode === 'pending-resume') return false;
|
|
50
|
+
if (mode === 'pending-resume' || mode === 'goal-continuation') return false;
|
|
48
51
|
return isQueuedEntryEditable(entry);
|
|
49
52
|
}
|
|
50
53
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
import { createSessionApi } from './session-api.mjs';
|
|
5
|
+
import {
|
|
6
|
+
SESSION_CONFIGURE_ACTIONS,
|
|
7
|
+
SESSION_READ_ACTIONS,
|
|
8
|
+
} from '../../standalone/session-protocol.mjs';
|
|
9
|
+
|
|
10
|
+
// The daemon resolves every session action by NAME on the object
|
|
11
|
+
// createLocalSessionRuntime returns — this surface. An action that only exists
|
|
12
|
+
// on the runtime beneath it passes the protocol allowlist and then dies in the
|
|
13
|
+
// session runtime worker as "session action <name> is unavailable" (desktop
|
|
14
|
+
// /inherit shipped exactly that way).
|
|
15
|
+
function stubBag(overrides = {}) {
|
|
16
|
+
const defaults = {
|
|
17
|
+
flags: {},
|
|
18
|
+
autoClearState: {},
|
|
19
|
+
pending: [],
|
|
20
|
+
listeners: new Set(),
|
|
21
|
+
};
|
|
22
|
+
return new Proxy({}, {
|
|
23
|
+
get: (_target, key) => {
|
|
24
|
+
if (key in overrides) return overrides[key];
|
|
25
|
+
if (key in defaults) return defaults[key];
|
|
26
|
+
return () => ({});
|
|
27
|
+
},
|
|
28
|
+
has: () => true,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
test('every advertised session action exists on the session surface', () => {
|
|
33
|
+
const api = createSessionApi(stubBag());
|
|
34
|
+
const missing = [...SESSION_READ_ACTIONS, ...SESSION_CONFIGURE_ACTIONS]
|
|
35
|
+
.filter((action) => typeof api[action] !== 'function');
|
|
36
|
+
assert.deepEqual(missing, []);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('extension editor actions are advertised and forwarded', async () => {
|
|
40
|
+
const calls = [];
|
|
41
|
+
let state = { commandBusy: false, stats: {} };
|
|
42
|
+
const api = createSessionApi(stubBag({
|
|
43
|
+
runtime: {
|
|
44
|
+
getMcpServerConfig: (name) => {
|
|
45
|
+
calls.push(['getMcpServerConfig', name]);
|
|
46
|
+
return { name };
|
|
47
|
+
},
|
|
48
|
+
saveMcpServer: async (input) => {
|
|
49
|
+
calls.push(['saveMcpServer', input]);
|
|
50
|
+
return { name: input.name };
|
|
51
|
+
},
|
|
52
|
+
saveSkill: async (input) => {
|
|
53
|
+
calls.push(['saveSkill', input]);
|
|
54
|
+
return { skill: { name: input.name } };
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
getState: () => state,
|
|
58
|
+
set: (patch) => { state = { ...state, ...patch }; },
|
|
59
|
+
routeState: () => ({}),
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
assert.ok(SESSION_READ_ACTIONS.includes('getMcpServerConfig'));
|
|
63
|
+
assert.ok(SESSION_CONFIGURE_ACTIONS.includes('saveMcpServer'));
|
|
64
|
+
assert.ok(SESSION_CONFIGURE_ACTIONS.includes('saveSkill'));
|
|
65
|
+
assert.deepEqual(api.getMcpServerConfig('UnityMCP'), { name: 'UnityMCP' });
|
|
66
|
+
await api.saveMcpServer({ name: 'UnityMCP' });
|
|
67
|
+
await api.saveSkill({ name: 'unity-helper' });
|
|
68
|
+
assert.deepEqual(calls, [
|
|
69
|
+
['getMcpServerConfig', 'UnityMCP'],
|
|
70
|
+
['saveMcpServer', { name: 'UnityMCP' }],
|
|
71
|
+
['saveSkill', { name: 'unity-helper' }],
|
|
72
|
+
]);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('inheritFrom carries the conversation and rebuilds the heir transcript', async () => {
|
|
76
|
+
const carried = [
|
|
77
|
+
{ role: 'system', content: 'target system prompt' },
|
|
78
|
+
{ role: 'user', content: 'first question' },
|
|
79
|
+
{ role: 'assistant', content: 'first answer' },
|
|
80
|
+
];
|
|
81
|
+
let state = { sessionId: 'sess_heir', stats: {}, items: [] };
|
|
82
|
+
const calls = [];
|
|
83
|
+
const api = createSessionApi(stubBag({
|
|
84
|
+
runtime: {
|
|
85
|
+
inheritFrom: (sourceSessionId) => {
|
|
86
|
+
calls.push(sourceSessionId);
|
|
87
|
+
return { sessionId: 'sess_heir', sourceSessionId, messages: 2 };
|
|
88
|
+
},
|
|
89
|
+
readModelMessages: () => ({ messageCount: carried.length, messages: carried }),
|
|
90
|
+
},
|
|
91
|
+
getState: () => state,
|
|
92
|
+
set: (patch) => { state = { ...state, ...patch }; },
|
|
93
|
+
replaceItems: (items) => items,
|
|
94
|
+
routeState: () => ({}),
|
|
95
|
+
}));
|
|
96
|
+
|
|
97
|
+
const result = await api.inheritFrom('sess_source');
|
|
98
|
+
|
|
99
|
+
assert.deepEqual(calls, ['sess_source']);
|
|
100
|
+
assert.equal(result.messages, 2);
|
|
101
|
+
assert.equal(state.sessionId, 'sess_heir');
|
|
102
|
+
// Only the conversation travels: the target's own system block never becomes
|
|
103
|
+
// a transcript item.
|
|
104
|
+
assert.deepEqual(
|
|
105
|
+
state.items.map((item) => [item.kind, item.text]),
|
|
106
|
+
[['user', 'first question'], ['assistant', 'first answer']],
|
|
107
|
+
);
|
|
108
|
+
});
|
|
@@ -437,9 +437,40 @@ export function sessionContextSnapshotProjection(session, contextStatus) {
|
|
|
437
437
|
|
|
438
438
|
export function createSessionApiB(bag) {
|
|
439
439
|
const {
|
|
440
|
-
runtime, nextId, flags, lifecycle, listeners, getState, set, flushEmitImmediate, disposeEmit, replaceItems, pushNotice, removeNotice, setProgressHint, clearToastTimers, disposeTranscriptSpill, routeState, syncContextStats, finishToolApproval, denyAllToolApprovals, restoreLeadSteeringFromDisk, resetStats, clearUiActivityBeforeContextSync, resetTuiForPendingSessionReset, snapshotTuiBeforeSessionReset, restoreTuiAfterFailedSessionReset, commitTuiSessionReset, resetStatsAndSyncContext,
|
|
440
|
+
runtime, nextId, flags, lifecycle, listeners, getState, set, flushEmitImmediate, disposeEmit, replaceItems, pushNotice, removeNotice, setProgressHint, clearToastTimers, disposeTranscriptSpill, disposeGoalContinuation, routeState, syncContextStats, finishToolApproval, denyAllToolApprovals, restoreLeadSteeringFromDisk, resetStats, clearUiActivityBeforeContextSync, resetTuiForPendingSessionReset, snapshotTuiBeforeSessionReset, restoreTuiAfterFailedSessionReset, commitTuiSessionReset, resetStatsAndSyncContext,
|
|
441
441
|
} = bag;
|
|
442
442
|
const oauthFlows = createSessionOAuthFlowRegistry();
|
|
443
|
+
/**
|
|
444
|
+
* Session inheritance as ONE addressable session action. THIS session is the
|
|
445
|
+
* already-created heir, so only the carry step runs here — the desktop
|
|
446
|
+
* creates and routes the new session first, then calls this by name. The
|
|
447
|
+
* daemon resolves actions on THIS surface, so `inheritFrom` has to live here
|
|
448
|
+
* and not only on the runtime beneath it.
|
|
449
|
+
*/
|
|
450
|
+
const inheritFrom = async (sourceSessionId) => {
|
|
451
|
+
const result = await runtime.inheritFrom(sourceSessionId);
|
|
452
|
+
const sessionId = String(result?.sessionId || runtime.sessionId || getState().sessionId || '');
|
|
453
|
+
// Only model messages travel with the conversation. Rebuild the visible
|
|
454
|
+
// transcript from them right here so the heir opens showing the carried
|
|
455
|
+
// conversation; without it the view stays blank until a cold reopen
|
|
456
|
+
// resumes the session from disk and restores the same items.
|
|
457
|
+
const carried = runtime.readModelMessages?.(0)?.messages;
|
|
458
|
+
const items = restoreTranscriptItems(Array.isArray(carried) ? carried : [], { sessionId });
|
|
459
|
+
resetStatsAndSyncContext();
|
|
460
|
+
set({
|
|
461
|
+
sessionId,
|
|
462
|
+
items: replaceItems(items),
|
|
463
|
+
toasts: [],
|
|
464
|
+
queued: [],
|
|
465
|
+
thinking: null,
|
|
466
|
+
spinner: null,
|
|
467
|
+
lastTurn: null,
|
|
468
|
+
...routeState(),
|
|
469
|
+
stats: { ...getState().stats },
|
|
470
|
+
});
|
|
471
|
+
flushEmitImmediate();
|
|
472
|
+
return result;
|
|
473
|
+
};
|
|
443
474
|
return {
|
|
444
475
|
resolveToolApproval: (id, decision = {}) => {
|
|
445
476
|
const approved = decision === true || decision?.approved === true;
|
|
@@ -639,17 +670,17 @@ export function createSessionApiB(bag) {
|
|
|
639
670
|
getTurnReviewDiff: async (options = {}) => {
|
|
640
671
|
return (await runtime.getTurnReviewDiff?.(options)) ?? { supported: false, files: [], patch: '' };
|
|
641
672
|
},
|
|
642
|
-
revertTurnReview: async () => {
|
|
673
|
+
revertTurnReview: async (checkpointId) => {
|
|
643
674
|
if (typeof runtime.revertTurnReview !== 'function') {
|
|
644
675
|
throw new Error('Turn review revert is unavailable');
|
|
645
676
|
}
|
|
646
|
-
return await runtime.revertTurnReview();
|
|
677
|
+
return await runtime.revertTurnReview(checkpointId);
|
|
647
678
|
},
|
|
648
|
-
revertTurnReviewFile: async (file) => {
|
|
679
|
+
revertTurnReviewFile: async (file, checkpointId) => {
|
|
649
680
|
if (typeof runtime.revertTurnReviewFile !== 'function') {
|
|
650
681
|
throw new Error('Turn review revert is unavailable');
|
|
651
682
|
}
|
|
652
|
-
return await runtime.revertTurnReviewFile(file);
|
|
683
|
+
return await runtime.revertTurnReviewFile(file, checkpointId);
|
|
653
684
|
},
|
|
654
685
|
getOnboardingStatus: () => {
|
|
655
686
|
return runtime.getOnboardingStatus?.() || { completed: true, workflowRoutes: {} };
|
|
@@ -915,6 +946,7 @@ export function createSessionApiB(bag) {
|
|
|
915
946
|
* conversation into it. The source session file is left as it is, so the
|
|
916
947
|
* two transcripts share a prefix and then diverge.
|
|
917
948
|
*/
|
|
949
|
+
inheritFrom,
|
|
918
950
|
inheritSession: async () => {
|
|
919
951
|
if (getState().commandBusy) return false;
|
|
920
952
|
const sourceId = getState().sessionId || null;
|
|
@@ -922,9 +954,7 @@ export function createSessionApiB(bag) {
|
|
|
922
954
|
set({ commandBusy: true });
|
|
923
955
|
try {
|
|
924
956
|
await runtime.newSession();
|
|
925
|
-
|
|
926
|
-
set({ sessionId: runtime.sessionId ?? getState().sessionId });
|
|
927
|
-
return result;
|
|
957
|
+
return await inheritFrom(sourceId);
|
|
928
958
|
} finally {
|
|
929
959
|
set({ commandBusy: false });
|
|
930
960
|
}
|
|
@@ -1071,6 +1101,7 @@ export function createSessionApiB(bag) {
|
|
|
1071
1101
|
try { runtime.clearSessionPresence?.(); } catch { /* best-effort */ }
|
|
1072
1102
|
clearToastTimers();
|
|
1073
1103
|
disposeTranscriptSpill?.();
|
|
1104
|
+
disposeGoalContinuation?.();
|
|
1074
1105
|
try { clearInterval(lifecycle.runtimePulseTimer); } catch {}
|
|
1075
1106
|
try { lifecycle.unsubscribeRuntimeNotifications?.(); } catch {}
|
|
1076
1107
|
lifecycle.unsubscribeRuntimeNotifications = null;
|
|
@@ -85,6 +85,9 @@ export function createSessionApiA(bag) {
|
|
|
85
85
|
// turn or the background task. The returned running snapshot creates the
|
|
86
86
|
// normal post-tool boundary, where this queued prompt is injected.
|
|
87
87
|
if (accepted !== false) {
|
|
88
|
+
if ((intake.queueOptions.mode || 'prompt') === 'prompt') {
|
|
89
|
+
bag.archiveCompletedGoalOnUserInput?.();
|
|
90
|
+
}
|
|
88
91
|
try { runtime.interruptTaskWait?.('user-message'); } catch {}
|
|
89
92
|
}
|
|
90
93
|
return accepted;
|
|
@@ -381,6 +384,18 @@ export function createSessionApiA(bag) {
|
|
|
381
384
|
set({ commandBusy: false });
|
|
382
385
|
}
|
|
383
386
|
},
|
|
387
|
+
// Background-task control (list/read/monitor/cancel) is a CONTROL surface,
|
|
388
|
+
// not a conversation turn: the desktop stop button fires it WHILE a turn
|
|
389
|
+
// runs, so it neither waits on commandBusy nor leaves a transcript card.
|
|
390
|
+
// It must exist here because the daemon addresses actions on this surface.
|
|
391
|
+
taskControl: (args = {}) => {
|
|
392
|
+
return runtime.taskControl?.(args) ?? null;
|
|
393
|
+
},
|
|
394
|
+
goalControl: async (args = {}) => {
|
|
395
|
+
const result = await runtime.goalControl?.(args);
|
|
396
|
+
set({ goal: runtime.goalStatus?.() || result?.goal || null });
|
|
397
|
+
return result;
|
|
398
|
+
},
|
|
384
399
|
toolsStatus: (query = '') => {
|
|
385
400
|
return runtime.toolsStatus?.(query) || { mode: getState().toolMode, count: 0, activeCount: 0, tools: [] };
|
|
386
401
|
},
|
|
@@ -428,6 +443,9 @@ export function createSessionApiA(bag) {
|
|
|
428
443
|
mcpStatus: () => {
|
|
429
444
|
return runtime.mcpStatus?.() || { servers: [], configuredCount: 0, connectedCount: 0, failedCount: 0 };
|
|
430
445
|
},
|
|
446
|
+
getMcpServerConfig: (name) => {
|
|
447
|
+
return runtime.getMcpServerConfig?.(name);
|
|
448
|
+
},
|
|
431
449
|
reconnectMcp: async () => {
|
|
432
450
|
if (getState().commandBusy) return null;
|
|
433
451
|
set({ commandBusy: true });
|
|
@@ -457,6 +475,19 @@ export function createSessionApiA(bag) {
|
|
|
457
475
|
set({ commandBusy: false });
|
|
458
476
|
}
|
|
459
477
|
},
|
|
478
|
+
saveMcpServer: async (input) => {
|
|
479
|
+
if (getState().commandBusy) return null;
|
|
480
|
+
set({ commandBusy: true });
|
|
481
|
+
try {
|
|
482
|
+
const result = await runtime.saveMcpServer?.(input);
|
|
483
|
+
resetStatsAndSyncContext();
|
|
484
|
+
set({ ...routeState(), stats: { ...getState().stats } });
|
|
485
|
+
pushNotice(`mcp saved: ${result?.name || input?.name || 'server'}`, 'info');
|
|
486
|
+
return result;
|
|
487
|
+
} finally {
|
|
488
|
+
set({ commandBusy: false });
|
|
489
|
+
}
|
|
490
|
+
},
|
|
460
491
|
removeMcpServer: async (name) => {
|
|
461
492
|
if (getState().commandBusy) return null;
|
|
462
493
|
set({ commandBusy: true });
|
|
@@ -514,6 +545,19 @@ export function createSessionApiA(bag) {
|
|
|
514
545
|
set({ commandBusy: false });
|
|
515
546
|
}
|
|
516
547
|
},
|
|
548
|
+
saveSkill: async (input) => {
|
|
549
|
+
if (getState().commandBusy) return null;
|
|
550
|
+
set({ commandBusy: true });
|
|
551
|
+
try {
|
|
552
|
+
const result = await runtime.saveSkill?.(input);
|
|
553
|
+
resetStatsAndSyncContext();
|
|
554
|
+
set({ ...routeState(), stats: { ...getState().stats } });
|
|
555
|
+
pushNotice(`skill saved: ${result?.skill?.name || input?.name || 'skill'}`, 'info');
|
|
556
|
+
return result;
|
|
557
|
+
} finally {
|
|
558
|
+
set({ commandBusy: false });
|
|
559
|
+
}
|
|
560
|
+
},
|
|
517
561
|
reloadSkills: async () => {
|
|
518
562
|
if (getState().commandBusy) return null;
|
|
519
563
|
set({ commandBusy: true });
|
|
@@ -569,6 +613,20 @@ export function createSessionApiA(bag) {
|
|
|
569
613
|
set({ commandBusy: false });
|
|
570
614
|
}
|
|
571
615
|
},
|
|
616
|
+
setPluginEnabled: async (plugin, enabled) => {
|
|
617
|
+
if (getState().commandBusy) return null;
|
|
618
|
+
set({ commandBusy: true });
|
|
619
|
+
try {
|
|
620
|
+
const result = await runtime.setPluginEnabled?.(plugin, enabled);
|
|
621
|
+
resetStatsAndSyncContext();
|
|
622
|
+
set({ ...routeState(), stats: { ...getState().stats } });
|
|
623
|
+
pushNotice(`plugin ${enabled === false ? 'disabled' : 'enabled'}: ${
|
|
624
|
+
result?.plugin?.title || result?.plugin?.name || plugin?.name || plugin}`, 'info');
|
|
625
|
+
return result;
|
|
626
|
+
} finally {
|
|
627
|
+
set({ commandBusy: false });
|
|
628
|
+
}
|
|
629
|
+
},
|
|
572
630
|
removePlugin: async (plugin) => {
|
|
573
631
|
if (getState().commandBusy) return null;
|
|
574
632
|
set({ commandBusy: true });
|
|
@@ -88,6 +88,8 @@ export function createSessionFlow(bag) {
|
|
|
88
88
|
: [],
|
|
89
89
|
steeringPersistId: options.steeringPersistId || null,
|
|
90
90
|
steeringPersistRestored: options.steeringPersistRestored === true,
|
|
91
|
+
isMeta: options.isMeta === true,
|
|
92
|
+
goalId: options.goalId || null,
|
|
91
93
|
};
|
|
92
94
|
}
|
|
93
95
|
|
|
@@ -215,6 +217,10 @@ export function createSessionFlow(bag) {
|
|
|
215
217
|
});
|
|
216
218
|
firstBatch = false;
|
|
217
219
|
if (batch.length === 0) break;
|
|
220
|
+
if (batch[0]?.mode === 'goal-continuation'
|
|
221
|
+
&& bag.shouldRunGoalContinuation?.(batch[0]) !== true) {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
218
224
|
tuiDebug(`busy-queue drain batch=${batch.length} remaining=${pending.length}`);
|
|
219
225
|
const ids = new Set(batch.map((e) => e.id));
|
|
220
226
|
const merged = mergePromptContents(batch);
|
package/src/tui/session/turn.mjs
CHANGED
|
@@ -1384,6 +1384,7 @@ export function createRunTurn(bag) {
|
|
|
1384
1384
|
flushEmit?.();
|
|
1385
1385
|
_publishedThinkingActive = false; // turn teardown cleared getState().thinking
|
|
1386
1386
|
const finalStatus = cancelled ? 'cancelled' : (failed ? 'failed' : 'done');
|
|
1387
|
+
try { bag.onGoalTurnSettled?.(finalStatus); } catch {}
|
|
1387
1388
|
tuiDebug(`runTurn end turn=${turnIndex} status=${finalStatus} elapsedMs=${Date.now() - startedAt} pending=${pending.length}`);
|
|
1388
1389
|
return finalStatus;
|
|
1389
1390
|
}
|
|
@@ -31,6 +31,7 @@ import { listThemes, getThemeSetting, setThemeSetting } from './theme.mjs';
|
|
|
31
31
|
import { resetAllStreamingMarkdownStablePrefixes } from './markdown/streaming-markdown.mjs';
|
|
32
32
|
import { bootProfile } from './session/boot-profile.mjs';
|
|
33
33
|
import { createSessionStats, applyUsageDelta } from './session/session-stats.mjs';
|
|
34
|
+
import { createGoalContinuation } from './session/goal-continuation.mjs';
|
|
34
35
|
import {
|
|
35
36
|
pickVerb,
|
|
36
37
|
pickDoneVerb,
|
|
@@ -896,6 +897,14 @@ export async function createLocalSessionRuntime({
|
|
|
896
897
|
kickExecutionPendingResume, flushDeferredExecutionPendingResumeKick, scheduleExecutionPendingResumeKick, discardExecutionPendingResume, updateAgentJobCard, subscribeRuntimeNotifications,
|
|
897
898
|
});
|
|
898
899
|
Object.assign(bag, createSessionFlow(bag));
|
|
900
|
+
Object.assign(bag, createGoalContinuation({
|
|
901
|
+
runtime,
|
|
902
|
+
flags,
|
|
903
|
+
getState: () => state,
|
|
904
|
+
set,
|
|
905
|
+
getPending: () => pending,
|
|
906
|
+
enqueue: (...args) => bag.enqueue(...args),
|
|
907
|
+
}));
|
|
899
908
|
bag.runTurn = createRunTurn(bag);
|
|
900
909
|
const api = createSessionApi(bag);
|
|
901
910
|
// Cross-surface share: presence + the durable pending spool remain the
|
|
@@ -1069,6 +1078,9 @@ export async function createLocalSessionRuntime({
|
|
|
1069
1078
|
const base = api[method].bind(api);
|
|
1070
1079
|
api[method] = async (...args) => {
|
|
1071
1080
|
const result = await base(...args);
|
|
1081
|
+
bag.cancelQueuedGoalContinuations?.();
|
|
1082
|
+
bag.refreshGoalState?.();
|
|
1083
|
+
bag.scheduleGoalContinuation?.();
|
|
1072
1084
|
reconcileLiveShareNow();
|
|
1073
1085
|
if (method === 'resume' && result === true && state.sessionRemoteAttached) {
|
|
1074
1086
|
const id = String(state.sessionId || '');
|
|
@@ -1165,6 +1177,8 @@ export async function createLocalSessionRuntime({
|
|
|
1165
1177
|
} catch { /* best-effort */ }
|
|
1166
1178
|
}, 3000);
|
|
1167
1179
|
remoteAttachTimer.unref?.();
|
|
1168
|
-
void bag.restoreLeadSteeringFromDisk()
|
|
1180
|
+
void Promise.resolve(bag.restoreLeadSteeringFromDisk())
|
|
1181
|
+
.catch(() => {})
|
|
1182
|
+
.finally(() => bag.scheduleGoalContinuation?.());
|
|
1169
1183
|
return api;
|
|
1170
1184
|
}
|