@open-mercato/cezar 0.9.0-pr613.619 → 0.9.0-pr613.643
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 +1 -0
- package/dist/server/server.d.ts +7 -0
- package/dist/server/server.js +96 -4
- package/dist/server/server.js.map +1 -1
- package/dist/skills-update.d.ts +88 -0
- package/dist/skills-update.js +392 -0
- package/dist/skills-update.js.map +1 -0
- package/dist/workspace/config.d.ts +11 -0
- package/dist/workspace/config.js +13 -0
- package/dist/workspace/config.js.map +1 -1
- package/package.json +1 -1
- package/web/dist/assets/{centered-state-NF79blq7.js → centered-state-Cbkbpf8N.js} +11 -11
- package/web/dist/assets/circle-check-ClxqLJCI.js +1 -0
- package/web/dist/assets/{commit-list-CL8vSWGw.js → commit-list-BMcVHyAW.js} +1 -1
- package/web/dist/assets/{compare-variants-CSlZhNkL.js → compare-variants-DkoNyxFm.js} +1 -1
- package/web/dist/assets/{diff-view-CQHHFGFs.js → diff-view-C2upXV6G.js} +1 -1
- package/web/dist/assets/{dist-COA_NSud.js → dist-DwHezjuX.js} +1 -1
- package/web/dist/assets/{git-toolbar-DYcTQXMw.js → git-toolbar-CJ7N7_oE.js} +1 -1
- package/web/dist/assets/github-B6NhXGVY.js +1 -0
- package/web/dist/assets/index-C6QS6Ehw.js +30 -0
- package/web/dist/assets/index-h7UyZlMB.css +2 -0
- package/web/dist/assets/{project-router-CBzGUqvS.js → project-router-EIJm5QPB.js} +1 -1
- package/web/dist/assets/repo-git-DNycvKWr.js +1 -0
- package/web/dist/assets/{run-diff-CkBfZAmd.js → run-diff-CiXR4rs0.js} +2 -2
- package/web/dist/assets/run-header-BB71WawC.js +1 -0
- package/web/dist/assets/skills-CjeBe7jq.js +1 -0
- package/web/dist/assets/{tab-link-DTQ9FBZY.js → tab-link-ByGxILtf.js} +1 -1
- package/web/dist/assets/{task-changes-BJ-Z_6E-.js → task-changes-DubwrEv1.js} +1 -1
- package/web/dist/assets/{task-commits-BDt6B_rd.js → task-commits-BlJ0Vp2F.js} +1 -1
- package/web/dist/assets/{task-files-Cf8tq1Bm.js → task-files-CfbGcY1R.js} +2 -2
- package/web/dist/assets/task-thread-Cxk0OUb-.js +9 -0
- package/web/dist/assets/{use-desktop-CIaK6C4E.js → use-desktop-CJn6q9w5.js} +2 -2
- package/web/dist/assets/{workflows-Bwz9MmC1.js → workflows-C3LEesQu.js} +3 -3
- package/web/dist/index.html +5 -5
- package/web/dist/assets/github-Bsa5A7cy.js +0 -1
- package/web/dist/assets/index-3xCBmiOj.js +0 -30
- package/web/dist/assets/index-Mov2Rrbw.css +0 -2
- package/web/dist/assets/repo-git-CeOp4SuF.js +0 -1
- package/web/dist/assets/run-header-DynOcA_S.js +0 -1
- package/web/dist/assets/skills-DeNJs1CQ.js +0 -1
- package/web/dist/assets/task-thread-C91GpKlJ.js +0 -9
package/README.md
CHANGED
|
@@ -434,6 +434,7 @@ Useful environment variables:
|
|
|
434
434
|
| `CEZ_OPENCODE_BIN=/path/to/opencode` | Override which `opencode` binary is used. |
|
|
435
435
|
| `CEZ_BROWSE_ROOT=~/` | Default root for **Add project → Open local folder…**. The picker cannot navigate above it; a saved workspace value overrides the environment default and must name an existing folder. |
|
|
436
436
|
| `CEZ_PROJECTS_DIR=~/cezar/projects` | Default destination for **Clone from GitHub**. Saved workspace settings override it, and missing directories are created recursively. |
|
|
437
|
+
| `CEZ_SKILLS_AUTO_UPDATE=0` | Disable automatic checks and updates for upstream-CLI-tracked Open Mercato skill installations. On by default; a saved global Skills setting overrides this environment default. Checks are delayed, bounded, cached, and non-blocking. |
|
|
437
438
|
| `GITHUB_TOKEN` | Fallback for GitHub reads/PRs when `gh` isn't authenticated. |
|
|
438
439
|
| `CEZ_ENV_PASSTHROUGH=A,B` | Forward these extra host env vars to spawned agents. By default agents get a least-privilege env (safe shell/toolchain vars + the backend's own auth + `GITHUB_TOKEN` + `CEZ_*`), not your full environment — use this to add a var an agent needs. |
|
|
439
440
|
| `CEZ_AGENT_ENV_FULL=1` | Escape hatch: give spawned agents the full host environment (pre-hardening behavior). Off by default; only set it if you understand that this hands every host secret to the agent process. |
|
package/dist/server/server.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
2
|
import { type ServerType } from '@hono/node-server';
|
|
3
3
|
import { RunnerModelCatalog } from '../core/runner-model-catalog.js';
|
|
4
|
+
import { SkillsUpdateService } from '../skills-update.js';
|
|
4
5
|
import type { RunRecord, RunStatus, RunStore } from '../runs/store.js';
|
|
5
6
|
import type { RunManager } from '../workflows/run.js';
|
|
6
7
|
import { type ProjectListEntry } from '../workspace/projects.js';
|
|
@@ -51,6 +52,9 @@ export interface ServerDeps {
|
|
|
51
52
|
cloneRunner?: CloneRunner;
|
|
52
53
|
/** Host-wide model discovery service. Tests inject a deterministic adapter. */
|
|
53
54
|
modelCatalog?: RunnerModelCatalog;
|
|
55
|
+
/** Process-wide Open Mercato skills update detector. Injected in tests and
|
|
56
|
+
* shared by every workspace route/project; createApp owns the default. */
|
|
57
|
+
skillsUpdate?: SkillsUpdateService;
|
|
54
58
|
}
|
|
55
59
|
/** One row of the mirrored project-route table. */
|
|
56
60
|
export interface ProjectRouteInfo {
|
|
@@ -122,6 +126,9 @@ export interface WorkspaceConfigResponse {
|
|
|
122
126
|
browseRoot: string;
|
|
123
127
|
/** Checkout root for GUI-cloned projects — stored as written (`~` kept). */
|
|
124
128
|
projectsDir: string;
|
|
129
|
+
/** Stored override; null means inherit CEZ_SKILLS_AUTO_UPDATE, then true. */
|
|
130
|
+
skillsAutoUpdate: boolean | null;
|
|
131
|
+
effectiveSkillsAutoUpdate: boolean;
|
|
125
132
|
resources: {
|
|
126
133
|
maxParallel: number;
|
|
127
134
|
memoryLimitMb: number | null;
|
package/dist/server/server.js
CHANGED
|
@@ -16,6 +16,7 @@ import { WORKFLOWS_DIR, loadWorkflows } from '../workflows/load.js';
|
|
|
16
16
|
import { QUICK_TASK_WORKFLOW, normalizeWorkflowDoc, skillStackOf, skillsToSteps, stepsIssue, workflowFileSchema, workflowStepSchema, } from '../workflows/types.js';
|
|
17
17
|
import { planChain, slugify } from '../planner.js';
|
|
18
18
|
import { discoverSkills } from '../skills.js';
|
|
19
|
+
import { SkillsUpdateConflictError, SkillsUpdateCoordinator, SkillsUpdateService } from '../skills-update.js';
|
|
19
20
|
import { getTeamSkillsCached, refreshTeamSkills, waitForTeamSkills } from '../skills-remote.js';
|
|
20
21
|
import { appendHandoffHeartbeat, handoffProgressExcerpt, readHandoff } from '../handoff.js';
|
|
21
22
|
import { markStarted, onTodosChanged, readTodos, removeTodo, todoTaskText } from '../todos.js';
|
|
@@ -28,7 +29,7 @@ import { gatedSkillsRepos, loadConfig, resolveWorktreeRetention } from '../confi
|
|
|
28
29
|
import { findConfigFile } from '../agent-config/catalog.js';
|
|
29
30
|
import { readConfigFile, writeConfigFile } from '../agent-config/files.js';
|
|
30
31
|
import { listAgentConfig } from '../agent-config/service.js';
|
|
31
|
-
import { PROJECT_ID_RE, defaultWorkspaceConfig, loadWorkspaceConfig, mergeWriteWorkspaceConfig, } from '../workspace/config.js';
|
|
32
|
+
import { PROJECT_ID_RE, defaultWorkspaceConfig, effectiveSkillsAutoUpdate, loadWorkspaceConfig, mergeWriteWorkspaceConfig, } from '../workspace/config.js';
|
|
32
33
|
import { allocateProjectSlug, listProjects, probeProjectStatus, registerProject, removeProject, shouldRegisterProject, } from '../workspace/projects.js';
|
|
33
34
|
import { mergeWriteWorkspaceUiState, readWorkspaceUiState } from '../workspace/ui-state.js';
|
|
34
35
|
import { checkoutRepo } from './checkout.js';
|
|
@@ -477,6 +478,7 @@ export function createApp(deps) {
|
|
|
477
478
|
const modelCatalog = deps.modelCatalog ?? new RunnerModelCatalog({
|
|
478
479
|
adapters: { codex: { discover: () => discoverCodexModels({ cwd: bootRoot }) } },
|
|
479
480
|
});
|
|
481
|
+
const skillsUpdate = deps.skillsUpdate ?? new SkillsUpdateService();
|
|
480
482
|
// ---- workspace boot-project identity (multi-project spec) ----------------
|
|
481
483
|
// The boot flow (`initWorkspace` in src/index.ts) registers the boot repo
|
|
482
484
|
// and plumbs its registry id in via `deps.bootProjectId`. Legacy callers and
|
|
@@ -1053,6 +1055,64 @@ export function createApp(deps) {
|
|
|
1053
1055
|
const body = { removed: true, id };
|
|
1054
1056
|
return c.json(body);
|
|
1055
1057
|
});
|
|
1058
|
+
// Workspace-level by design: update state spans project and global installs,
|
|
1059
|
+
// but the selected registered project supplies the safe, server-owned cwd.
|
|
1060
|
+
const skillsUpdateInputSchema = z.object({ projectId: projectIdSchema }).strict();
|
|
1061
|
+
const resolveSkillsUpdateRoot = async (raw) => {
|
|
1062
|
+
if (!projectIdSchema.safeParse(raw).success)
|
|
1063
|
+
return { status: 404, error: `unknown project: ${raw}` };
|
|
1064
|
+
const bootId = await resolveBootProject();
|
|
1065
|
+
if (raw === 'default' || raw === bootId)
|
|
1066
|
+
return { root: bootRoot };
|
|
1067
|
+
const project = (await loadWorkspaceConfig()).projects.find((entry) => entry.id === raw);
|
|
1068
|
+
if (!project)
|
|
1069
|
+
return { status: 404, error: `unknown project: ${raw}` };
|
|
1070
|
+
if ((await probeProjectStatus(project.root)).status === 'missing') {
|
|
1071
|
+
return { status: 409, error: `project folder not found: ${raw}` };
|
|
1072
|
+
}
|
|
1073
|
+
return { root: project.root };
|
|
1074
|
+
};
|
|
1075
|
+
const skillsUpdateResponse = async (state) => {
|
|
1076
|
+
const config = await loadWorkspaceConfig();
|
|
1077
|
+
return { ...state, autoUpdateEnabled: effectiveSkillsAutoUpdate(config), inherited: config.skillsAutoUpdate === undefined };
|
|
1078
|
+
};
|
|
1079
|
+
app.get('/api/workspace/skills-update', async (c) => {
|
|
1080
|
+
const parsed = skillsUpdateInputSchema.safeParse({ projectId: c.req.query('projectId') });
|
|
1081
|
+
if (!parsed.success)
|
|
1082
|
+
return c.json({ error: 'projectId is required' }, 400);
|
|
1083
|
+
const resolved = await resolveSkillsUpdateRoot(parsed.data.projectId);
|
|
1084
|
+
if ('error' in resolved)
|
|
1085
|
+
return c.json({ error: resolved.error }, resolved.status);
|
|
1086
|
+
const state = skillsUpdate.snapshot(resolved.root);
|
|
1087
|
+
void skillsUpdate.check(resolved.root).catch(() => { });
|
|
1088
|
+
return c.json(await skillsUpdateResponse(state));
|
|
1089
|
+
});
|
|
1090
|
+
app.post('/api/workspace/skills-update/check', async (c) => {
|
|
1091
|
+
const parsed = skillsUpdateInputSchema.safeParse(await c.req.json().catch(() => null));
|
|
1092
|
+
if (!parsed.success)
|
|
1093
|
+
return c.json({ error: 'body must contain only projectId' }, 400);
|
|
1094
|
+
const resolved = await resolveSkillsUpdateRoot(parsed.data.projectId);
|
|
1095
|
+
if ('error' in resolved)
|
|
1096
|
+
return c.json({ error: resolved.error }, resolved.status);
|
|
1097
|
+
return c.json(await skillsUpdateResponse(await skillsUpdate.check(resolved.root, true)));
|
|
1098
|
+
});
|
|
1099
|
+
app.post('/api/workspace/skills-update/apply', async (c) => {
|
|
1100
|
+
const parsed = skillsUpdateInputSchema.safeParse(await c.req.json().catch(() => null));
|
|
1101
|
+
if (!parsed.success)
|
|
1102
|
+
return c.json({ error: 'body must contain only projectId' }, 400);
|
|
1103
|
+
const resolved = await resolveSkillsUpdateRoot(parsed.data.projectId);
|
|
1104
|
+
if ('error' in resolved)
|
|
1105
|
+
return c.json({ error: resolved.error }, resolved.status);
|
|
1106
|
+
try {
|
|
1107
|
+
return c.json(await skillsUpdateResponse(await skillsUpdate.update(resolved.root, true)));
|
|
1108
|
+
}
|
|
1109
|
+
catch (error) {
|
|
1110
|
+
if (error instanceof SkillsUpdateConflictError) {
|
|
1111
|
+
return c.json({ error: 'another skills update operation is running', state: await skillsUpdateResponse(skillsUpdate.snapshot(resolved.root)) }, 409);
|
|
1112
|
+
}
|
|
1113
|
+
throw error;
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
1056
1116
|
// ---- GUI clone (multi-project spec, step 4.3) ----------------------------
|
|
1057
1117
|
// "Add project → Clone from GitHub": clone into the checkout root, then
|
|
1058
1118
|
// register the result through `registerFolder` above (same guards, same
|
|
@@ -1110,6 +1170,8 @@ export function createApp(deps) {
|
|
|
1110
1170
|
const workspaceConfigBody = (config) => ({
|
|
1111
1171
|
browseRoot: config.browseRoot,
|
|
1112
1172
|
projectsDir: config.projectsDir,
|
|
1173
|
+
skillsAutoUpdate: config.skillsAutoUpdate ?? null,
|
|
1174
|
+
effectiveSkillsAutoUpdate: effectiveSkillsAutoUpdate(config),
|
|
1113
1175
|
resources: {
|
|
1114
1176
|
maxParallel: config.resources.maxParallel,
|
|
1115
1177
|
memoryLimitMb: config.resources.memoryLimitMb,
|
|
@@ -1123,6 +1185,7 @@ export function createApp(deps) {
|
|
|
1123
1185
|
const workspaceConfigUpdateSchema = z.object({
|
|
1124
1186
|
browseRoot: z.string().trim().min(1).max(4096).optional(),
|
|
1125
1187
|
projectsDir: z.string().trim().min(1).max(4096).optional(),
|
|
1188
|
+
skillsAutoUpdate: z.boolean().nullable().optional(),
|
|
1126
1189
|
resources: z
|
|
1127
1190
|
.object({
|
|
1128
1191
|
maxParallel: z.number().int().min(1).max(16).optional(),
|
|
@@ -1136,7 +1199,7 @@ export function createApp(deps) {
|
|
|
1136
1199
|
if (!parsed.success) {
|
|
1137
1200
|
return c.json({ error: parsed.error.issues.map((i) => i.message).join('; ') }, 400);
|
|
1138
1201
|
}
|
|
1139
|
-
const { browseRoot, projectsDir, resources } = parsed.data;
|
|
1202
|
+
const { browseRoot, projectsDir, skillsAutoUpdate, resources } = parsed.data;
|
|
1140
1203
|
for (const [configuredRoot, create] of [
|
|
1141
1204
|
[browseRoot, false],
|
|
1142
1205
|
[projectsDir, true],
|
|
@@ -1175,6 +1238,10 @@ export function createApp(deps) {
|
|
|
1175
1238
|
config.browseRoot = browseRoot;
|
|
1176
1239
|
if (projectsDir !== undefined)
|
|
1177
1240
|
config.projectsDir = projectsDir;
|
|
1241
|
+
if (skillsAutoUpdate === null)
|
|
1242
|
+
delete config.skillsAutoUpdate;
|
|
1243
|
+
else if (skillsAutoUpdate !== undefined)
|
|
1244
|
+
config.skillsAutoUpdate = skillsAutoUpdate;
|
|
1178
1245
|
if (resources?.maxParallel !== undefined)
|
|
1179
1246
|
config.resources.maxParallel = resources.maxParallel;
|
|
1180
1247
|
if (resources?.memoryLimitMb !== undefined)
|
|
@@ -2836,17 +2903,42 @@ export function createApp(deps) {
|
|
|
2836
2903
|
return app;
|
|
2837
2904
|
}
|
|
2838
2905
|
export function startServer(deps, port) {
|
|
2839
|
-
const
|
|
2906
|
+
const workspaceEvents = deps.workspaceEvents ?? new WorkspaceEventBus();
|
|
2907
|
+
const skillsUpdate = deps.skillsUpdate ?? new SkillsUpdateService({ invalidateCatalog: refreshTeamSkills });
|
|
2908
|
+
const app = createApp({ ...deps, workspaceEvents, skillsUpdate });
|
|
2840
2909
|
// SECURITY: default to loopback. This server executes agents locally and its endpoints are
|
|
2841
2910
|
// same-origin-trusted (only /api/health is CORS-open); binding to a non-loopback host would
|
|
2842
2911
|
// expose an agent-executing box to the network. `bindHost` exists only for a deliberate
|
|
2843
2912
|
// hosted/VPS deployment (which also flips CEZ_REMOTE to gate the local-handoff endpoints) —
|
|
2844
2913
|
// src/index.ts never passes it, so the loopback guarantee holds for the normal CLI.
|
|
2845
|
-
|
|
2914
|
+
const server = serve({
|
|
2846
2915
|
fetch: app.fetch,
|
|
2847
2916
|
port,
|
|
2848
2917
|
hostname: deps.bindHost ?? '127.0.0.1',
|
|
2849
2918
|
});
|
|
2919
|
+
const coordinator = new SkillsUpdateCoordinator(skillsUpdate, async () => effectiveSkillsAutoUpdate(await loadWorkspaceConfig()));
|
|
2920
|
+
const unsubscribe = workspaceEvents.on((event, data) => {
|
|
2921
|
+
if (event === 'project-added') {
|
|
2922
|
+
const project = data.project;
|
|
2923
|
+
if (project && typeof project.id === 'string' && typeof project.root === 'string' && project.status !== 'missing') {
|
|
2924
|
+
coordinator.add(project.id, project.root);
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
else if (event === 'project-removed') {
|
|
2928
|
+
const id = data.id;
|
|
2929
|
+
if (typeof id === 'string')
|
|
2930
|
+
coordinator.remove(id);
|
|
2931
|
+
}
|
|
2932
|
+
});
|
|
2933
|
+
server.once('listening', () => {
|
|
2934
|
+
void listProjects().then((projects) => {
|
|
2935
|
+
const all = projects.some((project) => project.root === deps.repoRoot)
|
|
2936
|
+
? projects : [{ id: deps.bootProjectId ?? 'default', root: deps.repoRoot, status: 'ok' }, ...projects];
|
|
2937
|
+
coordinator.start(all);
|
|
2938
|
+
}).catch(() => undefined);
|
|
2939
|
+
});
|
|
2940
|
+
server.once('close', () => { unsubscribe(); coordinator.stop(); });
|
|
2941
|
+
return server;
|
|
2850
2942
|
}
|
|
2851
2943
|
/** The bare hostname of a `Host` header — see `normalizeHostname`. `''` when the
|
|
2852
2944
|
* header is absent, which no allowlist matches. */
|