@qodeca/xezar 0.11.2 → 0.13.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/README.md +69 -18
- package/dist/agent-config/catalog.d.ts +10 -0
- package/dist/agent-config/catalog.js.map +1 -1
- package/dist/automations/coordinator.js +8 -0
- package/dist/automations/coordinator.js.map +1 -1
- package/dist/automations/types.d.ts +2 -0
- package/dist/contract/automations.d.ts +8 -0
- package/dist/contract/events.test.d.ts +1 -0
- package/dist/contract/index.js +87 -3
- package/dist/contract/runs.d.ts +25 -0
- package/dist/contract/runs.test.d.ts +1 -0
- package/dist/contract/skills.d.ts +1 -0
- package/dist/contract/workflows.d.ts +6 -0
- package/dist/contract/workspace.d.ts +37 -4
- package/dist/core/agent-env.js +8 -1
- package/dist/core/agent-env.js.map +1 -1
- package/dist/core/agent-runner.d.ts +13 -0
- package/dist/core/agent-runner.js +22 -0
- package/dist/core/agent-runner.js.map +1 -1
- package/dist/core/backend-detect.js +11 -1
- package/dist/core/backend-detect.js.map +1 -1
- package/dist/core/claude-cli-runner.js +8 -2
- package/dist/core/claude-cli-runner.js.map +1 -1
- package/dist/core/codex-app-server-runner.js +6 -2
- package/dist/core/codex-app-server-runner.js.map +1 -1
- package/dist/core/opencode-server-runner.d.ts +12 -0
- package/dist/core/opencode-server-runner.js +326 -17
- package/dist/core/opencode-server-runner.js.map +1 -1
- package/dist/core/pi-model-catalog.d.ts +52 -0
- package/dist/core/pi-model-catalog.js +135 -0
- package/dist/core/pi-model-catalog.js.map +1 -0
- package/dist/core/pi-runner.d.ts +3 -0
- package/dist/core/pi-runner.js +139 -12
- package/dist/core/pi-runner.js.map +1 -1
- package/dist/core/pi-ui-mapper.js +39 -3
- package/dist/core/pi-ui-mapper.js.map +1 -1
- package/dist/core/runner-model-catalog.js +11 -2
- package/dist/core/runner-model-catalog.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/paths.d.ts +7 -0
- package/dist/paths.js +8 -0
- package/dist/paths.js.map +1 -1
- package/dist/runs/event-corrections.d.ts +12 -0
- package/dist/runs/event-corrections.js +141 -0
- package/dist/runs/event-corrections.js.map +1 -0
- package/dist/runs/event-history.d.ts +5 -1
- package/dist/runs/event-history.js +66 -5
- package/dist/runs/event-history.js.map +1 -1
- package/dist/runs/project-writer.d.ts +11 -0
- package/dist/runs/project-writer.js +115 -0
- package/dist/runs/project-writer.js.map +1 -0
- package/dist/runs/store.d.ts +2 -0
- package/dist/runs/store.js +13 -2
- package/dist/runs/store.js.map +1 -1
- package/dist/runs/task-markers.d.ts +4 -0
- package/dist/runs/task-markers.js +63 -5
- package/dist/runs/task-markers.js.map +1 -1
- package/dist/server/capabilities.d.ts +7 -5
- package/dist/server/capabilities.js +11 -7
- package/dist/server/capabilities.js.map +1 -1
- package/dist/server/project-context.js +2 -0
- package/dist/server/project-context.js.map +1 -1
- package/dist/server/server.d.ts +199 -47
- package/dist/server/server.js +195 -11
- package/dist/server/server.js.map +1 -1
- package/dist/workflows/run.d.ts +67 -2
- package/dist/workflows/run.js +190 -44
- package/dist/workflows/run.js.map +1 -1
- package/dist/workflows/types.d.ts +41 -0
- package/dist/workflows/types.js +72 -0
- package/dist/workflows/types.js.map +1 -1
- package/dist/workspace/config.d.ts +42 -0
- package/dist/workspace/config.js +132 -3
- package/dist/workspace/config.js.map +1 -1
- package/dist/workspace/semaphore.d.ts +63 -3
- package/dist/workspace/semaphore.js +76 -3
- package/dist/workspace/semaphore.js.map +1 -1
- package/package.json +3 -3
- package/web/dist/assets/{alert-dialog-CGskMvgc.js → alert-dialog-BVsVcbFy.js} +1 -1
- package/web/dist/assets/arrow-down-CYgAlZtW.js +1 -0
- package/web/dist/assets/arrow-left-BOqE0a3k.js +1 -0
- package/web/dist/assets/{centered-state-B_V_Ai2h.js → centered-state-Djgly6Ni.js} +1 -1
- package/web/dist/assets/chevron-right-Cc9BWnbN.js +1 -0
- package/web/dist/assets/{collapsible-B_8uYwHb.js → collapsible-DZ4D-DHG.js} +1 -1
- package/web/dist/assets/{commit-list-C4Dv6vUL.js → commit-list-B-z1JL9G.js} +1 -1
- package/web/dist/assets/{compare-variants-et0mplGi.js → compare-variants-CdS8r8E2.js} +1 -1
- package/web/dist/assets/{diff-CbozNUJq.js → diff-BKew9rQX.js} +2 -2
- package/web/dist/assets/{diff-stat-btAmKFJr.js → diff-stat-D0mc5V6Z.js} +1 -1
- package/web/dist/assets/{diff-view-BpFTcG3R.js → diff-view-xpIcuLIC.js} +1 -1
- package/web/dist/assets/{dropdown-menu-Z80KMInL.js → dropdown-menu-CEQziH7X.js} +1 -1
- package/web/dist/assets/editable-title-BCX3T2BY.js +1 -0
- package/web/dist/assets/{ellipsis-vertical-Bmi2u2TG.js → ellipsis-vertical-ByVdDc4z.js} +1 -1
- package/web/dist/assets/{file-DKoYWRsv.js → file-xBSB5hXO.js} +1 -1
- package/web/dist/assets/{folder-u01HScRT.js → folder-CldgbZ5A.js} +1 -1
- package/web/dist/assets/{git-toolbar-Q2kk2F7v.js → git-toolbar-BYlnf4qf.js} +1 -1
- package/web/dist/assets/github-CqGK62Lw.js +1 -0
- package/web/dist/assets/{image-preview-BR3iIIhf.js → image-preview-Cv5mZr0n.js} +1 -1
- package/web/dist/assets/index-3nxVmdn5.css +2 -0
- package/web/dist/assets/index-qX7n11FX.js +9 -0
- package/web/dist/assets/{markdown-5FuCZDA4.js → markdown-CgEEE2ND.js} +1 -1
- package/web/dist/assets/{new-task-form-BydHBbef.js → new-task-form-1r2bgsdD.js} +1 -1
- package/web/dist/assets/{pill-vNzaaYxj.js → pill-q-x22_ej.js} +1 -1
- package/web/dist/assets/{project-router-BbCzrHKs.js → project-router-VDN4jQM8.js} +1 -1
- package/web/dist/assets/{prompt-templates-B7Fs2b1S.js → prompt-templates-BmZtCe-L.js} +1 -1
- package/web/dist/assets/{refresh-cw-DGliez3E.js → refresh-cw-DcNCj2nu.js} +1 -1
- package/web/dist/assets/{repo-git-h1RFfly3.js → repo-git-C2On8SlI.js} +1 -1
- package/web/dist/assets/{run-diff-Bq9vkSNK.js → run-diff-vguEMJzh.js} +1 -1
- package/web/dist/assets/{run-header-Cd8mSKBG.js → run-header-DM7MiKj9.js} +1 -1
- package/web/dist/assets/{search-x-69n_672Y.js → search-x-QmhaAbg5.js} +1 -1
- package/web/dist/assets/{skill-empty-hint-CL0g7WBL.js → skill-empty-hint-BrzUwByZ.js} +1 -1
- package/web/dist/assets/{skills-BqAzyGS3.js → skills-Bj-uG_Hz.js} +1 -1
- package/web/dist/assets/{sparkles-B8agSxBZ.js → sparkles-CP8QDOgX.js} +1 -1
- package/web/dist/assets/{square-terminal-Cf4Og6ct.js → square-terminal-CGypshSD.js} +1 -1
- package/web/dist/assets/{tab-link-Cp2-CJS8.js → tab-link-D4lFzzI1.js} +1 -1
- package/web/dist/assets/{task-changes-DwXPFXqG.js → task-changes-6aopqAmV.js} +1 -1
- package/web/dist/assets/{task-commits-Bessua-c.js → task-commits-u-dwaCyw.js} +1 -1
- package/web/dist/assets/{task-files-DxFIhRPP.js → task-files-IMOZnIUK.js} +1 -1
- package/web/dist/assets/task-thread-CbMPmrWq.js +9 -0
- package/web/dist/assets/{textarea--cpYshee.js → textarea-CyBCyKIk.js} +1 -1
- package/web/dist/assets/{thread-loading-ZhJref0J.js → thread-loading-Cuq2jchC.js} +1 -1
- package/web/dist/assets/{trash-2--ZtrzUx3.js → trash-2-B6Dp3ZNs.js} +1 -1
- package/web/dist/assets/{triangle-alert-D5tJwRD2.js → triangle-alert-xBya8FUp.js} +1 -1
- package/web/dist/assets/{upload-SnwVxKOA.js → upload-D6MXEB9p.js} +1 -1
- package/web/dist/assets/{use-desktop-Sr1QlxhT.js → use-desktop-fLDKEJoB.js} +1 -1
- package/web/dist/assets/{use-submit-shortcut-JY8M_MPL.js → use-submit-shortcut-JTpNzU-u.js} +1 -1
- package/web/dist/assets/{utils-Bb8ohcr3.js → utils--z9hRtl3.js} +1 -1
- package/web/dist/assets/{workflows-9HC240Wx.js → workflows-BY3KTkFw.js} +3 -3
- package/web/dist/assets/{zoomable-image-0G3IBXWC.js → zoomable-image-GTYwLtkS.js} +1 -1
- package/web/dist/index.html +23 -23
- package/web/dist/assets/arrow-down-G8adPhbu.js +0 -1
- package/web/dist/assets/arrow-left-BJsIW6A0.js +0 -1
- package/web/dist/assets/chevron-right-CFzn-von.js +0 -1
- package/web/dist/assets/editable-title-wS5NWwob.js +0 -1
- package/web/dist/assets/github-DjEYCxwt.js +0 -1
- package/web/dist/assets/index-BcI5hug3.css +0 -2
- package/web/dist/assets/index-Cz4GSAqV.js +0 -7
- package/web/dist/assets/task-thread-Dhkupf1-.js +0 -9
package/dist/server/server.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { EventCorrectionError } from '../runs/event-corrections.js';
|
|
2
|
+
import { validateLegacyHistoryResume } from '../runs/event-history.js';
|
|
1
3
|
import { projectDataDir } from '../project-data-paths.js';
|
|
2
4
|
import { projectKitDir } from '../project-kit-paths.js';
|
|
3
5
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
@@ -21,13 +23,14 @@ import { z } from 'zod';
|
|
|
21
23
|
import { setWorkspaceUiStateInputSchema, } from '../contract/index.js';
|
|
22
24
|
// A contract VALUE, like `workspaceUiStateSchema` in workspace/migrations.ts — the request
|
|
23
25
|
// schema this route validates with is the same one the client compiles against.
|
|
24
|
-
import { attachmentInputSchema, modelDiscoveryRunnerSchema, openProjectInSchema, updateProjectInputSchema, } from '../contract/index.js';
|
|
26
|
+
import { attachmentInputSchema, MODEL_DISCOVERY_RUNNERS, modelDiscoveryRunnerSchema, openProjectInSchema, updateProjectInputSchema, } from '../contract/index.js';
|
|
25
27
|
import { detectEnvironment } from '../core/backend-detect.js';
|
|
26
28
|
import { RUNNER_IDS } from '../core/agent-runner.js';
|
|
27
29
|
import { AGENT_MODELS_LOCKED_ERROR, agentModelsLocked } from '../core/agent-model-policy.js';
|
|
28
30
|
import { discoverClaudeModels } from '../core/claude-model-catalog.js';
|
|
29
31
|
import { discoverCodexModels } from '../core/codex-model-catalog.js';
|
|
30
32
|
import { discoverOpencodeModels } from '../core/opencode-model-catalog.js';
|
|
33
|
+
import { discoverPiModels } from '../core/pi-model-catalog.js';
|
|
31
34
|
import { PROVIDER_IDS, ProviderAuthService, providerAuthChecksDisabled, } from '../core/provider-auth.js';
|
|
32
35
|
import { applyProviderEnablement } from '../core/provider-availability.js';
|
|
33
36
|
import { RunnerModelCatalog } from '../core/runner-model-catalog.js';
|
|
@@ -56,7 +59,7 @@ import { readAgentModelDefaults } from '../agent-config/models.js';
|
|
|
56
59
|
import { listAgentConfig } from '../agent-config/service.js';
|
|
57
60
|
import { listConfigFiles } from '../agent-config/catalog.js';
|
|
58
61
|
import { readAccountIdentity } from '../agent-config/account-identity.js';
|
|
59
|
-
import { PROJECT_ID_RE, defaultWorkspaceConfig, effectiveSkillsAutoUpdate, loadWorkspaceConfig, mergeWriteWorkspaceConfig, effectiveComposerDefault, } from '../workspace/config.js';
|
|
62
|
+
import { DEFAULT_MEMORY_LIMIT_MB, PROJECT_ID_RE, defaultWorkspaceConfig, effectiveSkillsAutoUpdate, loadWorkspaceConfig, mergeWriteWorkspaceConfig, effectiveComposerDefault, } from '../workspace/config.js';
|
|
60
63
|
import { CONTROL_CHARS_RE, DEFAULT_AGENT_ACCOUNT_ID, defaultAgentAccountStore, isAbsoluteConfigDir, loadAgentAccounts, mergeWriteAgentAccounts, } from '../workspace/agent-accounts.js';
|
|
61
64
|
import { defaultAgentProfile, listAgentProfiles, profileDirState, resolveProfileEnvForRoot, resolveStoredProfile, sameProfileDir, } from '../workspace/agent-profiles.js';
|
|
62
65
|
import { PROFILE_CAPABLE_PROVIDERS, profileEnv, supportsProfiles } from '../core/agent-profiles.js';
|
|
@@ -65,6 +68,7 @@ import { allocateProjectSlug, listProjects, normalizeProjectTags, probeProjectSt
|
|
|
65
68
|
import { mergeWriteWorkspaceUiState, readWorkspaceUiState } from '../workspace/ui-state.js';
|
|
66
69
|
import { checkoutRepo } from './checkout.js';
|
|
67
70
|
import { ProjectContextError, ProjectContexts } from './project-context.js';
|
|
71
|
+
import { ProjectWriterError } from '../runs/project-writer.js';
|
|
68
72
|
import { reviewGateEnabled } from '../runs/review-gate.js';
|
|
69
73
|
import { readUiState, uiStatePath } from '../ui-state.js';
|
|
70
74
|
import { agentHomePaths, expandTilde } from '../paths.js';
|
|
@@ -685,6 +689,9 @@ export function createApp(deps) {
|
|
|
685
689
|
claude: { discover: () => discoverClaudeModels({ cwd: bootRoot }) },
|
|
686
690
|
codex: { discover: () => discoverCodexModels({ cwd: bootRoot }) },
|
|
687
691
|
opencode: { discover: () => discoverOpencodeModels({ cwd: bootRoot }) },
|
|
692
|
+
// No `cwd`: pi's catalog is its per-user config, not a project-local answer, so this one
|
|
693
|
+
// adapter is correct for every project the workspace holds (#152).
|
|
694
|
+
pi: { discover: () => discoverPiModels() },
|
|
688
695
|
},
|
|
689
696
|
});
|
|
690
697
|
const providerAuth = deps.providerAuth ?? new ProviderAuthService();
|
|
@@ -788,7 +795,10 @@ export function createApp(deps) {
|
|
|
788
795
|
};
|
|
789
796
|
// Hosted-mode gate (spec §"Deployment modes") — read per request so
|
|
790
797
|
// XEZ_REMOTE flips take effect live (and tests can toggle it).
|
|
791
|
-
|
|
798
|
+
// The stored Inbox setting (F) wins over `XEZ_FOLLOWUPS` when the workspace config sets
|
|
799
|
+
// one. Read through the shared semaphore's cache so it is live: `refresh()` re-reads it on
|
|
800
|
+
// every workspace-config PUT, which is the one reload hook this file already fires.
|
|
801
|
+
const capabilities = () => resolveCapabilities(process.env, bindHost, deps.semaphore?.storedFollowups());
|
|
792
802
|
const singleProjectRefusal = (action) => ({ error: `single-project mode is enabled; ${action} is disabled` });
|
|
793
803
|
// Inbox live updates (spec 007). Opt-in (#471): no capability, no watcher —
|
|
794
804
|
// and since step 2.3 the per-dataDir watch is created lazily by the first
|
|
@@ -965,6 +975,8 @@ export function createApp(deps) {
|
|
|
965
975
|
c.set('project', await contexts.context(raw));
|
|
966
976
|
}
|
|
967
977
|
catch (err) {
|
|
978
|
+
if (err instanceof ProjectWriterError)
|
|
979
|
+
return c.json({ error: err.message }, 409);
|
|
968
980
|
if (err instanceof ProjectContextError) {
|
|
969
981
|
return err.reason === 'missing-root'
|
|
970
982
|
? c.json({ error: `project folder not found: ${err.projectId}` }, 409)
|
|
@@ -1247,9 +1259,13 @@ export function createApp(deps) {
|
|
|
1247
1259
|
// ---- chained family: host model catalog (workspace-level) ----
|
|
1248
1260
|
const modelsRoutes = new Hono()
|
|
1249
1261
|
// `modelDiscoveryRunnerSchema` is the contract's own list of the runners with an
|
|
1250
|
-
// authoritative host-local catalog (#794, #784), so the client compiles against exactly
|
|
1251
|
-
// this validates. A runner absent from it has no discovery path and this 400s.
|
|
1252
|
-
|
|
1262
|
+
// authoritative host-local catalog (#794, #784, #152), so the client compiles against exactly
|
|
1263
|
+
// what this validates. A runner absent from it has no discovery path and this 400s.
|
|
1264
|
+
//
|
|
1265
|
+
// The message is DERIVED from that list, never spelled out. It used to name the three runners
|
|
1266
|
+
// literally, so adding a fourth here would have left it refusing `pi` by a name it no longer
|
|
1267
|
+
// rejected — and a refusal that names the wrong set is worse than a generic one (#152).
|
|
1268
|
+
.get('/models', queryZodValidator(z.object({ runner: z.union([z.string(), z.array(z.string()).transform((v) => v[0])]).pipe(modelDiscoveryRunnerSchema) }), { message: `runner must be one of: ${MODEL_DISCOVERY_RUNNERS.join(', ')}` }), async (c) => {
|
|
1253
1269
|
const query = { data: c.req.valid('query') };
|
|
1254
1270
|
return c.json(await modelCatalog.get(query.data.runner));
|
|
1255
1271
|
});
|
|
@@ -2351,6 +2367,17 @@ export function createApp(deps) {
|
|
|
2351
2367
|
projectsDir: config.projectsDir,
|
|
2352
2368
|
skillsAutoUpdate: config.skillsAutoUpdate ?? null,
|
|
2353
2369
|
effectiveSkillsAutoUpdate: effectiveSkillsAutoUpdate(config),
|
|
2370
|
+
// Tri-state (F): `null` means no stored key, so `XEZ_FOLLOWUPS` decides. The
|
|
2371
|
+
// `effective*` twin is what the pane renders, so "(default)" and the actual state can
|
|
2372
|
+
// both be shown without the client re-implementing the precedence rule.
|
|
2373
|
+
followups: config.followups ?? null,
|
|
2374
|
+
effectiveFollowups: config.followups ?? process.env.XEZ_FOLLOWUPS === '1',
|
|
2375
|
+
agentEnvPassthrough: config.agentEnvPassthrough ?? null,
|
|
2376
|
+
effectiveAgentEnvPassthrough: config.agentEnvPassthrough ??
|
|
2377
|
+
(process.env.XEZ_ENV_PASSTHROUGH ?? '')
|
|
2378
|
+
.split(',')
|
|
2379
|
+
.map((name) => name.trim())
|
|
2380
|
+
.filter(Boolean),
|
|
2354
2381
|
composerDefaults: {
|
|
2355
2382
|
autonomous: config.composerDefaults.autonomous ?? null,
|
|
2356
2383
|
worktree: config.composerDefaults.worktree ?? null,
|
|
@@ -2366,7 +2393,11 @@ export function createApp(deps) {
|
|
|
2366
2393
|
maxMonitoringSessions: config.resources.maxMonitoringSessions,
|
|
2367
2394
|
monitoringWakeIntervalMinutes: config.resources.monitoringWakeIntervalMinutes,
|
|
2368
2395
|
autoResumeOnUsageLimit: config.resources.autoResumeOnUsageLimit,
|
|
2396
|
+
idleTimeoutMinutes: config.resources.idleTimeoutMinutes,
|
|
2369
2397
|
memoryLimitMb: config.resources.memoryLimitMb,
|
|
2398
|
+
// Reported, not stored: the settings pane names this machine's derived ceiling so the
|
|
2399
|
+
// user can see what an unset `memoryLimitMb` actually means here (B1).
|
|
2400
|
+
memoryLimitDefaultMb: DEFAULT_MEMORY_LIMIT_MB,
|
|
2370
2401
|
worktreeRetentionDefault: config.resources.worktreeRetentionDefault,
|
|
2371
2402
|
},
|
|
2372
2403
|
// SPREAD, never `runner: maybeUndefined`: hono would type the key as always-present while
|
|
@@ -2382,7 +2413,7 @@ export function createApp(deps) {
|
|
|
2382
2413
|
.get('/workspace/config', async (c) => c.json(workspaceConfigBody(await loadWorkspaceConfig())))
|
|
2383
2414
|
.put('/workspace/config', jsonZodValidator(() => workspaceConfigUpdateSchema), async (c) => {
|
|
2384
2415
|
const parsed = { data: c.req.valid('json') };
|
|
2385
|
-
const { browseRoot, projectsDir, skillsAutoUpdate, composerDefaults, resources, agentDefaults } = parsed.data;
|
|
2416
|
+
const { browseRoot, projectsDir, skillsAutoUpdate, followups, agentEnvPassthrough, composerDefaults, resources, agentDefaults, } = parsed.data;
|
|
2386
2417
|
for (const [configuredRoot, create] of [
|
|
2387
2418
|
[browseRoot, false],
|
|
2388
2419
|
[projectsDir, true],
|
|
@@ -2425,6 +2456,16 @@ export function createApp(deps) {
|
|
|
2425
2456
|
delete config.skillsAutoUpdate;
|
|
2426
2457
|
else if (skillsAutoUpdate !== undefined)
|
|
2427
2458
|
config.skillsAutoUpdate = skillsAutoUpdate;
|
|
2459
|
+
// `null` clears the key back to the env default; `false`/`[]` are real stored
|
|
2460
|
+
// choices and must survive as written (F).
|
|
2461
|
+
if (followups === null)
|
|
2462
|
+
delete config.followups;
|
|
2463
|
+
else if (followups !== undefined)
|
|
2464
|
+
config.followups = followups;
|
|
2465
|
+
if (agentEnvPassthrough === null)
|
|
2466
|
+
delete config.agentEnvPassthrough;
|
|
2467
|
+
else if (agentEnvPassthrough !== undefined)
|
|
2468
|
+
config.agentEnvPassthrough = agentEnvPassthrough;
|
|
2428
2469
|
if (composerDefaults?.autonomous === null)
|
|
2429
2470
|
delete config.composerDefaults.autonomous;
|
|
2430
2471
|
else if (composerDefaults?.autonomous !== undefined) {
|
|
@@ -2446,6 +2487,9 @@ export function createApp(deps) {
|
|
|
2446
2487
|
if (resources?.autoResumeOnUsageLimit !== undefined) {
|
|
2447
2488
|
config.resources.autoResumeOnUsageLimit = resources.autoResumeOnUsageLimit;
|
|
2448
2489
|
}
|
|
2490
|
+
if (resources?.idleTimeoutMinutes !== undefined) {
|
|
2491
|
+
config.resources.idleTimeoutMinutes = resources.idleTimeoutMinutes;
|
|
2492
|
+
}
|
|
2449
2493
|
if (resources?.memoryLimitMb !== undefined)
|
|
2450
2494
|
config.resources.memoryLimitMb = resources.memoryLimitMb;
|
|
2451
2495
|
if (resources?.worktreeRetentionDefault !== undefined) {
|
|
@@ -2480,8 +2524,11 @@ export function createApp(deps) {
|
|
|
2480
2524
|
}
|
|
2481
2525
|
// A resource change takes effect WITHOUT a restart: refresh the shared
|
|
2482
2526
|
// semaphore's in-memory snapshot and pump every manager (step 2.5's hook).
|
|
2483
|
-
|
|
2527
|
+
// `followups` and `agentEnvPassthrough` are cached by the same semaphore snapshot
|
|
2528
|
+
// (F), so they refresh through the same hook rather than a second reload path.
|
|
2529
|
+
if (resources !== undefined || followups !== undefined || agentEnvPassthrough !== undefined) {
|
|
2484
2530
|
await deps.semaphore?.refresh();
|
|
2531
|
+
}
|
|
2485
2532
|
return c.json(workspaceConfigBody(written));
|
|
2486
2533
|
})
|
|
2487
2534
|
// Global GUI state (`~/.xezar/ui-state.json`) — same parse/key-cap/shallow-
|
|
@@ -2514,6 +2561,12 @@ export function createApp(deps) {
|
|
|
2514
2561
|
browseRoot: z.string().trim().min(1).max(4096).optional(),
|
|
2515
2562
|
projectsDir: z.string().trim().min(1).max(4096).optional(),
|
|
2516
2563
|
skillsAutoUpdate: z.boolean().nullable().optional(),
|
|
2564
|
+
followups: z.boolean().nullable().optional(),
|
|
2565
|
+
agentEnvPassthrough: z
|
|
2566
|
+
.array(z.string().trim().min(1).max(200))
|
|
2567
|
+
.max(64)
|
|
2568
|
+
.nullable()
|
|
2569
|
+
.optional(),
|
|
2517
2570
|
composerDefaults: z
|
|
2518
2571
|
.object({
|
|
2519
2572
|
autonomous: z.boolean().nullable().optional(),
|
|
@@ -2526,6 +2579,7 @@ export function createApp(deps) {
|
|
|
2526
2579
|
maxMonitoringSessions: z.number().int().min(0).max(16).optional(),
|
|
2527
2580
|
monitoringWakeIntervalMinutes: z.number().int().min(1).max(60).nullable().optional(),
|
|
2528
2581
|
autoResumeOnUsageLimit: z.boolean().optional(),
|
|
2582
|
+
idleTimeoutMinutes: z.number().int().min(1).max(1440).nullable().optional(),
|
|
2529
2583
|
memoryLimitMb: z.number().int().min(0).max(1_048_576).nullable().optional(),
|
|
2530
2584
|
worktreeRetentionDefault: z.number().int().min(0).max(1000).optional(),
|
|
2531
2585
|
})
|
|
@@ -3165,6 +3219,8 @@ export function createApp(deps) {
|
|
|
3165
3219
|
catch (error) {
|
|
3166
3220
|
if (error instanceof HistoryCursorError)
|
|
3167
3221
|
return c.json({ error: error.message }, error.status);
|
|
3222
|
+
if (error instanceof EventCorrectionError)
|
|
3223
|
+
return c.json({ error: error.message }, 409);
|
|
3168
3224
|
throw error;
|
|
3169
3225
|
}
|
|
3170
3226
|
})
|
|
@@ -3173,7 +3229,14 @@ export function createApp(deps) {
|
|
|
3173
3229
|
const { id } = c.req.valid('param');
|
|
3174
3230
|
if (!store.getRun(id))
|
|
3175
3231
|
return c.json({ error: 'not found' }, 404);
|
|
3176
|
-
|
|
3232
|
+
try {
|
|
3233
|
+
return c.json(await deriveRunContextEvents(join(dataDir, 'runs', `${id}.ndjson`)));
|
|
3234
|
+
}
|
|
3235
|
+
catch (error) {
|
|
3236
|
+
if (error instanceof EventCorrectionError)
|
|
3237
|
+
return c.json({ error: error.message }, 409);
|
|
3238
|
+
throw error;
|
|
3239
|
+
}
|
|
3177
3240
|
})
|
|
3178
3241
|
// Editable titles (#389). The UI displays `titleSummary ?? title`, so a
|
|
3179
3242
|
// user edit sets BOTH: `title` (the record's own name — the raw task stops
|
|
@@ -4103,11 +4166,25 @@ export function createApp(deps) {
|
|
|
4103
4166
|
catch (error) {
|
|
4104
4167
|
if (error instanceof HistoryCursorError)
|
|
4105
4168
|
return c.json({ error: error.message }, error.status);
|
|
4169
|
+
if (error instanceof EventCorrectionError)
|
|
4170
|
+
return c.json({ error: error.message }, 409);
|
|
4106
4171
|
throw error;
|
|
4107
4172
|
}
|
|
4108
4173
|
}
|
|
4109
4174
|
const lastEventId = Number.parseInt(c.req.header('Last-Event-ID') ?? '', 10);
|
|
4110
4175
|
const requestedAfter = Math.max(query.afterSeq ?? 0, Number.isSafeInteger(lastEventId) && lastEventId >= 0 ? lastEventId : 0);
|
|
4176
|
+
if (!query.cursor) {
|
|
4177
|
+
try {
|
|
4178
|
+
validateLegacyHistoryResume(eventsPath, requestedAfter);
|
|
4179
|
+
}
|
|
4180
|
+
catch (error) {
|
|
4181
|
+
if (error instanceof HistoryCursorError)
|
|
4182
|
+
return c.json({ error: error.message }, error.status);
|
|
4183
|
+
if (error instanceof EventCorrectionError)
|
|
4184
|
+
return c.json({ error: error.message }, 409);
|
|
4185
|
+
throw error;
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
4111
4188
|
return streamSSENoBuffer(c, async (stream) => {
|
|
4112
4189
|
let replaying = true;
|
|
4113
4190
|
let maxSeq = requestedAfter;
|
|
@@ -4599,6 +4676,13 @@ export function createApp(deps) {
|
|
|
4599
4676
|
// Optional review gate (#489): tri-state — null means "no config key, the
|
|
4600
4677
|
// XEZ_REVIEW_GATE env default (OFF) decides".
|
|
4601
4678
|
reviewGate: config.reviewGate ?? null,
|
|
4679
|
+
// Planner/namer models and team skill sources (E). Declared in the file schema since
|
|
4680
|
+
// spec 008 but absent from this answer and from `setConfigSchema`, so the only way to
|
|
4681
|
+
// change them was to hand-edit `.xezar/config.json`. All three are `.default()`ed by
|
|
4682
|
+
// the file schema, so they are always materialized here — never tri-state.
|
|
4683
|
+
plannerModel: config.plannerModel,
|
|
4684
|
+
namerModel: config.namerModel,
|
|
4685
|
+
skillsRepos: config.skillsRepos,
|
|
4602
4686
|
};
|
|
4603
4687
|
};
|
|
4604
4688
|
// ---- chained family: per-repo config (project-scoped) ----
|
|
@@ -4672,6 +4756,26 @@ export function createApp(deps) {
|
|
|
4672
4756
|
raw.memoryLimitMb = parsed.data.memoryLimitMb;
|
|
4673
4757
|
}
|
|
4674
4758
|
}
|
|
4759
|
+
if (parsed.data.plannerModel !== undefined) {
|
|
4760
|
+
if (parsed.data.plannerModel === null)
|
|
4761
|
+
delete raw.plannerModel;
|
|
4762
|
+
else
|
|
4763
|
+
raw.plannerModel = parsed.data.plannerModel;
|
|
4764
|
+
}
|
|
4765
|
+
if (parsed.data.namerModel !== undefined) {
|
|
4766
|
+
if (parsed.data.namerModel === null)
|
|
4767
|
+
delete raw.namerModel;
|
|
4768
|
+
else
|
|
4769
|
+
raw.namerModel = parsed.data.namerModel;
|
|
4770
|
+
}
|
|
4771
|
+
if (parsed.data.skillsRepos !== undefined) {
|
|
4772
|
+
// `null` clears back to the default catalog; `[]` is stored, because an empty list
|
|
4773
|
+
// is how a repo turns team skills OFF (`gatedSkillsRepos` reads the key's presence).
|
|
4774
|
+
if (parsed.data.skillsRepos === null)
|
|
4775
|
+
delete raw.skillsRepos;
|
|
4776
|
+
else
|
|
4777
|
+
raw.skillsRepos = parsed.data.skillsRepos;
|
|
4778
|
+
}
|
|
4675
4779
|
if (parsed.data.defaultModels !== undefined) {
|
|
4676
4780
|
// Per-runner merge, so setting codex's preset never clobbers claude's.
|
|
4677
4781
|
const current = raw.defaultModels && typeof raw.defaultModels === 'object'
|
|
@@ -4697,6 +4801,13 @@ export function createApp(deps) {
|
|
|
4697
4801
|
catch (err) {
|
|
4698
4802
|
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
4699
4803
|
}
|
|
4804
|
+
// B2: the per-repo `memoryLimitMb` is enforced again, and enforcement reads it from
|
|
4805
|
+
// the semaphore's cached snapshot — so this write has to refresh that snapshot, the
|
|
4806
|
+
// same way `PUT /api/v1/workspace/config` and `PATCH /api/v1/projects/:id` do. Without
|
|
4807
|
+
// it the key would save and do nothing until the next boot, which is the exact defect
|
|
4808
|
+
// B2 exists to fix.
|
|
4809
|
+
if (parsed.data.memoryLimitMb !== undefined)
|
|
4810
|
+
await deps.semaphore?.refresh();
|
|
4700
4811
|
// Pre-R6 answer shape ({baseBranch, defaultRunner}) + additive R6 fields.
|
|
4701
4812
|
return c.json(await configAnswer(repoRoot, await loadConfig(repoRoot)));
|
|
4702
4813
|
});
|
|
@@ -4732,6 +4843,19 @@ export function createApp(deps) {
|
|
|
4732
4843
|
// Optional review gate toggle (Settings → Agents, #489): null clears the key
|
|
4733
4844
|
// back to the env-default behavior (OFF).
|
|
4734
4845
|
reviewGate: z.boolean().nullable().optional(),
|
|
4846
|
+
// Planner/namer models and team skill sources (E). `null` clears each key back to its
|
|
4847
|
+
// schema default; `skillsRepos: []` is a real value that disables team skills, which is
|
|
4848
|
+
// why the empty array is stored rather than treated as a clear.
|
|
4849
|
+
plannerModel: z.string().trim().min(1).max(200).nullable().optional(),
|
|
4850
|
+
namerModel: z.string().trim().min(1).max(200).nullable().optional(),
|
|
4851
|
+
skillsRepos: z
|
|
4852
|
+
.array(z.object({
|
|
4853
|
+
repo: z.string().trim().min(1).max(500),
|
|
4854
|
+
ref: z.string().trim().min(1).max(200).optional(),
|
|
4855
|
+
}))
|
|
4856
|
+
.max(32)
|
|
4857
|
+
.nullable()
|
|
4858
|
+
.optional(),
|
|
4735
4859
|
});
|
|
4736
4860
|
const setAgentConfigSchema = z.object({
|
|
4737
4861
|
content: z.string().max(2_000_000),
|
|
@@ -4849,8 +4973,38 @@ export function createApp(deps) {
|
|
|
4849
4973
|
}
|
|
4850
4974
|
return numbers;
|
|
4851
4975
|
};
|
|
4852
|
-
|
|
4976
|
+
/**
|
|
4977
|
+
* How many DISTINCT backends a run's recorded steps used. A step that never ran recorded no
|
|
4978
|
+
* backend and is not counted — absent is "nothing happened here", never a second backend.
|
|
4979
|
+
*
|
|
4980
|
+
* Derived HERE because the index row carries no `steps[]` by design (see the schema's header).
|
|
4981
|
+
* The cockpit's `stepBackendCount` applies the identical rule to the fat record on the
|
|
4982
|
+
* per-project table; keep the two the same.
|
|
4983
|
+
*/
|
|
4984
|
+
const distinctStepBackends = (run) => new Set(run.steps.flatMap((step) => (step.backend ? [step.backend] : []))).size;
|
|
4985
|
+
/**
|
|
4986
|
+
* The runner a run RAN AS, in order of how good the evidence is — `undefined` only when the run
|
|
4987
|
+
* carries none, which means it never started.
|
|
4988
|
+
*
|
|
4989
|
+
* `RunRecord.runner` is not "what the caller asked for": `execute` writes the resolved backend
|
|
4990
|
+
* onto the record the moment a run begins (`workflows/run.ts`), so for anything that ever ran it
|
|
4991
|
+
* is a fact. A record written before run-level backend affinity existed still has step backends,
|
|
4992
|
+
* stamped at spawn — and resolving one of THOSE against today's `defaultRunner` would name a
|
|
4993
|
+
* backend the run never touched.
|
|
4994
|
+
*/
|
|
4995
|
+
const recordedRunner = (run) => {
|
|
4996
|
+
if (run.runner)
|
|
4997
|
+
return run.runner;
|
|
4998
|
+
for (let index = run.steps.length - 1; index >= 0; index -= 1) {
|
|
4999
|
+
const backend = run.steps[index]?.backend;
|
|
5000
|
+
if (backend)
|
|
5001
|
+
return backend;
|
|
5002
|
+
}
|
|
5003
|
+
return undefined;
|
|
5004
|
+
};
|
|
5005
|
+
const runIndexEntry = (projectId, run, runner) => {
|
|
4853
5006
|
const usage = currentUsage(run.id);
|
|
5007
|
+
const backends = distinctStepBackends(run);
|
|
4854
5008
|
return {
|
|
4855
5009
|
projectId,
|
|
4856
5010
|
id: run.id,
|
|
@@ -4865,6 +5019,15 @@ export function createApp(deps) {
|
|
|
4865
5019
|
archived: run.archived,
|
|
4866
5020
|
...(run.autoResumeAt !== undefined ? { autoResumeAt: run.autoResumeAt } : {}),
|
|
4867
5021
|
workflow: run.workflow,
|
|
5022
|
+
// Resolved HERE because rows span projects: the browser would need one config request per
|
|
5023
|
+
// project to finish it. `inherited` marks the one case that is not history — a run carrying
|
|
5024
|
+
// no evidence at all has not started, so the value is what it WOULD run as.
|
|
5025
|
+
runner: runner.runner,
|
|
5026
|
+
...(runner.inherited ? { runnerInherited: true } : {}),
|
|
5027
|
+
...(run.model !== undefined ? { model: run.model } : {}),
|
|
5028
|
+
// Only when it says something: 0 or 1 backend is every ordinary run, and a key nobody reads
|
|
5029
|
+
// is exactly the weight this slim row exists to refuse.
|
|
5030
|
+
...(backends > 1 ? { stepBackends: backends } : {}),
|
|
4868
5031
|
...(run.branch !== undefined ? { branch: run.branch } : {}),
|
|
4869
5032
|
...(run.startedAt !== undefined ? { startedAt: run.startedAt } : {}),
|
|
4870
5033
|
// The tracker-reference inputs, verbatim — the cockpit's `taskReference()` owns the rule
|
|
@@ -4933,9 +5096,30 @@ export function createApp(deps) {
|
|
|
4933
5096
|
const recent = (owned ? owned.store.listRuns() : readRunIndexFromDisk(projectDataDir(project.root))).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
4934
5097
|
if (recent.length > RUNS_INDEX_PER_PROJECT)
|
|
4935
5098
|
truncated.push(project.id);
|
|
5099
|
+
// LAZY, and at most once per project. This route is polled every 15s and invalidated on a
|
|
5100
|
+
// 400ms debounce during a run, so a config read per project per request would be dozens of
|
|
5101
|
+
// file reads a second on a large registry. Almost every row answers for itself — a started
|
|
5102
|
+
// run carries its own resolved `runner` — so in practice this never runs at all.
|
|
5103
|
+
let projectDefault;
|
|
5104
|
+
const defaultRunnerFor = async () => {
|
|
5105
|
+
if (projectDefault === undefined) {
|
|
5106
|
+
try {
|
|
5107
|
+
projectDefault = (await loadConfig(project.root)).defaultRunner;
|
|
5108
|
+
}
|
|
5109
|
+
catch {
|
|
5110
|
+
// Documented contract: this read degrades to fewer or plainer rows, never a 500.
|
|
5111
|
+
projectDefault = 'claude';
|
|
5112
|
+
}
|
|
5113
|
+
}
|
|
5114
|
+
return projectDefault;
|
|
5115
|
+
};
|
|
4936
5116
|
const mentioned = [];
|
|
4937
5117
|
for (const run of recent.slice(0, RUNS_INDEX_PER_PROJECT)) {
|
|
4938
|
-
|
|
5118
|
+
const recorded = recordedRunner(run);
|
|
5119
|
+
const runner = recorded
|
|
5120
|
+
? { runner: recorded, inherited: false }
|
|
5121
|
+
: { runner: await defaultRunnerFor(), inherited: true };
|
|
5122
|
+
runs.push(runIndexEntry(project.id, run, runner));
|
|
4939
5123
|
mentioned.push(...mentionedReferenceNumbers(run));
|
|
4940
5124
|
}
|
|
4941
5125
|
if (mentioned.length > 0) {
|