refactor: centralize task runtime contracts (#199)
This commit is contained in:
@@ -10,10 +10,16 @@ import { z } from 'zod';
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { CronExpressionParser } from 'cron-parser';
|
import { CronExpressionParser } from 'cron-parser';
|
||||||
import { EJCLAW_ENV, normalizePairedRoomRole } from 'ejclaw-runners-shared';
|
import {
|
||||||
|
DEFAULT_SCHEDULE_TASK_CONTEXT_MODE,
|
||||||
|
DEFAULT_TASK_CONTEXT_MODE,
|
||||||
|
DEFAULT_WATCH_CI_CONTEXT_MODE,
|
||||||
|
EJCLAW_ENV,
|
||||||
|
TASK_CONTEXT_MODES,
|
||||||
|
normalizePairedRoomRole,
|
||||||
|
} from 'ejclaw-runners-shared';
|
||||||
import {
|
import {
|
||||||
buildCiWatchPrompt,
|
buildCiWatchPrompt,
|
||||||
DEFAULT_WATCH_CI_CONTEXT_MODE,
|
|
||||||
normalizeWatchCiIntervalSeconds,
|
normalizeWatchCiIntervalSeconds,
|
||||||
} from './watch-ci.js';
|
} from './watch-ci.js';
|
||||||
import { resolveHostEvidenceResponsesDir } from './host-evidence.js';
|
import { resolveHostEvidenceResponsesDir } from './host-evidence.js';
|
||||||
@@ -142,10 +148,10 @@ SCHEDULE VALUE FORMAT (all times are LOCAL timezone):
|
|||||||
'cron: "*/5 * * * *" | interval: milliseconds like "300000" | once: local timestamp like "2026-02-01T15:30:00" (no Z suffix!)',
|
'cron: "*/5 * * * *" | interval: milliseconds like "300000" | once: local timestamp like "2026-02-01T15:30:00" (no Z suffix!)',
|
||||||
),
|
),
|
||||||
context_mode: z
|
context_mode: z
|
||||||
.enum(['group', 'isolated'])
|
.enum(TASK_CONTEXT_MODES)
|
||||||
.default('group')
|
.default(DEFAULT_SCHEDULE_TASK_CONTEXT_MODE)
|
||||||
.describe(
|
.describe(
|
||||||
'group=runs with chat history and memory, isolated=fresh session (include context in prompt)',
|
'group=runs with chat history and memory, isolated=fresh session (include context in prompt). Default: group.',
|
||||||
),
|
),
|
||||||
target_group_jid: z
|
target_group_jid: z
|
||||||
.string()
|
.string()
|
||||||
@@ -226,7 +232,7 @@ SCHEDULE VALUE FORMAT (all times are LOCAL timezone):
|
|||||||
schedule_value: args.schedule_value,
|
schedule_value: args.schedule_value,
|
||||||
agent_type: currentAgentType(),
|
agent_type: currentAgentType(),
|
||||||
room_role: currentRoomRole(),
|
room_role: currentRoomRole(),
|
||||||
context_mode: args.context_mode || 'group',
|
context_mode: args.context_mode || DEFAULT_SCHEDULE_TASK_CONTEXT_MODE,
|
||||||
targetJid,
|
targetJid,
|
||||||
createdBy: groupFolder,
|
createdBy: groupFolder,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
@@ -300,7 +306,7 @@ server.tool(
|
|||||||
'How often to poll in seconds. Defaults to 60 for generic watchers and 15 for GitHub host-driven watchers. Generic watchers require 30+, GitHub host-driven watchers allow 10+.',
|
'How often to poll in seconds. Defaults to 60 for generic watchers and 15 for GitHub host-driven watchers. Generic watchers require 30+, GitHub host-driven watchers allow 10+.',
|
||||||
),
|
),
|
||||||
context_mode: z
|
context_mode: z
|
||||||
.enum(['group', 'isolated'])
|
.enum(TASK_CONTEXT_MODES)
|
||||||
.default(DEFAULT_WATCH_CI_CONTEXT_MODE)
|
.default(DEFAULT_WATCH_CI_CONTEXT_MODE)
|
||||||
.describe(
|
.describe(
|
||||||
'group=runs with chat history and memory, isolated=fresh session (include all context in check_instructions). Default: isolated.',
|
'group=runs with chat history and memory, isolated=fresh session (include all context in check_instructions). Default: isolated.',
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
|
import {
|
||||||
|
DEFAULT_WATCH_CI_CONTEXT_MODE,
|
||||||
|
WATCH_CI_PROMPT_PREFIX,
|
||||||
|
} from 'ejclaw-runners-shared';
|
||||||
|
|
||||||
export const DEFAULT_WATCH_CI_INTERVAL_SECONDS = 60;
|
export const DEFAULT_WATCH_CI_INTERVAL_SECONDS = 60;
|
||||||
export const MIN_WATCH_CI_INTERVAL_SECONDS = 30;
|
export const MIN_WATCH_CI_INTERVAL_SECONDS = 30;
|
||||||
export const DEFAULT_GITHUB_WATCH_CI_INTERVAL_SECONDS = 15;
|
export const DEFAULT_GITHUB_WATCH_CI_INTERVAL_SECONDS = 15;
|
||||||
export const MIN_GITHUB_WATCH_CI_INTERVAL_SECONDS = 10;
|
export const MIN_GITHUB_WATCH_CI_INTERVAL_SECONDS = 10;
|
||||||
export const MAX_WATCH_CI_INTERVAL_SECONDS = 3600;
|
export const MAX_WATCH_CI_INTERVAL_SECONDS = 3600;
|
||||||
export const DEFAULT_WATCH_CI_CONTEXT_MODE = 'isolated';
|
|
||||||
|
|
||||||
export interface NormalizeWatchCiIntervalOptions {
|
export interface NormalizeWatchCiIntervalOptions {
|
||||||
ciProvider?: 'github';
|
ciProvider?: 'github';
|
||||||
@@ -48,7 +52,7 @@ export function buildCiWatchPrompt({
|
|||||||
checkInstructions,
|
checkInstructions,
|
||||||
}: BuildCiWatchPromptArgs): string {
|
}: BuildCiWatchPromptArgs): string {
|
||||||
return `
|
return `
|
||||||
[BACKGROUND CI WATCH]
|
${WATCH_CI_PROMPT_PREFIX}
|
||||||
|
|
||||||
You are running as an EJClaw background CI watcher.
|
You are running as an EJClaw background CI watcher.
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import {
|
||||||
|
DEFAULT_WATCH_CI_CONTEXT_MODE,
|
||||||
|
WATCH_CI_PROMPT_PREFIX,
|
||||||
|
} from 'ejclaw-runners-shared';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
buildCiWatchPrompt,
|
buildCiWatchPrompt,
|
||||||
DEFAULT_WATCH_CI_CONTEXT_MODE,
|
|
||||||
DEFAULT_GITHUB_WATCH_CI_INTERVAL_SECONDS,
|
DEFAULT_GITHUB_WATCH_CI_INTERVAL_SECONDS,
|
||||||
normalizeWatchCiIntervalSeconds,
|
normalizeWatchCiIntervalSeconds,
|
||||||
} from '../src/watch-ci.js';
|
} from '../src/watch-ci.js';
|
||||||
@@ -16,6 +19,7 @@ describe('watch-ci helpers', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(prompt).toContain('PR #42 checks');
|
expect(prompt).toContain('PR #42 checks');
|
||||||
|
expect(prompt.startsWith(WATCH_CI_PROMPT_PREFIX)).toBe(true);
|
||||||
expect(prompt).not.toContain('Task ID:');
|
expect(prompt).not.toContain('Task ID:');
|
||||||
expect(prompt).toContain('cancel_task');
|
expect(prompt).toContain('cancel_task');
|
||||||
expect(prompt).toContain('send_message');
|
expect(prompt).toContain('send_message');
|
||||||
|
|||||||
@@ -27,6 +27,16 @@ export {
|
|||||||
type GitHubEvidenceAction,
|
type GitHubEvidenceAction,
|
||||||
type HostEvidenceAction,
|
type HostEvidenceAction,
|
||||||
} from './evidence-actions.js';
|
} from './evidence-actions.js';
|
||||||
|
export {
|
||||||
|
DEFAULT_SCHEDULE_TASK_CONTEXT_MODE,
|
||||||
|
DEFAULT_TASK_CONTEXT_MODE,
|
||||||
|
DEFAULT_WATCH_CI_CONTEXT_MODE,
|
||||||
|
TASK_CONTEXT_MODES,
|
||||||
|
WATCH_CI_PROMPT_PREFIX,
|
||||||
|
isTaskContextMode,
|
||||||
|
normalizeTaskContextMode,
|
||||||
|
type TaskContextMode,
|
||||||
|
} from './task-runtime.js';
|
||||||
export {
|
export {
|
||||||
extractMarkdownImageAttachments,
|
extractMarkdownImageAttachments,
|
||||||
extractMediaAttachments,
|
extractMediaAttachments,
|
||||||
|
|||||||
18
runners/shared/src/task-runtime.ts
Normal file
18
runners/shared/src/task-runtime.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export const TASK_CONTEXT_MODES = ['group', 'isolated'] as const;
|
||||||
|
export type TaskContextMode = (typeof TASK_CONTEXT_MODES)[number];
|
||||||
|
|
||||||
|
export const DEFAULT_TASK_CONTEXT_MODE: TaskContextMode = 'isolated';
|
||||||
|
export const DEFAULT_SCHEDULE_TASK_CONTEXT_MODE: TaskContextMode = 'group';
|
||||||
|
export const DEFAULT_WATCH_CI_CONTEXT_MODE: TaskContextMode = 'isolated';
|
||||||
|
export const WATCH_CI_PROMPT_PREFIX = '[BACKGROUND CI WATCH]';
|
||||||
|
|
||||||
|
export function isTaskContextMode(value: unknown): value is TaskContextMode {
|
||||||
|
return value === 'group' || value === 'isolated';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeTaskContextMode(
|
||||||
|
value: unknown,
|
||||||
|
fallback: TaskContextMode = DEFAULT_TASK_CONTEXT_MODE,
|
||||||
|
): TaskContextMode {
|
||||||
|
return isTaskContextMode(value) ? value : fallback;
|
||||||
|
}
|
||||||
32
runners/shared/test/task-runtime.test.ts
Normal file
32
runners/shared/test/task-runtime.test.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import {
|
||||||
|
DEFAULT_SCHEDULE_TASK_CONTEXT_MODE,
|
||||||
|
DEFAULT_TASK_CONTEXT_MODE,
|
||||||
|
DEFAULT_WATCH_CI_CONTEXT_MODE,
|
||||||
|
TASK_CONTEXT_MODES,
|
||||||
|
WATCH_CI_PROMPT_PREFIX,
|
||||||
|
isTaskContextMode,
|
||||||
|
normalizeTaskContextMode,
|
||||||
|
} from '../src/task-runtime.js';
|
||||||
|
|
||||||
|
describe('task runtime constants', () => {
|
||||||
|
it('defines the task context modes and default', () => {
|
||||||
|
expect(TASK_CONTEXT_MODES).toEqual(['group', 'isolated']);
|
||||||
|
expect(DEFAULT_TASK_CONTEXT_MODE).toBe('isolated');
|
||||||
|
expect(DEFAULT_SCHEDULE_TASK_CONTEXT_MODE).toBe('group');
|
||||||
|
expect(DEFAULT_WATCH_CI_CONTEXT_MODE).toBe('isolated');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('normalizes task context modes consistently', () => {
|
||||||
|
expect(isTaskContextMode('group')).toBe(true);
|
||||||
|
expect(isTaskContextMode('isolated')).toBe(true);
|
||||||
|
expect(isTaskContextMode('invalid')).toBe(false);
|
||||||
|
expect(normalizeTaskContextMode('group')).toBe('group');
|
||||||
|
expect(normalizeTaskContextMode('invalid')).toBe('isolated');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('defines the watch CI prompt sentinel', () => {
|
||||||
|
expect(WATCH_CI_PROMPT_PREFIX).toBe('[BACKGROUND CI WATCH]');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
import { Database } from 'bun:sqlite';
|
import { Database } from 'bun:sqlite';
|
||||||
|
import {
|
||||||
|
DEFAULT_TASK_CONTEXT_MODE,
|
||||||
|
WATCH_CI_PROMPT_PREFIX,
|
||||||
|
} from 'ejclaw-runners-shared';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AgentType,
|
AgentType,
|
||||||
@@ -70,7 +74,7 @@ export function createTaskInDatabase(
|
|||||||
task.prompt,
|
task.prompt,
|
||||||
task.schedule_type,
|
task.schedule_type,
|
||||||
task.schedule_value,
|
task.schedule_value,
|
||||||
task.context_mode || 'isolated',
|
task.context_mode || DEFAULT_TASK_CONTEXT_MODE,
|
||||||
task.next_run,
|
task.next_run,
|
||||||
task.status,
|
task.status,
|
||||||
task.created_at,
|
task.created_at,
|
||||||
@@ -217,10 +221,10 @@ export function hasActiveCiWatcherForChatInDatabase(
|
|||||||
const row = database
|
const row = database
|
||||||
.prepare(
|
.prepare(
|
||||||
`SELECT 1 FROM scheduled_tasks
|
`SELECT 1 FROM scheduled_tasks
|
||||||
WHERE chat_jid = ? AND status = 'active' AND prompt LIKE '[BACKGROUND CI WATCH]%'
|
WHERE chat_jid = ? AND status = 'active' AND prompt LIKE ?
|
||||||
LIMIT 1`,
|
LIMIT 1`,
|
||||||
)
|
)
|
||||||
.get(chatJid);
|
.get(chatJid, `${WATCH_CI_PROMPT_PREFIX}%`);
|
||||||
return !!row;
|
return !!row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { CronExpressionParser } from 'cron-parser';
|
import { CronExpressionParser } from 'cron-parser';
|
||||||
|
import { normalizeTaskContextMode } from 'ejclaw-runners-shared';
|
||||||
|
|
||||||
import { createTask, findDuplicateCiWatcher } from '../db.js';
|
import { createTask, findDuplicateCiWatcher } from '../db.js';
|
||||||
import { normalizeStoredAgentType } from '../db/room-registration.js';
|
import { normalizeStoredAgentType } from '../db/room-registration.js';
|
||||||
@@ -49,10 +50,7 @@ export function handleScheduleTask(
|
|||||||
const taskId =
|
const taskId =
|
||||||
data.taskId ||
|
data.taskId ||
|
||||||
`task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
`task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
const contextMode =
|
const contextMode = normalizeTaskContextMode(data.context_mode);
|
||||||
data.context_mode === 'group' || data.context_mode === 'isolated'
|
|
||||||
? data.context_mode
|
|
||||||
: 'isolated';
|
|
||||||
const scheduledAgentType =
|
const scheduledAgentType =
|
||||||
normalizeStoredAgentType(data.agent_type) ??
|
normalizeStoredAgentType(data.agent_type) ??
|
||||||
target.room.agentType ??
|
target.room.agentType ??
|
||||||
|
|||||||
@@ -1,43 +1,39 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
isBotMessageSourceKind,
|
inferMessageSourceKindFromBotFlag,
|
||||||
normalizeMessageSourceKind,
|
normalizeMessageSourceKind,
|
||||||
resolveInjectedMessageSourceKind,
|
resolveInjectedMessageSourceKind,
|
||||||
} from './message-source.js';
|
} from './message-source.js';
|
||||||
|
import { DEFAULT_MESSAGE_SOURCE_KIND, MESSAGE_SOURCE_KINDS } from './types.js';
|
||||||
|
|
||||||
describe('message source helpers', () => {
|
describe('message source kind helpers', () => {
|
||||||
it('defaults IPC injected messages to trusted human-equivalent provenance', () => {
|
it('uses the canonical source kind list', () => {
|
||||||
|
expect(MESSAGE_SOURCE_KINDS).toEqual([
|
||||||
|
'human',
|
||||||
|
'bot',
|
||||||
|
'trusted_external_bot',
|
||||||
|
'ipc_injected_human',
|
||||||
|
'ipc_injected_bot',
|
||||||
|
]);
|
||||||
|
expect(DEFAULT_MESSAGE_SOURCE_KIND).toBe('human');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('normalizes unknown source kinds to the default', () => {
|
||||||
|
expect(normalizeMessageSourceKind('trusted_external_bot')).toBe(
|
||||||
|
'trusted_external_bot',
|
||||||
|
);
|
||||||
|
expect(normalizeMessageSourceKind('unknown')).toBe('human');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('derives source kinds from legacy and injected inputs', () => {
|
||||||
|
expect(inferMessageSourceKindFromBotFlag(true)).toBe('bot');
|
||||||
|
expect(inferMessageSourceKindFromBotFlag(false)).toBe('human');
|
||||||
expect(resolveInjectedMessageSourceKind({ treatAsHuman: true })).toBe(
|
expect(resolveInjectedMessageSourceKind({ treatAsHuman: true })).toBe(
|
||||||
'trusted_external_bot',
|
'trusted_external_bot',
|
||||||
);
|
);
|
||||||
expect(
|
|
||||||
isBotMessageSourceKind(
|
|
||||||
resolveInjectedMessageSourceKind({ treatAsHuman: true }),
|
|
||||||
),
|
|
||||||
).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('defaults non-human IPC injected messages to bot-equivalent provenance', () => {
|
|
||||||
expect(resolveInjectedMessageSourceKind({ treatAsHuman: false })).toBe(
|
expect(resolveInjectedMessageSourceKind({ treatAsHuman: false })).toBe(
|
||||||
'ipc_injected_bot',
|
'ipc_injected_bot',
|
||||||
);
|
);
|
||||||
expect(
|
|
||||||
isBotMessageSourceKind(
|
|
||||||
resolveInjectedMessageSourceKind({ treatAsHuman: false }),
|
|
||||||
),
|
|
||||||
).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('honors valid explicit source kinds and normalizes invalid values', () => {
|
|
||||||
expect(
|
|
||||||
resolveInjectedMessageSourceKind({
|
|
||||||
treatAsHuman: true,
|
|
||||||
sourceKind: 'ipc_injected_human',
|
|
||||||
}),
|
|
||||||
).toBe('ipc_injected_human');
|
|
||||||
|
|
||||||
expect(normalizeMessageSourceKind('bot', 'human')).toBe('bot');
|
|
||||||
expect(normalizeMessageSourceKind('not-real', 'human')).toBe('human');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import type { MessageSourceKind } from './types.js';
|
import {
|
||||||
|
DEFAULT_MESSAGE_SOURCE_KIND,
|
||||||
|
MESSAGE_SOURCE_KINDS,
|
||||||
|
type MessageSourceKind,
|
||||||
|
} from './types.js';
|
||||||
|
|
||||||
const MESSAGE_SOURCE_KINDS = new Set<MessageSourceKind>([
|
const MESSAGE_SOURCE_KIND_SET = new Set<MessageSourceKind>(
|
||||||
'human',
|
MESSAGE_SOURCE_KINDS,
|
||||||
'bot',
|
);
|
||||||
'trusted_external_bot',
|
|
||||||
'ipc_injected_human',
|
|
||||||
'ipc_injected_bot',
|
|
||||||
]);
|
|
||||||
|
|
||||||
export function normalizeMessageSourceKind(
|
export function normalizeMessageSourceKind(
|
||||||
value: unknown,
|
value: unknown,
|
||||||
fallback: MessageSourceKind = 'human',
|
fallback: MessageSourceKind = DEFAULT_MESSAGE_SOURCE_KIND,
|
||||||
): MessageSourceKind {
|
): MessageSourceKind {
|
||||||
return typeof value === 'string' &&
|
return typeof value === 'string' &&
|
||||||
MESSAGE_SOURCE_KINDS.has(value as MessageSourceKind)
|
MESSAGE_SOURCE_KIND_SET.has(value as MessageSourceKind)
|
||||||
? (value as MessageSourceKind)
|
? (value as MessageSourceKind)
|
||||||
: fallback;
|
: fallback;
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@ export function isBotMessageSourceKind(kind: MessageSourceKind): boolean {
|
|||||||
export function inferMessageSourceKindFromBotFlag(
|
export function inferMessageSourceKindFromBotFlag(
|
||||||
isBotMessage: boolean | number | null | undefined,
|
isBotMessage: boolean | number | null | undefined,
|
||||||
): MessageSourceKind {
|
): MessageSourceKind {
|
||||||
return isBotMessage ? 'bot' : 'human';
|
return isBotMessage ? 'bot' : DEFAULT_MESSAGE_SOURCE_KIND;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveInjectedMessageSourceKind(args: {
|
export function resolveInjectedMessageSourceKind(args: {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { ArbiterVerdict } from './types.js';
|
||||||
|
|
||||||
export type VisibleVerdict =
|
export type VisibleVerdict =
|
||||||
| 'step_done'
|
| 'step_done'
|
||||||
| 'task_done'
|
| 'task_done'
|
||||||
@@ -7,12 +9,7 @@ export type VisibleVerdict =
|
|||||||
| 'needs_context'
|
| 'needs_context'
|
||||||
| 'continue';
|
| 'continue';
|
||||||
|
|
||||||
export type ArbiterVerdictResult =
|
export type ArbiterVerdictResult = ArbiterVerdict | 'unknown';
|
||||||
| 'proceed'
|
|
||||||
| 'revise'
|
|
||||||
| 'reset'
|
|
||||||
| 'escalate'
|
|
||||||
| 'unknown';
|
|
||||||
|
|
||||||
const VISIBLE_VERDICT_SCAN_LINE_LIMIT = 5;
|
const VISIBLE_VERDICT_SCAN_LINE_LIMIT = 5;
|
||||||
|
|
||||||
@@ -81,7 +78,7 @@ export function classifyArbiterVerdict(
|
|||||||
const normalized = verdictMatch[1].toLowerCase();
|
const normalized = verdictMatch[1].toLowerCase();
|
||||||
return normalized === 'continue'
|
return normalized === 'continue'
|
||||||
? 'proceed'
|
? 'proceed'
|
||||||
: (normalized as ArbiterVerdictResult);
|
: (normalized as ArbiterVerdict);
|
||||||
}
|
}
|
||||||
return 'unknown';
|
return 'unknown';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { WATCH_CI_PROMPT_PREFIX } from 'ejclaw-runners-shared';
|
||||||
|
|
||||||
import { TIMEZONE } from './config.js';
|
import { TIMEZONE } from './config.js';
|
||||||
import type { ScheduledTask } from './types.js';
|
import type { ScheduledTask } from './types.js';
|
||||||
import { formatElapsedKorean } from './utils.js';
|
import { formatElapsedKorean } from './utils.js';
|
||||||
@@ -8,7 +10,7 @@ export type WatcherStatusPhase =
|
|||||||
| 'retrying'
|
| 'retrying'
|
||||||
| 'completed';
|
| 'completed';
|
||||||
|
|
||||||
export const WATCH_CI_PREFIX = '[BACKGROUND CI WATCH]';
|
export const WATCH_CI_PREFIX = WATCH_CI_PROMPT_PREFIX;
|
||||||
export const TASK_STATUS_MESSAGE_PREFIX = '\u2063\u2063\u2063';
|
export const TASK_STATUS_MESSAGE_PREFIX = '\u2063\u2063\u2063';
|
||||||
export const DEFAULT_WATCH_CI_MAX_DURATION_MS = 24 * 60 * 60 * 1000;
|
export const DEFAULT_WATCH_CI_MAX_DURATION_MS = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
|||||||
18
src/types.ts
18
src/types.ts
@@ -4,6 +4,7 @@ import {
|
|||||||
normalizePairedRoomRole,
|
normalizePairedRoomRole,
|
||||||
normalizePairedRoomRoleOrNull,
|
normalizePairedRoomRoleOrNull,
|
||||||
type PairedRoomRole,
|
type PairedRoomRole,
|
||||||
|
type TaskContextMode,
|
||||||
} from 'ejclaw-runners-shared';
|
} from 'ejclaw-runners-shared';
|
||||||
import type { VisibleVerdict } from './paired-verdict.js';
|
import type { VisibleVerdict } from './paired-verdict.js';
|
||||||
|
|
||||||
@@ -208,12 +209,15 @@ export interface RegisteredGroup {
|
|||||||
workDir?: string; // Working directory for the agent (defaults to group folder)
|
workDir?: string; // Working directory for the agent (defaults to group folder)
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MessageSourceKind =
|
export const MESSAGE_SOURCE_KINDS = [
|
||||||
| 'human'
|
'human',
|
||||||
| 'bot'
|
'bot',
|
||||||
| 'trusted_external_bot'
|
'trusted_external_bot',
|
||||||
| 'ipc_injected_human'
|
'ipc_injected_human',
|
||||||
| 'ipc_injected_bot';
|
'ipc_injected_bot',
|
||||||
|
] as const;
|
||||||
|
export type MessageSourceKind = (typeof MESSAGE_SOURCE_KINDS)[number];
|
||||||
|
export const DEFAULT_MESSAGE_SOURCE_KIND: MessageSourceKind = 'human';
|
||||||
|
|
||||||
export interface NewMessage {
|
export interface NewMessage {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -242,7 +246,7 @@ export interface ScheduledTask {
|
|||||||
prompt: string;
|
prompt: string;
|
||||||
schedule_type: 'cron' | 'interval' | 'once';
|
schedule_type: 'cron' | 'interval' | 'once';
|
||||||
schedule_value: string;
|
schedule_value: string;
|
||||||
context_mode: 'group' | 'isolated';
|
context_mode: TaskContextMode;
|
||||||
next_run: string | null;
|
next_run: string | null;
|
||||||
last_run: string | null;
|
last_run: string | null;
|
||||||
last_result: string | null;
|
last_result: string | null;
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { CronExpressionParser } from 'cron-parser';
|
import { CronExpressionParser } from 'cron-parser';
|
||||||
|
import {
|
||||||
|
DEFAULT_TASK_CONTEXT_MODE,
|
||||||
|
isTaskContextMode,
|
||||||
|
} from 'ejclaw-runners-shared';
|
||||||
|
|
||||||
import { TIMEZONE } from './config.js';
|
import { TIMEZONE } from './config.js';
|
||||||
import { isWatchCiTask } from './task-watch-status.js';
|
import { isWatchCiTask } from './task-watch-status.js';
|
||||||
@@ -100,10 +104,6 @@ function isScheduleType(
|
|||||||
return value === 'cron' || value === 'interval' || value === 'once';
|
return value === 'cron' || value === 'interval' || value === 'once';
|
||||||
}
|
}
|
||||||
|
|
||||||
function isContextMode(value: unknown): value is ScheduledTask['context_mode'] {
|
|
||||||
return value === 'group' || value === 'isolated';
|
|
||||||
}
|
|
||||||
|
|
||||||
function isAgentType(value: unknown): value is AgentType {
|
function isAgentType(value: unknown): value is AgentType {
|
||||||
return value === 'claude-code' || value === 'codex';
|
return value === 'claude-code' || value === 'codex';
|
||||||
}
|
}
|
||||||
@@ -294,9 +294,9 @@ async function handleTaskCreateRoute(
|
|||||||
return jsonResponse({ error: next.error }, { status: 400 });
|
return jsonResponse({ error: next.error }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const contextMode = isContextMode(body.contextMode)
|
const contextMode = isTaskContextMode(body.contextMode)
|
||||||
? body.contextMode
|
? body.contextMode
|
||||||
: 'isolated';
|
: DEFAULT_TASK_CONTEXT_MODE;
|
||||||
const agentType = isAgentType(body.agentType)
|
const agentType = isAgentType(body.agentType)
|
||||||
? body.agentType
|
? body.agentType
|
||||||
: (room.group.agentType ?? 'claude-code');
|
: (room.group.agentType ?? 'claude-code');
|
||||||
|
|||||||
Reference in New Issue
Block a user