agent-nuvira 3.5.6 → 3.5.7
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/.agents/skills/index.json +16 -0
- package/.agents/skills/motion/SKILL.md +27 -0
- package/README.md +56 -1
- package/dist/cli/chat.d.ts +76 -17
- package/dist/cli/chat.d.ts.map +1 -1
- package/dist/cli/chat.js +258 -118
- package/dist/cli/chat.js.map +1 -1
- package/dist/cli/doctor.d.ts +31 -2
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +143 -7
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/loop-executor.d.ts.map +1 -1
- package/dist/cli/loop-executor.js +4 -1
- package/dist/cli/loop-executor.js.map +1 -1
- package/dist/cli/parity.d.ts +12 -0
- package/dist/cli/parity.d.ts.map +1 -1
- package/dist/cli/parity.js +46 -1
- package/dist/cli/parity.js.map +1 -1
- package/dist/cli/tool-install-prompt.d.ts +2 -7
- package/dist/cli/tool-install-prompt.d.ts.map +1 -1
- package/dist/cli/tool-install-prompt.js +11 -28
- package/dist/cli/tool-install-prompt.js.map +1 -1
- package/dist/config/scratch-workspace.d.ts +134 -0
- package/dist/config/scratch-workspace.d.ts.map +1 -0
- package/dist/config/scratch-workspace.js +213 -0
- package/dist/config/scratch-workspace.js.map +1 -0
- package/dist/gateway/adapters.d.ts +24 -0
- package/dist/gateway/adapters.d.ts.map +1 -1
- package/dist/gateway/adapters.js +12 -0
- package/dist/gateway/adapters.js.map +1 -1
- package/dist/gateway/oversized-reply.d.ts +39 -0
- package/dist/gateway/oversized-reply.d.ts.map +1 -0
- package/dist/gateway/oversized-reply.js +54 -0
- package/dist/gateway/oversized-reply.js.map +1 -0
- package/dist/gateway/registry.d.ts +0 -10
- package/dist/gateway/registry.d.ts.map +1 -1
- package/dist/gateway/registry.js +148 -4
- package/dist/gateway/registry.js.map +1 -1
- package/dist/learning/quota-ledger.d.ts +24 -0
- package/dist/learning/quota-ledger.d.ts.map +1 -1
- package/dist/learning/quota-ledger.js +40 -0
- package/dist/learning/quota-ledger.js.map +1 -1
- package/dist/learning/runtime-dependencies.d.ts +183 -0
- package/dist/learning/runtime-dependencies.d.ts.map +1 -0
- package/dist/learning/runtime-dependencies.js +349 -0
- package/dist/learning/runtime-dependencies.js.map +1 -0
- package/dist/learning/skill-store.d.ts.map +1 -1
- package/dist/learning/skill-store.js +6 -5
- package/dist/learning/skill-store.js.map +1 -1
- package/dist/skills/bundled-skills-phase3.d.ts +14 -0
- package/dist/skills/bundled-skills-phase3.d.ts.map +1 -1
- package/dist/skills/bundled-skills-phase3.js +50 -0
- package/dist/skills/bundled-skills-phase3.js.map +1 -1
- package/dist/tools/modality/motion-presets.d.ts +62 -0
- package/dist/tools/modality/motion-presets.d.ts.map +1 -0
- package/dist/tools/modality/motion-presets.js +399 -0
- package/dist/tools/modality/motion-presets.js.map +1 -0
- package/dist/tools/modality/motion-tool.d.ts +62 -0
- package/dist/tools/modality/motion-tool.d.ts.map +1 -0
- package/dist/tools/modality/motion-tool.js +220 -0
- package/dist/tools/modality/motion-tool.js.map +1 -0
- package/dist/tools/modality/motion.d.ts +298 -0
- package/dist/tools/modality/motion.d.ts.map +1 -0
- package/dist/tools/modality/motion.js +514 -0
- package/dist/tools/modality/motion.js.map +1 -0
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +75 -0
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/tool-loop.d.ts.map +1 -1
- package/dist/tools/tool-loop.js +66 -16
- package/dist/tools/tool-loop.js.map +1 -1
- package/dist/tools/toolsets.js +2 -2
- package/dist/tools/toolsets.js.map +1 -1
- package/dist/utils/workspace-need.d.ts +84 -0
- package/dist/utils/workspace-need.d.ts.map +1 -0
- package/dist/utils/workspace-need.js +227 -0
- package/dist/utils/workspace-need.js.map +1 -0
- package/dist/web-dashboard/chat-console.d.ts +22 -0
- package/dist/web-dashboard/chat-console.d.ts.map +1 -1
- package/dist/web-dashboard/chat-console.js +65 -11
- package/dist/web-dashboard/chat-console.js.map +1 -1
- package/dist/web-dashboard/hub-data.d.ts +9 -1
- package/dist/web-dashboard/hub-data.d.ts.map +1 -1
- package/dist/web-dashboard/hub-data.js +2 -0
- package/dist/web-dashboard/hub-data.js.map +1 -1
- package/dist/web-dashboard/project-context.d.ts +1 -1
- package/dist/web-dashboard/project-context.d.ts.map +1 -1
- package/dist/web-dashboard/project-context.js +7 -1
- package/dist/web-dashboard/project-context.js.map +1 -1
- package/dist/web-dashboard/server.d.ts +33 -0
- package/dist/web-dashboard/server.d.ts.map +1 -1
- package/dist/web-dashboard/server.js +204 -21
- package/dist/web-dashboard/server.js.map +1 -1
- package/dist/web-dashboard/src/types.d.ts +46 -0
- package/dist/web-dashboard/src/types.d.ts.map +1 -1
- package/dist/web-dashboard/workspace-guard.d.ts +20 -21
- package/dist/web-dashboard/workspace-guard.d.ts.map +1 -1
- package/dist/web-dashboard/workspace-guard.js +20 -153
- package/dist/web-dashboard/workspace-guard.js.map +1 -1
- package/dist/web-dashboard/workspace-resolution.d.ts +23 -5
- package/dist/web-dashboard/workspace-resolution.d.ts.map +1 -1
- package/dist/web-dashboard/workspace-resolution.js +22 -4
- package/dist/web-dashboard/workspace-resolution.js.map +1 -1
- package/package.json +1 -1
- package/src/web-dashboard/public/assets/{index-DeSsNU94.css → index-CzRU84ET.css} +1 -1
- package/src/web-dashboard/public/assets/index-V0rIqVbg.js +236 -0
- package/src/web-dashboard/public/assets/index-V0rIqVbg.js.map +1 -0
- package/src/web-dashboard/public/index.html +2 -2
- package/src/web-dashboard/public/assets/index-Do_O67Kv.js +0 -236
- package/src/web-dashboard/public/assets/index-Do_O67Kv.js.map +0 -1
|
@@ -25,6 +25,7 @@ import { loadEnv } from '../utils/env.js';
|
|
|
25
25
|
import { ConfigManager } from '../config/manager.js';
|
|
26
26
|
import { parseRuntimeSpec } from '../mcp/runtime.js';
|
|
27
27
|
import { runAllChecks } from '../cli/doctor.js';
|
|
28
|
+
import { dependencySummary, probeRuntimeDependencies, } from '../learning/runtime-dependencies.js';
|
|
28
29
|
import { getAutoRouter } from '../learning/auto-router.js';
|
|
29
30
|
import { classifyEntries, } from '../learning/cost-classification.js';
|
|
30
31
|
import { acceptanceSummary, collectLabelledTurns, serializeLabelledTurns, parseCorpusText, detectCorpusFormat, mergeImportedLabels, } from '../learning/acceptance-model.js';
|
|
@@ -55,6 +56,7 @@ import { isCuratedProcessEnvVar, normalizeProcessEnvValue, } from '../config/pro
|
|
|
55
56
|
import { FRESH_DAYS, FRESHNESS_COPY, REACHABILITY_COPY, classifyFreshness, classifyReachability, } from '../learning/model-reachability.js';
|
|
56
57
|
import { VERIFY_BACKLOG_DEFAULT_PER_RUN, VERIFY_BACKLOG_MAX_PER_RUN, getVerifyBacklogState, startVerifyBacklogRun, } from '../learning/model-verify-job.js';
|
|
57
58
|
import { setToolsetEnabled } from '../tools/toolsets.js';
|
|
59
|
+
import { ArtifactStore } from '../tools/artifact-store.js';
|
|
58
60
|
import { setSkillEnabled } from '../learning/hub-skill-catalog.js';
|
|
59
61
|
// I11 — dashboard channel send-test: the same registry/adapters `nuvira gateway
|
|
60
62
|
// send` uses, so a Channels-tab test message behaves identically to the CLI.
|
|
@@ -68,6 +70,7 @@ import { ChannelDirectory, PLATFORM_ENV_VARS, platformConfigVars } from '../gate
|
|
|
68
70
|
import { applyEnvToProcess, configurablePlatforms, envVarState, platformConfigStatus, platformEnvVarMeta, redactValue, writeEnvFile, } from '../gateway/platform-config.js';
|
|
69
71
|
import { SERVICE_CATALOG, getServiceDefinition, } from '../config/service-catalog.js';
|
|
70
72
|
import { probeService } from '../config/service-probe.js';
|
|
73
|
+
import { pruneScratchWorkspaces } from '../config/scratch-workspace.js';
|
|
71
74
|
// ─── Constants ──────────────────────────────────────────────────────────────
|
|
72
75
|
const PORT = parseInt(envBuff('DASHBOARD_PORT') || '3030', 10);
|
|
73
76
|
const HOST = envBuff('DASHBOARD_HOST') || '127.0.0.1';
|
|
@@ -119,6 +122,40 @@ let alwaysWatchQuota = false;
|
|
|
119
122
|
* loadApiKeysFromConfig uses). Best-effort — a missing/corrupt config just
|
|
120
123
|
* keeps the default (false = arm-on-connect only).
|
|
121
124
|
*/
|
|
125
|
+
/**
|
|
126
|
+
* The content type for an artifact file: the recorded mime when there is one,
|
|
127
|
+
* otherwise the extension. Video and image types are the ones the panel renders
|
|
128
|
+
* inline, so a wrong type is a blank frame rather than a failed download.
|
|
129
|
+
*/
|
|
130
|
+
function artifactFileMime(file, recorded) {
|
|
131
|
+
if (recorded && /^[a-z]+\/[a-z0-9.+-]+$/i.test(recorded))
|
|
132
|
+
return recorded;
|
|
133
|
+
const ext = (file.split('.').pop() ?? '').toLowerCase();
|
|
134
|
+
const byExt = {
|
|
135
|
+
mp4: 'video/mp4',
|
|
136
|
+
webm: 'video/webm',
|
|
137
|
+
mov: 'video/quicktime',
|
|
138
|
+
mkv: 'video/x-matroska',
|
|
139
|
+
gif: 'image/gif',
|
|
140
|
+
png: 'image/png',
|
|
141
|
+
jpg: 'image/jpeg',
|
|
142
|
+
jpeg: 'image/jpeg',
|
|
143
|
+
webp: 'image/webp',
|
|
144
|
+
svg: 'image/svg+xml',
|
|
145
|
+
mp3: 'audio/mpeg',
|
|
146
|
+
wav: 'audio/wav',
|
|
147
|
+
m4a: 'audio/mp4',
|
|
148
|
+
srt: 'application/x-subrip',
|
|
149
|
+
vtt: 'text/vtt',
|
|
150
|
+
json: 'application/json',
|
|
151
|
+
md: 'text/markdown',
|
|
152
|
+
txt: 'text/plain',
|
|
153
|
+
log: 'text/plain',
|
|
154
|
+
csv: 'text/csv',
|
|
155
|
+
pdf: 'application/pdf',
|
|
156
|
+
};
|
|
157
|
+
return byExt[ext] ?? 'application/octet-stream';
|
|
158
|
+
}
|
|
122
159
|
function loadAlwaysWatchQuotaFlag() {
|
|
123
160
|
try {
|
|
124
161
|
const configPath = resolveBuffConfigPath();
|
|
@@ -2807,14 +2844,42 @@ function buildServiceRow(def, canReadValues) {
|
|
|
2807
2844
|
envVars,
|
|
2808
2845
|
};
|
|
2809
2846
|
}
|
|
2847
|
+
/**
|
|
2848
|
+
* One runtime tool as the panel's payload. Exported for the server test, which
|
|
2849
|
+
* asserts the panel is fed the same list `nuvira doctor` prints.
|
|
2850
|
+
*/
|
|
2851
|
+
export function runtimeToolSummary(status) {
|
|
2852
|
+
return {
|
|
2853
|
+
id: status.dependency.id,
|
|
2854
|
+
label: status.dependency.label,
|
|
2855
|
+
need: status.dependency.need,
|
|
2856
|
+
present: status.present,
|
|
2857
|
+
enables: status.dependency.enables,
|
|
2858
|
+
neededBy: status.dependency.neededBy,
|
|
2859
|
+
// A tool the machine ALREADY has gets no command on the wire, the same way
|
|
2860
|
+
// `nuvira doctor` prints a fix only for what is missing: sending one anyway
|
|
2861
|
+
// invites a user to reinstall something that is working.
|
|
2862
|
+
installCommand: status.present ? null : status.installCommand,
|
|
2863
|
+
note: status.dependency.note,
|
|
2864
|
+
needsHumanInput: status.needsHumanInput,
|
|
2865
|
+
};
|
|
2866
|
+
}
|
|
2810
2867
|
/** Run ALL state checks — the dashboard command-runner payload. */
|
|
2811
2868
|
async function runAdminChecks() {
|
|
2812
2869
|
const configManager = new ConfigManager();
|
|
2813
2870
|
const { system, enterprise } = await runAllChecks(configManager);
|
|
2871
|
+
// The SAME declaration the CLI's `nuvira doctor`, the `--fix` installer and the
|
|
2872
|
+
// setup scripts read, probed once for this payload. The rows are also in
|
|
2873
|
+
// `system` (one check each) because a pass/warn verdict is what a check list
|
|
2874
|
+
// is for; this is the actionable half — the command to copy, the
|
|
2875
|
+
// recommended/optional split, and the caveat.
|
|
2876
|
+
const statuses = probeRuntimeDependencies();
|
|
2814
2877
|
return {
|
|
2815
2878
|
system,
|
|
2816
2879
|
enterprise,
|
|
2817
2880
|
providers: buildAdminProviderSummary(configManager),
|
|
2881
|
+
runtimeTools: statuses.map(runtimeToolSummary),
|
|
2882
|
+
runtimeToolsSummary: dependencySummary(statuses),
|
|
2818
2883
|
serverTime: Date.now(),
|
|
2819
2884
|
};
|
|
2820
2885
|
}
|
|
@@ -3312,6 +3377,52 @@ function handleRequest(req, res) {
|
|
|
3312
3377
|
})();
|
|
3313
3378
|
return;
|
|
3314
3379
|
}
|
|
3380
|
+
// ── An artifact's BYTES — how the Artifacts tab shows a rendered video rather
|
|
3381
|
+
// than a filename. The path is looked up in OUR OWN artifact store by (session,
|
|
3382
|
+
// artifact id) and is never taken from the query string, so a caller cannot ask
|
|
3383
|
+
// this endpoint for an arbitrary file; a bad pair is a 404, not a read.
|
|
3384
|
+
if (pathname === '/api/artifact-file') {
|
|
3385
|
+
const sessionId = url.searchParams.get('session') ?? '';
|
|
3386
|
+
const artifactId = url.searchParams.get('artifact') ?? '';
|
|
3387
|
+
const artifact = artifactId
|
|
3388
|
+
? new ArtifactStore().read(sessionId).find((a) => a.id === artifactId)
|
|
3389
|
+
: undefined;
|
|
3390
|
+
if (!artifact || !artifact.path || !existsSync(artifact.path) || !statSync(artifact.path).isFile()) {
|
|
3391
|
+
writeJson(res, 404, { ok: false, error: 'No such artifact file.' });
|
|
3392
|
+
return;
|
|
3393
|
+
}
|
|
3394
|
+
const size = statSync(artifact.path).size;
|
|
3395
|
+
const type = artifactFileMime(artifact.path, artifact.mime);
|
|
3396
|
+
// Range support, because a <video> that cannot seek cannot be reviewed: a
|
|
3397
|
+
// player asks for byte ranges the moment the user drags the scrubber.
|
|
3398
|
+
const range = /^bytes=(\d*)-(\d*)$/.exec(req.headers.range ?? '');
|
|
3399
|
+
if (range) {
|
|
3400
|
+
const start = range[1] ? Number(range[1]) : 0;
|
|
3401
|
+
const end = range[2] ? Math.min(Number(range[2]), size - 1) : size - 1;
|
|
3402
|
+
if (start >= size || end < start) {
|
|
3403
|
+
res.writeHead(416, { 'Content-Range': `bytes */${size}` });
|
|
3404
|
+
res.end();
|
|
3405
|
+
return;
|
|
3406
|
+
}
|
|
3407
|
+
res.writeHead(206, {
|
|
3408
|
+
'Content-Type': type,
|
|
3409
|
+
'Content-Length': String(end - start + 1),
|
|
3410
|
+
'Content-Range': `bytes ${start}-${end}/${size}`,
|
|
3411
|
+
'Accept-Ranges': 'bytes',
|
|
3412
|
+
'Cache-Control': 'no-store',
|
|
3413
|
+
});
|
|
3414
|
+
createReadStream(artifact.path, { start, end }).pipe(res);
|
|
3415
|
+
return;
|
|
3416
|
+
}
|
|
3417
|
+
res.writeHead(200, {
|
|
3418
|
+
'Content-Type': type,
|
|
3419
|
+
'Content-Length': String(size),
|
|
3420
|
+
'Accept-Ranges': 'bytes',
|
|
3421
|
+
'Cache-Control': 'no-store',
|
|
3422
|
+
});
|
|
3423
|
+
createReadStream(artifact.path).pipe(res);
|
|
3424
|
+
return;
|
|
3425
|
+
}
|
|
3315
3426
|
if (pathname === '/api/benchmarks') {
|
|
3316
3427
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
3317
3428
|
res.end(JSON.stringify(readBenchmarkData()));
|
|
@@ -3391,7 +3502,17 @@ function handleRequest(req, res) {
|
|
|
3391
3502
|
res.end(JSON.stringify(payload));
|
|
3392
3503
|
}).catch((err) => {
|
|
3393
3504
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
3394
|
-
res.end(JSON.stringify({
|
|
3505
|
+
res.end(JSON.stringify({
|
|
3506
|
+
error: String(err),
|
|
3507
|
+
system: [],
|
|
3508
|
+
enterprise: [],
|
|
3509
|
+
providers: [],
|
|
3510
|
+
// An empty list, not a missing one: the panel must be able to say
|
|
3511
|
+
// "nothing reported" on a failed run rather than crash on undefined.
|
|
3512
|
+
runtimeTools: [],
|
|
3513
|
+
runtimeToolsSummary: 'unavailable — the checks run failed',
|
|
3514
|
+
serverTime: Date.now(),
|
|
3515
|
+
}));
|
|
3395
3516
|
});
|
|
3396
3517
|
return;
|
|
3397
3518
|
}
|
|
@@ -7598,13 +7719,51 @@ function handleRequest(req, res) {
|
|
|
7598
7719
|
*/
|
|
7599
7720
|
const configuredCwd = resolveDashboardCwd();
|
|
7600
7721
|
const { resolveTurnWorkspace, formatWorkspaceNoticeText } = await import('./workspace-resolution.js');
|
|
7722
|
+
const { workspaceNeedForMissingFolder } = await import('./workspace-guard.js');
|
|
7601
7723
|
const sessionProjectPath = chatConsole.get(sessionId)?.projectPath;
|
|
7602
|
-
|
|
7724
|
+
/**
|
|
7725
|
+
* The same ladder, asked twice when the last rung is needed.
|
|
7726
|
+
*
|
|
7727
|
+
* WHY TWO PASSES. The scratch folder is a real directory on disk, so it is
|
|
7728
|
+
* only created when the ladder has actually RUN OUT (nothing attached,
|
|
7729
|
+
* nothing named in the message, no configured default) AND the ask needs
|
|
7730
|
+
* somewhere to PUT new work. Creating it eagerly would leave an empty
|
|
7731
|
+
* temporary folder behind on every turn that already had a folder, and
|
|
7732
|
+
* handing it to a READ ask would be the kuttaaddon defect again — an empty
|
|
7733
|
+
* folder cannot answer "assess this project".
|
|
7734
|
+
*/
|
|
7735
|
+
const resolveWorkspaceNow = (scratchPath) => resolveTurnWorkspace({
|
|
7603
7736
|
...(attachedPath ? { attachedPath } : {}),
|
|
7604
7737
|
...(sessionProjectPath ? { sessionPath: sessionProjectPath } : {}),
|
|
7605
7738
|
...(configuredCwd ? { configuredCwd } : {}),
|
|
7606
7739
|
message,
|
|
7740
|
+
...(scratchPath ? { scratchPath } : {}),
|
|
7607
7741
|
});
|
|
7742
|
+
let workspace = resolveWorkspaceNow();
|
|
7743
|
+
/**
|
|
7744
|
+
* What this ask needs a folder FOR — `'produce'` is the case a temporary
|
|
7745
|
+
* folder can serve, `'read'` is the case it cannot (and keeps the folder
|
|
7746
|
+
* prompt), `null` is plain chat, which needs none.
|
|
7747
|
+
*/
|
|
7748
|
+
const missingWorkspaceNeed = workspace.path ? null : workspaceNeedForMissingFolder(message);
|
|
7749
|
+
if (missingWorkspaceNeed === 'produce') {
|
|
7750
|
+
// The scratch folder is offered through the SAME rule the gateway uses
|
|
7751
|
+
// (`scratchWorkspaceForTurn`), so the two surfaces cannot disagree about
|
|
7752
|
+
// when a temporary folder is the right answer — in particular about an
|
|
7753
|
+
// ISOLATED turn, which a scratch folder can never serve: it has no base
|
|
7754
|
+
// commit to diff against. There, this turn has no folder at all, so it
|
|
7755
|
+
// falls through to the folder request below rather than isolating a
|
|
7756
|
+
// directory the user never chose.
|
|
7757
|
+
const { scratchWorkspaceForTurn } = await import('../config/scratch-workspace.js');
|
|
7758
|
+
const { resolveIsolationRequest } = await import('../tools/worktree.js');
|
|
7759
|
+
const scratch = scratchWorkspaceForTurn({
|
|
7760
|
+
key: sessionId,
|
|
7761
|
+
need: missingWorkspaceNeed,
|
|
7762
|
+
isolationAsked: resolveIsolationRequest({ worktree }).asked,
|
|
7763
|
+
});
|
|
7764
|
+
if (scratch)
|
|
7765
|
+
workspace = resolveWorkspaceNow(scratch);
|
|
7766
|
+
}
|
|
7608
7767
|
if (workspace.path) {
|
|
7609
7768
|
projectPath = workspace.path;
|
|
7610
7769
|
const bundle = getProjectBundle(workspace.path);
|
|
@@ -7622,24 +7781,23 @@ function handleRequest(req, res) {
|
|
|
7622
7781
|
* needs no caption.
|
|
7623
7782
|
*/
|
|
7624
7783
|
const workspaceNotice = formatWorkspaceNoticeText(workspace);
|
|
7625
|
-
//
|
|
7626
|
-
//
|
|
7627
|
-
//
|
|
7628
|
-
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
7633
|
-
|
|
7634
|
-
|
|
7635
|
-
|
|
7636
|
-
|
|
7637
|
-
|
|
7638
|
-
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
}
|
|
7784
|
+
// Still no workspace: either the ask must READ or CHANGE work that already
|
|
7785
|
+
// exists (a temporary folder holds none of it), or the scratch folder could
|
|
7786
|
+
// not be created. Both are the case for a folder request rather than an
|
|
7787
|
+
// answer about the wrong tree. General chat — `null` — is unaffected.
|
|
7788
|
+
if (!workspace.path && missingWorkspaceNeed !== null) {
|
|
7789
|
+
const { projectAttachmentPrompt, projectAttachmentFollowups } = await import('./workspace-guard.js');
|
|
7790
|
+
writeJson(res, 200, {
|
|
7791
|
+
ok: true,
|
|
7792
|
+
sessionId,
|
|
7793
|
+
content: projectAttachmentPrompt(),
|
|
7794
|
+
followups: projectAttachmentFollowups(),
|
|
7795
|
+
provider: null,
|
|
7796
|
+
model: null,
|
|
7797
|
+
generationFailed: false,
|
|
7798
|
+
needsProject: true,
|
|
7799
|
+
});
|
|
7800
|
+
return;
|
|
7643
7801
|
}
|
|
7644
7802
|
// P4 (revised) — a client DISCONNECT no longer cancels the turn. It used
|
|
7645
7803
|
// to: the server treated `res` close as an abort, which meant a page
|
|
@@ -7677,6 +7835,14 @@ function handleRequest(req, res) {
|
|
|
7677
7835
|
const result = await withStrictModel(strict, () => chatConsole.answer(sessionId, message, {
|
|
7678
7836
|
provider,
|
|
7679
7837
|
model,
|
|
7838
|
+
// P8b — the STRICT half of the pin rides into the console so the
|
|
7839
|
+
// CONVERSATION can remember it. `withStrictModel` above scopes it to
|
|
7840
|
+
// this turn, but a conversation whose lock is forgotten between turns
|
|
7841
|
+
// is a pin the user cannot actually rely on: measured live
|
|
7842
|
+
// (trace-1792068695325-42wb1j) the pin survived a reload and the lock
|
|
7843
|
+
// did not, so the engine handed the turn to another model with the
|
|
7844
|
+
// user's "this model only" instruction never in force.
|
|
7845
|
+
strict,
|
|
7680
7846
|
projectContext,
|
|
7681
7847
|
projectPath,
|
|
7682
7848
|
// Cluster G — carry the "no folder attached" state into the turn so a
|
|
@@ -7745,7 +7911,12 @@ function handleRequest(req, res) {
|
|
|
7745
7911
|
routingNotice = `Auto routing took over: this turn ran on ${served} because your pinned ${requested} was not available. To work with ${requested} only, enable strict model mode.`;
|
|
7746
7912
|
}
|
|
7747
7913
|
else if (result.generationFailed === true) {
|
|
7748
|
-
|
|
7914
|
+
// The turn ran on the pair the user pinned and stopped there. Saying
|
|
7915
|
+
// "auto routing may hand the work to another model" described the old
|
|
7916
|
+
// behaviour (a pinned turn followed the health ranking the moment its
|
|
7917
|
+
// pair returned an error) and would now be a lie: a pin waits on itself
|
|
7918
|
+
// and then asks. See `pinnedEscalation`.
|
|
7919
|
+
routingNotice = `Your pinned ${requested} did not answer, and nothing was sent to another model — the turn stopped instead of substituting. Retry it, or switch models.`;
|
|
7749
7920
|
}
|
|
7750
7921
|
}
|
|
7751
7922
|
// Did the offer we are about to send get a mechanism behind it? The client
|
|
@@ -8348,6 +8519,18 @@ export function createDashboardServer(opts) {
|
|
|
8348
8519
|
handle.ipv6Twin = undefined;
|
|
8349
8520
|
}
|
|
8350
8521
|
}
|
|
8522
|
+
// Scratch workspaces are temporary BY DEFINITION, so the ones nobody came back
|
|
8523
|
+
// to are cleaned on boot rather than accumulating in the user's home for the
|
|
8524
|
+
// life of the install (see `pruneScratchWorkspaces`). Best-effort: a failed
|
|
8525
|
+
// prune must never stop the server from coming up.
|
|
8526
|
+
try {
|
|
8527
|
+
const removed = pruneScratchWorkspaces();
|
|
8528
|
+
if (removed > 0)
|
|
8529
|
+
console.log(` 🧹 Removed ${removed} expired scratch workspace(s)`);
|
|
8530
|
+
}
|
|
8531
|
+
catch {
|
|
8532
|
+
/* best-effort */
|
|
8533
|
+
}
|
|
8351
8534
|
server.listen(bindPort, bindHost, () => {
|
|
8352
8535
|
console.log(`\n 🌐 Agent-Nuvira Dashboard`);
|
|
8353
8536
|
console.log(` ─────────────────────────`);
|