mixdog 0.9.52 → 0.9.53
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/package.json +1 -1
- package/scripts/bench-run.mjs +2 -2
- package/scripts/compact-pressure-test.mjs +104 -0
- package/scripts/desktop-session-bridge-test.mjs +704 -0
- package/scripts/freevar-smoke.mjs +7 -4
- package/scripts/lifecycle-api-test.mjs +65 -4
- package/scripts/max-output-recovery-test.mjs +31 -0
- package/scripts/memory-core-input-test.mjs +10 -0
- package/scripts/openai-oauth-ws-1006-retry-test.mjs +63 -3
- package/scripts/openai-ws-early-settle-test.mjs +40 -0
- package/scripts/parent-abort-link-test.mjs +24 -0
- package/scripts/process-lifecycle-test.mjs +80 -22
- package/scripts/provider-contract-test.mjs +257 -0
- package/scripts/provider-toolcall-test.mjs +172 -10
- package/scripts/session-orphan-sweep-test.mjs +27 -1
- package/src/lib/keychain-cjs.cjs +36 -23
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +1 -13
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +7 -8
- package/src/runtime/agent/orchestrator/agent-trace.mjs +33 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +7 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +14 -300
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +2 -4
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +18 -266
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +18 -1
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +15 -130
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +5 -115
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +224 -0
- package/src/runtime/agent/orchestrator/providers/lib/env-utils.mjs +6 -0
- package/src/runtime/agent/orchestrator/providers/lib/gemini-model-catalog.mjs +119 -0
- package/src/runtime/agent/orchestrator/providers/lib/grok-tool-schema.mjs +109 -0
- package/src/runtime/agent/orchestrator/providers/lib/openai-tool-args.mjs +70 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +14 -71
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +47 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +1 -7
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +72 -77
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +15 -20
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +10 -10
- package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +30 -3
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +37 -28
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +5 -7
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/context-compaction-policy.mjs +170 -0
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +20 -223
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +11 -17
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +12 -5
- package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +21 -5
- package/src/runtime/agent/orchestrator/session/manager/prefetch-bridge.mjs +3 -58
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +24 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +57 -16
- package/src/runtime/agent/orchestrator/session/save-session-worker.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +52 -0
- package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +62 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +305 -127
- package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +46 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/search-grep-chunks.mjs +173 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/search-input-helpers.mjs +117 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-insights.mjs +199 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-spawn-helpers.mjs +107 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +1 -40
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +19 -277
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +22 -298
- package/src/runtime/agent/orchestrator/tools/lib/shell-spawn-retry.mjs +67 -0
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +1 -71
- package/src/runtime/channels/backends/discord-gateway.mjs +1 -1
- package/src/runtime/channels/backends/discord.mjs +6 -6
- package/src/runtime/channels/lib/config.mjs +15 -2
- package/src/runtime/channels/lib/memory-client.mjs +20 -3
- package/src/runtime/channels/lib/owned-runtime.mjs +19 -12
- package/src/runtime/channels/lib/status-snapshot.mjs +9 -0
- package/src/runtime/channels/lib/tool-dispatch.mjs +9 -5
- package/src/runtime/channels/lib/worker-main.mjs +16 -5
- package/src/runtime/memory/index.mjs +24 -198
- package/src/runtime/memory/lib/memory-action-handlers.mjs +2 -53
- package/src/runtime/memory/lib/memory-daemon-lifecycle.mjs +115 -0
- package/src/runtime/memory/lib/memory-port-advertiser.mjs +105 -0
- package/src/runtime/memory/lib/pg/supervisor.mjs +0 -4
- package/src/runtime/memory/lib/query-handlers.mjs +2 -122
- package/src/runtime/memory/lib/query-maintenance-handlers.mjs +126 -0
- package/src/runtime/memory/lib/tool-call-handler.mjs +57 -0
- package/src/runtime/search/lib/http-fetch.mjs +1 -1
- package/src/runtime/shared/config.mjs +58 -13
- package/src/runtime/shared/llm/cost.mjs +14 -4
- package/src/runtime/shared/memory-snapshot.mjs +236 -0
- package/src/runtime/shared/process-lifecycle.mjs +92 -19
- package/src/runtime/shared/process-shutdown.mjs +6 -0
- package/src/runtime/shared/resource-admission.mjs +7 -2
- package/src/session-runtime/channel-config-api.mjs +7 -7
- package/src/session-runtime/config-lifecycle.mjs +20 -17
- package/src/session-runtime/cwd-plugins.mjs +9 -7
- package/src/session-runtime/env.mjs +1 -2
- package/src/session-runtime/hitch-profile.mjs +45 -0
- package/src/session-runtime/lifecycle-api.mjs +36 -9
- package/src/session-runtime/mcp-glue.mjs +6 -11
- package/src/session-runtime/provider-init-key.mjs +17 -0
- package/src/session-runtime/runtime-core.mjs +44 -103
- package/src/session-runtime/runtime-paths.mjs +20 -0
- package/src/session-runtime/runtime-tool-routing.mjs +55 -0
- package/src/session-runtime/tool-catalog-data.mjs +51 -0
- package/src/session-runtime/tool-catalog.mjs +15 -89
- package/src/standalone/agent-tool/spawn-preset.mjs +73 -0
- package/src/standalone/agent-tool/worker-rows.mjs +93 -0
- package/src/standalone/agent-tool.mjs +12 -162
- package/src/standalone/channel-admin.mjs +29 -0
- package/src/tui/App.jsx +11 -5
- package/src/tui/dist/index.mjs +202 -65
- package/src/tui/engine/session-api-ext.mjs +37 -4
- package/src/tui/index.jsx +1 -0
- package/src/tui/lib/voice-setup.mjs +5 -5
- package/scripts/_devtools-stub.mjs +0 -1
- package/src/runtime/lib/keychain-cjs.cjs +0 -288
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { isKnownProvider } from '../provider-admin.mjs';
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_AGENT_PRESETS,
|
|
4
|
+
DEFAULT_PROVIDER,
|
|
5
|
+
} from './tool-def.mjs';
|
|
6
|
+
import {
|
|
7
|
+
agentPresetName,
|
|
8
|
+
clean,
|
|
9
|
+
findPreset,
|
|
10
|
+
normalizeAgentName,
|
|
11
|
+
normalizeAgentRoute,
|
|
12
|
+
synthesizePreset,
|
|
13
|
+
} from './helpers.mjs';
|
|
14
|
+
|
|
15
|
+
export function resolveAgentSpawnPreset(config, args = {}) {
|
|
16
|
+
if (args.provider && args.model) {
|
|
17
|
+
return {
|
|
18
|
+
presetName: args.preset || '__direct__',
|
|
19
|
+
preset: {
|
|
20
|
+
id: '__direct__',
|
|
21
|
+
name: '__DIRECT__',
|
|
22
|
+
type: 'agent',
|
|
23
|
+
provider: clean(args.provider),
|
|
24
|
+
model: clean(args.model),
|
|
25
|
+
effort: clean(args.effort) || undefined,
|
|
26
|
+
fast: args.fast === true,
|
|
27
|
+
tools: 'full',
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const agentName = normalizeAgentName(args.agent);
|
|
33
|
+
const configuredDefault = clean(config?.defaultProvider);
|
|
34
|
+
const fallbackProvider = configuredDefault && isKnownProvider(configuredDefault)
|
|
35
|
+
? configuredDefault
|
|
36
|
+
: DEFAULT_PROVIDER;
|
|
37
|
+
const workflowSlot = agentName === 'explore' ? 'explorer'
|
|
38
|
+
: (agentName === 'maintainer' ? 'memory' : '');
|
|
39
|
+
const maintenanceSlot = agentName === 'explore' ? 'explore'
|
|
40
|
+
: (agentName === 'maintainer' ? 'memory' : '');
|
|
41
|
+
const agentRoute = !clean(args.preset)
|
|
42
|
+
? (normalizeAgentRoute(config?.agents?.[agentName], fallbackProvider)
|
|
43
|
+
|| (agentName === 'maintainer' ? normalizeAgentRoute(config?.agents?.maintenance, fallbackProvider) : null)
|
|
44
|
+
|| normalizeAgentRoute(config?.workflowRoutes?.[workflowSlot], fallbackProvider)
|
|
45
|
+
|| normalizeAgentRoute(config?.maintenance?.[maintenanceSlot], fallbackProvider))
|
|
46
|
+
: null;
|
|
47
|
+
if (agentRoute) {
|
|
48
|
+
return {
|
|
49
|
+
presetName: agentPresetName(agentName),
|
|
50
|
+
preset: {
|
|
51
|
+
id: `agent-${agentName}`,
|
|
52
|
+
name: agentPresetName(agentName),
|
|
53
|
+
type: 'agent',
|
|
54
|
+
provider: agentRoute.provider,
|
|
55
|
+
model: agentRoute.model,
|
|
56
|
+
effort: agentRoute.effort,
|
|
57
|
+
fast: agentRoute.fast === true,
|
|
58
|
+
tools: 'full',
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const mainPreset = !clean(args.preset) && (agentName === 'explore' || agentName === 'maintainer')
|
|
64
|
+
? findPreset(config, config?.default)
|
|
65
|
+
: null;
|
|
66
|
+
if (mainPreset) return { presetName: mainPreset.id || mainPreset.name, preset: mainPreset };
|
|
67
|
+
|
|
68
|
+
const presetName = clean(args.preset) || DEFAULT_AGENT_PRESETS[agentName];
|
|
69
|
+
if (!presetName) throw new Error(`agent: agent "${agentName}" has no model assignment`);
|
|
70
|
+
const preset = findPreset(config, presetName) || synthesizePreset(config, presetName);
|
|
71
|
+
if (!preset) throw new Error(`agent: preset "${presetName}" not found`);
|
|
72
|
+
return { presetName, preset };
|
|
73
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { clean, positiveInt } from './helpers.mjs';
|
|
2
|
+
|
|
3
|
+
export const TAG_TOMBSTONE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
4
|
+
const MAX_TAG_TOMBSTONES = 500;
|
|
5
|
+
|
|
6
|
+
export function workerRowKey(row = {}) {
|
|
7
|
+
return clean(row.sessionId) || clean(row.tag);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function workerRowTime(row = {}) {
|
|
11
|
+
return Date.parse(row.updatedAt || row.finishedAt || row.lastUsedAt || row.createdAt || '') || 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isTerminalWorkerStatus(status) {
|
|
15
|
+
return /^(idle|closed|completed|failed|error|cancelled|canceled|killed|timeout)$/i.test(clean(status));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function tagTombstoneKey(row = {}) {
|
|
19
|
+
return `${positiveInt(row.clientHostPid) || 0}\0${clean(row.tag)}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function normalizeTagTombstones(value, { cap = true, priorityKeys = null } = {}) {
|
|
23
|
+
const source = Array.isArray(value?.tombstones)
|
|
24
|
+
? value.tombstones
|
|
25
|
+
: (value?.tombstones && typeof value.tombstones === 'object'
|
|
26
|
+
? Object.values(value.tombstones)
|
|
27
|
+
: []);
|
|
28
|
+
const now = Date.now();
|
|
29
|
+
const cutoff = now - TAG_TOMBSTONE_TTL_MS;
|
|
30
|
+
const rows = source
|
|
31
|
+
.filter((row) => row && typeof row === 'object')
|
|
32
|
+
.map((row) => {
|
|
33
|
+
const parsedReapedAt = Date.parse(clean(row.reapedAt)) || 0;
|
|
34
|
+
return {
|
|
35
|
+
tag: clean(row.tag),
|
|
36
|
+
agent: clean(row.agent) || null,
|
|
37
|
+
cwd: clean(row.cwd) || null,
|
|
38
|
+
clientHostPid: positiveInt(row.clientHostPid),
|
|
39
|
+
// A future clock must not outrank tombstones created by this process.
|
|
40
|
+
reapedAt: parsedReapedAt ? new Date(Math.min(parsedReapedAt, now)).toISOString() : null,
|
|
41
|
+
};
|
|
42
|
+
})
|
|
43
|
+
.filter((row) => row.tag && row.reapedAt && (Date.parse(row.reapedAt) || 0) >= cutoff)
|
|
44
|
+
.sort((a, b) => {
|
|
45
|
+
const aPriority = priorityKeys?.has(tagTombstoneKey(a)) ? 1 : 0;
|
|
46
|
+
const bPriority = priorityKeys?.has(tagTombstoneKey(b)) ? 1 : 0;
|
|
47
|
+
return bPriority - aPriority
|
|
48
|
+
|| (Date.parse(b.reapedAt) || 0) - (Date.parse(a.reapedAt) || 0);
|
|
49
|
+
});
|
|
50
|
+
return cap ? rows.slice(0, MAX_TAG_TOMBSTONES) : rows;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function applyWorkerRowUpsert(byKey, normalized) {
|
|
54
|
+
if (!normalized) return;
|
|
55
|
+
const key = workerRowKey(normalized);
|
|
56
|
+
if (!key) return;
|
|
57
|
+
const prev = byKey.get(key) || {};
|
|
58
|
+
const merged = { ...prev, ...normalized };
|
|
59
|
+
for (const field of ['agent', 'provider', 'model', 'preset', 'effort', 'fast', 'clientHostPid', 'cwd', 'task_id', 'permission', 'toolPermission']) {
|
|
60
|
+
if ((merged[field] === null || merged[field] === '') && prev[field] != null && prev[field] !== '') {
|
|
61
|
+
merged[field] = prev[field];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
byKey.set(key, {
|
|
65
|
+
...merged,
|
|
66
|
+
createdAt: normalized.createdAt || prev.createdAt || new Date().toISOString(),
|
|
67
|
+
updatedAt: normalized.updatedAt || new Date().toISOString(),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function workerRowToSession(row = {}) {
|
|
72
|
+
return {
|
|
73
|
+
id: row.sessionId,
|
|
74
|
+
agentTag: row.tag,
|
|
75
|
+
agent: row.agent || null,
|
|
76
|
+
provider: row.provider || null,
|
|
77
|
+
model: row.model || null,
|
|
78
|
+
presetName: row.preset || null,
|
|
79
|
+
effort: row.effort || null,
|
|
80
|
+
fast: row.fast === true,
|
|
81
|
+
status: row.status || 'idle',
|
|
82
|
+
stage: row.stage || row.status || 'idle',
|
|
83
|
+
createdAt: row.createdAt || null,
|
|
84
|
+
updatedAt: row.updatedAt || null,
|
|
85
|
+
lastUsedAt: row.lastUsedAt || null,
|
|
86
|
+
clientHostPid: row.clientHostPid || null,
|
|
87
|
+
cwd: row.cwd || null,
|
|
88
|
+
permission: row.permission || null,
|
|
89
|
+
toolPermission: row.toolPermission || null,
|
|
90
|
+
messageCount: Math.max(0, Number(row.messages || 0)),
|
|
91
|
+
toolCount: Math.max(0, Number(row.tools || 0)),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -28,32 +28,25 @@ import {
|
|
|
28
28
|
} from '../runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs';
|
|
29
29
|
import { buildAgentTaskProgressFields } from './agent-task-status.mjs';
|
|
30
30
|
import { AGENT_OWNER } from '../runtime/agent/orchestrator/agent-owner.mjs';
|
|
31
|
-
import { isKnownProvider } from './provider-admin.mjs';
|
|
32
31
|
import {
|
|
33
32
|
ACTIVE_STAGES,
|
|
34
33
|
AGENT_TOOL,
|
|
35
|
-
DEFAULT_AGENT_PRESETS,
|
|
36
|
-
DEFAULT_PROVIDER,
|
|
37
34
|
WORKER_INDEX_FILE,
|
|
38
35
|
} from './agent-tool/tool-def.mjs';
|
|
39
36
|
import {
|
|
40
|
-
agentPresetName,
|
|
41
37
|
agentScope,
|
|
42
38
|
agentTagOf,
|
|
43
39
|
clean,
|
|
44
40
|
clearAgentStatuslineRoute,
|
|
45
41
|
envTimeoutMs,
|
|
46
|
-
findPreset,
|
|
47
42
|
nonNegativeInt,
|
|
48
43
|
normalizeAgentName,
|
|
49
|
-
normalizeAgentRoute,
|
|
50
44
|
positiveInt,
|
|
51
45
|
presetKey,
|
|
52
46
|
readAgentFrontmatterPermission,
|
|
53
47
|
resolvePrompt,
|
|
54
48
|
rowMatchesContext,
|
|
55
49
|
sessionMatchesContext,
|
|
56
|
-
synthesizePreset,
|
|
57
50
|
terminalPidForContext,
|
|
58
51
|
withCwdHeader,
|
|
59
52
|
writeAgentStatuslineRoute,
|
|
@@ -61,10 +54,22 @@ import {
|
|
|
61
54
|
import { abnormalEmptyFinishError, renderResult } from './agent-tool/render.mjs';
|
|
62
55
|
import { createProviderInit } from './agent-tool/provider-init.mjs';
|
|
63
56
|
import { createNotify } from './agent-tool/notify.mjs';
|
|
57
|
+
import { resolveAgentSpawnPreset } from './agent-tool/spawn-preset.mjs';
|
|
58
|
+
import {
|
|
59
|
+
TAG_TOMBSTONE_TTL_MS,
|
|
60
|
+
applyWorkerRowUpsert,
|
|
61
|
+
isTerminalWorkerStatus,
|
|
62
|
+
normalizeTagTombstones,
|
|
63
|
+
tagTombstoneKey,
|
|
64
|
+
workerRowKey,
|
|
65
|
+
workerRowTime,
|
|
66
|
+
workerRowToSession,
|
|
67
|
+
} from './agent-tool/worker-rows.mjs';
|
|
64
68
|
import { resolveAgentTerminalReapMs } from '../session-runtime/config-helpers.mjs';
|
|
65
69
|
// Re-export the static tool descriptor so importers of this facade keep the
|
|
66
70
|
// identical public surface (`import { AGENT_TOOL } from './agent-tool.mjs'`).
|
|
67
71
|
export { AGENT_TOOL };
|
|
72
|
+
export { resolveAgentSpawnPreset } from './agent-tool/spawn-preset.mjs';
|
|
68
73
|
|
|
69
74
|
ensureProcessListenerHeadroom(64);
|
|
70
75
|
|
|
@@ -77,72 +82,6 @@ const STANDALONE_SOURCE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)),
|
|
|
77
82
|
// cap and restore strictly-unbounded prep.
|
|
78
83
|
const DEFAULT_SPAWN_PREP_TIMEOUT_MS = envTimeoutMs('MIXDOG_AGENT_SPAWN_PREP_TIMEOUT_MS', 120_000);
|
|
79
84
|
|
|
80
|
-
const TAG_TOMBSTONE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
81
|
-
const MAX_TAG_TOMBSTONES = 500;
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Resolve the route for a public agent spawn. Explore and Maintainer inherit
|
|
85
|
-
* Main only after their explicit agent, workflow, and maintenance routes.
|
|
86
|
-
*/
|
|
87
|
-
export function resolveAgentSpawnPreset(config, args = {}) {
|
|
88
|
-
if (args.provider && args.model) {
|
|
89
|
-
return {
|
|
90
|
-
presetName: args.preset || '__direct__',
|
|
91
|
-
preset: {
|
|
92
|
-
id: '__direct__',
|
|
93
|
-
name: '__DIRECT__',
|
|
94
|
-
type: 'agent',
|
|
95
|
-
provider: clean(args.provider),
|
|
96
|
-
model: clean(args.model),
|
|
97
|
-
effort: clean(args.effort) || undefined,
|
|
98
|
-
fast: args.fast === true,
|
|
99
|
-
tools: 'full',
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const agentName = normalizeAgentName(args.agent);
|
|
105
|
-
const configuredDefault = clean(config?.defaultProvider);
|
|
106
|
-
const fallbackProvider = configuredDefault && isKnownProvider(configuredDefault)
|
|
107
|
-
? configuredDefault
|
|
108
|
-
: DEFAULT_PROVIDER;
|
|
109
|
-
const workflowSlot = agentName === 'explore' ? 'explorer'
|
|
110
|
-
: (agentName === 'maintainer' ? 'memory' : '');
|
|
111
|
-
const maintenanceSlot = agentName === 'explore' ? 'explore'
|
|
112
|
-
: (agentName === 'maintainer' ? 'memory' : '');
|
|
113
|
-
const agentRoute = !clean(args.preset)
|
|
114
|
-
? (normalizeAgentRoute(config?.agents?.[agentName], fallbackProvider)
|
|
115
|
-
|| (agentName === 'maintainer' ? normalizeAgentRoute(config?.agents?.maintenance, fallbackProvider) : null)
|
|
116
|
-
|| normalizeAgentRoute(config?.workflowRoutes?.[workflowSlot], fallbackProvider)
|
|
117
|
-
|| normalizeAgentRoute(config?.maintenance?.[maintenanceSlot], fallbackProvider))
|
|
118
|
-
: null;
|
|
119
|
-
if (agentRoute) {
|
|
120
|
-
return {
|
|
121
|
-
presetName: agentPresetName(agentName),
|
|
122
|
-
preset: {
|
|
123
|
-
id: `agent-${agentName}`,
|
|
124
|
-
name: agentPresetName(agentName),
|
|
125
|
-
type: 'agent',
|
|
126
|
-
provider: agentRoute.provider,
|
|
127
|
-
model: agentRoute.model,
|
|
128
|
-
effort: agentRoute.effort,
|
|
129
|
-
fast: agentRoute.fast === true,
|
|
130
|
-
tools: 'full',
|
|
131
|
-
},
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const mainPreset = !clean(args.preset) && (agentName === 'explore' || agentName === 'maintainer')
|
|
136
|
-
? findPreset(config, config?.default)
|
|
137
|
-
: null;
|
|
138
|
-
if (mainPreset) return { presetName: mainPreset.id || mainPreset.name, preset: mainPreset };
|
|
139
|
-
|
|
140
|
-
const presetName = clean(args.preset) || DEFAULT_AGENT_PRESETS[agentName];
|
|
141
|
-
if (!presetName) throw new Error(`agent: agent "${agentName}" has no model assignment`);
|
|
142
|
-
const preset = findPreset(config, presetName) || synthesizePreset(config, presetName);
|
|
143
|
-
if (!preset) throw new Error(`agent: preset "${presetName}" not found`);
|
|
144
|
-
return { presetName, preset };
|
|
145
|
-
}
|
|
146
85
|
export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultCwd, onSubagentEvent }) {
|
|
147
86
|
// Optional bridge to the standard hook bus for SubagentStart / SubagentStop.
|
|
148
87
|
// Best-effort: a hook error must never affect worker spawn/finish.
|
|
@@ -160,18 +99,6 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
160
99
|
return dataDir ? resolve(dataDir, WORKER_INDEX_FILE) : null;
|
|
161
100
|
}
|
|
162
101
|
|
|
163
|
-
function workerRowKey(row = {}) {
|
|
164
|
-
return clean(row.sessionId) || clean(row.tag);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function workerRowTime(row = {}) {
|
|
168
|
-
return Date.parse(row.updatedAt || row.finishedAt || row.lastUsedAt || row.createdAt || '') || 0;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function isTerminalWorkerStatus(status) {
|
|
172
|
-
return /^(idle|closed|completed|failed|error|cancelled|canceled|killed|timeout)$/i.test(clean(status));
|
|
173
|
-
}
|
|
174
|
-
|
|
175
102
|
function keepWorkerRow(row = {}) {
|
|
176
103
|
if (!clean(row.tag) || !clean(row.sessionId)) return false;
|
|
177
104
|
const t = workerRowTime(row);
|
|
@@ -216,41 +143,6 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
216
143
|
.filter(keepWorkerRow);
|
|
217
144
|
}
|
|
218
145
|
|
|
219
|
-
function normalizeTagTombstones(value, { cap = true, priorityKeys = null } = {}) {
|
|
220
|
-
const source = Array.isArray(value?.tombstones)
|
|
221
|
-
? value.tombstones
|
|
222
|
-
: (value?.tombstones && typeof value.tombstones === 'object'
|
|
223
|
-
? Object.values(value.tombstones)
|
|
224
|
-
: []);
|
|
225
|
-
const now = Date.now();
|
|
226
|
-
const cutoff = now - TAG_TOMBSTONE_TTL_MS;
|
|
227
|
-
const rows = source
|
|
228
|
-
.filter((row) => row && typeof row === 'object')
|
|
229
|
-
.map((row) => {
|
|
230
|
-
const parsedReapedAt = Date.parse(clean(row.reapedAt)) || 0;
|
|
231
|
-
return {
|
|
232
|
-
tag: clean(row.tag),
|
|
233
|
-
agent: clean(row.agent) || null,
|
|
234
|
-
cwd: clean(row.cwd) || null,
|
|
235
|
-
clientHostPid: positiveInt(row.clientHostPid),
|
|
236
|
-
// A future clock must not outrank tombstones created by this process.
|
|
237
|
-
reapedAt: parsedReapedAt ? new Date(Math.min(parsedReapedAt, now)).toISOString() : null,
|
|
238
|
-
};
|
|
239
|
-
})
|
|
240
|
-
.filter((row) => row.tag && row.reapedAt && (Date.parse(row.reapedAt) || 0) >= cutoff)
|
|
241
|
-
.sort((a, b) => {
|
|
242
|
-
const aPriority = priorityKeys?.has(tagTombstoneKey(a)) ? 1 : 0;
|
|
243
|
-
const bPriority = priorityKeys?.has(tagTombstoneKey(b)) ? 1 : 0;
|
|
244
|
-
return bPriority - aPriority
|
|
245
|
-
|| (Date.parse(b.reapedAt) || 0) - (Date.parse(a.reapedAt) || 0);
|
|
246
|
-
});
|
|
247
|
-
return cap ? rows.slice(0, MAX_TAG_TOMBSTONES) : rows;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function tagTombstoneKey(row = {}) {
|
|
251
|
-
return `${positiveInt(row.clientHostPid) || 0}\0${clean(row.tag)}`;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
146
|
// Mtime-keyed parse cache for the worker index. A single spawn calls
|
|
255
147
|
// refreshTagsFromSessions()/resolveTag()/nextTag() which each re-read and
|
|
256
148
|
// re-JSON.parse this file; across a parallel fanout that is O(spawns^2)
|
|
@@ -343,24 +235,6 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
343
235
|
}
|
|
344
236
|
}
|
|
345
237
|
|
|
346
|
-
function applyWorkerRowUpsert(byKey, normalized) {
|
|
347
|
-
if (!normalized) return;
|
|
348
|
-
const key = workerRowKey(normalized);
|
|
349
|
-
if (!key) return;
|
|
350
|
-
const prev = byKey.get(key) || {};
|
|
351
|
-
const merged = { ...prev, ...normalized };
|
|
352
|
-
for (const field of ['agent', 'provider', 'model', 'preset', 'effort', 'fast', 'clientHostPid', 'cwd', 'task_id', 'permission', 'toolPermission']) {
|
|
353
|
-
if ((merged[field] === null || merged[field] === '') && prev[field] != null && prev[field] !== '') {
|
|
354
|
-
merged[field] = prev[field];
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
byKey.set(key, {
|
|
358
|
-
...merged,
|
|
359
|
-
createdAt: normalized.createdAt || prev.createdAt || new Date().toISOString(),
|
|
360
|
-
updatedAt: normalized.updatedAt || new Date().toISOString(),
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
|
|
364
238
|
function flushWorkerIndexMutations() {
|
|
365
239
|
if (workerIndexFlushTimer) {
|
|
366
240
|
try { clearImmediate(workerIndexFlushTimer); } catch {}
|
|
@@ -420,30 +294,6 @@ export function createStandaloneAgent({ cfgMod, reg, mgr, dataDir, cwd: defaultC
|
|
|
420
294
|
};
|
|
421
295
|
}
|
|
422
296
|
|
|
423
|
-
function workerRowToSession(row = {}) {
|
|
424
|
-
return {
|
|
425
|
-
id: row.sessionId,
|
|
426
|
-
agentTag: row.tag,
|
|
427
|
-
agent: row.agent || null,
|
|
428
|
-
provider: row.provider || null,
|
|
429
|
-
model: row.model || null,
|
|
430
|
-
presetName: row.preset || null,
|
|
431
|
-
effort: row.effort || null,
|
|
432
|
-
fast: row.fast === true,
|
|
433
|
-
status: row.status || 'idle',
|
|
434
|
-
stage: row.stage || row.status || 'idle',
|
|
435
|
-
createdAt: row.createdAt || null,
|
|
436
|
-
updatedAt: row.updatedAt || null,
|
|
437
|
-
lastUsedAt: row.lastUsedAt || null,
|
|
438
|
-
clientHostPid: row.clientHostPid || null,
|
|
439
|
-
cwd: row.cwd || null,
|
|
440
|
-
permission: row.permission || null,
|
|
441
|
-
toolPermission: row.toolPermission || null,
|
|
442
|
-
messageCount: Math.max(0, Number(row.messages || 0)),
|
|
443
|
-
toolCount: Math.max(0, Number(row.tools || 0)),
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
|
|
447
297
|
function upsertWorkerRow(row, { defer = false } = {}) {
|
|
448
298
|
const normalized = normalizeWorkerRows({ workers: [row] })[0];
|
|
449
299
|
if (!normalized) return false;
|
|
@@ -217,6 +217,22 @@ export function setChannel({ channelId, backend = null } = {}) {
|
|
|
217
217
|
});
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
export async function setChannelAsync({ channelId, backend = null } = {}) {
|
|
221
|
+
const id = String(channelId || '').trim();
|
|
222
|
+
if (!id) throw new Error('channelId is required');
|
|
223
|
+
const targetBackend = backend === 'telegram' || backend === 'discord' ? backend : null;
|
|
224
|
+
return updateChannelsSectionAsync((cfg) => {
|
|
225
|
+
const activeBackend = cfg.backend === 'telegram' ? 'telegram' : 'discord';
|
|
226
|
+
const writeBackend = targetBackend || activeBackend;
|
|
227
|
+
const current = seedBackendChannelIds(resolveChannelEntry(cfg), activeBackend);
|
|
228
|
+
const nextEntry = { ...current };
|
|
229
|
+
if (writeBackend === 'telegram') nextEntry.telegramChatId = id;
|
|
230
|
+
else nextEntry.discordChannelId = id;
|
|
231
|
+
nextEntry.channelId = channelIdForBackend(nextEntry, activeBackend);
|
|
232
|
+
return { ...cfg, channel: nextEntry };
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
220
236
|
export function setWebhookConfig(patch = {}) {
|
|
221
237
|
return updateChannelsSection((cfg) => ({
|
|
222
238
|
...cfg,
|
|
@@ -230,6 +246,19 @@ export function setWebhookConfig(patch = {}) {
|
|
|
230
246
|
}));
|
|
231
247
|
}
|
|
232
248
|
|
|
249
|
+
export async function setWebhookConfigAsync(patch = {}) {
|
|
250
|
+
return updateChannelsSectionAsync((cfg) => ({
|
|
251
|
+
...cfg,
|
|
252
|
+
webhook: {
|
|
253
|
+
...(cfg.webhook || {}),
|
|
254
|
+
...(Object.prototype.hasOwnProperty.call(patch, 'enabled') ? { enabled: patch.enabled === true } : {}),
|
|
255
|
+
...(patch.port ? { port: Number(patch.port) || 3333 } : {}),
|
|
256
|
+
...(patch.domain ? { domain: String(patch.domain).trim() } : {}),
|
|
257
|
+
...(patch.ngrokDomain ? { ngrokDomain: String(patch.ngrokDomain).trim() } : {}),
|
|
258
|
+
},
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
|
|
233
262
|
function validateBackend(name) {
|
|
234
263
|
const value = String(name || '').trim();
|
|
235
264
|
if (value !== 'discord' && value !== 'telegram') {
|
package/src/tui/App.jsx
CHANGED
|
@@ -1889,8 +1889,11 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false })
|
|
|
1889
1889
|
}
|
|
1890
1890
|
if (channelPrompt.kind === 'webhook-domain') {
|
|
1891
1891
|
if (!commandText) return false;
|
|
1892
|
-
store.setWebhookConfig?.({ ngrokDomain: commandText })
|
|
1893
|
-
|
|
1892
|
+
Promise.resolve(store.setWebhookConfig?.({ ngrokDomain: commandText }))
|
|
1893
|
+
.then(() => resumeAfterChannelPrompt(channelPrompt))
|
|
1894
|
+
.catch((e) => {
|
|
1895
|
+
store.pushNotice(`webhook config update failed: ${e?.message || e}`, 'error');
|
|
1896
|
+
});
|
|
1894
1897
|
return true;
|
|
1895
1898
|
}
|
|
1896
1899
|
const parts = commandText.split('|').map((part) => part.trim());
|
|
@@ -1900,11 +1903,14 @@ export function App({ store, initialStatusLine = '', forceOnboarding = false })
|
|
|
1900
1903
|
// field) so old muscle memory does not break.
|
|
1901
1904
|
const isPipe = parts.length > 1;
|
|
1902
1905
|
const channelId = isPipe ? parts[1] : parts[0];
|
|
1903
|
-
store.setChannel({
|
|
1906
|
+
Promise.resolve(store.setChannel({
|
|
1904
1907
|
channelId,
|
|
1905
1908
|
backend: channelPrompt.backend,
|
|
1906
|
-
})
|
|
1907
|
-
|
|
1909
|
+
}))
|
|
1910
|
+
.then(() => resumeAfterChannelPrompt(channelPrompt))
|
|
1911
|
+
.catch((e) => {
|
|
1912
|
+
store.pushNotice(`channel save failed: ${e?.message || e}`, 'error');
|
|
1913
|
+
});
|
|
1908
1914
|
return true;
|
|
1909
1915
|
}
|
|
1910
1916
|
if (channelPrompt.kind === 'schedule-add') {
|