fix: job edit schedule format error and refactor services directory

- Fix #25: job update sends schedule as plain string but upstream expects
  { kind, expr, display } object, causing "'str' object has no attribute 'get'"
- Move hermes-cli.ts, hermes.ts, hermes-profile.ts into services/hermes/
  for multi-agent namespacing consistency
- Fix ts-node Set spread compatibility in filesystem.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
ekko
2026-04-17 16:48:24 +08:00
parent 9a0b50f370
commit 3d2b1c5e47
15 changed files with 40 additions and 19 deletions
+1 -1
View File
@@ -45,7 +45,7 @@ export interface CreateJobRequest {
export interface UpdateJobRequest {
name?: string
schedule?: string
schedule?: string | { kind: string; expr: string; display: string }
prompt?: string
deliver?: string
skills?: string[]
@@ -48,6 +48,8 @@ const targetOptions = computed(() => [
{ label: t('jobs.local'), value: 'local' },
])
const originalSchedule = ref<{ kind: string; expr: string; display: string } | null>(null)
onMounted(async () => {
if (props.jobId) {
try {
@@ -60,6 +62,9 @@ onMounted(async () => {
deliver: job.deliver || 'origin',
repeat_times: typeof job.repeat === 'number' ? job.repeat : (typeof job.repeat === 'object' ? job.repeat.times : null),
}
if (typeof job.schedule === 'object' && job.schedule) {
originalSchedule.value = job.schedule
}
} catch (e: any) {
message.error(t('jobs.loadFailed') + ': ' + e.message)
}
@@ -86,6 +91,14 @@ async function handleSave() {
repeat: formData.value.repeat_times ?? undefined,
}
if (isEdit.value && originalSchedule.value) {
(payload as any).schedule = {
kind: originalSchedule.value.kind,
expr: formData.value.schedule,
display: formData.value.schedule,
}
}
if (isEdit.value) {
await jobsStore.updateJob(props.jobId!, payload)
message.success(t('jobs.jobUpdated'))
+2 -2
View File
@@ -10,7 +10,7 @@ import { config } from './config'
import { hermesRoutes, setupTerminalWebSocket, proxyMiddleware } from './routes/hermes'
import { uploadRoutes } from './routes/upload'
import { webhookRoutes } from './routes/webhook'
import * as hermesCli from './services/hermes-cli'
import * as hermesCli from './services/hermes/hermes-cli'
import { getToken, authMiddleware } from './services/auth'
function getLocalVersion(): string {
@@ -232,7 +232,7 @@ function bindShutdown() {
async function ensureApiServerConfig() {
const { readFileSync, writeFileSync, existsSync, copyFileSync } = await import('fs')
const yaml = (await import('js-yaml')).default
const { getActiveConfigPath } = await import('./services/hermes-profile')
const { getActiveConfigPath } = await import('./services/hermes/hermes-profile')
const configPath = getActiveConfigPath()
const defaults: Record<string, any> = {
+2 -2
View File
@@ -3,8 +3,8 @@ import { readFile, writeFile, copyFile } from 'fs/promises'
import { chmod } from 'fs/promises'
import { join } from 'path'
import YAML from 'js-yaml'
import { restartGateway } from '../../services/hermes-cli'
import { getActiveConfigPath, getActiveEnvPath, getActiveProfileDir } from '../../services/hermes-profile'
import { restartGateway } from '../../services/hermes/hermes-cli'
import { getActiveConfigPath, getActiveEnvPath, getActiveProfileDir } from '../../services/hermes/hermes-profile'
// Platform sections that require gateway restart after config change
const PLATFORM_SECTIONS = new Set([
@@ -2,8 +2,8 @@ import Router from '@koa/router'
import { readdir, readFile, stat, writeFile, mkdir, copyFile } from 'fs/promises'
import { join, resolve } from 'path'
import YAML from 'js-yaml'
import { getActiveProfileDir, getActiveConfigPath, getActiveAuthPath, getActiveEnvPath } from '../../services/hermes-profile'
import * as hermesCli from '../../services/hermes-cli'
import { getActiveProfileDir, getActiveConfigPath, getActiveAuthPath, getActiveEnvPath } from '../../services/hermes/hermes-profile'
import * as hermesCli from '../../services/hermes/hermes-cli'
// --- Provider env var mapping (from hermes providers.py HERMES_OVERLAYS + config.py) ---
// Maps provider key → { api_key_envs: all env var aliases for API key, base_url_env: env var for base URL }
@@ -513,7 +513,7 @@ fsRoutes.get('/api/hermes/available-models', async (ctx) => {
for (const result of results) {
if (result.status === 'fulfilled' && result.value.models.length > 0) {
const { key, label, base_url, models } = result.value
groups.push({ provider: key, label, base_url, models: [...new Set(models)] })
groups.push({ provider: key, label, base_url, models: Array.from(new Set(models)) })
} else if (result.status === 'rejected') {
console.error(`[available-models] Failed: ${result.reason?.message || result.reason}`)
}
@@ -524,7 +524,7 @@ fsRoutes.get('/api/hermes/available-models', async (ctx) => {
const dedupedGroups: typeof groups = []
const seenProviders = new Map<string, number>()
for (const g of groups) {
g.models = [...new Set(g.models)]
g.models = Array.from(new Set(g.models))
const existingIdx = seenProviders.get(g.provider)
if (existingIdx !== undefined) {
// Merge models into existing group
+1 -1
View File
@@ -1,5 +1,5 @@
import Router from '@koa/router'
import * as hermesCli from '../../services/hermes-cli'
import * as hermesCli from '../../services/hermes/hermes-cli'
export const logRoutes = new Router()
@@ -4,7 +4,7 @@ import { mkdir, writeFile } from 'fs/promises'
import { basename, join } from 'path'
import { tmpdir, homedir } from 'os'
import YAML from 'js-yaml'
import * as hermesCli from '../../services/hermes-cli'
import * as hermesCli from '../../services/hermes/hermes-cli'
const apiServerDefaults = {
enabled: true,
@@ -1,5 +1,5 @@
import Router from '@koa/router'
import * as hermesCli from '../../services/hermes-cli'
import * as hermesCli from '../../services/hermes/hermes-cli'
export const sessionRoutes = new Router()
+2 -2
View File
@@ -3,8 +3,8 @@ import axios from 'axios'
import { readFile, writeFile } from 'fs/promises'
import { chmod } from 'fs/promises'
import { resolve } from 'path'
import { restartGateway } from '../../services/hermes-cli'
import { getActiveEnvPath } from '../../services/hermes-profile'
import { restartGateway } from '../../services/hermes/hermes-cli'
import { getActiveEnvPath } from '../../services/hermes/hermes-profile'
const envPath = () => getActiveEnvPath()
const ILINK_BASE = 'https://ilinkai.weixin.qq.com'
+1 -1
View File
@@ -1,5 +1,5 @@
import Router from '@koa/router'
import { emitWebhook } from '../services/hermes'
import { emitWebhook } from '../services/hermes/hermes'
export const webhookRoutes = new Router()
@@ -1,4 +1,4 @@
import { config } from '../config'
import { config } from '../../config'
const UPSTREAM = config.upstream.replace(/\/$/, '')