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.
Files changed (38) hide show
  1. package/package.json +2 -2
  2. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +4 -2
  3. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +125 -16
  4. package/src/runtime/agent/orchestrator/session/manager.mjs +3 -0
  5. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +9 -2
  6. package/src/runtime/agent/orchestrator/tools/env-scrub.mjs +8 -0
  7. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +4 -3
  8. package/src/runtime/agent/orchestrator/tools/token-manifest.json +23 -2
  9. package/src/runtime/memory/lib/memory-action-handlers.mjs +4 -1
  10. package/src/runtime/memory/tool-defs.mjs +19 -3
  11. package/src/session-runtime/lifecycle-api.mjs +15 -0
  12. package/src/session-runtime/provider-auth-api.mjs +8 -0
  13. package/src/session-runtime/provider-usage.mjs +30 -5
  14. package/src/session-runtime/runtime-core.mjs +267 -481
  15. package/src/session-runtime/session-lifecycle.mjs +378 -0
  16. package/src/standalone/agent-shard/shard-pool.mjs +6 -5
  17. package/src/standalone/agent-tool/job-views.mjs +329 -0
  18. package/src/standalone/agent-tool/spawn-flow.mjs +629 -0
  19. package/src/standalone/agent-tool/tag-registry.mjs +340 -0
  20. package/src/standalone/agent-tool.mjs +100 -1151
  21. package/src/standalone/usage-dashboard.mjs +23 -1
  22. package/src/tui/App.jsx +362 -2576
  23. package/src/tui/app/app-view.jsx +504 -0
  24. package/src/tui/app/create-app-pickers.mjs +313 -0
  25. package/src/tui/app/prompt-submit.mjs +501 -0
  26. package/src/tui/app/shell-layout.mjs +563 -0
  27. package/src/tui/app/usage-context-panels.mjs +288 -0
  28. package/src/tui/app/use-copy-selection.mjs +56 -0
  29. package/src/tui/app/use-global-key-input.mjs +151 -0
  30. package/src/tui/app/use-pasted-buffers.mjs +115 -0
  31. package/src/tui/app/use-prompt-draft-flow.mjs +167 -0
  32. package/src/tui/app/use-prompt-hint.mjs +58 -0
  33. package/src/tui/app/use-prompt-queue-history.mjs +100 -0
  34. package/src/tui/app/use-terminal-chrome.mjs +72 -0
  35. package/src/tui/app/use-transcript-activity.mjs +131 -0
  36. package/src/tui/app/use-welcome-prompt-hint.mjs +96 -0
  37. package/src/tui/dist/index.mjs +9802 -9024
  38. 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
+ }