cc-viewer 1.9.12 → 1.9.14
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/README.md +8 -0
- package/dist/assets/{App-Xqmx9TR5.js → App-BzV1ieDH.js} +2 -2
- package/dist/assets/{MdxEditorPanel-BwTbSU48.js → MdxEditorPanel-DI3B4gsM.js} +1 -1
- package/dist/assets/{Mobile-BOXHO9-C.js → Mobile-qBkAnXy3.js} +1 -1
- package/dist/assets/{ProxyStatsModal-BzfIUfG1.js → ProxyStatsModal-B8K0AtAq.js} +1 -1
- package/dist/assets/{_baseUniq-BvPjRimO.js → _baseUniq-CsF04anE.js} +1 -1
- package/dist/assets/{arc-BzZyqc6p.js → arc-BOacYe6N.js} +1 -1
- package/dist/assets/{architectureDiagram-Q4EWVU46-B1oszHzA.js → architectureDiagram-Q4EWVU46-BSUEfjC_.js} +1 -1
- package/dist/assets/{blockDiagram-DXYQGD6D-Ndp4o_QB.js → blockDiagram-DXYQGD6D-CC37UGtW.js} +1 -1
- package/dist/assets/{c4Diagram-AHTNJAMY-DlDE-ALt.js → c4Diagram-AHTNJAMY-IzgRD4uO.js} +1 -1
- package/dist/assets/{channel-BhXx9y6q.js → channel-C6PxyQUn.js} +1 -1
- package/dist/assets/{chunk-4BX2VUAB-CM-2e6q_.js → chunk-4BX2VUAB-cdQt_dcU.js} +1 -1
- package/dist/assets/{chunk-4TB4RGXK-GWLkQKRO.js → chunk-4TB4RGXK-BpTDA_Pb.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-B6LOFqRZ.js → chunk-55IACEB6-CcitNo0P.js} +1 -1
- package/dist/assets/{chunk-EDXVE4YY-Ct3e09Ku.js → chunk-EDXVE4YY-BjKwZ9I7.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-DK-vXpZP.js → chunk-FMBD7UC4-vZDVX3tI.js} +1 -1
- package/dist/assets/{chunk-OYMX7WX6-CWxZc_DU.js → chunk-OYMX7WX6-DGkYahGo.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-jp8ISHue.js → chunk-QZHKN3VN-C9065hSE.js} +1 -1
- package/dist/assets/{chunk-YZCP3GAM-DtYet6WV.js → chunk-YZCP3GAM-D_pcTg6_.js} +1 -1
- package/dist/assets/classDiagram-6PBFFD2Q-BUmU_G-i.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-BUmU_G-i.js +1 -0
- package/dist/assets/clone-CvoOne2K.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-lNC-slVX.js → cose-bilkent-S5V4N54A-BPPFMIGv.js} +1 -1
- package/dist/assets/{dagre-KV5264BT-vm6U82qq.js → dagre-KV5264BT-fjzhKO5o.js} +1 -1
- package/dist/assets/{diagram-5BDNPKRD-Dq3FXh-U.js → diagram-5BDNPKRD-5L6FzTry.js} +1 -1
- package/dist/assets/{diagram-G4DWMVQ6-BxF85wcF.js → diagram-G4DWMVQ6-GOwcGpff.js} +1 -1
- package/dist/assets/{diagram-MMDJMWI5-CFCTeMz0.js → diagram-MMDJMWI5-BDsFE25W.js} +1 -1
- package/dist/assets/{diagram-TYMM5635-Bui5Azdg.js → diagram-TYMM5635-BqjpbSGH.js} +1 -1
- package/dist/assets/{erDiagram-SMLLAGMA-DrpUO1Kb.js → erDiagram-SMLLAGMA-DWO3Dk1v.js} +1 -1
- package/dist/assets/{flowDiagram-DWJPFMVM-Bb3amxiK.js → flowDiagram-DWJPFMVM-Br4nNBVd.js} +1 -1
- package/dist/assets/{ganttDiagram-T4ZO3ILL-BRwCFM3C.js → ganttDiagram-T4ZO3ILL-9sN-VOhw.js} +1 -1
- package/dist/assets/{gitGraphDiagram-UUTBAWPF-DVXbBwhs.js → gitGraphDiagram-UUTBAWPF-BsPcTAKr.js} +1 -1
- package/dist/assets/{graph-Dh22wYWs.js → graph-o31GzaB6.js} +1 -1
- package/dist/assets/{index-CfFFgxxf.js → index-B3b0lRUB.js} +1 -1
- package/dist/assets/{index-CWZz2pyj.js → index-CU0gt6m1.js} +2 -2
- package/dist/assets/{index-BwZB7UwN.js → index-CXMr3tiK.js} +1 -1
- package/dist/assets/{index-UlO7Q5dX.js → index-CuvDJTY7.js} +1 -1
- package/dist/assets/{index-Cfm4N4ao.js → index-DAjx9lT3.js} +1 -1
- package/dist/assets/{index-xZXOwglE.js → index-DyV81jSF.js} +1 -1
- package/dist/assets/{index-CQ1Nswa3.js → index-PDvAAbti.js} +1 -1
- package/dist/assets/{index-e8wejtj_.js → index-tZqiJJGq.js} +1 -1
- package/dist/assets/{infoDiagram-42DDH7IO-cQPbF7Q6.js → infoDiagram-42DDH7IO-VUMzEo4o.js} +1 -1
- package/dist/assets/{ishikawaDiagram-UXIWVN3A-cp-ir90Y.js → ishikawaDiagram-UXIWVN3A-BuzrU3sx.js} +1 -1
- package/dist/assets/{journeyDiagram-VCZTEJTY-aoMcyNLC.js → journeyDiagram-VCZTEJTY-BZtR5BAF.js} +1 -1
- package/dist/assets/{jszip.min-C7BBsGL5.js → jszip.min-CtodH5MV.js} +1 -1
- package/dist/assets/{kanban-definition-6JOO6SKY-Cugr0o0f.js → kanban-definition-6JOO6SKY-Cj_97xXb.js} +1 -1
- package/dist/assets/{layout-CL0eiocE.js → layout-Du0I-tlh.js} +1 -1
- package/dist/assets/{linear-D6EJvwNs.js → linear-BJtxCASI.js} +1 -1
- package/dist/assets/{mermaid.core-CNYcCP19.js → mermaid.core-QlorX0Yw.js} +2 -2
- package/dist/assets/{min-dFBWKmZ_.js → min-BVnQfunZ.js} +1 -1
- package/dist/assets/{mindmap-definition-QFDTVHPH-4fbr7E2n.js → mindmap-definition-QFDTVHPH-Bh_KL4qU.js} +1 -1
- package/dist/assets/{pieDiagram-DEJITSTG-UHWzU_JW.js → pieDiagram-DEJITSTG-Bvv0rC_q.js} +1 -1
- package/dist/assets/{quadrantDiagram-34T5L4WZ-e87bZkAb.js → quadrantDiagram-34T5L4WZ-Bw8cGCRU.js} +1 -1
- package/dist/assets/{requirementDiagram-MS252O5E-l_97YNSi.js → requirementDiagram-MS252O5E-Cg9jQ71h.js} +1 -1
- package/dist/assets/{sankeyDiagram-XADWPNL6-D3JFnltO.js → sankeyDiagram-XADWPNL6-BXJo6-CD.js} +1 -1
- package/dist/assets/{seqResourceLoaders-ChbSSNu8.css → seqResourceLoaders-CxUFE36z.css} +1 -1
- package/dist/assets/seqResourceLoaders-nGCs11zu.js +2 -0
- package/dist/assets/{sequenceDiagram-FGHM5R23-5jDKcsRZ.js → sequenceDiagram-FGHM5R23-D7zpGxwq.js} +1 -1
- package/dist/assets/{stateDiagram-FHFEXIEX-D5_gBKn8.js → stateDiagram-FHFEXIEX-Der2CqR1.js} +1 -1
- package/dist/assets/{stateDiagram-v2-QKLJ7IA2-BP5uQ5EH.js → stateDiagram-v2-QKLJ7IA2-DetvG_Lc.js} +1 -1
- package/dist/assets/{timeline-definition-GMOUNBTQ-7IVQRwK1.js → timeline-definition-GMOUNBTQ-0UenzEZ6.js} +1 -1
- package/dist/assets/{vendor-antd-CfuxfYfy.js → vendor-antd-I6I2F7Rv.js} +1 -1
- package/dist/assets/{vendor-codemirror-C4Jn4TTc.js → vendor-codemirror-Zkm6UHQg.js} +1 -1
- package/dist/assets/{vendor-mdxeditor-BreKt5Hd.js → vendor-mdxeditor-CC09MDyZ.js} +2 -2
- package/dist/assets/{vendor-qrcode-CutTqXgh.js → vendor-qrcode-gZpmSeeh.js} +1 -1
- package/dist/assets/{vendor-virtuoso-Cc9ovCDf.js → vendor-virtuoso-BGwznQ5y.js} +1 -1
- package/dist/assets/{vennDiagram-DHZGUBPP-D6qzGquN.js → vennDiagram-DHZGUBPP-COzYyZ5h.js} +1 -1
- package/dist/assets/{wardley-RL74JXVD-DzAFqZmY.js → wardley-RL74JXVD-VZdOtN8t.js} +1 -1
- package/dist/assets/{wardleyDiagram-NUSXRM2D-CImP9kv8.js → wardleyDiagram-NUSXRM2D-CVkL75Qy.js} +1 -1
- package/dist/assets/{xychartDiagram-5P7HB3ND-Co45rRaM.js → xychartDiagram-5P7HB3ND-6CHBAYyF.js} +1 -1
- package/dist/index.html +4 -4
- package/node_modules/@ccv/core/src/context-rules.js +6 -2
- package/package.json +1 -1
- package/server/lib/chat-queue.js +14 -1
- package/server/lib/config-backup.js +4 -2
- package/server/lib/cron.js +181 -0
- package/server/lib/file-access-policy.js +45 -1
- package/server/lib/im-deny.js +12 -9
- package/server/lib/json-store.js +27 -1
- package/server/lib/model-system-prompts.js +4 -1
- package/server/lib/project-prefs.js +4 -1
- package/server/lib/schedule-model.js +149 -0
- package/server/lib/scheduled-task-executor.js +97 -0
- package/server/lib/scheduled-task-runs-store.js +99 -0
- package/server/lib/scheduled-task-scheduler.js +254 -0
- package/server/lib/scheduled-task-store.js +511 -0
- package/server/lib/sdk-manager.js +283 -12
- package/server/pty-manager.js +13 -2
- package/server/routes/preferences.js +24 -1
- package/server/routes/scheduled-tasks.js +310 -0
- package/server/server.js +135 -0
- package/server/system-prompt-templates/presets/kimi-k3.md +4 -0
- package/dist/assets/classDiagram-6PBFFD2Q-C0dwAyuU.js +0 -1
- package/dist/assets/classDiagram-v2-HSJHXN6E-C0dwAyuU.js +0 -1
- package/dist/assets/clone-fiofFlrV.js +0 -1
- package/dist/assets/seqResourceLoaders-t8OZr44t.js +0 -2
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
// Scheduled-task REST routes (L3).
|
|
2
|
+
//
|
|
3
|
+
// GET /api/scheduled-tasks?project=&instance= → list (+ serverTime/timezone/spawnAvailable)
|
|
4
|
+
// POST /api/scheduled-tasks → create
|
|
5
|
+
// POST /api/scheduled-tasks/update → update / pause / resume
|
|
6
|
+
// POST /api/scheduled-tasks/delete → delete
|
|
7
|
+
// POST /api/scheduled-tasks/run → Run now
|
|
8
|
+
// POST /api/scheduled-tasks/preview → next 3 base fire times for a schedule
|
|
9
|
+
// GET /api/scheduled-tasks/runs?id=&project= → recent runs for one task
|
|
10
|
+
//
|
|
11
|
+
// Auth: reads need only a logged-in viewer; WRITES are admin + same-origin (a scheduled
|
|
12
|
+
// task injects a prompt into a session / spawns a headless agent — same trust level as
|
|
13
|
+
// the resume/upload writers, mirroring routes/resume.js:492).
|
|
14
|
+
//
|
|
15
|
+
// project → cwd resolution (a task stores an ABSOLUTE cwd, not just a project name, so
|
|
16
|
+
// same-basename workspaces never cross-execute — review §6): explicit project → the
|
|
17
|
+
// bound project when it matches, else an instance/registry match; ambiguous → 409;
|
|
18
|
+
// unresolvable → 404 (never silently fall back to the server's process cwd — P1-6).
|
|
19
|
+
//
|
|
20
|
+
// Boundary: L3 may import L1 (lib/*) and L2 (pty-manager/interceptor). The scheduler
|
|
21
|
+
// singleton is imported directly (L3→L1); server.js owns its init/start/stop.
|
|
22
|
+
|
|
23
|
+
import { _projectName } from '../interceptor.js';
|
|
24
|
+
import { LOG_DIR } from '../../findcc.js';
|
|
25
|
+
import { listLivePtys } from '../pty-manager.js';
|
|
26
|
+
import { sendEventToClients } from '../lib/log-watcher.js';
|
|
27
|
+
import { filterClientsByViewProject } from '../lib/v2/view-router.js';
|
|
28
|
+
import { isAdminReq } from '../lib/is-admin.js';
|
|
29
|
+
import { isSameOriginBrowserRequest } from '../lib/same-origin.js';
|
|
30
|
+
import { reportSwallowed } from '../lib/error-report.js';
|
|
31
|
+
import { normalizeSchedule, nextBaseFor } from '../lib/schedule-model.js';
|
|
32
|
+
import {
|
|
33
|
+
createTask, updateTask, deleteTask, getTask, readScheduledTasks,
|
|
34
|
+
} from '../lib/scheduled-task-store.js';
|
|
35
|
+
import { readScheduledTaskRuns } from '../lib/scheduled-task-runs-store.js';
|
|
36
|
+
import { runTaskNow, spawnAvailable } from '../lib/scheduled-task-scheduler.js';
|
|
37
|
+
import { loadWorkspaces } from '../workspace-registry.js';
|
|
38
|
+
import { listSdkSessionInstances } from '../lib/sdk-session-index.js';
|
|
39
|
+
|
|
40
|
+
const MAX_BODY = 1 << 20;
|
|
41
|
+
|
|
42
|
+
function sendJson(res, code, obj) {
|
|
43
|
+
res.writeHead(code, { 'Content-Type': 'application/json' });
|
|
44
|
+
res.end(JSON.stringify(obj));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function _readBody(req, res, deps, cb) {
|
|
48
|
+
let body = '';
|
|
49
|
+
req.on('data', (chunk) => { body += chunk; if (body.length > (deps?.MAX_POST_BODY || MAX_BODY)) req.destroy(); });
|
|
50
|
+
req.on('end', () => {
|
|
51
|
+
let parsed;
|
|
52
|
+
try { parsed = body ? JSON.parse(body) : {}; }
|
|
53
|
+
catch { sendJson(res, 400, { ok: false, error: 'invalid_json' }); return; }
|
|
54
|
+
cb(parsed);
|
|
55
|
+
});
|
|
56
|
+
req.on('error', () => { try { sendJson(res, 400, { ok: false, error: 'invalid_json' }); } catch {} });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function _guardWrite(req, res, parsedUrl, isLocal) {
|
|
60
|
+
if (!isAdminReq(req, isLocal) || !isSameOriginBrowserRequest(req, parsedUrl)) {
|
|
61
|
+
sendJson(res, 403, { ok: false, error: 'forbidden' });
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Broadcast a task_changed frame to the project's viewers. */
|
|
68
|
+
function _broadcastChanged(deps, project, taskId, action) {
|
|
69
|
+
try {
|
|
70
|
+
sendEventToClients(
|
|
71
|
+
filterClientsByViewProject(deps.clients, project, _projectName || ''),
|
|
72
|
+
'scheduled_tasks',
|
|
73
|
+
{ type: 'task_changed', project, taskId, action },
|
|
74
|
+
);
|
|
75
|
+
} catch (err) { reportSwallowed('scheduled-tasks.broadcast', err); }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Resolve a project name (or explicit instance) to an ABSOLUTE cwd. Returns
|
|
80
|
+
* { ok:true, cwd } | { ok:false, status, error }.
|
|
81
|
+
*/
|
|
82
|
+
function _resolveProjectToCwd(project, instance) {
|
|
83
|
+
const bound = _projectName || '';
|
|
84
|
+
// 1. An explicit instance (SDK session / live PTY) pins the cwd EXACTLY — it wins over
|
|
85
|
+
// the project name so two same-basename workspaces never cross-execute (review: the
|
|
86
|
+
// bound-project branch must not shadow a deliberate instance pick).
|
|
87
|
+
if (instance) {
|
|
88
|
+
try {
|
|
89
|
+
const sdkHit = listSdkSessionInstances().find(s => s.instanceKey === instance);
|
|
90
|
+
if (sdkHit && sdkHit.cwd) {
|
|
91
|
+
// Consistency: the instance's cwd must actually belong to the named project,
|
|
92
|
+
// else this is a forged/mismatched project+instance pair (security review).
|
|
93
|
+
if (!project || project === bound || _projectNameOf(sdkHit.cwd) === project) return { ok: true, cwd: sdkHit.cwd };
|
|
94
|
+
return { ok: false, status: 409, error: 'project_ambiguous' };
|
|
95
|
+
}
|
|
96
|
+
} catch { /* fall through */ }
|
|
97
|
+
const ptyHit = listLivePtys().find(p => p.instanceKey === instance && p.ptyKind === 'claude');
|
|
98
|
+
if (ptyHit) {
|
|
99
|
+
if (!project || project === bound || _projectNameOf(ptyHit.cwd) === project) return { ok: true, cwd: ptyHit.cwd };
|
|
100
|
+
return { ok: false, status: 409, error: 'project_ambiguous' };
|
|
101
|
+
}
|
|
102
|
+
// An explicit instance that resolves to nothing is an error, not a silent fallback.
|
|
103
|
+
return { ok: false, status: 404, error: 'project_unresolved' };
|
|
104
|
+
}
|
|
105
|
+
// 2. The bound project (the common case) → the server's own cwd / CCV_PROJECT_DIR.
|
|
106
|
+
if (!project || project === bound) {
|
|
107
|
+
const cwd = process.env.CCV_PROJECT_DIR || (bound ? process.cwd() : null);
|
|
108
|
+
// P1-6: no bound project AND no explicit project → unresolvable (never process.cwd()).
|
|
109
|
+
if (!cwd) return { ok: false, status: 404, error: 'project_unresolved' };
|
|
110
|
+
return { ok: true, cwd };
|
|
111
|
+
}
|
|
112
|
+
// 3. The workspace registry: a unique projectName match → its path.
|
|
113
|
+
let matches = [];
|
|
114
|
+
try {
|
|
115
|
+
matches = loadWorkspaces().filter(w => w.projectName === project && w.path);
|
|
116
|
+
} catch (err) { reportSwallowed('scheduled-tasks.registry', err); }
|
|
117
|
+
if (matches.length === 1) return { ok: true, cwd: matches[0].path };
|
|
118
|
+
if (matches.length > 1) return { ok: false, status: 409, error: 'project_ambiguous' };
|
|
119
|
+
return { ok: false, status: 404, error: 'project_unresolved' };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Project key (sanitized basename) for a cwd, matching the store/UI naming.
|
|
123
|
+
function _projectNameOf(cwd) {
|
|
124
|
+
const base = String(cwd || '').replace(/\\/g, '/').split('/').filter(Boolean).pop() || '';
|
|
125
|
+
return base.replace(/[^a-zA-Z0-9_\-.]/g, '_');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Public list for a project (bound project when omitted). */
|
|
129
|
+
function listHandler(req, res, parsedUrl) {
|
|
130
|
+
const project = parsedUrl.searchParams.get('project') || (_projectName || '');
|
|
131
|
+
const root = LOG_DIR;
|
|
132
|
+
let reg = { tasks: [], corrupt: false };
|
|
133
|
+
try {
|
|
134
|
+
reg = readScheduledTasks(root);
|
|
135
|
+
} catch (err) {
|
|
136
|
+
reportSwallowed('scheduled-tasks.list', err);
|
|
137
|
+
reg = { tasks: [], corrupt: true };
|
|
138
|
+
}
|
|
139
|
+
if (reg.corrupt) reportSwallowed('scheduled-tasks.list.corrupt', new Error('preferences.json scheduledTasks is corrupt'));
|
|
140
|
+
// Prompt secrecy (review): a project query only earns full prompts when it actually
|
|
141
|
+
// resolves to a known project (bound / registry / live). Otherwise fall back to the
|
|
142
|
+
// summary shape so an arbitrary ?project= can't pull another project's prompt text.
|
|
143
|
+
const resolved = project ? _resolveProjectToCwd(project, parsedUrl.searchParams.get('instance') || '') : { ok: false };
|
|
144
|
+
const fullPrompt = !!project && resolved.ok;
|
|
145
|
+
const tasks = (reg.tasks || []).filter(t => !project || t.project === project);
|
|
146
|
+
const running = new Set();
|
|
147
|
+
for (const t of tasks) if (t.claim) running.add(t.id);
|
|
148
|
+
const shaped = fullPrompt
|
|
149
|
+
? tasks.map(t => ({ ..._publicTask(t), running: running.has(t.id), ...(reg.legacy ? { legacy: true } : {}) }))
|
|
150
|
+
: tasks.map(t => ({ ..._listTask(t), running: running.has(t.id), ...(reg.legacy ? { legacy: true } : {}) }));
|
|
151
|
+
sendJson(res, 200, {
|
|
152
|
+
tasks: shaped,
|
|
153
|
+
serverTime: Date.now(),
|
|
154
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC',
|
|
155
|
+
spawnAvailable: _spawnAvailableSafe(),
|
|
156
|
+
storeCorrupt: !!reg.corrupt,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function _publicTask(t) {
|
|
161
|
+
const { runs, claim, ...rest } = t;
|
|
162
|
+
return rest;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// List payload keeps the full prompt OUT (it may carry secrets and every logged-in
|
|
166
|
+
// viewer can read the list — security P2-3). Full prompts only ship when the project
|
|
167
|
+
// resolves (bound / registry / live instance — see listHandler's fullPrompt gate).
|
|
168
|
+
function _listTask(t) {
|
|
169
|
+
const p = _publicTask(t);
|
|
170
|
+
p.promptSummary = String(t.prompt || '').replace(/\s+/g, ' ').trim().slice(0, 200);
|
|
171
|
+
delete p.prompt;
|
|
172
|
+
return p;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function _spawnAvailableSafe() {
|
|
176
|
+
try { return spawnAvailable(); } catch { return false; }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function createHandler(req, res, parsedUrl, isLocal, deps) {
|
|
180
|
+
if (!_guardWrite(req, res, parsedUrl, isLocal)) return;
|
|
181
|
+
_readBody(req, res, deps, async (body) => {
|
|
182
|
+
const project = (typeof body.project === 'string' && body.project) || (_projectName || '');
|
|
183
|
+
const resolved = _resolveProjectToCwd(project, body.instance);
|
|
184
|
+
if (!resolved.ok) return sendJson(res, resolved.status, { ok: false, error: resolved.error });
|
|
185
|
+
if (body.mode === 'spawn' && !_spawnAvailableSafe()) {
|
|
186
|
+
return sendJson(res, 400, { ok: false, error: 'spawn_unavailable' });
|
|
187
|
+
}
|
|
188
|
+
let out;
|
|
189
|
+
try {
|
|
190
|
+
out = await createTask(LOG_DIR, {
|
|
191
|
+
project, cwd: resolved.cwd, prompt: body.prompt, mode: body.mode,
|
|
192
|
+
schedule: body.schedule, enabled: body.enabled,
|
|
193
|
+
}, Date.now());
|
|
194
|
+
} catch (err) {
|
|
195
|
+
reportSwallowed('scheduled-tasks.create', err);
|
|
196
|
+
return sendJson(res, 500, { ok: false, error: 'store_corrupt' });
|
|
197
|
+
}
|
|
198
|
+
if (!out.ok) return sendJson(res, 400, { ok: false, error: out.reason, detail: out.detail });
|
|
199
|
+
_broadcastChanged(deps, project, out.task.id, 'created');
|
|
200
|
+
sendJson(res, 200, { ok: true, task: _publicTask(out.task) });
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function updateHandler(req, res, parsedUrl, isLocal, deps) {
|
|
205
|
+
if (!_guardWrite(req, res, parsedUrl, isLocal)) return;
|
|
206
|
+
_readBody(req, res, deps, async (body) => {
|
|
207
|
+
const id = typeof body.id === 'string' ? body.id : '';
|
|
208
|
+
if (!id) return sendJson(res, 400, { ok: false, error: 'task_not_found' });
|
|
209
|
+
let out;
|
|
210
|
+
try {
|
|
211
|
+
out = await updateTask(LOG_DIR, id, {
|
|
212
|
+
prompt: body.prompt, mode: body.mode, schedule: body.schedule, enabled: body.enabled,
|
|
213
|
+
}, Date.now());
|
|
214
|
+
} catch (err) {
|
|
215
|
+
reportSwallowed('scheduled-tasks.update', err);
|
|
216
|
+
return sendJson(res, 500, { ok: false, error: 'store_corrupt' });
|
|
217
|
+
}
|
|
218
|
+
if (!out.ok) return sendJson(res, out.reason === 'task_not_found' ? 404 : 400, { ok: false, error: out.reason, detail: out.detail });
|
|
219
|
+
_broadcastChanged(deps, out.task.project, id, 'updated');
|
|
220
|
+
sendJson(res, 200, { ok: true, task: _publicTask(out.task) });
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function deleteHandler(req, res, parsedUrl, isLocal, deps) {
|
|
225
|
+
if (!_guardWrite(req, res, parsedUrl, isLocal)) return;
|
|
226
|
+
_readBody(req, res, deps, async (body) => {
|
|
227
|
+
const id = typeof body.id === 'string' ? body.id : '';
|
|
228
|
+
let task = null;
|
|
229
|
+
try { task = getTask(LOG_DIR, id); } catch { /* tolerate */ }
|
|
230
|
+
let out;
|
|
231
|
+
try { out = await deleteTask(LOG_DIR, id, Date.now()); }
|
|
232
|
+
catch (err) {
|
|
233
|
+
reportSwallowed('scheduled-tasks.delete', err);
|
|
234
|
+
return sendJson(res, 500, { ok: false, error: 'store_corrupt' });
|
|
235
|
+
}
|
|
236
|
+
if (!out.ok) {
|
|
237
|
+
const status = out.reason === 'task_not_found' ? 404 : (out.reason === 'task_running' ? 409 : 400);
|
|
238
|
+
return sendJson(res, status, { ok: false, error: out.reason });
|
|
239
|
+
}
|
|
240
|
+
_broadcastChanged(deps, (task && task.project) || body.project || (_projectName || ''), id, 'deleted');
|
|
241
|
+
sendJson(res, 200, { ok: true });
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function runHandler(req, res, parsedUrl, isLocal, deps) {
|
|
246
|
+
if (!_guardWrite(req, res, parsedUrl, isLocal)) return;
|
|
247
|
+
_readBody(req, res, deps, async (body) => {
|
|
248
|
+
const id = typeof body.id === 'string' ? body.id : '';
|
|
249
|
+
let out;
|
|
250
|
+
try { out = await runTaskNow(id, { manual: true }); }
|
|
251
|
+
catch (err) {
|
|
252
|
+
reportSwallowed('scheduled-tasks.run', err);
|
|
253
|
+
return sendJson(res, 503, { ok: false, error: 'scheduler_unavailable' });
|
|
254
|
+
}
|
|
255
|
+
if (!out.ok) {
|
|
256
|
+
const status = out.reason === 'scheduler_unavailable' ? 503
|
|
257
|
+
: out.reason === 'running' ? 409
|
|
258
|
+
: out.reason === 'not-found' ? 404 : 400;
|
|
259
|
+
return sendJson(res, status, { ok: false, error: out.reason === 'not-found' ? 'task_not_found' : out.reason });
|
|
260
|
+
}
|
|
261
|
+
sendJson(res, 202, { ok: true, runId: out.runId ?? null });
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function previewHandler(req, res, parsedUrl, isLocal, deps) {
|
|
266
|
+
if (!_guardWrite(req, res, parsedUrl, isLocal)) return;
|
|
267
|
+
_readBody(req, res, deps, (body) => {
|
|
268
|
+
const now = Date.now();
|
|
269
|
+
const norm = normalizeSchedule(body.schedule, now);
|
|
270
|
+
if (!norm.ok) return sendJson(res, 400, { ok: false, error: norm.error, detail: norm.detail });
|
|
271
|
+
const next = [];
|
|
272
|
+
let cursor = now;
|
|
273
|
+
for (let i = 0; i < 3; i++) {
|
|
274
|
+
const t = nextBaseFor(norm.schedule, cursor);
|
|
275
|
+
if (t === null) break;
|
|
276
|
+
next.push(t);
|
|
277
|
+
cursor = t;
|
|
278
|
+
}
|
|
279
|
+
sendJson(res, 200, {
|
|
280
|
+
ok: true, next,
|
|
281
|
+
serverTime: now,
|
|
282
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC',
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function runsHandler(req, res, parsedUrl) {
|
|
288
|
+
const id = parsedUrl.searchParams.get('id') || '';
|
|
289
|
+
const reqProject = parsedUrl.searchParams.get('project') || (_projectName || '');
|
|
290
|
+
let task = null;
|
|
291
|
+
try { task = getTask(LOG_DIR, id); } catch (err) { reportSwallowed('scheduled-tasks.runs', err); }
|
|
292
|
+
if (!task) return sendJson(res, 404, { ok: false, error: 'task_not_found' });
|
|
293
|
+
// Runs carry the prompt summary + sessionId — scope them to the task's own project so a
|
|
294
|
+
// viewer of project A can't read project B's run history by id (security P2-3).
|
|
295
|
+
if (reqProject && task.project !== reqProject) return sendJson(res, 403, { ok: false, error: 'forbidden' });
|
|
296
|
+
let runs = [];
|
|
297
|
+
try { runs = readScheduledTaskRuns(LOG_DIR, id); }
|
|
298
|
+
catch (err) { reportSwallowed('scheduled-tasks.runs.read', err); }
|
|
299
|
+
sendJson(res, 200, { ok: true, runs: runs.slice(-20), serverTime: Date.now() });
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export const scheduledTasksRoutes = [
|
|
303
|
+
{ method: 'GET', match: 'exact', path: '/api/scheduled-tasks', handler: listHandler },
|
|
304
|
+
{ method: 'GET', match: 'exact', path: '/api/scheduled-tasks/runs', handler: runsHandler },
|
|
305
|
+
{ method: 'POST', match: 'exact', path: '/api/scheduled-tasks', handler: createHandler },
|
|
306
|
+
{ method: 'POST', match: 'exact', path: '/api/scheduled-tasks/update', handler: updateHandler },
|
|
307
|
+
{ method: 'POST', match: 'exact', path: '/api/scheduled-tasks/delete', handler: deleteHandler },
|
|
308
|
+
{ method: 'POST', match: 'exact', path: '/api/scheduled-tasks/run', handler: runHandler },
|
|
309
|
+
{ method: 'POST', match: 'exact', path: '/api/scheduled-tasks/preview', handler: previewHandler },
|
|
310
|
+
];
|
package/server/server.js
CHANGED
|
@@ -21,6 +21,7 @@ import { miscRoutes } from './routes/misc.js';
|
|
|
21
21
|
import { preferencesRoutes } from './routes/preferences.js';
|
|
22
22
|
import { projectPrefsRoutes } from './routes/project-prefs.js';
|
|
23
23
|
import { sessionPinRoutes } from './routes/session-pin.js';
|
|
24
|
+
import { scheduledTasksRoutes } from './routes/scheduled-tasks.js';
|
|
24
25
|
import { gitRoutes } from './routes/git.js';
|
|
25
26
|
import { pluginsRoutes } from './routes/plugins.js';
|
|
26
27
|
import { pluginServicesRoutes } from './routes/plugin-services.js';
|
|
@@ -50,6 +51,8 @@ import * as imCore from './lib/im/im-bridge-core.js';
|
|
|
50
51
|
import * as imProcMgr from './lib/im/im-process-manager.js';
|
|
51
52
|
import * as chatQueue from './lib/chat-queue.js';
|
|
52
53
|
import { reportSwallowed } from './lib/error-report.js';
|
|
54
|
+
import { initScheduledTaskScheduler, startScheduledTaskScheduler, stopScheduledTaskScheduler } from './lib/scheduled-task-scheduler.js';
|
|
55
|
+
import { executeScheduledTask } from './lib/scheduled-task-executor.js';
|
|
53
56
|
import './lib/adapters/dingtalk-adapter.js'; // side-effect: registers the DingTalk adapter
|
|
54
57
|
import './lib/adapters/feishu-adapter.js'; // side-effect: registers the Feishu adapter
|
|
55
58
|
import './lib/adapters/wecom-adapter.js'; // side-effect: registers the WeCom adapter
|
|
@@ -843,6 +846,7 @@ const _routes = [
|
|
|
843
846
|
...preferencesRoutes,
|
|
844
847
|
...projectPrefsRoutes,
|
|
845
848
|
...sessionPinRoutes,
|
|
849
|
+
...scheduledTasksRoutes,
|
|
846
850
|
...gitRoutes,
|
|
847
851
|
...pluginsRoutes,
|
|
848
852
|
...pluginServicesRoutes,
|
|
@@ -1167,6 +1171,130 @@ export async function startViewer() {
|
|
|
1167
1171
|
}
|
|
1168
1172
|
}
|
|
1169
1173
|
|
|
1174
|
+
// ─── Scheduled tasks (scheduler wiring) ─────────────────────────────────────
|
|
1175
|
+
// The scheduler is L1 pure logic; every SDK/PTY capability is injected here (server.js
|
|
1176
|
+
// is the only place that knows isSdkMode / LOG_DIR / the SSE client set). Paths are
|
|
1177
|
+
// compared case-insensitively on win32 (workspace-registry's pathEq semantics).
|
|
1178
|
+
function _schedSamePath(a, b) {
|
|
1179
|
+
if (typeof a !== 'string' || typeof b !== 'string' || !a || !b) return false;
|
|
1180
|
+
return process.platform === 'win32' ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
// Process-level capability filter (P0-1): a process only claims a task it can run.
|
|
1184
|
+
// inject needs a LIVE session for the task's cwd in THIS process; spawn needs SDK mode.
|
|
1185
|
+
function _schedCanExecute(task) {
|
|
1186
|
+
try {
|
|
1187
|
+
// A workspace-management process (bound no project / no launched workspace) must never
|
|
1188
|
+
// claim tasks — they belong to a tab's bound workspace (integration P0-1).
|
|
1189
|
+
if (isWorkspaceMode && !_workspaceLaunched) return false;
|
|
1190
|
+
// spawn capability is settled at execute time (the SDK accessors lazy-load on first
|
|
1191
|
+
// execute); the tick-level filter only needs the mode gate so a PTY-mode process
|
|
1192
|
+
// never claims a spawn task it can't run.
|
|
1193
|
+
if (task.mode === 'spawn') return isSdkMode;
|
|
1194
|
+
if (isSdkMode) {
|
|
1195
|
+
// SDK sessions register eagerly at init; if the accessor isn't loaded yet, be
|
|
1196
|
+
// permissive (execute will re-check and report no_live_session honestly).
|
|
1197
|
+
return _sdkListSessions ? _sdkListSessions().some(s => _schedSamePath(s.cwd, task.cwd)) : true;
|
|
1198
|
+
}
|
|
1199
|
+
return _ptyListLive ? _ptyListLive().some(p => p.ptyKind === 'claude' && _schedSamePath(p.cwd, task.cwd)) : true;
|
|
1200
|
+
} catch { return false; }
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
function _schedIsReady() {
|
|
1204
|
+
try {
|
|
1205
|
+
if (isSdkMode) return _sdkListSessions ? _sdkListSessions().length > 0 : true;
|
|
1206
|
+
return _ptyListLive ? _ptyListLive().some(p => p.ptyKind === 'claude') : true;
|
|
1207
|
+
} catch { return false; }
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
async function _schedExecute(task, claim) {
|
|
1211
|
+
// Assemble the io accessors lazily (dynamic import keeps the L2 managers out of the
|
|
1212
|
+
// L1 executor's module graph; server.js is L4 and may import them).
|
|
1213
|
+
if (!_sdkListSessions && isSdkMode) {
|
|
1214
|
+
try {
|
|
1215
|
+
const sdk = await import('./lib/sdk-manager.js');
|
|
1216
|
+
_sdkListSessions = sdk.listSdkSessions;
|
|
1217
|
+
_sdkGetQueueSnapshotFn = sdk.getQueueSnapshot;
|
|
1218
|
+
_sdkSendUserMessageFn = sdk.sendUserMessage;
|
|
1219
|
+
_sdkIsAvailable = sdk.isSdkAvailable;
|
|
1220
|
+
_sdkSpawnEphemeral = sdk.spawnEphemeralSession;
|
|
1221
|
+
} catch (err) { reportSwallowed('server.sched-sdk-import', err); }
|
|
1222
|
+
}
|
|
1223
|
+
if (!_ptyListLive && !isSdkMode) {
|
|
1224
|
+
try {
|
|
1225
|
+
const pm = await import('./pty-manager.js');
|
|
1226
|
+
_ptyListLive = pm.listLivePtys;
|
|
1227
|
+
_ptyGetKindFor = pm.getPtyKindFor;
|
|
1228
|
+
} catch (err) { reportSwallowed('server.sched-pty-import', err); }
|
|
1229
|
+
}
|
|
1230
|
+
const io = {
|
|
1231
|
+
isSdkMode,
|
|
1232
|
+
sdk: {
|
|
1233
|
+
isSdkAvailable: () => (_sdkIsAvailable ? _sdkIsAvailable() : false),
|
|
1234
|
+
listSdkSessions: () => (_sdkListSessions ? _sdkListSessions() : []),
|
|
1235
|
+
getQueueSnapshot: (anchor) => (_sdkGetQueueSnapshotFn ? _sdkGetQueueSnapshotFn(anchor) : []),
|
|
1236
|
+
sendUserMessage: (text, anchor) => (_sdkSendUserMessageFn ? _sdkSendUserMessageFn(text, anchor) : Promise.resolve()),
|
|
1237
|
+
spawnEphemeralSession: (prompt, opts) => (_sdkSpawnEphemeral ? _sdkSpawnEphemeral(prompt, opts) : Promise.resolve({ ok: false, reason: 'sdk_unavailable' })),
|
|
1238
|
+
},
|
|
1239
|
+
pty: {
|
|
1240
|
+
listLivePtys: () => (_ptyListLive ? _ptyListLive() : []),
|
|
1241
|
+
getPtyKindFor: (anchor) => (_ptyGetKindFor ? _ptyGetKindFor(anchor) : null),
|
|
1242
|
+
},
|
|
1243
|
+
chatQueue,
|
|
1244
|
+
samePath: _schedSamePath,
|
|
1245
|
+
};
|
|
1246
|
+
return executeScheduledTask(task, claim, io);
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// Lazily-resolved capability accessors (populated on first use / after SDK init).
|
|
1250
|
+
let _sdkListSessions = null;
|
|
1251
|
+
let _sdkGetQueueSnapshotFn = null;
|
|
1252
|
+
let _sdkSendUserMessageFn = null;
|
|
1253
|
+
let _sdkIsAvailable = null;
|
|
1254
|
+
let _sdkSpawnEphemeral = null;
|
|
1255
|
+
let _ptyListLive = null;
|
|
1256
|
+
let _ptyGetKindFor = null;
|
|
1257
|
+
|
|
1258
|
+
async function _initScheduledTasks() {
|
|
1259
|
+
try {
|
|
1260
|
+
// Preload the capability accessors BEFORE the first tick, so canExecute's very first
|
|
1261
|
+
// evaluation is real (not the permissive fail-open default). Without this an Electron
|
|
1262
|
+
// management process / tab worker would claim every due task on its first tick only
|
|
1263
|
+
// to fail them (review: integration P0-1).
|
|
1264
|
+
if (isSdkMode) {
|
|
1265
|
+
try {
|
|
1266
|
+
const sdk = await import('./lib/sdk-manager.js');
|
|
1267
|
+
_sdkListSessions = sdk.listSdkSessions;
|
|
1268
|
+
_sdkGetQueueSnapshotFn = sdk.getQueueSnapshot;
|
|
1269
|
+
_sdkSendUserMessageFn = sdk.sendUserMessage;
|
|
1270
|
+
_sdkIsAvailable = sdk.isSdkAvailable;
|
|
1271
|
+
_sdkSpawnEphemeral = sdk.spawnEphemeralSession;
|
|
1272
|
+
} catch (err) { reportSwallowed('server.sched-sdk-preload', err); }
|
|
1273
|
+
} else {
|
|
1274
|
+
try {
|
|
1275
|
+
const pm = await import('./pty-manager.js');
|
|
1276
|
+
_ptyListLive = pm.listLivePtys;
|
|
1277
|
+
_ptyGetKindFor = pm.getPtyKindFor;
|
|
1278
|
+
} catch (err) { reportSwallowed('server.sched-pty-preload', err); }
|
|
1279
|
+
}
|
|
1280
|
+
initScheduledTaskScheduler({
|
|
1281
|
+
getRoot: () => LOG_DIR,
|
|
1282
|
+
getMode: () => ({ sdk: isSdkMode }),
|
|
1283
|
+
getSpawnConfig: () => ({ available: isSdkMode && !!(_sdkIsAvailable && _sdkIsAvailable()) }),
|
|
1284
|
+
canExecute: _schedCanExecute,
|
|
1285
|
+
isReady: _schedIsReady,
|
|
1286
|
+
execute: _schedExecute,
|
|
1287
|
+
broadcast: (project, payload) => {
|
|
1288
|
+
try {
|
|
1289
|
+
sendEventToClients(_filterClientsByViewProject(clients, project), 'scheduled_tasks', payload);
|
|
1290
|
+
} catch (err) { reportSwallowed('server.sched-broadcast', err); }
|
|
1291
|
+
},
|
|
1292
|
+
report: reportSwallowed,
|
|
1293
|
+
});
|
|
1294
|
+
startScheduledTaskScheduler();
|
|
1295
|
+
} catch (err) { reportSwallowed('server.sched-init', err); }
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1170
1298
|
let _startingPromise = null;
|
|
1171
1299
|
|
|
1172
1300
|
async function _doStart() {
|
|
@@ -1363,6 +1491,12 @@ async function _doStart() {
|
|
|
1363
1491
|
if (isCliMode) {
|
|
1364
1492
|
await setupTerminalWebSocket(currentServer);
|
|
1365
1493
|
}
|
|
1494
|
+
// Scheduled tasks: only a CLI-mode process that is NOT an IM worker runs the
|
|
1495
|
+
// scheduler (an IM worker shares LOG_DIR but has no resident session to inject
|
|
1496
|
+
// into — review P0-1). A workspace-management/static viewer process never does.
|
|
1497
|
+
if (isCliMode && !process.env.CCV_IM_PLATFORM) {
|
|
1498
|
+
await _initScheduledTasks();
|
|
1499
|
+
}
|
|
1366
1500
|
// Install the unified upgrade router (all modes): plugin service WS prefixes + registered terminal/mux.
|
|
1367
1501
|
// Install after createServer and before the first request to close the upgrade gap.
|
|
1368
1502
|
installUpgradeRouter(currentServer);
|
|
@@ -3324,6 +3458,7 @@ async function _doStop() {
|
|
|
3324
3458
|
// Drop chat-queue timers (rescue / idle poll) so a stop/start cycle never injects a stale
|
|
3325
3459
|
// queued message into a fresh PTY session.
|
|
3326
3460
|
try { chatQueue.stop(); } catch { /* best-effort */ }
|
|
3461
|
+
try { stopScheduledTaskScheduler(); } catch { /* best-effort */ }
|
|
3327
3462
|
_clearTurnActiveHeartbeat();
|
|
3328
3463
|
// Per-workspace: tear down every scope bucket plus the per-scope session tags
|
|
3329
3464
|
// and streaming-edge records, so a restarted server never replays a stale
|
|
@@ -10,6 +10,10 @@ You are ${model.name}, an interactive coding agent that helps users with softwar
|
|
|
10
10
|
|
|
11
11
|
IMPORTANT: Never generate or guess URLs unless you are confident they help the user with programming. Prefer URLs the user provides or ones found in local files.
|
|
12
12
|
|
|
13
|
+
# Choosing skills
|
|
14
|
+
- At the skill-selection entry point, skip `claude-api` and the skill whose display title is "Building LLM-Powered Applications with Claude": they are the same Anthropic-only API skill, so treat the identifier and title as aliases. Do not invoke it or read its documentation unless the task explicitly requires Claude/Anthropic SDK or API integration. This applies even when Claude Code automatically recommends or triggers it.
|
|
15
|
+
- Working on model-name recognition, model logos, aliases, or context-window classification in an existing project is local application code, NOT LLM API integration. Do not load `claude-api` for these tasks merely because the code mentions models, Claude, or model IDs. Locate the local matcher and its tests first; for a genuinely unrelated skill, do not load it just to determine that it is unrelated.
|
|
16
|
+
|
|
13
17
|
# Doing tasks
|
|
14
18
|
- Read the relevant files before acting or answering; ground every claim and change in code you have actually looked at.
|
|
15
19
|
- Before your first tool call, output a one-sentence action plan; then act.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as styles_default,c as classRenderer_v3_unified_default,a as classDiagram_default,C as ClassDB}from"./chunk-4TB4RGXK-GWLkQKRO.js";import{_ as __name}from"./mermaid.core-CNYcCP19.js";import"./chunk-FMBD7UC4-DK-vXpZP.js";import"./chunk-YZCP3GAM-DtYet6WV.js";import"./chunk-55IACEB6-B6LOFqRZ.js";import"./chunk-EDXVE4YY-Ct3e09Ku.js";import"./vendor-markdown-DOJHsAxX.js";import"./vendor-mdxeditor-BreKt5Hd.js";import"./vendor-antd-CfuxfYfy.js";import"./vendor-codemirror-C4Jn4TTc.js";var diagram={parser:classDiagram_default,get db(){return new ClassDB},renderer:classRenderer_v3_unified_default,styles:styles_default,init:__name(cnf=>{cnf.class||(cnf.class={}),cnf.class.arrowMarkerAbsolute=cnf.arrowMarkerAbsolute},"init")};export{diagram};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as styles_default,c as classRenderer_v3_unified_default,a as classDiagram_default,C as ClassDB}from"./chunk-4TB4RGXK-GWLkQKRO.js";import{_ as __name}from"./mermaid.core-CNYcCP19.js";import"./chunk-FMBD7UC4-DK-vXpZP.js";import"./chunk-YZCP3GAM-DtYet6WV.js";import"./chunk-55IACEB6-B6LOFqRZ.js";import"./chunk-EDXVE4YY-Ct3e09Ku.js";import"./vendor-markdown-DOJHsAxX.js";import"./vendor-mdxeditor-BreKt5Hd.js";import"./vendor-antd-CfuxfYfy.js";import"./vendor-codemirror-C4Jn4TTc.js";var diagram={parser:classDiagram_default,get db(){return new ClassDB},renderer:classRenderer_v3_unified_default,styles:styles_default,init:__name(cnf=>{cnf.class||(cnf.class={}),cnf.class.arrowMarkerAbsolute=cnf.arrowMarkerAbsolute},"init")};export{diagram};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{b as baseClone}from"./graph-Dh22wYWs.js";function clone(value){return baseClone(value,4)}export{clone as c};
|