mixdog 0.9.86 → 0.9.87
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 +2 -2
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +4 -2
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +125 -16
- package/src/runtime/agent/orchestrator/session/manager.mjs +3 -0
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +9 -2
- package/src/runtime/agent/orchestrator/tools/env-scrub.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +4 -3
- package/src/runtime/agent/orchestrator/tools/token-manifest.json +23 -2
- package/src/runtime/memory/lib/memory-action-handlers.mjs +4 -1
- package/src/runtime/memory/tool-defs.mjs +19 -3
- package/src/session-runtime/lifecycle-api.mjs +15 -0
- package/src/session-runtime/provider-auth-api.mjs +8 -0
- package/src/session-runtime/provider-usage.mjs +30 -5
- package/src/session-runtime/runtime-core.mjs +267 -481
- package/src/session-runtime/session-lifecycle.mjs +378 -0
- package/src/standalone/agent-shard/shard-pool.mjs +6 -5
- package/src/standalone/agent-tool/job-views.mjs +329 -0
- package/src/standalone/agent-tool/spawn-flow.mjs +629 -0
- package/src/standalone/agent-tool/tag-registry.mjs +340 -0
- package/src/standalone/agent-tool.mjs +100 -1151
- package/src/standalone/usage-dashboard.mjs +23 -1
- package/src/tui/App.jsx +362 -2576
- package/src/tui/app/app-view.jsx +504 -0
- package/src/tui/app/create-app-pickers.mjs +313 -0
- package/src/tui/app/prompt-submit.mjs +501 -0
- package/src/tui/app/shell-layout.mjs +563 -0
- package/src/tui/app/usage-context-panels.mjs +288 -0
- package/src/tui/app/use-copy-selection.mjs +56 -0
- package/src/tui/app/use-global-key-input.mjs +151 -0
- package/src/tui/app/use-pasted-buffers.mjs +115 -0
- package/src/tui/app/use-prompt-draft-flow.mjs +167 -0
- package/src/tui/app/use-prompt-hint.mjs +58 -0
- package/src/tui/app/use-prompt-queue-history.mjs +100 -0
- package/src/tui/app/use-terminal-chrome.mjs +72 -0
- package/src/tui/app/use-transcript-activity.mjs +131 -0
- package/src/tui/app/use-welcome-prompt-hint.mjs +96 -0
- package/src/tui/dist/index.mjs +9802 -9024
- package/src/tui/engine/session-api-ext.mjs +23 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
// Job/session view layer, extracted from agent-tool.mjs: busy checks, the
|
|
2
|
+
// /agents list projection, per-session progress extras, job snapshots and
|
|
3
|
+
// lookup (including worker fallback), render shaping, and spawn-meta
|
|
4
|
+
// builders. Pure readers over the tag registry + job stores.
|
|
5
|
+
import { ACTIVE_STAGES } from './tool-def.mjs';
|
|
6
|
+
import { createProviderInit } from './provider-init.mjs';
|
|
7
|
+
import { resolveAgentWatchdogPolicy } from '../../runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs';
|
|
8
|
+
import { buildAgentTaskProgressFields } from '../agent-task-status.mjs';
|
|
9
|
+
import { clean, normalizeAgentName, positiveInt, presetKey, terminalPidForContext } from './helpers.mjs';
|
|
10
|
+
import { getBackgroundTask, listBackgroundTasks, sanitizeTaskMeta, taskIdFromArgs } from '../../runtime/shared/background-tasks.mjs';
|
|
11
|
+
import { resolveAgentSpawnPreset } from './spawn-preset.mjs';
|
|
12
|
+
export function createJobViews({
|
|
13
|
+
mgr,
|
|
14
|
+
getLiveSession,
|
|
15
|
+
reg,
|
|
16
|
+
DEFAULT_SPAWN_PREP_TIMEOUT_MS,
|
|
17
|
+
refreshTagsFromSessions,
|
|
18
|
+
agentSessionEntries,
|
|
19
|
+
tags,
|
|
20
|
+
cfgMod,
|
|
21
|
+
}) {
|
|
22
|
+
function isSessionBusy(sessionId) {
|
|
23
|
+
const runtime = mgr.getSessionRuntime?.(sessionId);
|
|
24
|
+
if (runtime?.controller?.signal && !runtime.controller.signal.aborted) return true;
|
|
25
|
+
if (runtime?.stage) return ACTIVE_STAGES.has(runtime.stage);
|
|
26
|
+
const session = getLiveSession(sessionId);
|
|
27
|
+
return ACTIVE_STAGES.has(session?.status || '');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Provider init de-dup lives in ./agent-tool/provider-init.mjs; the factory
|
|
31
|
+
// keeps its per-provider chain/ready state private per agent instance. The
|
|
32
|
+
// chain-gate defaults to the spawn-prep cap (see provider-init.mjs comments).
|
|
33
|
+
const { ensureProvider } = createProviderInit(reg, DEFAULT_SPAWN_PREP_TIMEOUT_MS);
|
|
34
|
+
|
|
35
|
+
function list({ scanSessions = false, context = {} } = {}) {
|
|
36
|
+
refreshTagsFromSessions({ scanSessions, context });
|
|
37
|
+
const now = Date.now();
|
|
38
|
+
const rows = [];
|
|
39
|
+
for (const { tag, session } of agentSessionEntries({ scanSessions, context })) {
|
|
40
|
+
const sessionId = session.id;
|
|
41
|
+
const runtime = mgr.getSessionRuntime?.(sessionId);
|
|
42
|
+
const status = session.closed === true ? 'closed' : (session.status || 'idle');
|
|
43
|
+
const stage = session.stage || (status === 'idle' || status === 'error' || status === 'closed'
|
|
44
|
+
? status
|
|
45
|
+
: (runtime?.stage || status));
|
|
46
|
+
const progress = sessionProgressExtras(sessionId, session.agent || null, now);
|
|
47
|
+
rows.push({
|
|
48
|
+
tag,
|
|
49
|
+
sessionId,
|
|
50
|
+
agent: session.agent || null,
|
|
51
|
+
provider: session.provider,
|
|
52
|
+
model: session.model,
|
|
53
|
+
preset: session.presetName || null,
|
|
54
|
+
effort: session.effort || null,
|
|
55
|
+
fast: session.fast === true,
|
|
56
|
+
status,
|
|
57
|
+
stage,
|
|
58
|
+
...progress,
|
|
59
|
+
createdAt: session.createdAt || null,
|
|
60
|
+
updatedAt: session.updatedAt || null,
|
|
61
|
+
lastUsedAt: session.lastUsedAt || null,
|
|
62
|
+
clientHostPid: session.clientHostPid || null,
|
|
63
|
+
lastStreamDeltaAt: runtime?.lastStreamDeltaAt ? new Date(runtime.lastStreamDeltaAt).toISOString() : null,
|
|
64
|
+
staleSeconds: runtime?.lastStreamDeltaAt ? Math.floor((now - runtime.lastStreamDeltaAt) / 1000) : null,
|
|
65
|
+
windowTokens: Number(session.lastContextTokens ?? session.lastInputTokens) || 0,
|
|
66
|
+
windowCap: Number(session.contextWindow) || null,
|
|
67
|
+
permission: session.permission || null,
|
|
68
|
+
toolPermission: session.toolPermission || null,
|
|
69
|
+
messages: Array.isArray(session.messages) ? session.messages.length : Math.max(0, Number(session.messageCount || 0)),
|
|
70
|
+
tools: Array.isArray(session.tools) ? session.tools.length : Math.max(0, Number(session.toolCount || 0)),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return rows;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function sessionProgressExtras(sessionId, role, now = Date.now(), taskStatus = null) {
|
|
77
|
+
if (!sessionId) return {};
|
|
78
|
+
const session = mgr.getSession(sessionId);
|
|
79
|
+
const runtime = mgr.getSessionRuntime?.(sessionId) || null;
|
|
80
|
+
const snapshot = typeof mgr.getSessionProgressSnapshot === 'function'
|
|
81
|
+
? mgr.getSessionProgressSnapshot(sessionId)
|
|
82
|
+
: null;
|
|
83
|
+
const policy = role ? resolveAgentWatchdogPolicy(role) : null;
|
|
84
|
+
const queuedFollowups = typeof mgr.getSessionPendingMessageDepth === 'function'
|
|
85
|
+
? mgr.getSessionPendingMessageDepth(sessionId)
|
|
86
|
+
: null;
|
|
87
|
+
return buildAgentTaskProgressFields({
|
|
88
|
+
now,
|
|
89
|
+
sessionStatus: session?.status || null,
|
|
90
|
+
runtimeStage: runtime?.stage || snapshot?.stage || session?.status || null,
|
|
91
|
+
snapshot,
|
|
92
|
+
runtime,
|
|
93
|
+
policy,
|
|
94
|
+
queuedFollowups,
|
|
95
|
+
taskStatus,
|
|
96
|
+
lastToolCall: runtime?.lastToolCall || null,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function jobWorkerSnapshot(sessionId) {
|
|
101
|
+
if (!sessionId) return null;
|
|
102
|
+
const session = mgr.getSession(sessionId);
|
|
103
|
+
if (!session) return null;
|
|
104
|
+
const runtime = mgr.getSessionRuntime?.(sessionId);
|
|
105
|
+
const status = session.closed === true ? 'closed' : (session.status || 'idle');
|
|
106
|
+
const progress = sessionProgressExtras(sessionId, session.agent || null);
|
|
107
|
+
return {
|
|
108
|
+
workerStatus: status,
|
|
109
|
+
stage: progress.worker_stage || runtime?.stage || status,
|
|
110
|
+
clientHostPid: session.clientHostPid || null,
|
|
111
|
+
lastStreamDeltaAt: runtime?.lastStreamDeltaAt ? new Date(runtime.lastStreamDeltaAt).toISOString() : null,
|
|
112
|
+
...progress,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function listJobs(context = {}) {
|
|
117
|
+
const wantedPid = terminalPidForContext(context);
|
|
118
|
+
const rows = listBackgroundTasks({ surface: 'agent', context }).map((task) => ({
|
|
119
|
+
task_id: task.task_id,
|
|
120
|
+
type: task.operation,
|
|
121
|
+
status: task.status,
|
|
122
|
+
tag: task.tag || null,
|
|
123
|
+
sessionId: task.sessionId || null,
|
|
124
|
+
agent: task.agent || null,
|
|
125
|
+
preset: task.preset || null,
|
|
126
|
+
provider: task.provider || null,
|
|
127
|
+
model: task.model || null,
|
|
128
|
+
effort: task.effort || null,
|
|
129
|
+
fast: task.fast === true || task.fast === false ? task.fast : null,
|
|
130
|
+
maxLoopIterations: task.maxLoopIterations || null,
|
|
131
|
+
startedAt: task.startedAt,
|
|
132
|
+
finishedAt: task.finishedAt || null,
|
|
133
|
+
error: task.error || null,
|
|
134
|
+
...jobWorkerSnapshot(task.sessionId),
|
|
135
|
+
...sessionProgressExtras(task.sessionId, task.agent || null, Date.now(), task.status),
|
|
136
|
+
}));
|
|
137
|
+
return wantedPid
|
|
138
|
+
? rows.filter((row) => {
|
|
139
|
+
const pid = positiveInt(row.clientHostPid);
|
|
140
|
+
// Spawn-prep rows have no worker session (and therefore no pid) yet;
|
|
141
|
+
// they already passed taskMatchesScope, so the queued spawn stays
|
|
142
|
+
// visible instead of vanishing until a pool slot frees up (user bug:
|
|
143
|
+
// "spawned 5, only 4 listed").
|
|
144
|
+
return pid ? pid === wantedPid : true;
|
|
145
|
+
})
|
|
146
|
+
: rows;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function getJob(args, context = {}) {
|
|
150
|
+
const taskId = taskIdFromArgs(args);
|
|
151
|
+
if (taskId) {
|
|
152
|
+
const task = getBackgroundTask(taskId, { surface: 'agent', context });
|
|
153
|
+
if (!task) throw new Error(`agent read/status: task "${taskId}" not found`);
|
|
154
|
+
return task;
|
|
155
|
+
}
|
|
156
|
+
// Fall back to tag/sessionId resolution, same precedence as close()
|
|
157
|
+
// (agent-tool.mjs close()): clean(args.tag || args.sessionId || ...).
|
|
158
|
+
const target = clean(args.tag || args.sessionId);
|
|
159
|
+
if (!target) throw new Error('agent read/status: task_id, tag, or sessionId is required');
|
|
160
|
+
const candidates = listBackgroundTasks({ surface: 'agent', context })
|
|
161
|
+
.filter(Boolean)
|
|
162
|
+
.filter((row) => row.tag === target || row.sessionId === target);
|
|
163
|
+
if (!candidates.length) throw new Error(`agent read/status: no task found for tag/sessionId "${target}"`);
|
|
164
|
+
// Prefer most recent when multiple tasks match the same tag.
|
|
165
|
+
candidates.sort((a, b) => new Date(b.startedAt || 0) - new Date(a.startedAt || 0));
|
|
166
|
+
const task = getBackgroundTask(candidates[0].task_id, { surface: 'agent', context });
|
|
167
|
+
if (!task) throw new Error(`agent read/status: task "${candidates[0].task_id}" not found`);
|
|
168
|
+
return task;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Background tasks are process-memory only; a worker SESSION can outlive
|
|
172
|
+
// its spawn task (task pruned, engine restarted). read/status by tag then
|
|
173
|
+
// used to throw "no task found" even though the worker exists. Fall back to
|
|
174
|
+
// a synthetic job view built from the worker session, with the last
|
|
175
|
+
// assistant message as the readable result.
|
|
176
|
+
function workerFallbackJob(target, context = {}) {
|
|
177
|
+
refreshTagsFromSessions({ scanSessions: true, context });
|
|
178
|
+
const sessionId = tags.get(target) || (mgr.getSession(target) ? target : null);
|
|
179
|
+
if (!sessionId) return null;
|
|
180
|
+
const session = mgr.getSession(sessionId);
|
|
181
|
+
if (!session) return null;
|
|
182
|
+
const status = session.closed === true ? 'closed' : (session.status || 'idle');
|
|
183
|
+
const msgs = Array.isArray(session.messages) ? session.messages : [];
|
|
184
|
+
const lastAssistant = [...msgs].reverse().find((m) => m?.role === 'assistant'
|
|
185
|
+
&& (typeof m.content === 'string' ? m.content.trim() : m.content));
|
|
186
|
+
const resultText = lastAssistant
|
|
187
|
+
? (typeof lastAssistant.content === 'string' ? lastAssistant.content : JSON.stringify(lastAssistant.content))
|
|
188
|
+
: '(worker session has no assistant output yet)';
|
|
189
|
+
return {
|
|
190
|
+
taskId: null,
|
|
191
|
+
operation: 'worker',
|
|
192
|
+
status,
|
|
193
|
+
startedAt: session.createdAt || null,
|
|
194
|
+
finishedAt: null,
|
|
195
|
+
error: null,
|
|
196
|
+
result: resultText,
|
|
197
|
+
meta: {
|
|
198
|
+
tag: tags.has(target) ? target : (session.tag || null),
|
|
199
|
+
sessionId,
|
|
200
|
+
agent: session.agent || null,
|
|
201
|
+
preset: session.presetName || null,
|
|
202
|
+
provider: session.provider || null,
|
|
203
|
+
model: session.model || null,
|
|
204
|
+
effort: session.effort || null,
|
|
205
|
+
fast: session.fast === true,
|
|
206
|
+
maxLoopIterations: null,
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function getJobOrWorker(args, context = {}) {
|
|
212
|
+
try {
|
|
213
|
+
return getJob(args, context);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
const target = clean(args.tag || args.sessionId);
|
|
216
|
+
const fallback = target ? workerFallbackJob(target, context) : null;
|
|
217
|
+
if (fallback) return fallback;
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function renderJob(job, includeResult = false) {
|
|
223
|
+
const meta = job.meta || {};
|
|
224
|
+
let progress = sessionProgressExtras(meta.sessionId, meta.agent || null, Date.now(), job.status);
|
|
225
|
+
// Spawn is deferred: before the worker session exists, sessionProgressExtras
|
|
226
|
+
// returns {} and the status card would show only "status: running" with no
|
|
227
|
+
// stage/progress. Fill a minimal stage so the caller can tell the job is
|
|
228
|
+
// still spinning up rather than silently stalled.
|
|
229
|
+
if (!meta.sessionId && (!progress || Object.keys(progress).length === 0)) {
|
|
230
|
+
const spawning = job.status === 'running';
|
|
231
|
+
progress = {
|
|
232
|
+
worker_stage: spawning ? 'spawning' : (job.status || 'unknown'),
|
|
233
|
+
last_progress: spawning ? 'spawning worker session' : (job.status || 'unknown'),
|
|
234
|
+
diagnostic: spawning ? 'worker session not started yet' : (job.status || 'unknown'),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
task_id: job.taskId,
|
|
239
|
+
type: job.operation,
|
|
240
|
+
status: job.status,
|
|
241
|
+
tag: meta.tag || null,
|
|
242
|
+
sessionId: meta.sessionId || null,
|
|
243
|
+
agent: meta.agent || null,
|
|
244
|
+
...(meta.respawned === true ? { respawned: true, note: 'previous session reaped — fresh session, no prior context; re-supply anchors if needed' } : {}),
|
|
245
|
+
preset: meta.preset || null,
|
|
246
|
+
provider: meta.provider || null,
|
|
247
|
+
model: meta.model || null,
|
|
248
|
+
effort: meta.effort || null,
|
|
249
|
+
fast: meta.fast === true || meta.fast === false ? meta.fast : null,
|
|
250
|
+
maxLoopIterations: meta.maxLoopIterations || null,
|
|
251
|
+
startedAt: job.startedAt,
|
|
252
|
+
finishedAt: job.finishedAt || null,
|
|
253
|
+
error: job.error || null,
|
|
254
|
+
...jobWorkerSnapshot(meta.sessionId),
|
|
255
|
+
...progress,
|
|
256
|
+
...(includeResult && job.result !== undefined ? { result: job.result } : {}),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function preparedSpawnMeta(prepared, extras = {}) {
|
|
261
|
+
return sanitizeTaskMeta({
|
|
262
|
+
...(extras || {}),
|
|
263
|
+
tag: prepared.tag,
|
|
264
|
+
sessionId: prepared.session.id,
|
|
265
|
+
agent: prepared.agent,
|
|
266
|
+
preset: presetKey(prepared.preset) || prepared.presetName,
|
|
267
|
+
provider: prepared.preset.provider,
|
|
268
|
+
model: prepared.preset.model,
|
|
269
|
+
effort: prepared.preset.effort || null,
|
|
270
|
+
fast: prepared.preset.fast === true,
|
|
271
|
+
maxLoopIterations: prepared.maxLoopIterations || null,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function pendingSpawnMeta(args = {}, extras = {}) {
|
|
276
|
+
const agent = normalizeAgentName(args.agent);
|
|
277
|
+
// Best-effort resolve the default preset so the pending "Spawn …" card can
|
|
278
|
+
// already show the model (e.g. "Spawn Heavy Worker (Opus 4.8)") even when
|
|
279
|
+
// the caller did not pass an explicit provider/model. Never throw: fall back
|
|
280
|
+
// to whatever raw args carry.
|
|
281
|
+
let resolved = null;
|
|
282
|
+
if (!clean(args.model) || !clean(args.provider)) {
|
|
283
|
+
try { resolved = resolveAgentSpawnPreset(cfgMod.loadConfig(), args)?.preset || null; }
|
|
284
|
+
catch { resolved = null; }
|
|
285
|
+
}
|
|
286
|
+
return sanitizeTaskMeta({
|
|
287
|
+
...(extras || {}),
|
|
288
|
+
tag: clean(args.tag) || null,
|
|
289
|
+
sessionId: null,
|
|
290
|
+
agent: agent || null,
|
|
291
|
+
preset: clean(args.preset) || presetKey(resolved) || null,
|
|
292
|
+
provider: clean(args.provider) || clean(resolved?.provider) || null,
|
|
293
|
+
model: clean(args.model) || clean(resolved?.model) || null,
|
|
294
|
+
effort: clean(args.effort) || clean(resolved?.effort) || null,
|
|
295
|
+
fast: args.fast === true ? true : (resolved?.fast === true ? true : null),
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function mergeJobMeta(job, meta = {}) {
|
|
300
|
+
if (!job || !meta || typeof meta !== 'object') return;
|
|
301
|
+
const next = sanitizeTaskMeta({ ...(job.meta || {}), ...meta });
|
|
302
|
+
job.meta = next;
|
|
303
|
+
if (job.input && typeof job.input === 'object') {
|
|
304
|
+
job.input = {
|
|
305
|
+
...job.input,
|
|
306
|
+
tag: next.tag || job.input.tag || null,
|
|
307
|
+
sessionId: next.sessionId || job.input.sessionId || null,
|
|
308
|
+
agent: next.agent || job.input.agent || null,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
job.label = next.tag || next.sessionId || job.label;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
isSessionBusy,
|
|
316
|
+
ensureProvider,
|
|
317
|
+
list,
|
|
318
|
+
sessionProgressExtras,
|
|
319
|
+
jobWorkerSnapshot,
|
|
320
|
+
listJobs,
|
|
321
|
+
getJob,
|
|
322
|
+
workerFallbackJob,
|
|
323
|
+
getJobOrWorker,
|
|
324
|
+
renderJob,
|
|
325
|
+
preparedSpawnMeta,
|
|
326
|
+
pendingSpawnMeta,
|
|
327
|
+
mergeJobMeta,
|
|
328
|
+
};
|
|
329
|
+
}
|