botmux 3.2.1 → 3.3.0
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/dist/adapters/cli/pi.d.ts.map +1 -1
- package/dist/adapters/cli/pi.js +4 -0
- package/dist/adapters/cli/pi.js.map +1 -1
- package/dist/adapters/cli/types.d.ts +8 -1
- package/dist/adapters/cli/types.d.ts.map +1 -1
- package/dist/cli/deferred-topic-send.d.ts +37 -0
- package/dist/cli/deferred-topic-send.d.ts.map +1 -0
- package/dist/cli/deferred-topic-send.js +69 -0
- package/dist/cli/deferred-topic-send.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +160 -34
- package/dist/cli.js.map +1 -1
- package/dist/core/command-handler.d.ts.map +1 -1
- package/dist/core/command-handler.js +17 -12
- package/dist/core/command-handler.js.map +1 -1
- package/dist/core/dashboard-ipc-server.d.ts +4 -1
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
- package/dist/core/dashboard-ipc-server.js +78 -8
- package/dist/core/dashboard-ipc-server.js.map +1 -1
- package/dist/core/deferred-schedule-settlement.d.ts +20 -0
- package/dist/core/deferred-schedule-settlement.d.ts.map +1 -0
- package/dist/core/deferred-schedule-settlement.js +18 -0
- package/dist/core/deferred-schedule-settlement.js.map +1 -0
- package/dist/core/deferred-topic-binding.d.ts +19 -0
- package/dist/core/deferred-topic-binding.d.ts.map +1 -0
- package/dist/core/deferred-topic-binding.js +55 -0
- package/dist/core/deferred-topic-binding.js.map +1 -0
- package/dist/core/scheduler.d.ts +26 -20
- package/dist/core/scheduler.d.ts.map +1 -1
- package/dist/core/scheduler.js +114 -56
- package/dist/core/scheduler.js.map +1 -1
- package/dist/core/session-discovery.d.ts.map +1 -1
- package/dist/core/session-discovery.js +47 -15
- package/dist/core/session-discovery.js.map +1 -1
- package/dist/core/session-manager.d.ts +17 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +178 -84
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/types.d.ts +4 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +10 -0
- package/dist/core/types.js.map +1 -1
- package/dist/core/worker-pool.d.ts +7 -0
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +51 -1
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +90 -5
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/schedule-card-model.d.ts +12 -3
- package/dist/dashboard/schedule-card-model.d.ts.map +1 -1
- package/dist/dashboard/schedule-card-model.js +32 -8
- package/dist/dashboard/schedule-card-model.js.map +1 -1
- package/dist/dashboard/web/dashboard-components.d.ts +7 -0
- package/dist/dashboard/web/dashboard-components.d.ts.map +1 -1
- package/dist/dashboard/web/dashboard-components.js +11 -2
- package/dist/dashboard/web/dashboard-components.js.map +1 -1
- package/dist/dashboard/web/i18n.d.ts.map +1 -1
- package/dist/dashboard/web/i18n.js +30 -6
- package/dist/dashboard/web/i18n.js.map +1 -1
- package/dist/dashboard/web/schedules-page.d.ts +2 -0
- package/dist/dashboard/web/schedules-page.d.ts.map +1 -1
- package/dist/dashboard/web/schedules-page.js +74 -27
- package/dist/dashboard/web/schedules-page.js.map +1 -1
- package/dist/dashboard-web/app.js +1 -1
- package/dist/dashboard-web/chunks/{bot-defaults-page-O37SAQD5.js → bot-defaults-page-B5AY4B2J.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-6QHMLMIM.js → chunk-CUV65BIQ.js} +3 -3
- package/dist/dashboard-web/chunks/{chunk-O2DPQL7C.js → chunk-JNRSPKLS.js} +1 -1
- package/dist/dashboard-web/chunks/{chunk-FYALHCPG.js → chunk-P6GO5KT4.js} +1 -1
- package/dist/dashboard-web/chunks/chunk-QFMH6ITB.js +1 -0
- package/dist/dashboard-web/chunks/{chunk-B42PCL5P.js → chunk-UBOLHQCK.js} +1 -1
- package/dist/dashboard-web/chunks/{connectors-page-DZBQ42MH.js → connectors-page-KTC2QWNW.js} +1 -1
- package/dist/dashboard-web/chunks/{groups-page-3IEGRZKQ.js → groups-page-B3EKGOLY.js} +1 -1
- package/dist/dashboard-web/chunks/{insights-page-X3ADRON3.js → insights-page-X6RXNCWX.js} +1 -1
- package/dist/dashboard-web/chunks/{monitor-room-IV36RGQY.js → monitor-room-LLRA5LQW.js} +1 -1
- package/dist/dashboard-web/chunks/{monitoring-page-ADFKVON4.js → monitoring-page-7MT5FEOS.js} +1 -1
- package/dist/dashboard-web/chunks/{office-page-CALAOWNS.js → office-page-KD46D5TD.js} +1 -1
- package/dist/dashboard-web/chunks/{overview-page-SLMGLK3M.js → overview-page-JUWNH32O.js} +1 -1
- package/dist/dashboard-web/chunks/{roles-page-L7JJ4FFV.js → roles-page-P6F7NLOI.js} +1 -1
- package/dist/dashboard-web/chunks/schedules-page-XIHWZST4.js +1 -0
- package/dist/dashboard-web/chunks/{sessions-page-OKJ5YWUZ.js → sessions-page-2VECQ27T.js} +1 -1
- package/dist/dashboard-web/chunks/{settings-page-P6MI2ZUE.js → settings-page-HMIUE2JA.js} +1 -1
- package/dist/dashboard-web/chunks/{skills-page-FT7BVZ6X.js → skills-page-A46KVWBL.js} +1 -1
- package/dist/dashboard-web/chunks/{team-federation-page-DEYCPUFB.js → team-federation-page-SDUC56DF.js} +1 -1
- package/dist/dashboard-web/chunks/{v3-page-4IOWREZG.js → v3-page-J5JOTUSQ.js} +1 -1
- package/dist/dashboard-web/chunks/{whiteboards-page-2VMBETZH.js → whiteboards-page-U7LSOAWI.js} +1 -1
- package/dist/dashboard-web/style.css +34 -4
- package/dist/dashboard.js +16 -1
- package/dist/dashboard.js.map +1 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +16 -7
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/zh.d.ts.map +1 -1
- package/dist/i18n/zh.js +16 -7
- package/dist/i18n/zh.js.map +1 -1
- package/dist/im/lark/card-handler.d.ts.map +1 -1
- package/dist/im/lark/card-handler.js +3 -0
- package/dist/im/lark/card-handler.js.map +1 -1
- package/dist/im/lark/event-dispatcher.d.ts +1 -0
- package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
- package/dist/im/lark/event-dispatcher.js +1 -1
- package/dist/im/lark/event-dispatcher.js.map +1 -1
- package/dist/im/lark/schedules-card.d.ts +2 -2
- package/dist/im/lark/schedules-card.d.ts.map +1 -1
- package/dist/im/lark/schedules-card.js +50 -34
- package/dist/im/lark/schedules-card.js.map +1 -1
- package/dist/index-daemon.js +13 -1
- package/dist/index-daemon.js.map +1 -1
- package/dist/services/schedule-store.d.ts +6 -2
- package/dist/services/schedule-store.d.ts.map +1 -1
- package/dist/services/schedule-store.js +19 -4
- package/dist/services/schedule-store.js.map +1 -1
- package/dist/skills/definitions.d.ts.map +1 -1
- package/dist/skills/definitions.js +4 -5
- package/dist/skills/definitions.js.map +1 -1
- package/dist/types.d.ts +36 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/child-env.d.ts +40 -1
- package/dist/utils/child-env.d.ts.map +1 -1
- package/dist/utils/child-env.js +45 -0
- package/dist/utils/child-env.js.map +1 -1
- package/dist/utils/pending-input-queue.d.ts +10 -0
- package/dist/utils/pending-input-queue.d.ts.map +1 -1
- package/dist/utils/pending-input-queue.js +15 -0
- package/dist/utils/pending-input-queue.js.map +1 -1
- package/dist/worker.js +102 -9
- package/dist/worker.js.map +1 -1
- package/dist/workflows/hostExecutors/botmux-schedule.d.ts +3 -1
- package/dist/workflows/hostExecutors/botmux-schedule.d.ts.map +1 -1
- package/dist/workflows/hostExecutors/botmux-schedule.js +14 -3
- package/dist/workflows/hostExecutors/botmux-schedule.js.map +1 -1
- package/dist/workflows/v3/architect.js +1 -1
- package/dist/workflows/v3/architect.js.map +1 -1
- package/dist/workflows/v3/contract.d.ts +8 -0
- package/dist/workflows/v3/contract.d.ts.map +1 -1
- package/dist/workflows/v3/contract.js.map +1 -1
- package/dist/workflows/v3/daemon-run.d.ts.map +1 -1
- package/dist/workflows/v3/daemon-run.js +2 -0
- package/dist/workflows/v3/daemon-run.js.map +1 -1
- package/dist/workflows/v3/ephemeral-pool.d.ts.map +1 -1
- package/dist/workflows/v3/ephemeral-pool.js +10 -2
- package/dist/workflows/v3/ephemeral-pool.js.map +1 -1
- package/dist/workflows/v3/run-envelope.d.ts +24 -24
- package/dist/workflows/v3/runtime.d.ts +5 -0
- package/dist/workflows/v3/runtime.d.ts.map +1 -1
- package/dist/workflows/v3/runtime.js +1 -0
- package/dist/workflows/v3/runtime.js.map +1 -1
- package/package.json +1 -1
- package/dist/dashboard-web/chunks/chunk-I35DXGM4.js +0 -1
- package/dist/dashboard-web/chunks/schedules-page-Z3672POL.js +0 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { DaemonSession } from './types.js';
|
|
2
|
+
export type DeferredScheduleSettlementResult = {
|
|
3
|
+
action: 'ignored';
|
|
4
|
+
} | {
|
|
5
|
+
action: 'materialized';
|
|
6
|
+
rootMessageId: string;
|
|
7
|
+
} | {
|
|
8
|
+
action: 'closed';
|
|
9
|
+
};
|
|
10
|
+
/** Execute the exact-turn lifecycle decision after a hidden schedule run
|
|
11
|
+
* reaches a terminal/idle edge. Timer debounce lives in daemon.ts; keeping the
|
|
12
|
+
* decision here makes the close-vs-retain contract independently testable. */
|
|
13
|
+
export declare function settleDeferredScheduleRun(ds: DaemonSession, context: {
|
|
14
|
+
turnId: string;
|
|
15
|
+
source: 'terminal' | 'idle';
|
|
16
|
+
}, deps: {
|
|
17
|
+
reconcile: (ds: DaemonSession) => string | undefined;
|
|
18
|
+
closeSession: (sessionId: string) => Promise<unknown>;
|
|
19
|
+
}): Promise<DeferredScheduleSettlementResult>;
|
|
20
|
+
//# sourceMappingURL=deferred-schedule-settlement.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deferred-schedule-settlement.d.ts","sourceRoot":"","sources":["../../src/core/deferred-schedule-settlement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,MAAM,gCAAgC,GACxC;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,GACrB;IAAE,MAAM,EAAE,cAAc,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,GACjD;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,CAAC;AAEzB;;8EAE8E;AAC9E,wBAAsB,yBAAyB,CAC7C,EAAE,EAAE,aAAa,EACjB,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAAA;CAAE,EACxD,IAAI,EAAE;IACJ,SAAS,EAAE,CAAC,EAAE,EAAE,aAAa,KAAK,MAAM,GAAG,SAAS,CAAC;IACrD,YAAY,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACvD,GACA,OAAO,CAAC,gCAAgC,CAAC,CAY3C"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Execute the exact-turn lifecycle decision after a hidden schedule run
|
|
2
|
+
* reaches a terminal/idle edge. Timer debounce lives in daemon.ts; keeping the
|
|
3
|
+
* decision here makes the close-vs-retain contract independently testable. */
|
|
4
|
+
export async function settleDeferredScheduleRun(ds, context, deps) {
|
|
5
|
+
const run = ds.session.deferredScheduleRun;
|
|
6
|
+
if (!run || run.turnId !== context.turnId || ds.session.status === 'closed') {
|
|
7
|
+
return { action: 'ignored' };
|
|
8
|
+
}
|
|
9
|
+
if (context.source === 'idle' && ds.lastScreenStatus !== 'idle') {
|
|
10
|
+
return { action: 'ignored' };
|
|
11
|
+
}
|
|
12
|
+
const rootMessageId = deps.reconcile(ds);
|
|
13
|
+
if (rootMessageId)
|
|
14
|
+
return { action: 'materialized', rootMessageId };
|
|
15
|
+
await deps.closeSession(ds.session.sessionId);
|
|
16
|
+
return { action: 'closed' };
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=deferred-schedule-settlement.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deferred-schedule-settlement.js","sourceRoot":"","sources":["../../src/core/deferred-schedule-settlement.ts"],"names":[],"mappings":"AAOA;;8EAE8E;AAC9E,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,EAAiB,EACjB,OAAwD,EACxD,IAGC;IAED,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,mBAAmB,CAAC;IAC3C,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC5E,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAC/B,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,IAAI,EAAE,CAAC,gBAAgB,KAAK,MAAM,EAAE,CAAC;QAChE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAC/B,CAAC;IACD,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IACzC,IAAI,aAAa;QAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,aAAa,EAAE,CAAC;IACpE,MAAM,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC9C,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface DeferredTopicBinding {
|
|
2
|
+
sessionId: string;
|
|
3
|
+
turnId: string;
|
|
4
|
+
chatId: string;
|
|
5
|
+
larkAppId: string;
|
|
6
|
+
routingAnchor: string;
|
|
7
|
+
rootMessageId: string;
|
|
8
|
+
createdAt: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function deferredTopicBindingPath(dataDir: string, sessionId: string): string;
|
|
11
|
+
export declare function readDeferredTopicBinding(dataDir: string, sessionId: string): DeferredTopicBinding | undefined;
|
|
12
|
+
export declare function writeDeferredTopicBinding(dataDir: string, binding: DeferredTopicBinding): void;
|
|
13
|
+
/** Serialize the first-message claim across concurrent `botmux send`
|
|
14
|
+
* processes. The callback may hold the lock across the Lark request: that is
|
|
15
|
+
* intentional, because releasing it before the provider returns would allow
|
|
16
|
+
* two callers to create two roots for the same deferred run. */
|
|
17
|
+
export declare function withDeferredTopicBindingLock<T>(dataDir: string, sessionId: string, fn: () => Promise<T>): Promise<T>;
|
|
18
|
+
export declare function removeDeferredTopicBinding(dataDir: string, sessionId: string): void;
|
|
19
|
+
//# sourceMappingURL=deferred-topic-binding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deferred-topic-binding.d.ts","sourceRoot":"","sources":["../../src/core/deferred-topic-binding.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAMD,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEnF;AAED,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,oBAAoB,GAAG,SAAS,CAmBlC;AAED,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,oBAAoB,GAC5B,IAAI,CAMN;AAED;;;gEAGgE;AAChE,wBAAgB,4BAA4B,CAAC,CAAC,EAC5C,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAIZ;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAGnF"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { withFileLock } from '../utils/file-lock.js';
|
|
5
|
+
function bindingDir(dataDir) {
|
|
6
|
+
return join(dataDir, 'deferred-topics');
|
|
7
|
+
}
|
|
8
|
+
export function deferredTopicBindingPath(dataDir, sessionId) {
|
|
9
|
+
return join(bindingDir(dataDir), `${sessionId}.json`);
|
|
10
|
+
}
|
|
11
|
+
export function readDeferredTopicBinding(dataDir, sessionId) {
|
|
12
|
+
const path = deferredTopicBindingPath(dataDir, sessionId);
|
|
13
|
+
if (!existsSync(path))
|
|
14
|
+
return undefined;
|
|
15
|
+
try {
|
|
16
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
17
|
+
if (parsed.sessionId !== sessionId
|
|
18
|
+
|| typeof parsed.turnId !== 'string'
|
|
19
|
+
|| typeof parsed.chatId !== 'string'
|
|
20
|
+
|| typeof parsed.larkAppId !== 'string'
|
|
21
|
+
|| typeof parsed.routingAnchor !== 'string'
|
|
22
|
+
|| typeof parsed.rootMessageId !== 'string'
|
|
23
|
+
|| !parsed.rootMessageId.startsWith('om_')
|
|
24
|
+
|| typeof parsed.createdAt !== 'string')
|
|
25
|
+
return undefined;
|
|
26
|
+
return parsed;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function writeDeferredTopicBinding(dataDir, binding) {
|
|
33
|
+
const path = deferredTopicBindingPath(dataDir, binding.sessionId);
|
|
34
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
35
|
+
const tmp = `${path}.${process.pid}.${randomUUID()}.tmp`;
|
|
36
|
+
writeFileSync(tmp, JSON.stringify(binding, null, 2), 'utf8');
|
|
37
|
+
renameSync(tmp, path);
|
|
38
|
+
}
|
|
39
|
+
/** Serialize the first-message claim across concurrent `botmux send`
|
|
40
|
+
* processes. The callback may hold the lock across the Lark request: that is
|
|
41
|
+
* intentional, because releasing it before the provider returns would allow
|
|
42
|
+
* two callers to create two roots for the same deferred run. */
|
|
43
|
+
export function withDeferredTopicBindingLock(dataDir, sessionId, fn) {
|
|
44
|
+
const path = deferredTopicBindingPath(dataDir, sessionId);
|
|
45
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
46
|
+
return withFileLock(path, fn, { maxWaitMs: 30_000 });
|
|
47
|
+
}
|
|
48
|
+
export function removeDeferredTopicBinding(dataDir, sessionId) {
|
|
49
|
+
const path = deferredTopicBindingPath(dataDir, sessionId);
|
|
50
|
+
try {
|
|
51
|
+
rmSync(path, { force: true });
|
|
52
|
+
}
|
|
53
|
+
catch { /* best-effort lifecycle cleanup */ }
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=deferred-topic-binding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deferred-topic-binding.js","sourceRoot":"","sources":["../../src/core/deferred-topic-binding.ts"],"names":[],"mappings":"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"}
|
package/dist/core/scheduler.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ScheduledTask, ParsedSchedule } from '../types.js';
|
|
1
|
+
import type { ScheduledTask, ParsedSchedule, ScheduleExecutionPosition } from '../types.js';
|
|
2
2
|
export declare function setExecuteCallback(cb: (task: ScheduledTask) => Promise<void>): void;
|
|
3
3
|
/**
|
|
4
4
|
* Bind the scheduler to a specific bot (larkAppId). In multi-bot setups every
|
|
@@ -33,13 +33,8 @@ interface ParseNLResult {
|
|
|
33
33
|
* handler where user types e.g. "/schedule 每日17:50 帮我看看AI新闻".
|
|
34
34
|
*/
|
|
35
35
|
export declare function parseNaturalSchedule(input: string): ParseNLResult | null;
|
|
36
|
-
/**
|
|
37
|
-
*
|
|
38
|
-
* it. Lets users write `/schedule 每日9:00 新话题 帮我看AI新闻` so every fire
|
|
39
|
-
* opens a brand-new topic in the chat. Returns the resolved delivery mode plus
|
|
40
|
-
* the prompt with the keyword removed. When no keyword is present (or nothing
|
|
41
|
-
* follows it) the prompt is returned unchanged with deliver='origin'.
|
|
42
|
-
*/
|
|
36
|
+
/** Backward-compatible parser; `deliver:new-topic` means a routing modifier
|
|
37
|
+
* was present. New callers should also read extractScheduleModifiers.position. */
|
|
43
38
|
export declare function extractDeliveryMode(prompt: string): {
|
|
44
39
|
deliver: 'origin' | 'new-topic';
|
|
45
40
|
prompt: string;
|
|
@@ -57,12 +52,14 @@ export declare function extractSilentMode(prompt: string): {
|
|
|
57
52
|
prompt: string;
|
|
58
53
|
};
|
|
59
54
|
/**
|
|
60
|
-
* Extract both /schedule prompt modifiers
|
|
61
|
-
*
|
|
62
|
-
*
|
|
55
|
+
* Extract both /schedule prompt modifiers regardless of their order.
|
|
56
|
+
* `deliver:new-topic` remains a compatibility token indicating that a position
|
|
57
|
+
* modifier was present. `executionPosition` carries the unambiguous modern
|
|
58
|
+
* value: group top level or a fresh topic on every run.
|
|
63
59
|
*/
|
|
64
60
|
export declare function extractScheduleModifiers(prompt: string): {
|
|
65
61
|
deliver: 'origin' | 'new-topic';
|
|
62
|
+
executionPosition?: Extract<ScheduleExecutionPosition, 'top-level' | 'new-topic'>;
|
|
66
63
|
silent: boolean;
|
|
67
64
|
prompt: string;
|
|
68
65
|
};
|
|
@@ -90,6 +87,8 @@ export declare function addTask(params: {
|
|
|
90
87
|
chatId: string;
|
|
91
88
|
rootMessageId?: string;
|
|
92
89
|
scope?: 'thread' | 'chat';
|
|
90
|
+
executionPosition?: ScheduleExecutionPosition;
|
|
91
|
+
topicTitle?: string;
|
|
93
92
|
chatType?: 'group' | 'p2p' | 'topic_group';
|
|
94
93
|
larkAppId?: string;
|
|
95
94
|
creatorChatId?: string;
|
|
@@ -103,6 +102,8 @@ export declare function addTask(params: {
|
|
|
103
102
|
deliver?: 'origin' | 'local' | 'new-topic';
|
|
104
103
|
silent?: boolean;
|
|
105
104
|
}): ScheduledTask;
|
|
105
|
+
export declare function normalizeTopicTitle(value: string | undefined): string | undefined;
|
|
106
|
+
export declare function resolveTaskExecutionPosition(task: Pick<ScheduledTask, 'executionPosition' | 'scope' | 'rootMessageId' | 'deliver'>): ScheduleExecutionPosition;
|
|
106
107
|
export declare function removeTask(id: string): boolean;
|
|
107
108
|
export declare function enableTask(id: string): boolean;
|
|
108
109
|
export declare function disableTask(id: string): boolean;
|
|
@@ -127,22 +128,24 @@ export declare function setEnabled(id: string, enabled: boolean): {
|
|
|
127
128
|
error?: string;
|
|
128
129
|
};
|
|
129
130
|
/**
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
* in-chat new-topic poster; 'local' is a CLI-only choice. Emits a
|
|
135
|
-
* `schedule.updated` event so the dashboard reflects the change immediately.
|
|
131
|
+
* Cycle a task's execution position: retained topic → group top level → fresh
|
|
132
|
+
* topic per run → retained topic (or group top level when no root is retained).
|
|
133
|
+
* Silent tasks skip the fresh-topic state because that state needs a visible
|
|
134
|
+
* seed message. The `deliver` response remains for cached clients.
|
|
136
135
|
*/
|
|
137
136
|
export declare function toggleDelivery(id: string): {
|
|
138
137
|
ok: boolean;
|
|
139
138
|
error?: string;
|
|
140
139
|
deliver?: 'origin' | 'new-topic';
|
|
140
|
+
executionPosition?: ScheduleExecutionPosition;
|
|
141
141
|
};
|
|
142
142
|
/**
|
|
143
|
-
* Update editable fields of a scheduled task (name, prompt, schedule,
|
|
144
|
-
*
|
|
145
|
-
* schedule
|
|
143
|
+
* Update editable fields of a scheduled task (name, prompt, schedule, silent,
|
|
144
|
+
* execution position and retained topic root).
|
|
145
|
+
* Re-parses the schedule expression and recomputes nextRunAt when the schedule
|
|
146
|
+
* string changes. A legacy `deliver` input is accepted and normalized to
|
|
147
|
+
* `origin` for normal writes. Legacy `deliver:new-topic` still maps to the
|
|
148
|
+
* explicit fresh-topic position for cached clients.
|
|
146
149
|
* Emits a `schedule.updated` event so the dashboard reflects changes live.
|
|
147
150
|
*/
|
|
148
151
|
export declare function updateTask(id: string, updates: {
|
|
@@ -151,6 +154,9 @@ export declare function updateTask(id: string, updates: {
|
|
|
151
154
|
schedule?: string;
|
|
152
155
|
deliver?: 'origin' | 'new-topic';
|
|
153
156
|
silent?: boolean;
|
|
157
|
+
executionPosition?: ScheduleExecutionPosition;
|
|
158
|
+
rootMessageId?: string;
|
|
159
|
+
topicTitle?: string;
|
|
154
160
|
}): {
|
|
155
161
|
ok: boolean;
|
|
156
162
|
error?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/core/scheduler.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/core/scheduler.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAsC5F,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAEnF;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI,CAG1E;AASD,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAE3D;AA4GD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,CAmD3D;AAWD,UAAU,aAAa;IACrB,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAWxE;AAkBD;kFACkF;AAClF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,QAAQ,GAAG,WAAW,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAKvG;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAMrF;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG;IACxD,OAAO,EAAE,QAAQ,GAAG,WAAW,CAAC;IAChC,iBAAiB,CAAC,EAAE,OAAO,CAAC,yBAAyB,EAAE,WAAW,GAAG,WAAW,CAAC,CAAC;IAClF,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAmBA;AAID,iGAAiG;AACjG,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA8BxF;AA4GD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,aAAa,EAAE,EACtB,OAAO,GAAE,CAAC,CAAC,EAAE,aAAa,KAAK,OAAoB,GAClD,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAS1C;AAcD,wBAAgB,cAAc,IAAI,IAAI,CAgCrC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAGpC;AAED,wBAAgB,OAAO,CAAC,MAAM,EAAE;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,yBAAyB,CAAC;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,aAAa,CAAC;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACrD,OAAO,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,WAAW,CAAC;IAC3C,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,GAAG,aAAa,CAuChB;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAMjF;AAED,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,IAAI,CAAC,aAAa,EAAE,mBAAmB,GAAG,OAAO,GAAG,eAAe,GAAG,SAAS,CAAC,GACrF,yBAAyB,CAO3B;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAM9C;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAK/C;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAU9C;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAIlD;AAOD;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAmClE;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAexF;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG;IAC1C,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;IACjC,iBAAiB,CAAC,EAAE,yBAAyB,CAAC;CAC/C,CAkBA;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CACxB,EAAE,EAAE,MAAM,EACV,OAAO,EAAE;IACP,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;IACjC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,iBAAiB,CAAC,EAAE,yBAAyB,CAAC;IAC9C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GACA;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAkDjC;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CASlF"}
|
package/dist/core/scheduler.js
CHANGED
|
@@ -243,26 +243,28 @@ export function parseNaturalSchedule(input) {
|
|
|
243
243
|
const name = prompt.length > 20 ? prompt.substring(0, 20) + '...' : prompt;
|
|
244
244
|
return { parsed: zh.parsed, prompt, name };
|
|
245
245
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
export function extractDeliveryMode(prompt) {
|
|
254
|
-
// Match a leading new-topic phrase: optional 每次/每回/每天/每日, then any run of
|
|
255
|
-
// 开/起/另/一/个/新/的/space fillers that MUST contain 新, immediately followed
|
|
256
|
-
// by 话题. `新` is mandatory so we don't match a normal prompt like
|
|
257
|
-
// "总结这个话题…" or "新闻话题…". Covers 新话题 / 新开话题 / 开新话题 /
|
|
258
|
-
// 每次开新话题 / 每次开一个新话题 / 新开一个话题 等变体。
|
|
246
|
+
function extractExecutionPositionModifier(prompt) {
|
|
247
|
+
const topLevelZh = prompt.match(/^\s*(?:群消息顶层|群顶层|顶层)(?:执行|运行)?[\s,,、::。-]+(.+)$/s);
|
|
248
|
+
if (topLevelZh && topLevelZh[1].trim())
|
|
249
|
+
return { executionPosition: 'top-level', prompt: topLevelZh[1].trim() };
|
|
250
|
+
const topLevelEn = prompt.match(/^\s*(?:group\s+)?top[\s-]?level[\s,::-]+(.+)$/is);
|
|
251
|
+
if (topLevelEn && topLevelEn[1].trim())
|
|
252
|
+
return { executionPosition: 'top-level', prompt: topLevelEn[1].trim() };
|
|
259
253
|
const zh = prompt.match(/^\s*(?:每次|每回|每天|每日)?\s*[开起另一个新的\s]*新[开起另一个新的\s]*话题[\s,,、::。-]*(.+)$/s);
|
|
260
254
|
if (zh && zh[1].trim())
|
|
261
|
-
return {
|
|
255
|
+
return { executionPosition: 'new-topic', prompt: zh[1].trim() };
|
|
262
256
|
const en = prompt.match(/^\s*(?:every\s+run\s+in\s+a\s+)?new[\s-]?topic[\s,::-]+(.+)$/is);
|
|
263
257
|
if (en && en[1].trim())
|
|
264
|
-
return {
|
|
265
|
-
return {
|
|
258
|
+
return { executionPosition: 'new-topic', prompt: en[1].trim() };
|
|
259
|
+
return { prompt };
|
|
260
|
+
}
|
|
261
|
+
/** Backward-compatible parser; `deliver:new-topic` means a routing modifier
|
|
262
|
+
* was present. New callers should also read extractScheduleModifiers.position. */
|
|
263
|
+
export function extractDeliveryMode(prompt) {
|
|
264
|
+
const parsed = extractExecutionPositionModifier(prompt);
|
|
265
|
+
return parsed.executionPosition
|
|
266
|
+
? { deliver: 'new-topic', prompt: parsed.prompt }
|
|
267
|
+
: { deliver: 'origin', prompt };
|
|
266
268
|
}
|
|
267
269
|
/**
|
|
268
270
|
* Detect a leading "silent" keyword in a /schedule prompt and strip it. Lets
|
|
@@ -282,19 +284,22 @@ export function extractSilentMode(prompt) {
|
|
|
282
284
|
return { silent: false, prompt };
|
|
283
285
|
}
|
|
284
286
|
/**
|
|
285
|
-
* Extract both /schedule prompt modifiers
|
|
286
|
-
*
|
|
287
|
-
*
|
|
287
|
+
* Extract both /schedule prompt modifiers regardless of their order.
|
|
288
|
+
* `deliver:new-topic` remains a compatibility token indicating that a position
|
|
289
|
+
* modifier was present. `executionPosition` carries the unambiguous modern
|
|
290
|
+
* value: group top level or a fresh topic on every run.
|
|
288
291
|
*/
|
|
289
292
|
export function extractScheduleModifiers(prompt) {
|
|
290
293
|
let deliver = 'origin';
|
|
294
|
+
let executionPosition;
|
|
291
295
|
let silent = false;
|
|
292
296
|
let rest = prompt;
|
|
293
297
|
// Two keywords max — loop twice so either order is handled.
|
|
294
298
|
for (let i = 0; i < 2; i++) {
|
|
295
|
-
const d =
|
|
296
|
-
if (d.
|
|
299
|
+
const d = extractExecutionPositionModifier(rest);
|
|
300
|
+
if (d.executionPosition) {
|
|
297
301
|
deliver = 'new-topic';
|
|
302
|
+
executionPosition = d.executionPosition;
|
|
298
303
|
rest = d.prompt;
|
|
299
304
|
continue;
|
|
300
305
|
}
|
|
@@ -306,7 +311,7 @@ export function extractScheduleModifiers(prompt) {
|
|
|
306
311
|
}
|
|
307
312
|
break;
|
|
308
313
|
}
|
|
309
|
-
return { deliver, silent, prompt: rest };
|
|
314
|
+
return { deliver, ...(executionPosition ? { executionPosition } : {}), silent, prompt: rest };
|
|
310
315
|
}
|
|
311
316
|
// ─── next-run computation ───────────────────────────────────────────────────
|
|
312
317
|
/** Compute the next run time for a parsed schedule. Returns ISO string, or null if exhausted. */
|
|
@@ -513,14 +518,19 @@ export function stopScheduler() {
|
|
|
513
518
|
logger.info('[scheduler] Stopped');
|
|
514
519
|
}
|
|
515
520
|
export function addTask(params) {
|
|
516
|
-
// Single choke point for the invalid combination — every creation entry
|
|
517
|
-
// (CLI/slash/dashboard/workflow) funnels through here. Entry points give
|
|
518
|
-
// friendlier errors first; this guard catches anything they miss.
|
|
519
|
-
if (params.silent && params.deliver === 'new-topic') {
|
|
520
|
-
throw new Error('silent schedules cannot use deliver:new-topic — a new topic requires a first message');
|
|
521
|
-
}
|
|
522
521
|
const parsed = params.parsed ?? parseSchedule(params.schedule);
|
|
523
522
|
const nextRunAt = computeNextRun(parsed) ?? undefined;
|
|
523
|
+
const executionPosition = params.executionPosition
|
|
524
|
+
?? (params.deliver === 'new-topic'
|
|
525
|
+
? 'new-topic'
|
|
526
|
+
: params.scope === 'chat'
|
|
527
|
+
? 'top-level'
|
|
528
|
+
: params.rootMessageId ? 'topic' : 'top-level');
|
|
529
|
+
if (executionPosition === 'topic' && !params.rootMessageId) {
|
|
530
|
+
throw new Error('topic_root_required');
|
|
531
|
+
}
|
|
532
|
+
const topicTitle = normalizeTopicTitle(params.topicTitle);
|
|
533
|
+
const scope = executionPosition === 'topic' ? 'thread' : 'chat';
|
|
524
534
|
const task = scheduleStore.createTask({
|
|
525
535
|
name: params.name,
|
|
526
536
|
schedule: params.schedule,
|
|
@@ -529,7 +539,9 @@ export function addTask(params) {
|
|
|
529
539
|
workingDir: params.workingDir,
|
|
530
540
|
chatId: params.chatId,
|
|
531
541
|
rootMessageId: params.rootMessageId,
|
|
532
|
-
scope
|
|
542
|
+
scope,
|
|
543
|
+
executionPosition,
|
|
544
|
+
topicTitle,
|
|
533
545
|
chatType: params.chatType,
|
|
534
546
|
larkAppId: params.larkAppId,
|
|
535
547
|
creatorChatId: params.creatorChatId,
|
|
@@ -537,12 +549,33 @@ export function addTask(params) {
|
|
|
537
549
|
creatorLarkAppId: params.creatorLarkAppId,
|
|
538
550
|
nextRunAt,
|
|
539
551
|
repeat: params.repeat,
|
|
540
|
-
|
|
552
|
+
// Delivery shape is now expressed by scope/rootMessageId. Persist only the
|
|
553
|
+
// local-vs-chat distinction; schedule-store also normalizes legacy values.
|
|
554
|
+
deliver: params.deliver === 'local' ? 'local' : 'origin',
|
|
541
555
|
silent: params.silent,
|
|
542
556
|
});
|
|
543
557
|
logger.info(`[scheduler] Added task "${task.name}" (${task.id}) — ${parsed.display}, next: ${nextRunAt ?? 'N/A'}`);
|
|
544
558
|
return task;
|
|
545
559
|
}
|
|
560
|
+
export function normalizeTopicTitle(value) {
|
|
561
|
+
const trimmed = value?.trim();
|
|
562
|
+
if (!trimmed)
|
|
563
|
+
return undefined;
|
|
564
|
+
const chars = Array.from(trimmed);
|
|
565
|
+
if (chars.length > 200)
|
|
566
|
+
throw new Error('topic_title_too_long');
|
|
567
|
+
return trimmed;
|
|
568
|
+
}
|
|
569
|
+
export function resolveTaskExecutionPosition(task) {
|
|
570
|
+
if (task.executionPosition === 'top-level' || task.executionPosition === 'topic' || task.executionPosition === 'new-topic') {
|
|
571
|
+
return task.executionPosition === 'topic' && !task.rootMessageId ? 'top-level' : task.executionPosition;
|
|
572
|
+
}
|
|
573
|
+
if (task.deliver === 'new-topic')
|
|
574
|
+
return 'new-topic';
|
|
575
|
+
if (task.scope === 'chat')
|
|
576
|
+
return 'top-level';
|
|
577
|
+
return task.rootMessageId ? 'topic' : 'top-level';
|
|
578
|
+
}
|
|
546
579
|
export function removeTask(id) {
|
|
547
580
|
return scheduleStore.removeTask(id);
|
|
548
581
|
}
|
|
@@ -648,12 +681,10 @@ export function setEnabled(id, enabled) {
|
|
|
648
681
|
return { ok: true };
|
|
649
682
|
}
|
|
650
683
|
/**
|
|
651
|
-
*
|
|
652
|
-
*
|
|
653
|
-
*
|
|
654
|
-
*
|
|
655
|
-
* in-chat new-topic poster; 'local' is a CLI-only choice. Emits a
|
|
656
|
-
* `schedule.updated` event so the dashboard reflects the change immediately.
|
|
684
|
+
* Cycle a task's execution position: retained topic → group top level → fresh
|
|
685
|
+
* topic per run → retained topic (or group top level when no root is retained).
|
|
686
|
+
* Silent tasks skip the fresh-topic state because that state needs a visible
|
|
687
|
+
* seed message. The `deliver` response remains for cached clients.
|
|
657
688
|
*/
|
|
658
689
|
export function toggleDelivery(id) {
|
|
659
690
|
const task = scheduleStore.getTask(id);
|
|
@@ -661,44 +692,71 @@ export function toggleDelivery(id) {
|
|
|
661
692
|
return { ok: false, error: 'not_found' };
|
|
662
693
|
if (task.deliver === 'local')
|
|
663
694
|
return { ok: false, error: 'local_not_toggleable' };
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
if (
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
695
|
+
const current = resolveTaskExecutionPosition(task);
|
|
696
|
+
let executionPosition;
|
|
697
|
+
if (current === 'topic')
|
|
698
|
+
executionPosition = 'top-level';
|
|
699
|
+
else if (current === 'top-level')
|
|
700
|
+
executionPosition = 'new-topic';
|
|
701
|
+
else
|
|
702
|
+
executionPosition = task.rootMessageId ? 'topic' : 'top-level';
|
|
703
|
+
if (executionPosition === current)
|
|
704
|
+
return { ok: false, error: 'topic_root_required' };
|
|
705
|
+
const scope = executionPosition === 'topic' ? 'thread' : 'chat';
|
|
706
|
+
scheduleStore.updateTask(id, { scope, executionPosition });
|
|
707
|
+
const deliver = executionPosition === 'new-topic' ? 'new-topic' : 'origin';
|
|
671
708
|
dashboardEventBus.publish({
|
|
672
709
|
type: 'schedule.updated',
|
|
673
|
-
body: { id, patch: {
|
|
710
|
+
body: { id, patch: { scope, executionPosition } },
|
|
674
711
|
});
|
|
675
|
-
return { ok: true, deliver
|
|
712
|
+
return { ok: true, deliver, executionPosition };
|
|
676
713
|
}
|
|
677
714
|
/**
|
|
678
|
-
* Update editable fields of a scheduled task (name, prompt, schedule,
|
|
679
|
-
*
|
|
680
|
-
* schedule
|
|
715
|
+
* Update editable fields of a scheduled task (name, prompt, schedule, silent,
|
|
716
|
+
* execution position and retained topic root).
|
|
717
|
+
* Re-parses the schedule expression and recomputes nextRunAt when the schedule
|
|
718
|
+
* string changes. A legacy `deliver` input is accepted and normalized to
|
|
719
|
+
* `origin` for normal writes. Legacy `deliver:new-topic` still maps to the
|
|
720
|
+
* explicit fresh-topic position for cached clients.
|
|
681
721
|
* Emits a `schedule.updated` event so the dashboard reflects changes live.
|
|
682
722
|
*/
|
|
683
723
|
export function updateTask(id, updates) {
|
|
684
724
|
const task = scheduleStore.getTask(id);
|
|
685
725
|
if (!task)
|
|
686
726
|
return { ok: false, error: 'not_found' };
|
|
687
|
-
// Enforce silent ↔ new-topic mutual exclusion (same rule as addTask).
|
|
688
|
-
const nextDeliver = updates.deliver ?? task.deliver ?? 'origin';
|
|
689
|
-
const nextSilent = updates.silent ?? task.silent === true;
|
|
690
|
-
if (nextSilent && nextDeliver === 'new-topic') {
|
|
691
|
-
return { ok: false, error: 'silent_new_topic_exclusive' };
|
|
692
|
-
}
|
|
693
727
|
const patch = {};
|
|
694
728
|
if (updates.name !== undefined)
|
|
695
729
|
patch.name = updates.name;
|
|
696
730
|
if (updates.prompt !== undefined)
|
|
697
731
|
patch.prompt = updates.prompt;
|
|
698
|
-
if (updates.deliver !== undefined)
|
|
699
|
-
patch.deliver = updates.deliver;
|
|
700
732
|
if (updates.silent !== undefined)
|
|
701
733
|
patch.silent = updates.silent === true ? true : undefined;
|
|
734
|
+
const legacyPosition = updates.deliver === 'new-topic'
|
|
735
|
+
? 'new-topic'
|
|
736
|
+
: undefined;
|
|
737
|
+
const executionPosition = updates.executionPosition ?? legacyPosition;
|
|
738
|
+
const nextRootMessageId = updates.rootMessageId ?? task.rootMessageId;
|
|
739
|
+
if (executionPosition === 'topic' && !nextRootMessageId) {
|
|
740
|
+
return { ok: false, error: 'topic_root_required' };
|
|
741
|
+
}
|
|
742
|
+
if (updates.topicTitle !== undefined) {
|
|
743
|
+
try {
|
|
744
|
+
patch.topicTitle = normalizeTopicTitle(updates.topicTitle);
|
|
745
|
+
}
|
|
746
|
+
catch (err) {
|
|
747
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
if (updates.rootMessageId !== undefined)
|
|
751
|
+
patch.rootMessageId = updates.rootMessageId;
|
|
752
|
+
if (executionPosition !== undefined) {
|
|
753
|
+
patch.scope = executionPosition === 'topic' ? 'thread' : 'chat';
|
|
754
|
+
patch.executionPosition = executionPosition;
|
|
755
|
+
patch.deliver = 'origin';
|
|
756
|
+
}
|
|
757
|
+
else if (updates.deliver !== undefined) {
|
|
758
|
+
patch.deliver = 'origin';
|
|
759
|
+
}
|
|
702
760
|
// Re-parse + recompute next run when the schedule expression changes.
|
|
703
761
|
if (updates.schedule !== undefined && updates.schedule !== task.schedule) {
|
|
704
762
|
let parsed;
|