mixdog 0.9.66 → 0.9.68
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/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +20 -2
- package/src/runtime/channels/lib/config.mjs +13 -5
- package/src/runtime/channels/lib/owned-runtime.mjs +65 -5
- package/src/runtime/channels/lib/scheduler.mjs +7 -15
- package/src/runtime/channels/lib/status-snapshot.mjs +6 -30
- package/src/runtime/channels/lib/tool-dispatch.mjs +6 -1
- package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +203 -0
- package/src/runtime/channels/lib/webhook.mjs +46 -246
- package/src/runtime/channels/lib/worker-main.mjs +45 -92
- package/src/runtime/shared/automation-attachments.mjs +72 -0
- package/src/runtime/shared/automation-workflow.mjs +41 -0
- package/src/runtime/shared/config.mjs +0 -9
- package/src/runtime/shared/schedule-session-run.mjs +10 -1
- package/src/runtime/shared/schedules-db.mjs +18 -3
- package/src/runtime/shared/time-format.mjs +56 -0
- package/src/runtime/shared/tool-card-model.mjs +740 -0
- package/src/runtime/shared/webhook-session-run.mjs +57 -0
- package/src/runtime/shared/webhooks-db.mjs +19 -3
- package/src/session-runtime/channel-config-api.mjs +13 -1
- package/src/session-runtime/lifecycle-api.mjs +12 -0
- package/src/session-runtime/prewarm.mjs +13 -7
- package/src/session-runtime/runtime-core.mjs +127 -22
- package/src/session-runtime/session-text.mjs +6 -0
- package/src/session-runtime/settings-api.mjs +0 -12
- package/src/standalone/channel-admin.mjs +71 -22
- package/src/standalone/channel-daemon-client.mjs +5 -1
- package/src/standalone/channel-daemon-transport.mjs +112 -20
- package/src/standalone/channel-daemon.mjs +6 -4
- package/src/tui/App.jsx +2 -47
- package/src/tui/app/channel-pickers.mjs +8 -173
- package/src/tui/app/doctor.mjs +0 -1
- package/src/tui/app/slash-commands.mjs +1 -3
- package/src/tui/app/slash-dispatch.mjs +3 -3
- package/src/tui/components/ToolExecution.jsx +47 -196
- package/src/tui/components/tool-execution/surface-detail.mjs +33 -394
- package/src/tui/components/tool-execution/text-format.mjs +27 -104
- package/src/tui/dist/index.mjs +340 -346
- package/src/tui/engine/live-share.mjs +9 -0
- package/src/tui/engine/session-api-ext.mjs +32 -16
- package/src/tui/engine.mjs +14 -1
- package/src/tui/time-format.mjs +4 -51
- package/src/runtime/channels/lib/webhook/ngrok.mjs +0 -181
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a stored automation workflow id (schedule/webhook row) into
|
|
3
|
+
* createSession options: the workflow summary meta plus the WORKFLOW.md
|
|
4
|
+
* context block, exactly like a desktop New task session gets for the
|
|
5
|
+
* active workflow. An empty/unknown id resolves to the default pack via
|
|
6
|
+
* loadWorkflowPack's fallback; resolution failures degrade to no workflow
|
|
7
|
+
* context (the session still runs with the base lead ruleset).
|
|
8
|
+
*/
|
|
9
|
+
import { createWorkflowHelpers } from '../../session-runtime/workflow.mjs';
|
|
10
|
+
import { STANDALONE_ROOT, STANDALONE_DATA_DIR } from '../../session-runtime/runtime-paths.mjs';
|
|
11
|
+
import { readMarkdownDocument, normalizeAgentPermissionOrNone } from './markdown-frontmatter.mjs';
|
|
12
|
+
|
|
13
|
+
let _helpers = null;
|
|
14
|
+
function helpers() {
|
|
15
|
+
if (!_helpers) {
|
|
16
|
+
_helpers = createWorkflowHelpers({
|
|
17
|
+
rootDir: STANDALONE_ROOT,
|
|
18
|
+
dataDir: STANDALONE_DATA_DIR,
|
|
19
|
+
readMarkdownDocument,
|
|
20
|
+
normalizeAgentPermissionOrNone,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return _helpers;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** { workflow, workflowContext } createSession opts for a workflow id, or {} when unset/unresolvable. */
|
|
27
|
+
export function automationWorkflowOpts(workflowId) {
|
|
28
|
+
const id = String(workflowId || '').trim();
|
|
29
|
+
if (!id) return {};
|
|
30
|
+
try {
|
|
31
|
+
const h = helpers();
|
|
32
|
+
const pack = h.loadWorkflowPack(undefined, id);
|
|
33
|
+
if (!pack) return {};
|
|
34
|
+
return {
|
|
35
|
+
workflow: h.workflowSummary(pack),
|
|
36
|
+
workflowContext: h.workflowContextBlock({ workflow: { active: id } }, undefined),
|
|
37
|
+
};
|
|
38
|
+
} catch {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -437,7 +437,6 @@ function getCapabilities() {
|
|
|
437
437
|
export const SECRET_ACCOUNTS = Object.freeze({
|
|
438
438
|
discordToken: 'discord.token',
|
|
439
439
|
telegramToken: 'telegram.token',
|
|
440
|
-
webhookAuth: 'webhook.authtoken',
|
|
441
440
|
agentApiKey: (provider) => `agent.${provider}.apiKey`,
|
|
442
441
|
openaiUsageSessionKey: 'agent.openai.usageSessionKey',
|
|
443
442
|
opencodeGoAuthCookie: 'agent.opencode-go.authCookie',
|
|
@@ -497,14 +496,6 @@ export function getTelegramToken() {
|
|
|
497
496
|
return _readSecret(SECRET_ACCOUNTS.telegramToken)
|
|
498
497
|
}
|
|
499
498
|
|
|
500
|
-
/**
|
|
501
|
-
* Returns the ngrok/webhook authtoken.
|
|
502
|
-
* Priority: MIXDOG_WEBHOOK_AUTHTOKEN → keychain('webhook.authtoken') → null
|
|
503
|
-
*/
|
|
504
|
-
export function getWebhookAuthtoken() {
|
|
505
|
-
return _readSecret(SECRET_ACCOUNTS.webhookAuth)
|
|
506
|
-
}
|
|
507
|
-
|
|
508
499
|
export function getOpenAIUsageSessionKey() {
|
|
509
500
|
return process.env.OPENAI_USAGE_SESSION_KEY
|
|
510
501
|
|| process.env.OPENAI_DASHBOARD_SESSION_KEY
|
|
@@ -10,6 +10,8 @@ import { loadConfig } from '../agent/orchestrator/config.mjs';
|
|
|
10
10
|
import { createSession } from '../agent/orchestrator/session/manager/session-lifecycle.mjs';
|
|
11
11
|
import { askSession } from '../agent/orchestrator/session/manager/ask-session.mjs';
|
|
12
12
|
import { parseScheduleModelRef } from './schedule-model-ref.mjs';
|
|
13
|
+
import { automationWorkflowOpts } from './automation-workflow.mjs';
|
|
14
|
+
import { automationPromptContent } from './automation-attachments.mjs';
|
|
13
15
|
|
|
14
16
|
/** Resolve schedule.model into a {provider,model,effort?,fast?} route. */
|
|
15
17
|
function scheduleRouteFromModelRef(modelRef, config = null) {
|
|
@@ -43,6 +45,9 @@ export async function runScheduleSession(schedule, { config = null, prompt: prom
|
|
|
43
45
|
}
|
|
44
46
|
const route = scheduleRouteFromModelRef(schedule.model, config);
|
|
45
47
|
const cwd = schedule.cwd ? String(schedule.cwd) : null;
|
|
48
|
+
// Every fire is a fresh New task (user decision): one prompt, the
|
|
49
|
+
// schedule's model/workflow/project, a brand-new session in the sidebar
|
|
50
|
+
// Automations section (newest per name wins there).
|
|
46
51
|
const session = createSession({
|
|
47
52
|
provider: route.provider,
|
|
48
53
|
model: route.model,
|
|
@@ -51,13 +56,17 @@ export async function runScheduleSession(schedule, { config = null, prompt: prom
|
|
|
51
56
|
owner: 'user',
|
|
52
57
|
sourceType: 'schedule',
|
|
53
58
|
sourceName: schedule.name,
|
|
59
|
+
sourceDelivery: schedule.delivery || null,
|
|
54
60
|
...(cwd ? { cwd } : {}),
|
|
55
61
|
desktopSession: cwd
|
|
56
62
|
? { classification: 'project', projectPath: cwd }
|
|
57
63
|
: { classification: 'task', projectPath: null },
|
|
64
|
+
...automationWorkflowOpts(schedule.workflow),
|
|
58
65
|
});
|
|
59
66
|
// The user message is the schedule's instructions verbatim: the desktop
|
|
60
67
|
// title/preview derive from it, so no "[Scheduled task: …]" header noise.
|
|
61
|
-
|
|
68
|
+
// Stored attachments ride along as composer-style content parts.
|
|
69
|
+
const content = automationPromptContent(prompt, schedule.attachments);
|
|
70
|
+
const result = await askSession(session.id, content, null, null, cwd || undefined);
|
|
62
71
|
return { sessionId: session.id, result: String(result?.content || '') };
|
|
63
72
|
}
|
|
@@ -33,6 +33,9 @@ CREATE TABLE IF NOT EXISTS scheduler.schedules (
|
|
|
33
33
|
channel_id text,
|
|
34
34
|
model text,
|
|
35
35
|
cwd text,
|
|
36
|
+
workflow text,
|
|
37
|
+
attachments jsonb,
|
|
38
|
+
delivery text,
|
|
36
39
|
prompt text NOT NULL,
|
|
37
40
|
enabled boolean NOT NULL DEFAULT true,
|
|
38
41
|
status text NOT NULL DEFAULT 'active' CHECK (status IN ('active','done')),
|
|
@@ -45,6 +48,9 @@ CREATE TABLE IF NOT EXISTS scheduler.schedules (
|
|
|
45
48
|
CONSTRAINT schedules_when_xor CHECK ((when_at IS NOT NULL) <> (when_cron IS NOT NULL))
|
|
46
49
|
);
|
|
47
50
|
ALTER TABLE scheduler.schedules ADD COLUMN IF NOT EXISTS cwd text;
|
|
51
|
+
ALTER TABLE scheduler.schedules ADD COLUMN IF NOT EXISTS workflow text;
|
|
52
|
+
ALTER TABLE scheduler.schedules ADD COLUMN IF NOT EXISTS attachments jsonb;
|
|
53
|
+
ALTER TABLE scheduler.schedules ADD COLUMN IF NOT EXISTS delivery text;
|
|
48
54
|
`;
|
|
49
55
|
|
|
50
56
|
async function getDb(dataDir = resolvePluginData()) {
|
|
@@ -82,6 +88,9 @@ function rowToDef(row) {
|
|
|
82
88
|
channelId: row.channel_id,
|
|
83
89
|
model: row.model,
|
|
84
90
|
cwd: row.cwd,
|
|
91
|
+
workflow: row.workflow,
|
|
92
|
+
attachments: row.attachments || null,
|
|
93
|
+
delivery: row.delivery || null,
|
|
85
94
|
prompt: row.prompt,
|
|
86
95
|
enabled: row.enabled,
|
|
87
96
|
status: row.status,
|
|
@@ -94,7 +103,7 @@ function rowToDef(row) {
|
|
|
94
103
|
};
|
|
95
104
|
}
|
|
96
105
|
|
|
97
|
-
const COLS = 'name, description, when_at, when_cron, timezone, target, channel_id, model, cwd, prompt, enabled, status, last_fired_at, next_fire_at, deferred_until, skipped_until, created_at, updated_at';
|
|
106
|
+
const COLS = 'name, description, when_at, when_cron, timezone, target, channel_id, model, cwd, workflow, attachments, delivery, prompt, enabled, status, last_fired_at, next_fire_at, deferred_until, skipped_until, created_at, updated_at';
|
|
98
107
|
|
|
99
108
|
// ---------------------------------------------------------------------------
|
|
100
109
|
// Public API
|
|
@@ -130,6 +139,9 @@ export async function upsertSchedule(def, { dataDir } = {}) {
|
|
|
130
139
|
def.channelId ?? null,
|
|
131
140
|
def.model ?? null,
|
|
132
141
|
def.cwd ?? null,
|
|
142
|
+
def.workflow ?? null,
|
|
143
|
+
def.attachments ? JSON.stringify(def.attachments) : null,
|
|
144
|
+
def.delivery ?? null,
|
|
133
145
|
def.prompt,
|
|
134
146
|
def.enabled ?? true,
|
|
135
147
|
def.status ?? 'active',
|
|
@@ -137,8 +149,8 @@ export async function upsertSchedule(def, { dataDir } = {}) {
|
|
|
137
149
|
];
|
|
138
150
|
const { rows } = await db.query(
|
|
139
151
|
`INSERT INTO scheduler.schedules
|
|
140
|
-
(name, description, when_at, when_cron, timezone, target, channel_id, model, cwd, prompt, enabled, status, next_fire_at)
|
|
141
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13)
|
|
152
|
+
(name, description, when_at, when_cron, timezone, target, channel_id, model, cwd, workflow, attachments, delivery, prompt, enabled, status, next_fire_at)
|
|
153
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16)
|
|
142
154
|
ON CONFLICT (name) DO UPDATE SET
|
|
143
155
|
description = EXCLUDED.description,
|
|
144
156
|
when_at = EXCLUDED.when_at,
|
|
@@ -148,6 +160,9 @@ export async function upsertSchedule(def, { dataDir } = {}) {
|
|
|
148
160
|
channel_id = EXCLUDED.channel_id,
|
|
149
161
|
model = EXCLUDED.model,
|
|
150
162
|
cwd = EXCLUDED.cwd,
|
|
163
|
+
workflow = EXCLUDED.workflow,
|
|
164
|
+
attachments = EXCLUDED.attachments,
|
|
165
|
+
delivery = EXCLUDED.delivery,
|
|
151
166
|
prompt = EXCLUDED.prompt,
|
|
152
167
|
enabled = EXCLUDED.enabled,
|
|
153
168
|
next_fire_at = EXCLUDED.next_fire_at,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runtime/shared/time-format.mjs — compact elapsed-time labels shared by every
|
|
3
|
+
* surface (TUI, terminal, desktop renderer). Moved verbatim from
|
|
4
|
+
* src/tui/time-format.mjs so tool-card detail rows ("Running · 12s") derive
|
|
5
|
+
* from ONE formatter; the TUI module re-exports from here.
|
|
6
|
+
*
|
|
7
|
+
* Examples:
|
|
8
|
+
* 42s
|
|
9
|
+
* 9m 23s
|
|
10
|
+
* 1h 2m 3s
|
|
11
|
+
* 1d 3h 20m
|
|
12
|
+
*/
|
|
13
|
+
export function formatDuration(ms, options = {}) {
|
|
14
|
+
if (!Number.isFinite(Number(ms))) return '';
|
|
15
|
+
const value = Math.max(0, Number(ms) || 0);
|
|
16
|
+
if (value < 60_000) {
|
|
17
|
+
if (value < 1_000) return '';
|
|
18
|
+
return `${Math.floor(value / 1000)}s`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let days = Math.floor(value / 86_400_000);
|
|
22
|
+
let hours = Math.floor((value % 86_400_000) / 3_600_000);
|
|
23
|
+
let minutes = Math.floor((value % 3_600_000) / 60_000);
|
|
24
|
+
const seconds = Math.floor((value % 60_000) / 1000);
|
|
25
|
+
|
|
26
|
+
if (options.mostSignificantOnly) {
|
|
27
|
+
if (days > 0) return `${days}d`;
|
|
28
|
+
if (hours > 0) return `${hours}h`;
|
|
29
|
+
if (minutes > 0) return `${minutes}m`;
|
|
30
|
+
return `${seconds}s`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const hide = options.hideTrailingZeros;
|
|
34
|
+
if (days > 0) {
|
|
35
|
+
if (hide && hours === 0 && minutes === 0) return `${days}d`;
|
|
36
|
+
if (hide && minutes === 0) return `${days}d ${hours}h`;
|
|
37
|
+
return `${days}d ${hours}h ${minutes}m`;
|
|
38
|
+
}
|
|
39
|
+
if (hours > 0) {
|
|
40
|
+
if (hide && minutes === 0 && seconds === 0) return `${hours}h`;
|
|
41
|
+
if (hide && seconds === 0) return `${hours}h ${minutes}m`;
|
|
42
|
+
return `${hours}h ${minutes}m ${seconds}s`;
|
|
43
|
+
}
|
|
44
|
+
if (minutes > 0) {
|
|
45
|
+
if (hide && seconds === 0) return `${minutes}m`;
|
|
46
|
+
return `${minutes}m ${seconds}s`;
|
|
47
|
+
}
|
|
48
|
+
return `${seconds}s`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function formatElapsed(ms) {
|
|
52
|
+
const n = Math.max(0, Number(ms || 0));
|
|
53
|
+
if (!Number.isFinite(n) || n <= 0) return '';
|
|
54
|
+
if (n < 1000) return '';
|
|
55
|
+
return formatDuration(n);
|
|
56
|
+
}
|