@cluesmith/codev 3.3.2 → 3.3.4
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/dashboard-dist/assets/{index-D3XaKpZW.js → index-DZf6pNPE.js} +41 -41
- package/dashboard-dist/assets/index-DZf6pNPE.js.map +1 -0
- package/dashboard-dist/assets/index-DnYLy5C_.css +32 -0
- package/dashboard-dist/index.html +2 -2
- package/dist/agent-farm/cli.d.ts.map +1 -1
- package/dist/agent-farm/cli.js +48 -0
- package/dist/agent-farm/cli.js.map +1 -1
- package/dist/agent-farm/commands/ide.d.ts +20 -0
- package/dist/agent-farm/commands/ide.d.ts.map +1 -0
- package/dist/agent-farm/commands/ide.js +138 -0
- package/dist/agent-farm/commands/ide.js.map +1 -0
- package/dist/agent-farm/commands/inbox.d.ts.map +1 -1
- package/dist/agent-farm/commands/inbox.js +79 -10
- package/dist/agent-farm/commands/inbox.js.map +1 -1
- package/dist/agent-farm/commands/interrupt.d.ts +4 -1
- package/dist/agent-farm/commands/interrupt.d.ts.map +1 -1
- package/dist/agent-farm/commands/interrupt.js +6 -3
- package/dist/agent-farm/commands/interrupt.js.map +1 -1
- package/dist/agent-farm/commands/reset.d.ts +4 -1
- package/dist/agent-farm/commands/reset.d.ts.map +1 -1
- package/dist/agent-farm/commands/reset.js +6 -3
- package/dist/agent-farm/commands/reset.js.map +1 -1
- package/dist/agent-farm/commands/send.d.ts +28 -0
- package/dist/agent-farm/commands/send.d.ts.map +1 -1
- package/dist/agent-farm/commands/send.js +79 -6
- package/dist/agent-farm/commands/send.js.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn-worktree.js +16 -7
- package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
- package/dist/agent-farm/commands/tower.d.ts +21 -9
- package/dist/agent-farm/commands/tower.d.ts.map +1 -1
- package/dist/agent-farm/commands/tower.js +90 -30
- package/dist/agent-farm/commands/tower.js.map +1 -1
- package/dist/agent-farm/db/index.d.ts +3 -1
- package/dist/agent-farm/db/index.d.ts.map +1 -1
- package/dist/agent-farm/db/index.js +8 -443
- package/dist/agent-farm/db/index.js.map +1 -1
- package/dist/agent-farm/db/mailbox.d.ts +47 -7
- package/dist/agent-farm/db/mailbox.d.ts.map +1 -1
- package/dist/agent-farm/db/mailbox.js +56 -12
- package/dist/agent-farm/db/mailbox.js.map +1 -1
- package/dist/agent-farm/db/migrations.d.ts +57 -0
- package/dist/agent-farm/db/migrations.d.ts.map +1 -0
- package/dist/agent-farm/db/migrations.js +530 -0
- package/dist/agent-farm/db/migrations.js.map +1 -0
- package/dist/agent-farm/db/schema.d.ts +1 -1
- package/dist/agent-farm/db/schema.d.ts.map +1 -1
- package/dist/agent-farm/db/schema.js +1 -0
- package/dist/agent-farm/db/schema.js.map +1 -1
- package/dist/agent-farm/db/tower-owner.d.ts +168 -0
- package/dist/agent-farm/db/tower-owner.d.ts.map +1 -0
- package/dist/agent-farm/db/tower-owner.js +369 -0
- package/dist/agent-farm/db/tower-owner.js.map +1 -0
- package/dist/agent-farm/db/types.d.ts +47 -0
- package/dist/agent-farm/db/types.d.ts.map +1 -1
- package/dist/agent-farm/db/types.js.map +1 -1
- package/dist/agent-farm/lib/ide-record.d.ts +65 -0
- package/dist/agent-farm/lib/ide-record.d.ts.map +1 -0
- package/dist/agent-farm/lib/ide-record.js +118 -0
- package/dist/agent-farm/lib/ide-record.js.map +1 -0
- package/dist/agent-farm/lib/tunnel-client.d.ts +22 -0
- package/dist/agent-farm/lib/tunnel-client.d.ts.map +1 -1
- package/dist/agent-farm/lib/tunnel-client.js +96 -19
- package/dist/agent-farm/lib/tunnel-client.js.map +1 -1
- package/dist/agent-farm/servers/cron-delivery.d.ts +7 -1
- package/dist/agent-farm/servers/cron-delivery.d.ts.map +1 -1
- package/dist/agent-farm/servers/cron-delivery.js +2 -1
- package/dist/agent-farm/servers/cron-delivery.js.map +1 -1
- package/dist/agent-farm/servers/gate-profiles.d.ts +44 -2
- package/dist/agent-farm/servers/gate-profiles.d.ts.map +1 -1
- package/dist/agent-farm/servers/gate-profiles.js +79 -6
- package/dist/agent-farm/servers/gate-profiles.js.map +1 -1
- package/dist/agent-farm/servers/ide-forward.d.ts +62 -0
- package/dist/agent-farm/servers/ide-forward.d.ts.map +1 -0
- package/dist/agent-farm/servers/ide-forward.js +274 -0
- package/dist/agent-farm/servers/ide-forward.js.map +1 -0
- package/dist/agent-farm/servers/ide-server.d.ts +68 -0
- package/dist/agent-farm/servers/ide-server.d.ts.map +1 -0
- package/dist/agent-farm/servers/ide-server.js +281 -0
- package/dist/agent-farm/servers/ide-server.js.map +1 -0
- package/dist/agent-farm/servers/mailbox-delivery.d.ts +296 -12
- package/dist/agent-farm/servers/mailbox-delivery.d.ts.map +1 -1
- package/dist/agent-farm/servers/mailbox-delivery.js +566 -69
- package/dist/agent-farm/servers/mailbox-delivery.js.map +1 -1
- package/dist/agent-farm/servers/mailbox-wiring.d.ts +66 -19
- package/dist/agent-farm/servers/mailbox-wiring.d.ts.map +1 -1
- package/dist/agent-farm/servers/mailbox-wiring.js +204 -37
- package/dist/agent-farm/servers/mailbox-wiring.js.map +1 -1
- package/dist/agent-farm/servers/message-write.d.ts +138 -11
- package/dist/agent-farm/servers/message-write.d.ts.map +1 -1
- package/dist/agent-farm/servers/message-write.js +144 -35
- package/dist/agent-farm/servers/message-write.js.map +1 -1
- package/dist/agent-farm/servers/overview.d.ts +28 -12
- package/dist/agent-farm/servers/overview.d.ts.map +1 -1
- package/dist/agent-farm/servers/overview.js +130 -79
- package/dist/agent-farm/servers/overview.js.map +1 -1
- package/dist/agent-farm/servers/render-gate.d.ts +77 -1
- package/dist/agent-farm/servers/render-gate.d.ts.map +1 -1
- package/dist/agent-farm/servers/render-gate.js +174 -25
- package/dist/agent-farm/servers/render-gate.js.map +1 -1
- package/dist/agent-farm/servers/session-submit.d.ts +7 -4
- package/dist/agent-farm/servers/session-submit.d.ts.map +1 -1
- package/dist/agent-farm/servers/session-submit.js +7 -4
- package/dist/agent-farm/servers/session-submit.js.map +1 -1
- package/dist/agent-farm/servers/tower-cron.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-cron.js +7 -3
- package/dist/agent-farm/servers/tower-cron.js.map +1 -1
- package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-instances.js +17 -10
- package/dist/agent-farm/servers/tower-instances.js.map +1 -1
- package/dist/agent-farm/servers/tower-routes.d.ts +32 -0
- package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-routes.js +186 -26
- package/dist/agent-farm/servers/tower-routes.js.map +1 -1
- package/dist/agent-farm/servers/tower-server.js +79 -9
- package/dist/agent-farm/servers/tower-server.js.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.d.ts +21 -0
- package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-terminals.js +106 -13
- package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
- package/dist/agent-farm/servers/tower-utils.d.ts +35 -0
- package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-utils.js +39 -0
- package/dist/agent-farm/servers/tower-utils.js.map +1 -1
- package/dist/agent-farm/servers/tower-websocket.d.ts.map +1 -1
- package/dist/agent-farm/servers/tower-websocket.js +26 -2
- package/dist/agent-farm/servers/tower-websocket.js.map +1 -1
- package/dist/agent-farm/utils/message-format.d.ts +78 -2
- package/dist/agent-farm/utils/message-format.d.ts.map +1 -1
- package/dist/agent-farm/utils/message-format.js +107 -11
- package/dist/agent-farm/utils/message-format.js.map +1 -1
- package/dist/agent-farm/utils/port.d.ts +22 -0
- package/dist/agent-farm/utils/port.d.ts.map +1 -0
- package/dist/agent-farm/utils/port.js +35 -0
- package/dist/agent-farm/utils/port.js.map +1 -0
- package/dist/agent-farm/utils/server-utils.d.ts +26 -0
- package/dist/agent-farm/utils/server-utils.d.ts.map +1 -1
- package/dist/agent-farm/utils/server-utils.js +36 -0
- package/dist/agent-farm/utils/server-utils.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +3 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/consult/index.d.ts.map +1 -1
- package/dist/commands/consult/index.js +61 -1
- package/dist/commands/consult/index.js.map +1 -1
- package/dist/commands/consult/tree-tripwire.d.ts +177 -0
- package/dist/commands/consult/tree-tripwire.d.ts.map +1 -0
- package/dist/commands/consult/tree-tripwire.js +356 -0
- package/dist/commands/consult/tree-tripwire.js.map +1 -0
- package/dist/commands/doctor.d.ts +69 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +165 -2
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/generate-image.d.ts +25 -1
- package/dist/commands/generate-image.d.ts.map +1 -1
- package/dist/commands/generate-image.js +410 -2
- package/dist/commands/generate-image.js.map +1 -1
- package/dist/commands/porch/prompts.d.ts.map +1 -1
- package/dist/commands/porch/prompts.js +18 -2
- package/dist/commands/porch/prompts.js.map +1 -1
- package/dist/lib/agent-env.d.ts +86 -0
- package/dist/lib/agent-env.d.ts.map +1 -0
- package/dist/lib/agent-env.js +126 -0
- package/dist/lib/agent-env.js.map +1 -0
- package/dist/lib/forge-contracts.d.ts +76 -5
- package/dist/lib/forge-contracts.d.ts.map +1 -1
- package/dist/lib/forge-contracts.js.map +1 -1
- package/dist/lib/forge-rate-limit.d.ts +45 -0
- package/dist/lib/forge-rate-limit.d.ts.map +1 -0
- package/dist/lib/forge-rate-limit.js +89 -0
- package/dist/lib/forge-rate-limit.js.map +1 -0
- package/dist/lib/forge.d.ts +29 -1
- package/dist/lib/forge.d.ts.map +1 -1
- package/dist/lib/forge.js +96 -21
- package/dist/lib/forge.js.map +1 -1
- package/dist/lib/github.d.ts +13 -1
- package/dist/lib/github.d.ts.map +1 -1
- package/dist/lib/github.js +58 -15
- package/dist/lib/github.js.map +1 -1
- package/dist/lib/test-env.d.ts +17 -0
- package/dist/lib/test-env.d.ts.map +1 -1
- package/dist/lib/test-env.js +20 -0
- package/dist/lib/test-env.js.map +1 -1
- package/dist/terminal/pty-manager.d.ts +10 -1
- package/dist/terminal/pty-manager.d.ts.map +1 -1
- package/dist/terminal/pty-manager.js +38 -9
- package/dist/terminal/pty-manager.js.map +1 -1
- package/dist/terminal/pty-session.d.ts +174 -8
- package/dist/terminal/pty-session.d.ts.map +1 -1
- package/dist/terminal/pty-session.js +299 -25
- package/dist/terminal/pty-session.js.map +1 -1
- package/dist/terminal/session-manager.d.ts +23 -2
- package/dist/terminal/session-manager.d.ts.map +1 -1
- package/dist/terminal/session-manager.js +115 -36
- package/dist/terminal/session-manager.js.map +1 -1
- package/dist/terminal/session-screen.d.ts +34 -5
- package/dist/terminal/session-screen.d.ts.map +1 -1
- package/dist/terminal/session-screen.js +52 -6
- package/dist/terminal/session-screen.js.map +1 -1
- package/dist/terminal/shellper-client.d.ts +73 -0
- package/dist/terminal/shellper-client.d.ts.map +1 -1
- package/dist/terminal/shellper-client.js +151 -0
- package/dist/terminal/shellper-client.js.map +1 -1
- package/dist/terminal/shellper-main.js +20 -3
- package/dist/terminal/shellper-main.js.map +1 -1
- package/dist/terminal/shellper-process.d.ts +2 -0
- package/dist/terminal/shellper-process.d.ts.map +1 -1
- package/dist/terminal/shellper-process.js +17 -1
- package/dist/terminal/shellper-process.js.map +1 -1
- package/dist/terminal/shellper-protocol.d.ts +28 -0
- package/dist/terminal/shellper-protocol.d.ts.map +1 -1
- package/dist/terminal/shellper-protocol.js.map +1 -1
- package/dist/terminal/terminal-replies.d.ts +102 -0
- package/dist/terminal/terminal-replies.d.ts.map +1 -0
- package/dist/terminal/terminal-replies.js +181 -0
- package/dist/terminal/terminal-replies.js.map +1 -0
- package/package.json +4 -4
- package/scripts/forge/gitea/_lib.sh +214 -0
- package/scripts/forge/gitea/issue-comment.sh +8 -1
- package/scripts/forge/gitea/issue-view.sh +95 -4
- package/scripts/forge/gitea/pr-create.sh +155 -0
- package/scripts/forge/gitea/pr-exists.sh +32 -4
- package/scripts/forge/gitea/pr-list.sh +40 -31
- package/scripts/forge/gitea/pr-view.sh +65 -4
- package/scripts/forge/gitea/recently-merged.sh +91 -21
- package/scripts/forge/gitea/user-identity.sh +30 -1
- package/scripts/forge/github/issue-view.sh +5 -2
- package/scripts/forge/github/pr-create.sh +47 -0
- package/scripts/forge/github/pr-list.sh +6 -2
- package/scripts/forge/github/pr-search.sh +6 -2
- package/scripts/forge/gitlab/issue-search.sh +1 -0
- package/scripts/forge/gitlab/issue-view.sh +24 -4
- package/scripts/forge/gitlab/pr-create.sh +53 -0
- package/scripts/forge/gitlab/pr-search.sh +37 -2
- package/scripts/forge/linear/auth-status.sh +1 -0
- package/scripts/forge/linear/issue-comment.sh +1 -0
- package/scripts/forge/linear/issue-list.sh +1 -0
- package/scripts/forge/linear/issue-search.sh +1 -0
- package/scripts/forge/linear/issue-view.sh +14 -2
- package/scripts/forge/linear/recently-closed.sh +1 -0
- package/scripts/forge/linear/user-identity.sh +1 -0
- package/skeleton/.claude/skills/generate-image/SKILL.md +39 -1
- package/skeleton/.codex/skills/generate-image/SKILL.md +39 -1
- package/skeleton/protocols/air/prompts/pr.md +6 -1
- package/skeleton/protocols/aspir/prompts/review.md +6 -1
- package/skeleton/protocols/bugfix/prompts/pr.md +6 -1
- package/skeleton/protocols/bugfix/protocol.md +2 -1
- package/skeleton/protocols/maintain/prompts/review.md +6 -1
- package/skeleton/protocols/pir/prompts/review.md +10 -7
- package/skeleton/protocols/spir/prompts/review.md +6 -1
- package/skeleton/resources/commands/agent-farm.md +28 -3
- package/skeleton/roles/consultant.md +32 -0
- package/skeleton/templates/AGENTS.md +1 -1
- package/skeleton/templates/CLAUDE.md +1 -1
- package/templates/tower.html +16 -1
- package/dashboard-dist/assets/index-BZRTEMtH.css +0 -32
- package/dashboard-dist/assets/index-D3XaKpZW.js.map +0 -1
|
@@ -21,13 +21,16 @@ import { decodeWorkspacePath } from '../lib/tower-client.js';
|
|
|
21
21
|
import { readCloudConfig } from '../lib/cloud-config.js';
|
|
22
22
|
import { fileURLToPath } from 'node:url';
|
|
23
23
|
import { version } from '../../version.js';
|
|
24
|
+
import { sanitizeAgentEnv } from '../../lib/agent-env.js';
|
|
24
25
|
const execAsync = promisify(exec);
|
|
25
26
|
import { getBuilders, setArchitectByName } from '../state.js';
|
|
26
27
|
import { DEFAULT_COLS, defaultSessionOptions } from '../../terminal/index.js';
|
|
27
28
|
import { parseJsonBody, isRequestAllowed, isAllowedOrigin, isAllowedHost, getExpectedKey, escapeHtml } from '../utils/server-utils.js';
|
|
28
29
|
import { TOWER_KEY_HEADER, LEGACY_WEB_KEY_HEADER, VSCODE_USER_SENDER } from '@cluesmith/codev-types';
|
|
29
|
-
import { isRateLimited, normalizeWorkspacePath, getLanguageForExt, getMimeTypeForFile, serveStaticFile, } from './tower-utils.js';
|
|
30
|
+
import { isRateLimited, normalizeWorkspacePath, getLanguageForExt, getMimeTypeForFile, serveStaticFile, persistableCommand, logSessionIdentity, } from './tower-utils.js';
|
|
30
31
|
import { handleTunnelEndpoint } from './tower-tunnel.js';
|
|
32
|
+
import { handleIdeApi } from './ide-server.js';
|
|
33
|
+
import { matchIdePrefix, forwardIdeHttp } from './ide-forward.js';
|
|
31
34
|
import { getWorktreeConfig, getActivityHooks } from '../utils/config.js';
|
|
32
35
|
import { ensureCodevConfigWatcher } from './codev-config-watcher.js';
|
|
33
36
|
import { hasTeam, loadTeamMembers, loadMessages } from '../../lib/team.js';
|
|
@@ -35,12 +38,13 @@ import { fetchTeamGitHubData } from '../../lib/team-github.js';
|
|
|
35
38
|
import { resolveTarget, resolveAgentInRegistry, broadcastMessage, isResolveError } from './tower-messages.js';
|
|
36
39
|
import { handleCommandRoute, COMMAND_ROUTE } from './command-relay.js';
|
|
37
40
|
import { handleCanvasRoute, CANVAS_ROUTE_PREFIX } from './canvas-relay.js';
|
|
38
|
-
import { formatArchitectMessage, formatArchitectToBuilderMessage, formatBuilderMessage, formatUserViaVsCodeMessage, messageTooLargeError, MAX_MESSAGE_BYTES, } from '../utils/message-format.js';
|
|
39
|
-
import { writeMessageToSession, writeEscapeToSession } from './message-write.js';
|
|
40
|
-
import { makeDeliveryPorts, getMailboxDrainer } from './mailbox-wiring.js';
|
|
41
|
+
import { formatArchitectMessage, formatArchitectToBuilderMessage, formatBuilderMessage, formatUserViaVsCodeMessage, isSafeSenderId, messageTooLargeError, MAX_MESSAGE_BYTES, } from '../utils/message-format.js';
|
|
42
|
+
import { writeMessageToSession, writeEscapeToSession, writeStrategyForApp } from './message-write.js';
|
|
43
|
+
import { makeDeliveryPorts, getMailboxDrainer, resolveProfileForSession } from './mailbox-wiring.js';
|
|
41
44
|
import { deliverAgentMailSerialized } from './mailbox-delivery.js';
|
|
42
45
|
import { deliverCronMail, CRON_SENDER } from './cron-delivery.js';
|
|
43
46
|
import { enqueue as enqueueMailbox, getById as getMailboxById, markDelivered as markMailboxDelivered, listHeld as listHeldMailbox, dismiss as dismissMailbox, } from '../db/mailbox.js';
|
|
47
|
+
import { formatVerdict } from '@cluesmith/codev-sdk/hold-verdict';
|
|
44
48
|
// Spec 1273 per-terminal submission lock. Originally the anti-fusion lock for the two
|
|
45
49
|
// explicit human-bypass paths (escape + interrupt); since Issue #1365 the gated mailbox
|
|
46
50
|
// delivery path takes it too, so it now serializes the whole write edge of a terminal.
|
|
@@ -54,7 +58,7 @@ import { submitToSession, OPERATOR_SUBMIT_WAIT_CEILING_MS } from './session-subm
|
|
|
54
58
|
import { scheduleDelayedSend, validateDelaySeconds } from './delayed-send.js';
|
|
55
59
|
import { getKnownWorkspacePaths, getInstances, getDirectorySuggestions, launchInstance, killTerminalWithShellper, instancesReady, stopInstance, addArchitect, removeArchitect, } from './tower-instances.js';
|
|
56
60
|
import { OverviewCache } from './overview.js';
|
|
57
|
-
import { fetchIssue, fetchPR, searchIssues, fetchPRList, fetchCurrentUser, parseLinkedIssue, parseArea, } from '../../lib/github.js';
|
|
61
|
+
import { fetchIssue, fetchPR, searchIssues, fetchPRList, fetchCurrentUser, probeGhGraphqlRateLimit, parseLinkedIssue, parseArea, } from '../../lib/github.js';
|
|
58
62
|
import { computeAnalytics } from './analytics.js';
|
|
59
63
|
import { getAllTasks, executeTask, getTaskId } from './tower-cron.js';
|
|
60
64
|
import { getGlobalDb } from '../db/index.js';
|
|
@@ -65,7 +69,8 @@ import { getWorkspaceTerminals, getTerminalManager, getWorkspaceTerminalsEntry,
|
|
|
65
69
|
const __filename = fileURLToPath(import.meta.url);
|
|
66
70
|
const __dirname = path.dirname(__filename);
|
|
67
71
|
// Singleton cache for overview endpoint (Spec 0126 Phase 4)
|
|
68
|
-
|
|
72
|
+
// #1645: the gh reset probe is enabled here, in Tower, and nowhere else.
|
|
73
|
+
const overviewCache = new OverviewCache({ probe: probeGhGraphqlRateLimit });
|
|
69
74
|
const ROUTES = {
|
|
70
75
|
'GET /health': (_req, res, _url, ctx) => handleHealthCheck(res, ctx),
|
|
71
76
|
'GET /api/shellpers/husks': (_req, res, _url, ctx) => handleHuskPreview(res, ctx),
|
|
@@ -158,6 +163,18 @@ export async function handleRequest(req, res, ctx) {
|
|
|
158
163
|
return await handler(req, res, url, ctx);
|
|
159
164
|
}
|
|
160
165
|
// Pattern-based routes (require regex or prefix matching)
|
|
166
|
+
// IDE management endpoint: /api/ide (Issue #1668). Local-only — blocked from
|
|
167
|
+
// the tunnel in tunnel-client (like /api/tunnel/*) — and reached only after
|
|
168
|
+
// the isRequestAllowed key check above. Drives the IDE server lifecycle.
|
|
169
|
+
if (url.pathname === '/api/ide') {
|
|
170
|
+
return await handleIdeApi(req, res, ctx.log);
|
|
171
|
+
}
|
|
172
|
+
// IDE prefix forward: /ide/* → local IDE server (Issue #1668). A post-auth
|
|
173
|
+
// handler: isRequestAllowed above is the whole trust boundary (the forward
|
|
174
|
+
// adds no per-folder authorization). Path + query forwarded unchanged.
|
|
175
|
+
if (matchIdePrefix(url.pathname)) {
|
|
176
|
+
return await forwardIdeHttp(req, res, ctx.log);
|
|
177
|
+
}
|
|
161
178
|
// Tunnel endpoints: /api/tunnel/* (Spec 0097 Phase 4)
|
|
162
179
|
if (url.pathname.startsWith('/api/tunnel/')) {
|
|
163
180
|
const tunnelSub = url.pathname.slice('/api/tunnel/'.length);
|
|
@@ -609,9 +626,11 @@ async function handleTerminalCreate(req, res, ctx) {
|
|
|
609
626
|
if (requestPersistence && shellperManager && command && cwd) {
|
|
610
627
|
try {
|
|
611
628
|
const sessionId = crypto.randomUUID();
|
|
612
|
-
// Strip
|
|
613
|
-
|
|
614
|
-
|
|
629
|
+
// Strip Claude Code session markers so spawned Claude processes don't
|
|
630
|
+
// detect nesting and disable transcript saving (#1219). `env` is normally
|
|
631
|
+
// absent — `createPtySession` sends no env — so this is the path by which
|
|
632
|
+
// Tower's own environment becomes every builder's environment.
|
|
633
|
+
const sessionEnv = sanitizeAgentEnv(env || process.env);
|
|
615
634
|
const client = await shellperManager.createSession({
|
|
616
635
|
sessionId,
|
|
617
636
|
command,
|
|
@@ -650,7 +669,12 @@ async function handleTerminalCreate(req, res, ctx) {
|
|
|
650
669
|
else {
|
|
651
670
|
entry.shells.set(roleId, session.id);
|
|
652
671
|
}
|
|
653
|
-
|
|
672
|
+
// PIR #1475: prefer the identity the shellper reports. A fresh spawn is
|
|
673
|
+
// the no-op case (it reports back what we just asked for), but routing
|
|
674
|
+
// it through the same accessor keeps one rule everywhere: the row
|
|
675
|
+
// records what is RUNNING, not what was requested.
|
|
676
|
+
logSessionIdentity(ctx.log, 'terminal-create', session.id, ptySession, command ?? null);
|
|
677
|
+
saveTerminalSession(session.id, workspacePath, termType, roleId, shellperInfo.pid, shellperInfo.socketPath, shellperInfo.pid, shellperInfo.startTime, label ?? null, cwd ?? null, persistableCommand(ptySession) ?? command ?? null);
|
|
654
678
|
ctx.log('INFO', `Registered shellper terminal ${session.id} as ${termType} "${roleId}" for workspace ${workspacePath}`);
|
|
655
679
|
}
|
|
656
680
|
}
|
|
@@ -661,7 +685,7 @@ async function handleTerminalCreate(req, res, ctx) {
|
|
|
661
685
|
// Fallback: non-persistent session (graceful degradation per plan)
|
|
662
686
|
// Shellper is the only persistence backend for new sessions.
|
|
663
687
|
if (!info) {
|
|
664
|
-
info = await manager.createSession({ command, args, cols, rows, cwd, env, label });
|
|
688
|
+
info = await manager.createSession({ command, args, cols, rows, cwd, env: env && sanitizeAgentEnv(env), label });
|
|
665
689
|
persistent = false;
|
|
666
690
|
if (workspacePath && termType && roleId) {
|
|
667
691
|
const entry = getWorkspaceTerminalsEntry(normalizeWorkspacePath(workspacePath));
|
|
@@ -744,6 +768,9 @@ async function handleTerminalRoutes(req, res, url, match) {
|
|
|
744
768
|
res.end(JSON.stringify({ error: 'NOT_FOUND', message: `Session ${terminalId} not found` }));
|
|
745
769
|
return;
|
|
746
770
|
}
|
|
771
|
+
// Deliberately the default `'external'` origin (Issue #1473): this is a foreign writer, so
|
|
772
|
+
// its bytes count as input at the delivery gate and are reply-filtered on the way — both
|
|
773
|
+
// for free, because the recording lives inside `write()` rather than at each call site.
|
|
747
774
|
session.write(body.data);
|
|
748
775
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
749
776
|
res.end(JSON.stringify({ ok: true }));
|
|
@@ -765,8 +792,20 @@ async function handleTerminalRoutes(req, res, url, match) {
|
|
|
765
792
|
}
|
|
766
793
|
const info = manager.resizeSession(terminalId, body.cols, body.rows);
|
|
767
794
|
if (!info) {
|
|
768
|
-
|
|
769
|
-
|
|
795
|
+
// Issue #1482: a resize that did not reach the process is NOT a missing session, and it
|
|
796
|
+
// is not a success either — echoing back the requested dimensions (what a 200 here used
|
|
797
|
+
// to do) is how Tower came to believe a geometry the TUI never adopted.
|
|
798
|
+
if (!manager.getSession(terminalId)) {
|
|
799
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
800
|
+
res.end(JSON.stringify({ error: 'NOT_FOUND', message: `Session ${terminalId} not found` }));
|
|
801
|
+
}
|
|
802
|
+
else {
|
|
803
|
+
res.writeHead(409, { 'Content-Type': 'application/json' });
|
|
804
|
+
res.end(JSON.stringify({
|
|
805
|
+
error: 'RESIZE_DROPPED',
|
|
806
|
+
message: `Session ${terminalId} did not accept the resize (no live process or dropped shellper write); dimensions unchanged`,
|
|
807
|
+
}));
|
|
808
|
+
}
|
|
770
809
|
return;
|
|
771
810
|
}
|
|
772
811
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -1282,6 +1321,11 @@ function liveTargetIdentity(result) {
|
|
|
1282
1321
|
* architect reverse-map for architect targets, the resolved agent id otherwise),
|
|
1283
1322
|
* so the header a recipient reads names the same identity the row was addressed
|
|
1284
1323
|
* to. Passing anything else here would make the frame attest to a fiction.
|
|
1324
|
+
*
|
|
1325
|
+
* #1478: `from` is the SENDER, and it is threaded on the any → builder path too —
|
|
1326
|
+
* that branch used to discard it, so an `architect:<name>` origin reached the
|
|
1327
|
+
* builder's composer as a bare `ARCHITECT INSTRUCTION`. The two are complementary:
|
|
1328
|
+
* #1574 answers "was this addressed to me?", #1478 answers "who sent it?".
|
|
1285
1329
|
*/
|
|
1286
1330
|
function formatMessageForTarget(isArchitectTarget, from, toAgent, message, raw) {
|
|
1287
1331
|
// #1494: a human's approval relayed by the VS Code extension gets its own
|
|
@@ -1290,8 +1334,12 @@ function formatMessageForTarget(isArchitectTarget, from, toAgent, message, raw)
|
|
|
1290
1334
|
return formatUserViaVsCodeMessage(toAgent, message, undefined, raw);
|
|
1291
1335
|
if (isArchitectTarget && from)
|
|
1292
1336
|
return formatBuilderMessage(from, toAgent, message, undefined, raw); // builder → architect
|
|
1337
|
+
// any → builder: thread the sender so an `architect:<name>` origin is named in the
|
|
1338
|
+
// header (issue #1478) instead of collapsing to a bare `ARCHITECT INSTRUCTION`.
|
|
1293
1339
|
if (!isArchitectTarget)
|
|
1294
|
-
return formatArchitectToBuilderMessage(toAgent, message, undefined, raw); // any → builder
|
|
1340
|
+
return formatArchitectToBuilderMessage(toAgent, message, undefined, raw, from); // any → builder
|
|
1341
|
+
// unknown → architect: `from` is absent by construction on this branch, so there is
|
|
1342
|
+
// no sender to name.
|
|
1295
1343
|
return raw ? message : formatArchitectMessage(toAgent, message, undefined, false); // unknown → architect
|
|
1296
1344
|
}
|
|
1297
1345
|
/**
|
|
@@ -1347,7 +1395,7 @@ export async function deliverCronMessage(task, message, log) {
|
|
|
1347
1395
|
}
|
|
1348
1396
|
}
|
|
1349
1397
|
log('WARN', `Cron '${task.name}': target '${task.target}' not found — message not delivered`);
|
|
1350
|
-
return { outcome: 'unresolved', reason: null, mailboxId: null };
|
|
1398
|
+
return { outcome: 'unresolved', reason: null, detail: null, mailboxId: null };
|
|
1351
1399
|
}
|
|
1352
1400
|
/**
|
|
1353
1401
|
* Persist a `held` mailbox row and write the Spec 1313 `held` send response. Used
|
|
@@ -1368,6 +1416,10 @@ function holdAndRespond(res, ctx, input, reason) {
|
|
|
1368
1416
|
delivered: false,
|
|
1369
1417
|
held: true,
|
|
1370
1418
|
reason,
|
|
1419
|
+
// A hold from THIS path is always a non-gate one (no live PTY), so it carries no gate
|
|
1420
|
+
// detail (Issue #1482). Reported explicitly rather than omitted so the held response has
|
|
1421
|
+
// one shape whichever path produced it.
|
|
1422
|
+
detail: null,
|
|
1371
1423
|
mailboxId: row.id,
|
|
1372
1424
|
bodyLength: Buffer.byteLength(input.body, 'utf8'),
|
|
1373
1425
|
});
|
|
@@ -1561,6 +1613,25 @@ async function handleSend(req, res, ctx) {
|
|
|
1561
1613
|
}
|
|
1562
1614
|
// Optional fields
|
|
1563
1615
|
const from = typeof body.from === 'string' ? body.from : undefined;
|
|
1616
|
+
// Maintainer review (PR #1486): `from` was the one unguarded field on this route — the
|
|
1617
|
+
// body above is bounded by MAX_MESSAGE_BYTES, but the sender was taken verbatim, and
|
|
1618
|
+
// `/api/send` is a public local route whose only well-behaved caller is the CLI. It is
|
|
1619
|
+
// stored on the mailbox row and rendered by every attribution surface, and `afx inbox`
|
|
1620
|
+
// now sizes its `FROM → TO` column to the widest value it holds — so an unbounded
|
|
1621
|
+
// identity is a rendering cost paid by every row, not just its own. Enforced with the
|
|
1622
|
+
// SAME predicate the header formatter uses, so the boundary and the renderer cannot
|
|
1623
|
+
// disagree about what an identity is. Refused rather than silently dropped or
|
|
1624
|
+
// truncated: a message delivered under a quietly-discarded sender is exactly the
|
|
1625
|
+
// unattributed frame this PR exists to fix.
|
|
1626
|
+
if (from !== undefined && !isSafeSenderId(from)) {
|
|
1627
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
1628
|
+
res.end(JSON.stringify({
|
|
1629
|
+
error: 'INVALID_PARAMS',
|
|
1630
|
+
message: 'Invalid "from" field: a sender identity must be 1-128 characters of ' +
|
|
1631
|
+
'[A-Za-z0-9._:-] (no whitespace, brackets or newlines).',
|
|
1632
|
+
}));
|
|
1633
|
+
return;
|
|
1634
|
+
}
|
|
1564
1635
|
const workspace = typeof body.workspace === 'string' ? body.workspace : undefined;
|
|
1565
1636
|
const fromWorkspace = typeof body.fromWorkspace === 'string' ? body.fromWorkspace : undefined;
|
|
1566
1637
|
const options = typeof body.options === 'object' && body.options !== null
|
|
@@ -1787,7 +1858,10 @@ async function handleSend(req, res, ctx) {
|
|
|
1787
1858
|
let degraded = false;
|
|
1788
1859
|
await submitToSession(result.terminalId, () => {
|
|
1789
1860
|
session.write('\x03'); // Ctrl+C
|
|
1790
|
-
|
|
1861
|
+
// Issue #1567: the same per-harness write strategy the gated path uses — an
|
|
1862
|
+
// interrupt to an opted-out harness must not be bracketed just because it bypassed
|
|
1863
|
+
// the gate.
|
|
1864
|
+
return writeMessageToSession(session, formattedMessage, noEnter, 100, writeStrategyForApp(resolveProfileForSession(session)?.app));
|
|
1791
1865
|
}, undefined, {
|
|
1792
1866
|
waitCeilingMs: OPERATOR_SUBMIT_WAIT_CEILING_MS,
|
|
1793
1867
|
onCeilingExpired: (waitedMs) => {
|
|
@@ -1813,6 +1887,7 @@ async function handleSend(req, res, ctx) {
|
|
|
1813
1887
|
held: false,
|
|
1814
1888
|
mailboxId: row.id,
|
|
1815
1889
|
reason: null,
|
|
1890
|
+
detail: null,
|
|
1816
1891
|
bodyLength,
|
|
1817
1892
|
// Additive and present only when it happened, so older clients are unaffected.
|
|
1818
1893
|
...(degraded ? { degraded: true, degradedReason: DEGRADED_SUBMIT_REASON } : {}),
|
|
@@ -1842,14 +1917,25 @@ async function handleSend(req, res, ctx) {
|
|
|
1842
1917
|
...basePorts,
|
|
1843
1918
|
getSessionForAgent: (ws, agent) => ws === result.workspacePath && agent === toAgent ? session : basePorts.getSessionForAgent(ws, agent),
|
|
1844
1919
|
};
|
|
1920
|
+
// Issue #1584: keep the outcome — it carries whether the delivered message's header was
|
|
1921
|
+
// actually seen on the receiving terminal, which is the only thing the sender can act on now
|
|
1922
|
+
// that an unconfirmed delivery is recorded rather than re-written.
|
|
1923
|
+
let outcome = null;
|
|
1845
1924
|
try {
|
|
1846
|
-
await deliverAgentMailSerialized(ports, db, result.workspacePath, toAgent);
|
|
1925
|
+
outcome = await deliverAgentMailSerialized(ports, db, result.workspacePath, toAgent);
|
|
1847
1926
|
}
|
|
1848
1927
|
catch (err) {
|
|
1849
1928
|
// A gate/write error leaves the row HELD (markDelivered only runs on a
|
|
1850
1929
|
// completed write); the backstop drainer will retry. Report held, not a 500.
|
|
1851
1930
|
ctx.log('ERROR', `Delivery attempt errored for ${toAgent} (row ${row.id.slice(0, 8)}... stays held): ${err.message}`);
|
|
1852
1931
|
}
|
|
1932
|
+
// Issue #1473: this pass ran OUTSIDE the drainer, so hand it the outcome — otherwise a hold
|
|
1933
|
+
// on recent terminal input arms no re-drain here, and the send an operator is watching falls
|
|
1934
|
+
// through to the quiescence trigger or the backstop instead of clearing after one settle.
|
|
1935
|
+
// That is exactly the latency the re-drain exists to remove, in the case where somebody is
|
|
1936
|
+
// waiting for it. A no-op for every other outcome.
|
|
1937
|
+
if (outcome)
|
|
1938
|
+
getMailboxDrainer().noteOutcome(result.workspacePath, toAgent, outcome);
|
|
1853
1939
|
const stored = getMailboxById(db, row.id);
|
|
1854
1940
|
if (stored?.status === 'delivered') {
|
|
1855
1941
|
ctx.log('INFO', `Message delivered: ${from ?? 'unknown'} → ${toAgent} (terminal ${result.terminalId.slice(0, 8)}...)`);
|
|
@@ -1863,11 +1949,33 @@ async function handleSend(req, res, ctx) {
|
|
|
1863
1949
|
mailboxId: row.id,
|
|
1864
1950
|
reason: null,
|
|
1865
1951
|
bodyLength,
|
|
1952
|
+
// Additive (Issue #1584). `false` means the bytes were written and accepted but the header
|
|
1953
|
+
// never appeared on the terminal — delivered, flagged, and NOT re-written. Reported only
|
|
1954
|
+
// when THIS row is the one our own pass delivered: a pass picks the agent's OLDEST held
|
|
1955
|
+
// row, so without the id check a concurrent drainer delivery of this row could be
|
|
1956
|
+
// reported with another message's verification. Absent (verification skipped, or someone
|
|
1957
|
+
// else's pass delivered it) reads exactly as it did before this field existed.
|
|
1958
|
+
...(outcome?.delivered.includes(row.id) && outcome.verified !== undefined
|
|
1959
|
+
? { verified: outcome.verified }
|
|
1960
|
+
: {}),
|
|
1961
|
+
// Issue #1473. `verified` alone was not enough for the SENDER — the case that motivated
|
|
1962
|
+
// it is a human's Enter submitting our half-written body, where the header landed, so
|
|
1963
|
+
// `verified` is `true` (or absent) and this human read a plain "Message delivered". The
|
|
1964
|
+
// cause says which flag it is: `input-raced` (may be truncated or submitted early) or
|
|
1965
|
+
// `no-echo` (header never appeared). Same id guard as `verified` — reported only when
|
|
1966
|
+
// THIS pass is the one that delivered THIS row.
|
|
1967
|
+
...(outcome?.delivered.includes(row.id) && outcome.unverifiedCause !== undefined
|
|
1968
|
+
? { unverifiedCause: outcome.unverifiedCause }
|
|
1969
|
+
: {}),
|
|
1866
1970
|
});
|
|
1867
1971
|
return;
|
|
1868
1972
|
}
|
|
1869
1973
|
const reason = stored?.reason ?? 'busy';
|
|
1870
|
-
|
|
1974
|
+
// Issue #1482: read the detail back off the row the delivery pass just refreshed, rather
|
|
1975
|
+
// than re-deriving it — the row is the record every other surface reads, so the sender's
|
|
1976
|
+
// response and `afx inbox` can never disagree about the same hold.
|
|
1977
|
+
const detail = stored?.detail ?? null;
|
|
1978
|
+
ctx.log('INFO', `Message held (${formatVerdict(reason, detail)}): ${from ?? 'unknown'} → ${toAgent} (mailbox ${row.id.slice(0, 8)}...)`);
|
|
1871
1979
|
// The message stayed held → a new held row is in the set; refresh the indicator
|
|
1872
1980
|
// count (Spec 1313, Phase 7). The delivered branch above needs no fire — the
|
|
1873
1981
|
// delivery path's onHeldStateChange already broadcast when the row left the set.
|
|
@@ -1880,6 +1988,7 @@ async function handleSend(req, res, ctx) {
|
|
|
1880
1988
|
delivered: false,
|
|
1881
1989
|
held: true,
|
|
1882
1990
|
reason,
|
|
1991
|
+
detail,
|
|
1883
1992
|
mailboxId: row.id,
|
|
1884
1993
|
bodyLength,
|
|
1885
1994
|
});
|
|
@@ -1894,20 +2003,55 @@ async function handleSend(req, res, ctx) {
|
|
|
1894
2003
|
* redaction rule): id, addresses, why-held reason, escalation flag, and enqueue time —
|
|
1895
2004
|
* the message BODY is deliberately never surfaced here (it travels only over the live
|
|
1896
2005
|
* terminal stream on delivery). `escalated` is normalized from SQLite's 0/1 to a bool.
|
|
2006
|
+
*
|
|
2007
|
+
* Issue 1450: `workspaceOverride` lets the workspace-scoped route
|
|
2008
|
+
* (`/workspace/<base64>/api/inbox`, which backs the dashboard's held-mail popover) pass the
|
|
2009
|
+
* workspace resolved from the URL prefix, exactly as `handleOverview` / `handleAnalytics` do.
|
|
2010
|
+
* The override arrives already normalized by the prefix decoder, so `normalizeWorkspacePath`
|
|
2011
|
+
* re-running on it is a safe no-op.
|
|
2012
|
+
*
|
|
2013
|
+
* Two separate widening hazards are closed here, because a scoped caller must never receive
|
|
2014
|
+
* another workspace's held mail:
|
|
2015
|
+
* - `??` (not `||`) means `?workspace=` can never take effect once an override was passed.
|
|
2016
|
+
* - An EMPTY override does not fall through to the unscoped all-workspaces listing; it
|
|
2017
|
+
* scopes to a path that matches nothing. Today the dispatcher 400s a missing prefix so
|
|
2018
|
+
* this is unreachable, but "unreachable" is a property of the caller, not of this
|
|
2019
|
+
* function, and the safe failure for a scoped call is zero rows, never every row.
|
|
2020
|
+
* The all-workspaces listing therefore remains reachable only when NO override was passed —
|
|
2021
|
+
* the Tower-level route without `?workspace=`, the direct-caller convenience the CLI never uses.
|
|
2022
|
+
*
|
|
2023
|
+
* NOTE: this lists ALL held rows, including pre-due `--delay` rows, while the badge count
|
|
2024
|
+
* (`heldSummaryForWorkspace`) excludes them — see the `HeldMessage` type for why the two
|
|
2025
|
+
* legitimately disagree.
|
|
1897
2026
|
*/
|
|
1898
|
-
|
|
1899
|
-
|
|
2027
|
+
// Exported as a narrow test seam (Issue 1450): the empty-override branch below cannot be
|
|
2028
|
+
// reached through `handleRequest` — the workspace dispatcher rejects a missing or non-absolute
|
|
2029
|
+
// prefix with a 400 before this runs — so the only way to pin that guard's behaviour is to call
|
|
2030
|
+
// the handler directly. Production callers go through the two route tables, not this export.
|
|
2031
|
+
export function handleInboxList(res, url, workspaceOverride) {
|
|
2032
|
+
const scoped = workspaceOverride !== undefined;
|
|
2033
|
+
const rawWorkspace = workspaceOverride ?? url.searchParams.get('workspace');
|
|
1900
2034
|
// Normalize to the stored realpath key (mailbox workspace_path is normalized at
|
|
1901
2035
|
// enqueue — tower-routes handleSend / holdAndRespond — matching overview.ts). Without
|
|
1902
2036
|
// this a symlinked workspace root would miss its own held rows.
|
|
1903
|
-
|
|
2037
|
+
// `scoped ? '' : undefined` on the empty branch: '' matches no workspace_path, so a scoped
|
|
2038
|
+
// call with a blank override returns nothing rather than widening to every workspace.
|
|
2039
|
+
const workspace = rawWorkspace
|
|
2040
|
+
? normalizeWorkspacePath(rawWorkspace)
|
|
2041
|
+
: (scoped ? '' : undefined);
|
|
1904
2042
|
const rows = listHeldMailbox(getGlobalDb(), workspace);
|
|
2043
|
+
// Annotated, not inferred: `HeldMessage` is the shared contract this route owes its
|
|
2044
|
+
// clients, so a projection that drifts from it (a dropped field, or a `body` slipping in)
|
|
2045
|
+
// fails the server build rather than only surprising the dashboard at runtime.
|
|
1905
2046
|
const projected = rows.map((r) => ({
|
|
1906
2047
|
id: r.id,
|
|
1907
2048
|
workspacePath: r.workspace_path,
|
|
1908
2049
|
toAgent: r.to_agent,
|
|
1909
2050
|
fromAgent: r.from_agent,
|
|
1910
2051
|
reason: r.reason,
|
|
2052
|
+
// Issue #1482: the gate verdict behind a `busy` hold, so `afx inbox` and the dashboard
|
|
2053
|
+
// popover can distinguish an occupied composer from one the classifier could not read.
|
|
2054
|
+
detail: r.detail,
|
|
1911
2055
|
escalated: r.escalated === 1,
|
|
1912
2056
|
createdAt: r.created_at,
|
|
1913
2057
|
// Spec 1313 round 3: due time of a pre-due delayed (`--delay`) row; null = deliver-ASAP.
|
|
@@ -1973,6 +2117,7 @@ function handleInboxShow(req, res, match) {
|
|
|
1973
2117
|
fromWorkspace: row.from_workspace,
|
|
1974
2118
|
status: row.status,
|
|
1975
2119
|
reason: row.reason,
|
|
2120
|
+
detail: row.detail, // Issue #1482: the gate verdict behind a `busy` hold
|
|
1976
2121
|
escalated: row.escalated === 1,
|
|
1977
2122
|
body: row.body,
|
|
1978
2123
|
createdAt: row.created_at,
|
|
@@ -2392,6 +2537,19 @@ async function handleWorkspaceRoutes(req, res, ctx, url) {
|
|
|
2392
2537
|
if (req.method === 'GET' && apiPath === 'overview') {
|
|
2393
2538
|
return handleOverview(res, url, workspacePath, ctx);
|
|
2394
2539
|
}
|
|
2540
|
+
// GET /api/inbox - held mailbox rows for THIS workspace (Issue 1450). Backs the
|
|
2541
|
+
// dashboard's clickable held-mail counter. Reuses the Tower-level handler with the
|
|
2542
|
+
// workspace resolved from the /workspace/<base64>/ prefix, the same way `overview`
|
|
2543
|
+
// and `analytics` above do — the dashboard calls relative `./api/...` and has no
|
|
2544
|
+
// absolute workspace path of its own to pass as `?workspace=`.
|
|
2545
|
+
//
|
|
2546
|
+
// Deliberately an EXACT match on 'inbox', not a prefix: `inbox/:id` (which returns the
|
|
2547
|
+
// message BODY) and `inbox/:id/dismiss` (which mutates) do not match, fall through to
|
|
2548
|
+
// the 404 below, and so stay off the dashboard surface. That is what keeps this read
|
|
2549
|
+
// path inside Spec 1313's redaction rule and decision 8 (dismissal is CLI-only).
|
|
2550
|
+
if (req.method === 'GET' && apiPath === 'inbox') {
|
|
2551
|
+
return handleInboxList(res, url, workspacePath);
|
|
2552
|
+
}
|
|
2395
2553
|
// POST /api/overview/refresh - Invalidate overview cache (Spec 0126 Phase 4)
|
|
2396
2554
|
if (req.method === 'POST' && apiPath === 'overview/refresh') {
|
|
2397
2555
|
return handleOverviewRefresh(res, ctx);
|
|
@@ -2576,9 +2734,9 @@ async function handleWorkspaceShellCreate(res, ctx, workspacePath) {
|
|
|
2576
2734
|
if (shellperManager) {
|
|
2577
2735
|
try {
|
|
2578
2736
|
const sessionId = crypto.randomUUID();
|
|
2579
|
-
// Strip
|
|
2580
|
-
|
|
2581
|
-
|
|
2737
|
+
// Strip Claude Code session markers so spawned Claude processes don't
|
|
2738
|
+
// detect nesting and disable transcript saving (#1219).
|
|
2739
|
+
const shellEnv = sanitizeAgentEnv(process.env);
|
|
2582
2740
|
// Inject session identity for afx rename (Spec 468)
|
|
2583
2741
|
shellEnv['SHELLPER_SESSION_ID'] = sessionId;
|
|
2584
2742
|
shellEnv['TOWER_PORT'] = String(ctx.port);
|
|
@@ -2612,7 +2770,9 @@ async function handleWorkspaceShellCreate(res, ctx, workspacePath) {
|
|
|
2612
2770
|
}
|
|
2613
2771
|
const entry = getWorkspaceTerminalsEntry(workspacePath);
|
|
2614
2772
|
entry.shells.set(shellId, session.id);
|
|
2615
|
-
|
|
2773
|
+
// PIR #1475: hydrated identity wins; a fresh spawn reports back `shellCmd`.
|
|
2774
|
+
logSessionIdentity(ctx.log, 'shell-create', session.id, ptySession, shellCmd);
|
|
2775
|
+
saveTerminalSession(session.id, workspacePath, 'shell', shellId, shellperInfo.pid, shellperInfo.socketPath, shellperInfo.pid, shellperInfo.startTime, session.label, workspacePath, persistableCommand(ptySession) ?? shellCmd);
|
|
2616
2776
|
shellCreated = true;
|
|
2617
2777
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
2618
2778
|
res.end(JSON.stringify({
|
|
@@ -2637,7 +2797,7 @@ async function handleWorkspaceShellCreate(res, ctx, workspacePath) {
|
|
|
2637
2797
|
args: shellArgs,
|
|
2638
2798
|
cwd: workspacePath,
|
|
2639
2799
|
label: `Shell ${shellId.replace('shell-', '')}`,
|
|
2640
|
-
env: process.env,
|
|
2800
|
+
env: sanitizeAgentEnv(process.env),
|
|
2641
2801
|
});
|
|
2642
2802
|
const entry = getWorkspaceTerminalsEntry(workspacePath);
|
|
2643
2803
|
entry.shells.set(shellId, session.id);
|