@open-mercato/cezar 0.9.1-pr694.821 → 0.9.1-pr697.822
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/index.js +1 -18
- package/dist/index.js.map +1 -1
- package/dist/runs/store.d.ts +0 -34
- package/dist/runs/store.js +0 -10
- package/dist/runs/store.js.map +1 -1
- package/dist/server/project-context.d.ts +0 -6
- package/dist/server/project-context.js +1 -6
- package/dist/server/project-context.js.map +1 -1
- package/dist/server/server.d.ts +1 -8
- package/dist/server/server.js +3 -331
- package/dist/server/server.js.map +1 -1
- package/dist/server-install/platforms/ubuntu-vps.d.ts +15 -0
- package/dist/server-install/platforms/ubuntu-vps.js +62 -1
- package/dist/server-install/platforms/ubuntu-vps.js.map +1 -1
- package/package.json +1 -1
- package/web/dist/assets/{centered-state-DsblJe6Q.js → centered-state-_B4KtyI9.js} +11 -11
- package/web/dist/assets/{commit-list-CAwllMBX.js → commit-list-DZ_dB5pp.js} +1 -1
- package/web/dist/assets/{compare-variants-JnaeMTz5.js → compare-variants-DApq2mrq.js} +1 -1
- package/web/dist/assets/{diff-Drvekr61.js → diff-COiZDXGN.js} +2 -2
- package/web/dist/assets/{diff-view-Ct3kYFRM.js → diff-view-a118OGM7.js} +1 -1
- package/web/dist/assets/{dist-CngV8tyE.js → dist-BDertzy3.js} +1 -1
- package/web/dist/assets/{git-toolbar-BLqu97v8.js → git-toolbar-D1hE6SY9.js} +1 -1
- package/web/dist/assets/github-Sh-4Tqz5.js +1 -0
- package/web/dist/assets/index-CPY_yMu5.js +22 -0
- package/web/dist/assets/index-CrdG_TLt.css +2 -0
- package/web/dist/assets/{project-router-CnKsyZKI.js → project-router-DHNVGMTs.js} +1 -1
- package/web/dist/assets/{repo-git-DnkvanyM.js → repo-git-BV1vqi4_.js} +1 -1
- package/web/dist/assets/{run-diff-LyXkHJCR.js → run-diff-VFfj5NpJ.js} +1 -1
- package/web/dist/assets/run-header-Y-KtnbDv.js +1 -0
- package/web/dist/assets/{skills-CE0HAI_G.js → skills-D_Z_u00d.js} +1 -1
- package/web/dist/assets/{tab-link-BbQLZUG5.js → tab-link-D6mbZhQc.js} +1 -1
- package/web/dist/assets/{task-changes-BYNpnV0E.js → task-changes-DXZTUtTU.js} +1 -1
- package/web/dist/assets/task-commits-CVAdLmlZ.js +1 -0
- package/web/dist/assets/{task-files-DAqPuHp1.js → task-files-BcEipRuc.js} +2 -2
- package/web/dist/assets/{task-thread-DMM5s2zt.js → task-thread-DFsV_WWl.js} +7 -7
- package/web/dist/assets/{use-desktop-BAHxMKrE.js → use-desktop-D4jq8HBV.js} +1 -1
- package/web/dist/assets/{workflows-DCkK4Bsn.js → workflows-T3uwAimN.js} +3 -3
- package/web/dist/index.html +5 -5
- package/dist/automations/coordinator.d.ts +0 -26
- package/dist/automations/coordinator.js +0 -66
- package/dist/automations/coordinator.js.map +0 -1
- package/dist/automations/github-poller.d.ts +0 -134
- package/dist/automations/github-poller.js +0 -159
- package/dist/automations/github-poller.js.map +0 -1
- package/dist/automations/scheduler.d.ts +0 -42
- package/dist/automations/scheduler.js +0 -177
- package/dist/automations/scheduler.js.map +0 -1
- package/dist/automations/store.d.ts +0 -65
- package/dist/automations/store.js +0 -298
- package/dist/automations/store.js.map +0 -1
- package/dist/automations/task-template.d.ts +0 -18
- package/dist/automations/task-template.js +0 -95
- package/dist/automations/task-template.js.map +0 -1
- package/dist/automations/types.d.ts +0 -3650
- package/dist/automations/types.js +0 -158
- package/dist/automations/types.js.map +0 -1
- package/web/dist/assets/github-hrWK-ST8.js +0 -1
- package/web/dist/assets/index-B96YnNHc.js +0 -22
- package/web/dist/assets/index-DdBHg6_F.css +0 -2
- package/web/dist/assets/run-header-BtH23tbQ.js +0 -1
- package/web/dist/assets/task-commits-DRnMTXtG.js +0 -1
package/dist/server/server.js
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
1
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
-
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import { AutomationStore } from '../automations/store.js';
|
|
4
|
-
import { AutomationCoordinator } from '../automations/coordinator.js';
|
|
5
|
-
import { GithubPoller } from '../automations/github-poller.js';
|
|
6
|
-
import { ProjectAutomationScheduler, WorkspaceAutomationScheduler } from '../automations/scheduler.js';
|
|
7
|
-
import { launchAutomationRun, reconcileAutomationReceipts, validateAutomationPrompt } from '../automations/task-template.js';
|
|
8
|
-
import { automationEventSchema, automationFiltersSchema, automationLogResultSchema, automationTaskSchema, } from '../automations/types.js';
|
|
9
2
|
import { access, constants as fsConstants, mkdir, readFile, realpath, stat, unlink, writeFile } from 'node:fs/promises';
|
|
10
3
|
import { basename, dirname, join, resolve, sep } from 'node:path';
|
|
11
4
|
import { fileURLToPath } from 'node:url';
|
|
@@ -49,7 +42,7 @@ import { expandTilde } from '../paths.js';
|
|
|
49
42
|
import { isLoopbackHostHeader, normalizeHostname, resolveCapabilities } from './capabilities.js';
|
|
50
43
|
import { createSocketHub } from './ws.js';
|
|
51
44
|
import { browseDirectory, isInsideBrowseRoot, isLexicallyInsideBrowseRoot, resolveBrowseRoot } from './fs-browse.js';
|
|
52
|
-
import {
|
|
45
|
+
import { resolveForge } from './forge/index.js';
|
|
53
46
|
import { fetchGithub, fetchGithubChecks, fetchGithubComments, fetchGithubPrDiff, GithubPrNotFoundError, GH_CHECKS_MAX } from './github.js';
|
|
54
47
|
import { ensureLaunchKey } from './launch-key.js';
|
|
55
48
|
import { openInTerminal } from './open-in-terminal.js';
|
|
@@ -71,39 +64,6 @@ const providerEnabledSchema = z.object({ enabled: z.boolean() }).strict();
|
|
|
71
64
|
const providerRetrySchema = z.object({
|
|
72
65
|
authFailureId: z.string().min(1).max(128),
|
|
73
66
|
}).strict();
|
|
74
|
-
const automationEditableSchema = z
|
|
75
|
-
.object({
|
|
76
|
-
name: z.string().trim().min(1).max(200),
|
|
77
|
-
description: z.string().max(2_000).optional(),
|
|
78
|
-
enabled: z.boolean().optional(),
|
|
79
|
-
events: z.array(automationEventSchema).min(1).max(4),
|
|
80
|
-
intervalSeconds: z.number().int().min(60).max(86_400),
|
|
81
|
-
filters: automationFiltersSchema,
|
|
82
|
-
task: automationTaskSchema,
|
|
83
|
-
})
|
|
84
|
-
.strict();
|
|
85
|
-
const automationCreateSchema = automationEditableSchema.extend({ enable: z.boolean().optional() });
|
|
86
|
-
const automationUpdateSchema = automationEditableSchema.extend({ expectedRevision: z.number().int().positive() });
|
|
87
|
-
const automationCheckSchema = z.object({ mode: z.enum(['preview', 'execute']) }).strict();
|
|
88
|
-
const automationLogQuerySchema = z.object({
|
|
89
|
-
automationId: z.string().optional(),
|
|
90
|
-
result: automationLogResultSchema.optional(),
|
|
91
|
-
event: automationEventSchema.optional(),
|
|
92
|
-
since: z.string().datetime().optional(),
|
|
93
|
-
cursor: z.coerce.number().int().positive().optional(),
|
|
94
|
-
limit: z.coerce.number().int().min(1).max(100).default(100),
|
|
95
|
-
});
|
|
96
|
-
function editableAutomation(definition) {
|
|
97
|
-
return {
|
|
98
|
-
name: definition.name,
|
|
99
|
-
description: definition.description,
|
|
100
|
-
enabled: definition.enabled,
|
|
101
|
-
events: definition.events,
|
|
102
|
-
intervalSeconds: definition.intervalSeconds,
|
|
103
|
-
filters: definition.filters,
|
|
104
|
-
task: definition.task,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
67
|
const SCOPED_PREFIX = '/api/p/:projectId';
|
|
108
68
|
/**
|
|
109
69
|
* The project-scoped route table of a `createApp()` app, derived from its
|
|
@@ -628,7 +588,6 @@ export function createApp(deps) {
|
|
|
628
588
|
dataDir: bootDataDir,
|
|
629
589
|
store: deps.store,
|
|
630
590
|
manager: deps.manager,
|
|
631
|
-
automationStore: deps.automationStore ?? AutomationStore.open(bootDataDir),
|
|
632
591
|
launchKey: ensureLaunchKey(bootDataDir), // bookmarklet auto-start secret (spec 011)
|
|
633
592
|
};
|
|
634
593
|
// Non-boot projects build lazily on first scoped request; their managers
|
|
@@ -645,13 +604,6 @@ export function createApp(deps) {
|
|
|
645
604
|
// Workspace-level SSE bus (step 2.8) — the registry mutators and the
|
|
646
605
|
// checkout flow (Phase 4) emit here; /api/workspace/events relays.
|
|
647
606
|
const workspaceEvents = deps.workspaceEvents ?? new WorkspaceEventBus();
|
|
648
|
-
const emitAutomationChange = (project, automationId, revision, deleted = false) => workspaceEvents.emit('automation-change', {
|
|
649
|
-
project: project.id,
|
|
650
|
-
automationId,
|
|
651
|
-
revision,
|
|
652
|
-
...(deleted ? { deleted: true } : {}),
|
|
653
|
-
});
|
|
654
|
-
const automationsChanged = () => deps.automationsChanged?.();
|
|
655
607
|
const providerRuntimeAuth = deps.providerRuntimeAuth
|
|
656
608
|
?? new ProviderRuntimeAuthObserver(providerAuth, (status) => {
|
|
657
609
|
workspaceEvents.emit('provider-status', status);
|
|
@@ -1879,215 +1831,6 @@ export function createApp(deps) {
|
|
|
1879
1831
|
return c.json({ error: blocked }, 409);
|
|
1880
1832
|
return c.json(await planChain(repoRoot, parsed.data.task));
|
|
1881
1833
|
});
|
|
1882
|
-
const manualChecks = new Map();
|
|
1883
|
-
api.get('/automations', async (c) => {
|
|
1884
|
-
const { root, automationStore } = c.get('project');
|
|
1885
|
-
const forge = resolveForge(await getRepoInfo(root));
|
|
1886
|
-
const availability = forge?.detectCached() ?? {
|
|
1887
|
-
available: false,
|
|
1888
|
-
reason: forge ? 'GitHub availability is still being checked' : 'No GitHub remote is configured',
|
|
1889
|
-
};
|
|
1890
|
-
const automations = automationStore.list().map((automation) => {
|
|
1891
|
-
const logs = automationStore.logs({ automationId: automation.id, limit: 100 });
|
|
1892
|
-
return {
|
|
1893
|
-
...automation,
|
|
1894
|
-
state: automationStore.state(automation.id),
|
|
1895
|
-
latestLog: logs[0],
|
|
1896
|
-
counts: {
|
|
1897
|
-
matches: logs.filter((row) => row.result === 'launched' || row.result === 'duplicate').length,
|
|
1898
|
-
launched: logs.filter((row) => row.result === 'launched').length,
|
|
1899
|
-
duplicates: logs.filter((row) => row.result === 'duplicate').length,
|
|
1900
|
-
errors: logs.filter((row) => row.result === 'error' || row.result === 'rate-limited').length,
|
|
1901
|
-
},
|
|
1902
|
-
};
|
|
1903
|
-
});
|
|
1904
|
-
return c.json({
|
|
1905
|
-
...availability,
|
|
1906
|
-
scheduler: {
|
|
1907
|
-
state: automations.some((item) => item.enabled) ? 'scheduled' : 'idle',
|
|
1908
|
-
nextDue: automations.map((item) => item.state?.nextCheckAt).filter(Boolean).sort()[0],
|
|
1909
|
-
},
|
|
1910
|
-
automations,
|
|
1911
|
-
});
|
|
1912
|
-
});
|
|
1913
|
-
api.post('/automations', async (c) => {
|
|
1914
|
-
const { automationStore } = c.get('project');
|
|
1915
|
-
const parsed = automationCreateSchema.safeParse(await c.req.json().catch(() => null));
|
|
1916
|
-
if (!parsed.success)
|
|
1917
|
-
return c.json({ error: parsed.error.issues.map((issue) => issue.message).join('; ') }, 400);
|
|
1918
|
-
const promptIssue = validateAutomationPrompt(parsed.data.task.prompt);
|
|
1919
|
-
if (promptIssue)
|
|
1920
|
-
return c.json({ error: promptIssue }, 400);
|
|
1921
|
-
const { enable, ...input } = parsed.data;
|
|
1922
|
-
try {
|
|
1923
|
-
const automation = automationStore.create({ ...input, enabled: enable === true });
|
|
1924
|
-
if (enable) {
|
|
1925
|
-
const baselineAt = new Date().toISOString();
|
|
1926
|
-
automationStore.setState(automation.id, {
|
|
1927
|
-
revision: automation.revision,
|
|
1928
|
-
baselineAt,
|
|
1929
|
-
cursor: { timestamp: baselineAt },
|
|
1930
|
-
nextCheckAt: new Date(Date.now() + automation.intervalSeconds * 1_000).toISOString(),
|
|
1931
|
-
});
|
|
1932
|
-
}
|
|
1933
|
-
emitAutomationChange(c.get('project'), automation.id, automation.revision);
|
|
1934
|
-
automationsChanged();
|
|
1935
|
-
return c.json({ automation }, 201);
|
|
1936
|
-
}
|
|
1937
|
-
catch (error) {
|
|
1938
|
-
return c.json({ error: error instanceof Error ? error.message : String(error) }, 400);
|
|
1939
|
-
}
|
|
1940
|
-
});
|
|
1941
|
-
api.get('/automations/:id', (c) => {
|
|
1942
|
-
const { automationStore } = c.get('project');
|
|
1943
|
-
const automation = automationStore.get(c.req.param('id'));
|
|
1944
|
-
return automation
|
|
1945
|
-
? c.json({ automation, state: automationStore.state(automation.id), latestLog: automationStore.logs({ automationId: automation.id, limit: 1 })[0] })
|
|
1946
|
-
: c.json({ error: 'not found' }, 404);
|
|
1947
|
-
});
|
|
1948
|
-
api.put('/automations/:id', async (c) => {
|
|
1949
|
-
const { automationStore } = c.get('project');
|
|
1950
|
-
const parsed = automationUpdateSchema.safeParse(await c.req.json().catch(() => null));
|
|
1951
|
-
if (!parsed.success)
|
|
1952
|
-
return c.json({ error: parsed.error.issues.map((issue) => issue.message).join('; ') }, 400);
|
|
1953
|
-
const promptIssue = validateAutomationPrompt(parsed.data.task.prompt);
|
|
1954
|
-
if (promptIssue)
|
|
1955
|
-
return c.json({ error: promptIssue }, 400);
|
|
1956
|
-
const { expectedRevision, ...input } = parsed.data;
|
|
1957
|
-
if (!automationStore.get(c.req.param('id')))
|
|
1958
|
-
return c.json({ error: 'not found' }, 404);
|
|
1959
|
-
try {
|
|
1960
|
-
const automation = automationStore.update(c.req.param('id'), expectedRevision, { ...input, enabled: input.enabled ?? false });
|
|
1961
|
-
emitAutomationChange(c.get('project'), automation.id, automation.revision);
|
|
1962
|
-
automationsChanged();
|
|
1963
|
-
return c.json({ automation });
|
|
1964
|
-
}
|
|
1965
|
-
catch (error) {
|
|
1966
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1967
|
-
return c.json({ error: message }, message.includes('conflict') ? 409 : 400);
|
|
1968
|
-
}
|
|
1969
|
-
});
|
|
1970
|
-
api.delete('/automations/:id', (c) => {
|
|
1971
|
-
const id = c.req.param('id');
|
|
1972
|
-
const store = c.get('project').automationStore;
|
|
1973
|
-
const current = store.get(id);
|
|
1974
|
-
if (!current || !store.delete(id))
|
|
1975
|
-
return c.json({ error: 'not found' }, 404);
|
|
1976
|
-
emitAutomationChange(c.get('project'), id, current.revision, true);
|
|
1977
|
-
automationsChanged();
|
|
1978
|
-
return c.body(null, 204);
|
|
1979
|
-
});
|
|
1980
|
-
api.post('/automations/:id/enable', (c) => {
|
|
1981
|
-
const store = c.get('project').automationStore;
|
|
1982
|
-
const current = store.get(c.req.param('id'));
|
|
1983
|
-
if (!current)
|
|
1984
|
-
return c.json({ error: 'not found' }, 404);
|
|
1985
|
-
const automation = store.update(current.id, current.revision, { ...editableAutomation(current), enabled: true });
|
|
1986
|
-
const baselineAt = new Date().toISOString();
|
|
1987
|
-
store.setState(automation.id, {
|
|
1988
|
-
...store.state(automation.id),
|
|
1989
|
-
revision: automation.revision,
|
|
1990
|
-
baselineAt,
|
|
1991
|
-
cursor: { timestamp: baselineAt },
|
|
1992
|
-
nextCheckAt: new Date(Date.now() + automation.intervalSeconds * 1_000).toISOString(),
|
|
1993
|
-
});
|
|
1994
|
-
store.appendLog({ automationId: automation.id, revision: automation.revision, result: 'baseline', reason: 'Enabled from a current-time baseline; existing records were not launched.' });
|
|
1995
|
-
emitAutomationChange(c.get('project'), automation.id, automation.revision);
|
|
1996
|
-
automationsChanged();
|
|
1997
|
-
return c.json({ automation });
|
|
1998
|
-
});
|
|
1999
|
-
api.post('/automations/:id/pause', (c) => {
|
|
2000
|
-
const store = c.get('project').automationStore;
|
|
2001
|
-
const current = store.get(c.req.param('id'));
|
|
2002
|
-
if (!current)
|
|
2003
|
-
return c.json({ error: 'not found' }, 404);
|
|
2004
|
-
const automation = store.update(current.id, current.revision, { ...editableAutomation(current), enabled: false });
|
|
2005
|
-
emitAutomationChange(c.get('project'), automation.id, automation.revision);
|
|
2006
|
-
automationsChanged();
|
|
2007
|
-
return c.json({ automation });
|
|
2008
|
-
});
|
|
2009
|
-
api.post('/automations/:id/check', async (c) => {
|
|
2010
|
-
const project = c.get('project');
|
|
2011
|
-
const store = project.automationStore;
|
|
2012
|
-
const automation = store.get(c.req.param('id'));
|
|
2013
|
-
if (!automation)
|
|
2014
|
-
return c.json({ error: 'not found' }, 404);
|
|
2015
|
-
const parsed = automationCheckSchema.safeParse(await c.req.json().catch(() => null));
|
|
2016
|
-
if (!parsed.success)
|
|
2017
|
-
return c.json({ error: parsed.error.issues.map((issue) => issue.message).join('; ') }, 400);
|
|
2018
|
-
const id = randomUUID();
|
|
2019
|
-
const check = { id, automationId: automation.id, mode: parsed.data.mode, status: 'queued', createdAt: new Date().toISOString() };
|
|
2020
|
-
if (manualChecks.size >= 200)
|
|
2021
|
-
manualChecks.delete(manualChecks.keys().next().value);
|
|
2022
|
-
manualChecks.set(id, check);
|
|
2023
|
-
void (async () => {
|
|
2024
|
-
check.status = 'running';
|
|
2025
|
-
try {
|
|
2026
|
-
const remote = parseRemote((await getRepoInfo(project.root))?.remote ?? '');
|
|
2027
|
-
if (!remote || remote.host !== 'github.com')
|
|
2028
|
-
throw new Error('No GitHub remote is configured');
|
|
2029
|
-
const scheduler = new ProjectAutomationScheduler({
|
|
2030
|
-
projectId: project.id,
|
|
2031
|
-
owner: remote.owner,
|
|
2032
|
-
repo: remote.repo,
|
|
2033
|
-
store,
|
|
2034
|
-
poller: new GithubPoller(),
|
|
2035
|
-
launch: parsed.data.mode === 'execute'
|
|
2036
|
-
? (definition, candidate, receiptId) => launchAutomationRun({ root: project.root, manager: project.manager, store: project.store, definition, candidate, receiptId })
|
|
2037
|
-
: undefined,
|
|
2038
|
-
onChange: (automationId, revision) => emitAutomationChange(project, automationId, revision),
|
|
2039
|
-
});
|
|
2040
|
-
const result = await scheduler.check(automation, parsed.data.mode);
|
|
2041
|
-
Object.assign(check, { status: 'complete', completedAt: new Date().toISOString(), matches: result.candidates.length, truncated: result.truncated });
|
|
2042
|
-
}
|
|
2043
|
-
catch (error) {
|
|
2044
|
-
Object.assign(check, { status: 'error', completedAt: new Date().toISOString(), error: error instanceof Error ? error.message : String(error) });
|
|
2045
|
-
}
|
|
2046
|
-
})();
|
|
2047
|
-
return c.json({ checkId: id }, 202);
|
|
2048
|
-
});
|
|
2049
|
-
api.get('/automation-checks/:checkId', (c) => {
|
|
2050
|
-
const check = manualChecks.get(c.req.param('checkId'));
|
|
2051
|
-
return check ? c.json(check) : c.json({ error: 'not found' }, 404);
|
|
2052
|
-
});
|
|
2053
|
-
api.get('/automation-log', (c) => {
|
|
2054
|
-
const parsed = automationLogQuerySchema.safeParse(c.req.query());
|
|
2055
|
-
if (!parsed.success)
|
|
2056
|
-
return c.json({ error: parsed.error.issues.map((issue) => issue.message).join('; ') }, 400);
|
|
2057
|
-
return c.json({ records: c.get('project').automationStore.logs(parsed.data) });
|
|
2058
|
-
});
|
|
2059
|
-
api.post('/automation-log/:receiptId/retry', async (c) => {
|
|
2060
|
-
const project = c.get('project');
|
|
2061
|
-
const store = project.automationStore;
|
|
2062
|
-
const receipt = [...store.latestReceipts().values()].find((row) => row.receiptId === c.req.param('receiptId'));
|
|
2063
|
-
if (!receipt)
|
|
2064
|
-
return c.json({ error: 'not found' }, 404);
|
|
2065
|
-
if (receipt.status !== 'launch-error' || receipt.runId)
|
|
2066
|
-
return c.json({ error: 'receipt is not retryable' }, 409);
|
|
2067
|
-
if (!receipt.candidate)
|
|
2068
|
-
return c.json({ error: 'receipt predates retry context and cannot be retried safely' }, 409);
|
|
2069
|
-
const definition = store.get(receipt.automationId);
|
|
2070
|
-
if (!definition)
|
|
2071
|
-
return c.json({ error: 'automation not found' }, 404);
|
|
2072
|
-
const lease = store.acquireLease();
|
|
2073
|
-
if (!lease)
|
|
2074
|
-
return c.json({ error: 'automation polling lease is held by another process' }, 409);
|
|
2075
|
-
const reserved = { ...receipt, status: 'reserved', error: undefined, updatedAt: new Date().toISOString() };
|
|
2076
|
-
store.appendReceipt(reserved);
|
|
2077
|
-
try {
|
|
2078
|
-
const launched = await launchAutomationRun({ root: project.root, manager: project.manager, store: project.store, definition, candidate: receipt.candidate, receiptId: receipt.receiptId });
|
|
2079
|
-
store.appendReceipt({ ...reserved, status: 'launched', runId: launched.runId, updatedAt: new Date().toISOString() });
|
|
2080
|
-
emitAutomationChange(project, definition.id, definition.revision);
|
|
2081
|
-
return c.json({ receiptId: receipt.receiptId, runId: launched.runId }, 202);
|
|
2082
|
-
}
|
|
2083
|
-
catch (error) {
|
|
2084
|
-
store.appendReceipt({ ...reserved, status: 'launch-error', error: error instanceof Error ? error.message : String(error), updatedAt: new Date().toISOString() });
|
|
2085
|
-
return c.json({ error: error instanceof Error ? error.message : String(error) }, 409);
|
|
2086
|
-
}
|
|
2087
|
-
finally {
|
|
2088
|
-
lease.release();
|
|
2089
|
-
}
|
|
2090
|
-
});
|
|
2091
1834
|
// ---- runs ----------------------------------------------------------------
|
|
2092
1835
|
// Additive `usage` field (#348): the latest CPU/RSS/proc-count sample of the
|
|
2093
1836
|
// run's live process tree — absent for finished runs and when `ps` yields
|
|
@@ -3641,24 +3384,7 @@ export function startServer(deps, port) {
|
|
|
3641
3384
|
// The subscription hub rides the same HTTP server (one port, zero config):
|
|
3642
3385
|
// createApp registers the topics, the `upgrade` hook below owns the socket.
|
|
3643
3386
|
const socketHub = deps.socketHub ?? createSocketHub();
|
|
3644
|
-
const
|
|
3645
|
-
const bootProjectId = deps.bootProjectId ?? 'default';
|
|
3646
|
-
const bootAutomationStore = automationCoordinator.store(bootProjectId, deps.repoRoot);
|
|
3647
|
-
const sharedContexts = deps.contexts ?? new ProjectContexts({
|
|
3648
|
-
listProjects,
|
|
3649
|
-
semaphore: deps.semaphore,
|
|
3650
|
-
automationStore: (projectId, root) => automationCoordinator.store(projectId, root),
|
|
3651
|
-
});
|
|
3652
|
-
let rescheduleAutomations = () => { };
|
|
3653
|
-
const app = createApp({
|
|
3654
|
-
...deps,
|
|
3655
|
-
contexts: sharedContexts,
|
|
3656
|
-
automationStore: bootAutomationStore,
|
|
3657
|
-
workspaceEvents,
|
|
3658
|
-
skillsUpdate,
|
|
3659
|
-
socketHub,
|
|
3660
|
-
automationsChanged: () => rescheduleAutomations(),
|
|
3661
|
-
});
|
|
3387
|
+
const app = createApp({ ...deps, workspaceEvents, skillsUpdate, socketHub });
|
|
3662
3388
|
// SECURITY: default to loopback. This server executes agents locally and its endpoints are
|
|
3663
3389
|
// same-origin-trusted (only /api/health is CORS-open); binding to a non-loopback host would
|
|
3664
3390
|
// expose an agent-executing box to the network. `bindHost` exists only for a deliberate
|
|
@@ -3670,60 +3396,17 @@ export function startServer(deps, port) {
|
|
|
3670
3396
|
hostname: deps.bindHost ?? '127.0.0.1',
|
|
3671
3397
|
});
|
|
3672
3398
|
const coordinator = new SkillsUpdateCoordinator(skillsUpdate, async () => effectiveSkillsAutoUpdate(await loadWorkspaceConfig()));
|
|
3673
|
-
const automationProjects = new Map();
|
|
3674
|
-
const automationScheduler = new WorkspaceAutomationScheduler({
|
|
3675
|
-
coordinator: automationCoordinator,
|
|
3676
|
-
handle: (projectId, store) => {
|
|
3677
|
-
const project = automationProjects.get(projectId);
|
|
3678
|
-
if (!project)
|
|
3679
|
-
return undefined;
|
|
3680
|
-
return {
|
|
3681
|
-
projectId,
|
|
3682
|
-
owner: project.owner,
|
|
3683
|
-
repo: project.repo,
|
|
3684
|
-
store,
|
|
3685
|
-
poller: new GithubPoller(),
|
|
3686
|
-
onChange: (automationId, revision) => workspaceEvents.emit('automation-change', { project: projectId, automationId, revision }),
|
|
3687
|
-
launch: async (definition, candidate, receiptId) => {
|
|
3688
|
-
const bootId = deps.bootProjectId ?? 'default';
|
|
3689
|
-
const context = projectId === bootId
|
|
3690
|
-
? { root: deps.repoRoot, manager: deps.manager, store: deps.store }
|
|
3691
|
-
: await sharedContexts.context(projectId);
|
|
3692
|
-
return launchAutomationRun({
|
|
3693
|
-
root: context.root,
|
|
3694
|
-
manager: context.manager,
|
|
3695
|
-
store: context.store,
|
|
3696
|
-
definition,
|
|
3697
|
-
candidate,
|
|
3698
|
-
receiptId,
|
|
3699
|
-
});
|
|
3700
|
-
},
|
|
3701
|
-
};
|
|
3702
|
-
},
|
|
3703
|
-
});
|
|
3704
|
-
rescheduleAutomations = () => { void automationScheduler.reschedule(); };
|
|
3705
3399
|
const unsubscribe = workspaceEvents.on((event, data) => {
|
|
3706
3400
|
if (event === 'project-added') {
|
|
3707
3401
|
const project = data.project;
|
|
3708
3402
|
if (project && typeof project.id === 'string' && typeof project.root === 'string' && project.status !== 'missing') {
|
|
3709
3403
|
coordinator.add(project.id, project.root);
|
|
3710
|
-
void getRepoInfo(project.root).then((info) => {
|
|
3711
|
-
const parsed = parseRemote(info?.remote ?? '');
|
|
3712
|
-
if (parsed?.host === 'github.com')
|
|
3713
|
-
automationProjects.set(project.id, { root: project.root, owner: parsed.owner, repo: parsed.repo });
|
|
3714
|
-
return automationScheduler.reschedule();
|
|
3715
|
-
});
|
|
3716
3404
|
}
|
|
3717
3405
|
}
|
|
3718
3406
|
else if (event === 'project-removed') {
|
|
3719
3407
|
const id = data.id;
|
|
3720
3408
|
if (typeof id === 'string')
|
|
3721
3409
|
coordinator.remove(id);
|
|
3722
|
-
if (typeof id === 'string') {
|
|
3723
|
-
automationCoordinator.remove(id);
|
|
3724
|
-
automationProjects.delete(id);
|
|
3725
|
-
void automationScheduler.reschedule();
|
|
3726
|
-
}
|
|
3727
3410
|
}
|
|
3728
3411
|
});
|
|
3729
3412
|
server.once('listening', () => {
|
|
@@ -3731,20 +3414,9 @@ export function startServer(deps, port) {
|
|
|
3731
3414
|
const all = projects.some((project) => project.root === deps.repoRoot)
|
|
3732
3415
|
? projects : [{ id: deps.bootProjectId ?? 'default', root: deps.repoRoot, status: 'ok' }, ...projects];
|
|
3733
3416
|
coordinator.start(all);
|
|
3734
|
-
void Promise.all(all.map(async (project) => {
|
|
3735
|
-
const parsed = parseRemote((await getRepoInfo(project.root))?.remote ?? '');
|
|
3736
|
-
if (parsed?.host === 'github.com')
|
|
3737
|
-
automationProjects.set(project.id, { root: project.root, owner: parsed.owner, repo: parsed.repo });
|
|
3738
|
-
const automationStore = automationCoordinator.store(project.id, project.root);
|
|
3739
|
-
const runStore = project.id === (deps.bootProjectId ?? 'default')
|
|
3740
|
-
? deps.store
|
|
3741
|
-
: sharedContexts.peek(project.id)?.store;
|
|
3742
|
-
if (automationStore && runStore)
|
|
3743
|
-
reconcileAutomationReceipts(automationStore, runStore);
|
|
3744
|
-
})).then(() => automationScheduler.start()).catch(() => undefined);
|
|
3745
3417
|
}).catch(() => undefined);
|
|
3746
3418
|
});
|
|
3747
|
-
server.once('close', () => { unsubscribe(); coordinator.stop();
|
|
3419
|
+
server.once('close', () => { unsubscribe(); coordinator.stop(); });
|
|
3748
3420
|
socketHub.attach(server, (req) => verifyWsUpgrade(req, deps.bindHost));
|
|
3749
3421
|
return server;
|
|
3750
3422
|
}
|