tmux-ide 2.9.0-beta.19 → 2.9.0-beta.21
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/bin/cli.js +1979 -783
- package/package.json +4 -2
- package/packages/contracts/src/__tests__/daemon-wire.test.ts +32 -0
- package/packages/contracts/src/daemon-events.ts +12 -12
- package/packages/contracts/src/daemon-wire.ts +30 -0
- package/packages/daemon/dist/command-center/agent-status-watch.js +9 -2
- package/packages/daemon/dist/command-center/diagnostics.js +65 -0
- package/packages/daemon/dist/command-center/log-stream.js +9 -0
- package/packages/daemon/dist/command-center/resources/fleet-preview-route.js +22 -6
- package/packages/daemon/dist/command-center/server.js +7 -0
- package/packages/daemon/dist/doctor.js +62 -0
- package/packages/daemon/dist/lib/__tests__/installed-recovery-fixture.js +400 -0
- package/packages/daemon/dist/lib/app-config.js +4 -2
- package/packages/daemon/dist/lib/canonical-daemon.js +1 -0
- package/packages/daemon/dist/lib/daemon-embed.js +24 -0
- package/packages/daemon/dist/lib/daemon-provenance.js +575 -0
- package/packages/daemon/dist/lib/headless-daemon.js +1 -0
- package/packages/daemon/dist/lib/log-sanitize.js +199 -0
- package/packages/daemon/dist/lib/log.js +123 -13
- package/packages/daemon/dist/lib/soak-diagnostics.js +124 -0
- package/packages/daemon/dist/lib/soak-verdict.js +472 -0
- package/packages/daemon/dist/lib/terminal-host-color.js +33 -0
- package/packages/daemon/dist/lib/tmux-external-interaction-observer.js +187 -39
- package/packages/daemon/dist/lib/tmux-interaction-retention.js +21 -0
- package/packages/daemon/dist/lib/workspace-promotion.js +55 -37
- package/packages/daemon/dist/terminal/mirror/session-channel.js +2 -1
- package/packages/daemon/dist/tui/mirror/automatic-contrast.js +161 -0
- package/packages/daemon/dist/tui/mirror/open-tui-workspace-runtime-port.js +9 -3
- package/packages/daemon/dist/tui/mirror/pane-surface.jsx +7 -5
- package/packages/daemon/dist/tui/mirror/resize-transaction.js +48 -20
- package/packages/daemon/dist/tui/mirror/runtime/application-appearance-owner.js +40 -6
- package/packages/daemon/dist/tui/mirror/runtime/application-fleet-preview.js +4 -1
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +63 -42
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js +138 -67
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-palette-owner.js +44 -28
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-workspace.jsx +49 -11
- package/packages/daemon/dist/tui/mirror/runtime/semantic-shell-viewport-resize.js +140 -2
- package/packages/daemon/dist/tui/mirror/runtime/workspace-terminal-fast-lane.js +3 -1
- package/packages/daemon/dist/tui/mirror/semantic-pane-render-source.js +2 -1
- package/packages/daemon/dist/tui/mirror/theme.js +4 -31
- package/packages/daemon/dist/tui/mirror/workspace/terminal-pane-header.jsx +15 -8
- package/packages/daemon/dist/tui/team/wait-receipts.js +112 -33
- package/packages/daemon/src/command-center/agent-status-watch.ts +8 -2
- package/packages/daemon/src/command-center/diagnostics.ts +75 -0
- package/packages/daemon/src/command-center/log-stream.ts +8 -0
- package/packages/daemon/src/command-center/resources/fleet-preview-route.ts +27 -5
- package/packages/daemon/src/command-center/server.ts +8 -0
- package/packages/daemon/src/doctor.ts +70 -0
- package/packages/daemon/src/lib/app-config.ts +11 -3
- package/packages/daemon/src/lib/canonical-daemon.ts +1 -0
- package/packages/daemon/src/lib/daemon-embed.ts +30 -0
- package/packages/daemon/src/lib/daemon-provenance.ts +769 -0
- package/packages/daemon/src/lib/fleet-preview-model.ts +1 -0
- package/packages/daemon/src/lib/headless-daemon.ts +1 -0
- package/packages/daemon/src/lib/log-sanitize.ts +248 -0
- package/packages/daemon/src/lib/log.ts +165 -14
- package/packages/daemon/src/lib/soak-diagnostics.ts +183 -0
- package/packages/daemon/src/lib/soak-verdict.ts +785 -0
- package/packages/daemon/src/lib/terminal-host-color.ts +43 -0
- package/packages/daemon/src/lib/tmux-external-interaction-observer.ts +233 -37
- package/packages/daemon/src/lib/tmux-interaction-retention.ts +24 -0
- package/packages/daemon/src/lib/workspace-promotion.ts +74 -45
- package/packages/daemon/src/terminal/mirror/session-channel.ts +2 -1
- package/packages/daemon/src/tui/mirror/automatic-contrast.ts +180 -0
- package/packages/daemon/src/tui/mirror/open-tui-workspace-runtime-port.ts +26 -5
- package/packages/daemon/src/tui/mirror/pane-surface.tsx +9 -8
- package/packages/daemon/src/tui/mirror/resize-transaction.ts +46 -22
- package/packages/daemon/src/tui/mirror/runtime/application-appearance-owner.ts +45 -6
- package/packages/daemon/src/tui/mirror/runtime/application-fleet-preview.ts +4 -0
- package/packages/daemon/src/tui/mirror/runtime/application-fleet-switcher.tsx +2 -1
- package/packages/daemon/src/tui/mirror/runtime/application-machine-sidebar.tsx +99 -75
- package/packages/daemon/src/tui/mirror/runtime/application-palette-preview.tsx +13 -3
- package/packages/daemon/src/tui/mirror/runtime/application-reference-sheet.tsx +125 -0
- package/packages/daemon/src/tui/mirror/runtime/application-root-v2.tsx +2 -1
- package/packages/daemon/src/tui/mirror/runtime/application-shell-overlays.tsx +199 -157
- package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +2 -0
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts +148 -69
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-palette-owner.ts +55 -28
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-workspace.tsx +59 -17
- package/packages/daemon/src/tui/mirror/runtime/semantic-shell-viewport-resize.ts +151 -3
- package/packages/daemon/src/tui/mirror/runtime/workspace-terminal-fast-lane.ts +3 -1
- package/packages/daemon/src/tui/mirror/semantic-pane-render-source.ts +2 -1
- package/packages/daemon/src/tui/mirror/theme.ts +4 -39
- package/packages/daemon/src/tui/mirror/workspace/terminal-pane-header.tsx +21 -8
- package/packages/daemon/src/tui/team/wait-receipts.ts +115 -40
- package/packages/daemon-client/src/terminal-fast-lane.test.ts +18 -0
- package/packages/daemon-client/src/terminal-fast-lane.ts +7 -2
- package/packages/tmux-bridge/src/index.ts +2 -0
- package/packages/tmux-bridge/src/runner.test.ts +41 -0
- package/packages/tmux-bridge/src/runner.ts +41 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tmux-ide",
|
|
3
|
-
"version": "2.9.0-beta.
|
|
3
|
+
"version": "2.9.0-beta.21",
|
|
4
4
|
"description": "A visual, agent-aware IDE for any tmux session, with optional workspace presets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"typecheck": "echo \"root typecheck deferred to per-package turbo run\"",
|
|
40
40
|
"dev": "node bin/cli.js",
|
|
41
41
|
"dev:instance": "node scripts/development-instance.mjs",
|
|
42
|
+
"dev:pilotty": "node scripts/pilotty-dev.mjs",
|
|
42
43
|
"dev:web": "node scripts/dev-web.mjs",
|
|
43
44
|
"test": "pnpm -r --workspace-concurrency=1 --filter @tmux-ide/daemon --filter @tmux-ide/contracts --filter @tmux-ide/core --filter @tmux-ide/daemon-client --filter @tmux-ide/sdk --filter @tmux-ide/desktop-renderer --filter @tmux-ide/web-workspace --filter @tmux-ide/electron-shell run test",
|
|
44
45
|
"test:unit": "pnpm -r --workspace-concurrency=1 --filter @tmux-ide/daemon --filter @tmux-ide/contracts --filter @tmux-ide/core --filter @tmux-ide/daemon-client --filter @tmux-ide/sdk --filter @tmux-ide/desktop-renderer --filter @tmux-ide/web-workspace --filter @tmux-ide/electron-shell run test",
|
|
@@ -63,11 +64,12 @@
|
|
|
63
64
|
"postinstall": "node scripts/postinstall.js",
|
|
64
65
|
"docs": "turbo run dev --filter=@tmux-ide/docs",
|
|
65
66
|
"demo:tui": "bun --preload @opentui/solid/preload docs/scripts/render-tui-demo.tsx",
|
|
66
|
-
"test:tui-renderer": "bun test --preload @opentui/solid/preload --preload ./packages/daemon/test-support/opentui-renderer-preload.ts ./packages/daemon/src/tui/mirror/pane-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/native-grid-projection-renderer.test.tsx ./packages/daemon/src/tui/mirror/widget-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/missions-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/recipes-gallery-renderer.test.tsx ./packages/daemon/src/tui/mirror/shell-chrome-renderer.test.tsx ./packages/daemon/src/tui/mirror/sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/home-files-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/changes-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/activity-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/files/session-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-terminal-workspace-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-view-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-root-error-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-backpressure-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-demand-cadence-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/files-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/changes-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/missions-activity-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/dialogs-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/optional-feature-registry-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/palette-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/pane-scoped-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/rich-preview/feature.test.ts ./packages/daemon/src/tui/mirror/runtime/rich-preview-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/application-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/pane-frame-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-chrome-view.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-window-strip-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-dock-dual-host-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/agent-terminal-canvas-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/command-palette-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/opentui-insertion-stability-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-home-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-header-polish-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-roster-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-flow-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-sidebar-catalog-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/ui-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-machine-sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-add-machine-dialog-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-switcher-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-palette-preview-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-palette-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-session-actions-renderer.test.tsx",
|
|
67
|
+
"test:tui-renderer": "bun test --preload @opentui/solid/preload --preload ./packages/daemon/test-support/opentui-renderer-preload.ts ./packages/daemon/src/tui/mirror/automatic-contrast-renderer.test.tsx ./packages/daemon/src/tui/mirror/pane-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/native-grid-projection-renderer.test.tsx ./packages/daemon/src/tui/mirror/widget-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/missions-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/recipes-gallery-renderer.test.tsx ./packages/daemon/src/tui/mirror/shell-chrome-renderer.test.tsx ./packages/daemon/src/tui/mirror/sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/home-files-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/changes-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/activity-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/files/session-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-terminal-workspace-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-view-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-root-error-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-backpressure-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-demand-cadence-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/files-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/changes-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/missions-activity-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/dialogs-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/optional-feature-registry-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/palette-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/pane-scoped-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/rich-preview/feature.test.ts ./packages/daemon/src/tui/mirror/runtime/rich-preview-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/application-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/pane-frame-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-chrome-view.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-window-strip-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-dock-dual-host-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/agent-terminal-canvas-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/command-palette-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/opentui-insertion-stability-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-home-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-header-polish-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-roster-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-flow-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-sidebar-catalog-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/ui-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-machine-sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-add-machine-dialog-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-switcher-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-palette-preview-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-palette-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-session-actions-renderer.test.tsx",
|
|
67
68
|
"test:tui-smoke": "bun scripts/smoke-tui-missions.mjs",
|
|
68
69
|
"test:tui-live": "node scripts/tui-testdrive.mjs smoke",
|
|
69
70
|
"test:tui-testdrive": "node --test scripts/lib/tui-testdrive-clipboard-hook.test.mjs scripts/lib/tui-testdrive-input.test.mjs",
|
|
70
71
|
"test:performance-qualification": "node scripts/performance-qualification.mjs",
|
|
72
|
+
"qualify:installed-latency": "node packages/daemon/scripts/qualify-installed-latency.mjs",
|
|
71
73
|
"benchmark:comparative": "node scripts/comparative-terminal.mjs",
|
|
72
74
|
"test:contracts-initializer-purity": "node --test scripts/lib/contracts-initializer-purity.test.mjs",
|
|
73
75
|
"test:benchmark-comparative": "node --test scripts/comparative-terminal-support.test.mjs scripts/lib/terminal-scroll-observations.test.mjs scripts/lib/comparative-terminal-resources.test.mjs scripts/lib/comparative-terminal-report.test.mjs",
|
|
@@ -4,6 +4,7 @@ import { describe, expect, it } from "vitest";
|
|
|
4
4
|
import {
|
|
5
5
|
CanonicalDaemonInfoSchema,
|
|
6
6
|
DAEMON_WIRE_PROTOCOL_VERSION,
|
|
7
|
+
DaemonProvenanceSchema,
|
|
7
8
|
DaemonHealthSchema,
|
|
8
9
|
DaemonHealthzSchema,
|
|
9
10
|
DaemonIdentitySchema,
|
|
@@ -115,3 +116,34 @@ describe("environment identity (additive, both directions)", () => {
|
|
|
115
116
|
expect(withId.daemon.startedAt).toBe(identityFields.startedAt);
|
|
116
117
|
});
|
|
117
118
|
});
|
|
119
|
+
|
|
120
|
+
describe("daemon provenance (additive)", () => {
|
|
121
|
+
const fixture = JSON.parse(readFileSync(fixturePath, "utf8")) as {
|
|
122
|
+
canonical: Record<string, unknown>;
|
|
123
|
+
};
|
|
124
|
+
const provenance = {
|
|
125
|
+
launcher: "headless",
|
|
126
|
+
supervisor: "systemd",
|
|
127
|
+
parentPid: 1,
|
|
128
|
+
stdout: { kind: "file", path: "/var/log/tmux-ide.out", dev: 1, ino: 2 },
|
|
129
|
+
stderr: { kind: "socket" },
|
|
130
|
+
warnings: ["stderr: path unresolved"],
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
it("parses records with and without provenance", () => {
|
|
134
|
+
expect(CanonicalDaemonInfoSchema.parse(fixture.canonical).provenance).toBeUndefined();
|
|
135
|
+
expect(
|
|
136
|
+
CanonicalDaemonInfoSchema.parse({ ...fixture.canonical, provenance }).provenance,
|
|
137
|
+
).toEqual(provenance);
|
|
138
|
+
expect(DaemonProvenanceSchema.parse(provenance)).toEqual(provenance);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("rejects unknown supervisors and stream kinds", () => {
|
|
142
|
+
expect(DaemonProvenanceSchema.safeParse({ ...provenance, supervisor: "cron" }).success).toBe(
|
|
143
|
+
false,
|
|
144
|
+
);
|
|
145
|
+
expect(
|
|
146
|
+
DaemonProvenanceSchema.safeParse({ ...provenance, stdout: { kind: "journal" } }).success,
|
|
147
|
+
).toBe(false);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -268,21 +268,21 @@ export const DaemonEventFleetChangedFrameSchemaZ = z
|
|
|
268
268
|
.strict();
|
|
269
269
|
|
|
270
270
|
/**
|
|
271
|
-
* RECEIPT — a pane
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
271
|
+
* RECEIPT — a sampled pane-state transition: the daemon's agent-status watcher
|
|
272
|
+
* observed `@agent_state` change from `working` to `done` or `idle` while the
|
|
273
|
+
* runtime pane and durable pane stamp remained equal. This does not establish
|
|
274
|
+
* agent process continuity or success of a submitted task. Session-status
|
|
275
|
+
* waits use it as a hint to re-read aggregate status, since another agent in
|
|
276
|
+
* the session may still be working or blocked.
|
|
277
277
|
*
|
|
278
|
-
* `agentId` is the wire-safe durable agent identity — the same
|
|
278
|
+
* `agentId` is the wire-safe durable pane-derived agent identity — the same
|
|
279
279
|
* `agent.<digest>` id the application-shell sidebar mints from the pane's
|
|
280
280
|
* durable `@tmux_ide_pane_id` stamp — or `null` when the pane carries no valid
|
|
281
|
-
* stamp
|
|
282
|
-
* runtime id or path ever crosses this frame. `at` is the daemon's
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
281
|
+
* stamp. Anonymous receipts provide no per-agent correlation. No raw tmux
|
|
282
|
+
* runtime id or path ever crosses this frame. `at` is the daemon's observation
|
|
283
|
+
* time, not the hook or task completion time. Transitions entirely between
|
|
284
|
+
* successful polling samples may be missed; this is not a durable turn log.
|
|
285
|
+
* At most one receipt is emitted per completing pane per poll tick.
|
|
286
286
|
*/
|
|
287
287
|
export const DaemonEventAgentTurnCompletedFrameSchemaZ = z
|
|
288
288
|
.object({
|
|
@@ -67,6 +67,35 @@ export const CanonicalDaemonReservationSchema = z
|
|
|
67
67
|
.strict();
|
|
68
68
|
export type CanonicalDaemonReservation = z.infer<typeof CanonicalDaemonReservationSchema>;
|
|
69
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Where a daemon's stdout/stderr actually go, captured by the running process
|
|
72
|
+
* at startup. `dev`/`ino` identify a regular file even when its path could not
|
|
73
|
+
* be resolved, so a provenance report can match on-disk log files against the
|
|
74
|
+
* live destination instead of guessing from file names.
|
|
75
|
+
*/
|
|
76
|
+
export const DaemonLogStreamSchema = z.object({
|
|
77
|
+
kind: z.enum(["file", "tty", "pipe", "socket", "null", "unknown"]),
|
|
78
|
+
path: z.string().min(1).max(4096).optional(),
|
|
79
|
+
dev: z.number().int().nonnegative().optional(),
|
|
80
|
+
ino: z.number().int().nonnegative().optional(),
|
|
81
|
+
detail: z.string().max(256).optional(),
|
|
82
|
+
});
|
|
83
|
+
export type DaemonLogStream = z.infer<typeof DaemonLogStreamSchema>;
|
|
84
|
+
|
|
85
|
+
export const DaemonSupervisorKindSchema = z.enum(["manual", "launchd", "systemd", "embedded"]);
|
|
86
|
+
export type DaemonSupervisorKind = z.infer<typeof DaemonSupervisorKindSchema>;
|
|
87
|
+
|
|
88
|
+
/** Startup provenance stamped into the daemon record; never carries credentials. */
|
|
89
|
+
export const DaemonProvenanceSchema = z.object({
|
|
90
|
+
launcher: z.enum(["headless", "embedded"]),
|
|
91
|
+
supervisor: DaemonSupervisorKindSchema,
|
|
92
|
+
parentPid: z.number().int().nonnegative(),
|
|
93
|
+
stdout: DaemonLogStreamSchema,
|
|
94
|
+
stderr: DaemonLogStreamSchema,
|
|
95
|
+
warnings: z.array(z.string().max(256)).max(8).optional(),
|
|
96
|
+
});
|
|
97
|
+
export type DaemonProvenance = z.infer<typeof DaemonProvenanceSchema>;
|
|
98
|
+
|
|
70
99
|
export const CanonicalDaemonInfoSchema = z.object({
|
|
71
100
|
supervisionId: DaemonSupervisionIdSchema.optional(),
|
|
72
101
|
pid: z.number().int().positive(),
|
|
@@ -78,6 +107,7 @@ export const CanonicalDaemonInfoSchema = z.object({
|
|
|
78
107
|
environmentId: EnvironmentIdSchema.optional(),
|
|
79
108
|
bindHostname: z.string().trim().min(1),
|
|
80
109
|
authToken: z.string().min(1).nullable(),
|
|
110
|
+
provenance: DaemonProvenanceSchema.optional(),
|
|
81
111
|
});
|
|
82
112
|
export type CanonicalDaemonInfo = z.infer<typeof CanonicalDaemonInfoSchema>;
|
|
83
113
|
|
|
@@ -74,6 +74,10 @@ export function diffChangedSessions(previous, next) {
|
|
|
74
74
|
* Completed turns between two readings: panes present in BOTH whose state word
|
|
75
75
|
* left `working` for `done` or `idle`. A pane that vanished mid-turn completed
|
|
76
76
|
* nothing; a pane that appeared already-done transitioned nothing we observed.
|
|
77
|
+
* The durable stamp must remain equal: gaining, losing or replacing it is not
|
|
78
|
+
* evidence that the replacement completed the prior turn. Two unstamped reads
|
|
79
|
+
* retain anonymous receipts for compatibility, without process/task correlation.
|
|
80
|
+
* These sampled status transitions do not prove submitted-task success.
|
|
77
81
|
* Sorted (session, then pane key) for deterministic emission order.
|
|
78
82
|
*/
|
|
79
83
|
export function diffTurnCompletions(previous, next) {
|
|
@@ -87,12 +91,15 @@ export function diffTurnCompletions(previous, next) {
|
|
|
87
91
|
const prior = before.get(paneId);
|
|
88
92
|
if (prior === undefined || agentStateWord(prior.state) !== "working")
|
|
89
93
|
continue;
|
|
90
|
-
const
|
|
94
|
+
const current = panes.get(paneId);
|
|
95
|
+
if (prior.paneStamp !== current.paneStamp)
|
|
96
|
+
continue;
|
|
97
|
+
const word = agentStateWord(current.state);
|
|
91
98
|
if (word !== "done" && word !== "idle")
|
|
92
99
|
continue;
|
|
93
100
|
completions.push({
|
|
94
101
|
sessionName,
|
|
95
|
-
paneStamp:
|
|
102
|
+
paneStamp: current.paneStamp,
|
|
96
103
|
fromStatus: "working",
|
|
97
104
|
toStatus: word,
|
|
98
105
|
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { performance } from "node:perf_hooks";
|
|
2
|
+
import { requireOwnerAuthority } from "./owner-authority.js";
|
|
3
|
+
/** One synchronous, demand-only sample. No observers, discovery, or retained baseline. */
|
|
4
|
+
export function sampleDaemonDiagnostics(daemon) {
|
|
5
|
+
const sampledAtMs = Date.now();
|
|
6
|
+
const memory = process.memoryUsage();
|
|
7
|
+
const cpu = process.cpuUsage();
|
|
8
|
+
const eventLoop = performance.eventLoopUtilization();
|
|
9
|
+
// Fixed output cardinality. Unknown runtime resource labels are never echoed.
|
|
10
|
+
const activeResources = {
|
|
11
|
+
Timeout: 0,
|
|
12
|
+
Immediate: 0,
|
|
13
|
+
TCPServerWrap: 0,
|
|
14
|
+
TCPSocketWrap: 0,
|
|
15
|
+
PipeWrap: 0,
|
|
16
|
+
ProcessWrap: 0,
|
|
17
|
+
FSEventWrap: 0,
|
|
18
|
+
other: 0,
|
|
19
|
+
};
|
|
20
|
+
const resourceNames = process.getActiveResourcesInfo?.();
|
|
21
|
+
for (const resource of resourceNames ?? []) {
|
|
22
|
+
if (Object.hasOwn(activeResources, resource) && resource !== "other") {
|
|
23
|
+
activeResources[resource]++;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
activeResources.other++;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
daemon: {
|
|
31
|
+
protocolVersion: daemon.protocolVersion,
|
|
32
|
+
productVersion: daemon.productVersion,
|
|
33
|
+
instanceId: daemon.instanceId,
|
|
34
|
+
startedAt: daemon.startedAt,
|
|
35
|
+
...(daemon.environmentId !== undefined ? { environmentId: daemon.environmentId } : {}),
|
|
36
|
+
},
|
|
37
|
+
pid: process.pid,
|
|
38
|
+
uptimeMs: process.uptime() * 1000,
|
|
39
|
+
sampledAtMs,
|
|
40
|
+
memory: {
|
|
41
|
+
rss: memory.rss,
|
|
42
|
+
heapTotal: memory.heapTotal,
|
|
43
|
+
heapUsed: memory.heapUsed,
|
|
44
|
+
external: memory.external,
|
|
45
|
+
arrayBuffers: memory.arrayBuffers,
|
|
46
|
+
},
|
|
47
|
+
cpu: { user: cpu.user, system: cpu.system },
|
|
48
|
+
eventLoop: {
|
|
49
|
+
idle: eventLoop.idle,
|
|
50
|
+
active: eventLoop.active,
|
|
51
|
+
utilization: eventLoop.utilization,
|
|
52
|
+
},
|
|
53
|
+
activeResources: resourceNames === undefined ? null : activeResources,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function mountDiagnosticsRoute(app, options) {
|
|
57
|
+
app.get("/api/diagnostics", requireOwnerAuthority(options.ownerToken, {
|
|
58
|
+
whenOwnerless: "unavailable",
|
|
59
|
+
unavailableMessage: "Diagnostics unavailable",
|
|
60
|
+
mismatchMessage: "Diagnostics require owner authority",
|
|
61
|
+
}), (c) => {
|
|
62
|
+
c.header("Cache-Control", "no-store");
|
|
63
|
+
return c.json(sampleDaemonDiagnostics(options.daemon));
|
|
64
|
+
});
|
|
65
|
+
}
|
|
@@ -8,6 +8,7 @@ export async function streamBoundedLogs(stream, options) {
|
|
|
8
8
|
let wake = null;
|
|
9
9
|
let unsubscribe = () => { };
|
|
10
10
|
let timer = null;
|
|
11
|
+
let heartbeatTimer = null;
|
|
11
12
|
let cancelWrite = null;
|
|
12
13
|
const cleanup = () => {
|
|
13
14
|
if (closed)
|
|
@@ -19,6 +20,9 @@ export async function streamBoundedLogs(stream, options) {
|
|
|
19
20
|
if (timer)
|
|
20
21
|
clearTimeout(timer);
|
|
21
22
|
timer = null;
|
|
23
|
+
if (heartbeatTimer)
|
|
24
|
+
clearInterval(heartbeatTimer);
|
|
25
|
+
heartbeatTimer = null;
|
|
22
26
|
wake?.();
|
|
23
27
|
wake = null;
|
|
24
28
|
cancelWrite?.();
|
|
@@ -51,6 +55,11 @@ export async function streamBoundedLogs(stream, options) {
|
|
|
51
55
|
unsubscribe();
|
|
52
56
|
return;
|
|
53
57
|
}
|
|
58
|
+
heartbeatTimer = setInterval(() => {
|
|
59
|
+
if (!closed && queue.length === 0 && !push({ event: "heartbeat", data: "keep-alive" }))
|
|
60
|
+
abort();
|
|
61
|
+
}, options.heartbeatIntervalMs ?? 15_000);
|
|
62
|
+
heartbeatTimer.unref?.();
|
|
54
63
|
try {
|
|
55
64
|
// Keep newest bounded history, reserving room for gap/bookmark metadata.
|
|
56
65
|
const retained = [];
|
|
@@ -5,7 +5,7 @@ import { ownerAuthorityGate } from "../owner-authority.js";
|
|
|
5
5
|
import { discoverLiveSessionSummaries } from "../discovery.js";
|
|
6
6
|
const cleanText = (value) => stripVTControlCharacters(value).replace(/[^\P{Cc}\n\t]/gu, "");
|
|
7
7
|
export function createFleetPreviewCapture(run) {
|
|
8
|
-
const snapshot = async (liveSessionId, signal, windowId) => {
|
|
8
|
+
const snapshot = async (liveSessionId, signal, windowId, paneId) => {
|
|
9
9
|
const readSessions = async () => {
|
|
10
10
|
const raw = await run(["list-panes", "-a", "-F", "#{pid}\t#{session_id}\t#{session_created}\t#{session_name}"], signal);
|
|
11
11
|
return discoverLiveSessionSummaries(() => raw);
|
|
@@ -41,7 +41,14 @@ export function createFleetPreviewCapture(run) {
|
|
|
41
41
|
.slice(0, 80));
|
|
42
42
|
}
|
|
43
43
|
const windowRows = rows.filter((row) => /^@\d+$/u.test(row[3] ?? ""));
|
|
44
|
-
const
|
|
44
|
+
const requestedPane = paneId ? windowRows.find((row) => row[0] === paneId) : undefined;
|
|
45
|
+
if (paneId && (!requestedPane || (windowId && requestedPane[3] !== windowId)))
|
|
46
|
+
return null;
|
|
47
|
+
const selectedWindowId = requestedPane?.[3] ??
|
|
48
|
+
windowId ??
|
|
49
|
+
windowRows.find((row) => row[1] === "1")?.[3] ??
|
|
50
|
+
windowRows[0]?.[3] ??
|
|
51
|
+
null;
|
|
45
52
|
if (windowId && !windowRows.some((row) => row[3] === windowId))
|
|
46
53
|
return null;
|
|
47
54
|
const toWindow = (row) => ({
|
|
@@ -80,7 +87,7 @@ export function createFleetPreviewCapture(run) {
|
|
|
80
87
|
}
|
|
81
88
|
windows.sort((a, b) => a.index - b.index);
|
|
82
89
|
const candidates = selectedWindowId ? rows.filter((r) => r[3] === selectedWindowId) : rows;
|
|
83
|
-
const pane = candidates.find((r) => r[2] === "1")?.[0] ?? candidates[0]?.[0];
|
|
90
|
+
const pane = paneId ?? candidates.find((r) => r[2] === "1")?.[0] ?? candidates[0]?.[0];
|
|
84
91
|
if (!pane || !/^%\d+$/u.test(pane))
|
|
85
92
|
return null;
|
|
86
93
|
// Passive capture never changes active windows, size, input or terminal ownership.
|
|
@@ -92,6 +99,7 @@ export function createFleetPreviewCapture(run) {
|
|
|
92
99
|
return null;
|
|
93
100
|
return {
|
|
94
101
|
windows,
|
|
102
|
+
selectedPaneId: pane,
|
|
95
103
|
selectedWindowId,
|
|
96
104
|
text: cleanText(captured)
|
|
97
105
|
.split("\n")
|
|
@@ -106,6 +114,10 @@ export function createFleetPreviewCapture(run) {
|
|
|
106
114
|
}
|
|
107
115
|
const requestSchema = z.strictObject({
|
|
108
116
|
expectedInstanceId: z.uuid(),
|
|
117
|
+
paneId: z
|
|
118
|
+
.string()
|
|
119
|
+
.regex(/^%\d+$/u)
|
|
120
|
+
.optional(),
|
|
109
121
|
windowId: z
|
|
110
122
|
.string()
|
|
111
123
|
.regex(/^@\d+$/u)
|
|
@@ -152,10 +164,10 @@ export function mountFleetPreviewRoute(app, options) {
|
|
|
152
164
|
pending = true;
|
|
153
165
|
try {
|
|
154
166
|
const signal = AbortSignal.any([c.req.raw.signal, AbortSignal.timeout(1500)]);
|
|
155
|
-
if (input.data.windowId && !options.capture.snapshot)
|
|
167
|
+
if ((input.data.windowId || input.data.paneId) && !options.capture.snapshot)
|
|
156
168
|
return c.json({ error: "Window preview unavailable" }, 503);
|
|
157
169
|
const snapshot = options.capture.snapshot
|
|
158
|
-
? await options.capture.snapshot(input.data.liveSessionId, signal, input.data.windowId)
|
|
170
|
+
? await options.capture.snapshot(input.data.liveSessionId, signal, input.data.windowId, input.data.paneId)
|
|
159
171
|
: null;
|
|
160
172
|
// Older capture providers remain valid for the original text-only contract.
|
|
161
173
|
const legacy = !options.capture.snapshot
|
|
@@ -169,7 +181,11 @@ export function mountFleetPreviewRoute(app, options) {
|
|
|
169
181
|
liveSessionId: input.data.liveSessionId,
|
|
170
182
|
text,
|
|
171
183
|
...(snapshot
|
|
172
|
-
? {
|
|
184
|
+
? {
|
|
185
|
+
windows: snapshot.windows,
|
|
186
|
+
selectedWindowId: snapshot.selectedWindowId,
|
|
187
|
+
selectedPaneId: snapshot.selectedPaneId,
|
|
188
|
+
}
|
|
173
189
|
: {}),
|
|
174
190
|
});
|
|
175
191
|
}
|
|
@@ -63,6 +63,7 @@ import { mountWorkspaceResourceRoutes } from "./resources/workspace-resource-rou
|
|
|
63
63
|
import { mountFleetResourceRoute } from "./resources/fleet-resource-route.js";
|
|
64
64
|
import { mountWorkspaceMissionsRoute } from "./resources/workspace-missions-route.js";
|
|
65
65
|
import { ownerAuthorityGate, requireOwnerAuthority } from "./owner-authority.js";
|
|
66
|
+
import { mountDiagnosticsRoute } from "./diagnostics.js";
|
|
66
67
|
import { mountStartupReadinessRoute, } from "./resources/startup-readiness-route.js";
|
|
67
68
|
import { readWidgetAsset } from "../lib/widget-asset-store.js";
|
|
68
69
|
const defaultApplicationShellAppWindowBackend = {
|
|
@@ -316,6 +317,12 @@ export function createApp(options = {}) {
|
|
|
316
317
|
const app = new Hono();
|
|
317
318
|
// Allow cross-origin (Next.js dashboard, Tailscale, etc.)
|
|
318
319
|
app.use("/*", cors());
|
|
320
|
+
// Owner-only reads use the same early routing boundary as issuance below:
|
|
321
|
+
// remote/project credentials neither authorize nor block the owner bearer.
|
|
322
|
+
mountDiagnosticsRoute(app, {
|
|
323
|
+
daemon: daemonInstanceIdentity,
|
|
324
|
+
ownerToken: options.remoteAccess?.ownerToken ?? null,
|
|
325
|
+
});
|
|
319
326
|
// This exact route carries its own owner-only bearer and correlation gate.
|
|
320
327
|
// Mount it before remote and project auth so a valid host capability is
|
|
321
328
|
// neither rejected nor confused with any remotely shared user credential.
|
|
@@ -9,6 +9,7 @@ import { findCompiledTui, hasDevelopmentTuiSource, isBunAvailable } from "./tui/
|
|
|
9
9
|
import { claudeSettingsPath } from "./tui/integrations/claude.js";
|
|
10
10
|
import { readNotificationPrefs, resolveNativeMacosNotifierPath } from "./tui/chrome/notify.js";
|
|
11
11
|
import { resolveConfig } from "./lib/resolved-config.js";
|
|
12
|
+
import { collectDaemonProvenanceReport, } from "./lib/daemon-provenance.js";
|
|
12
13
|
/**
|
|
13
14
|
* PURE — the "agent integrations" doctor rows, one per DISCOVERED agent (absent
|
|
14
15
|
* agents produce nothing — no noise). All rows are optional (informational, they
|
|
@@ -91,6 +92,52 @@ export function notifierRow(present) {
|
|
|
91
92
|
optional: true,
|
|
92
93
|
};
|
|
93
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* PURE — the "canonical daemon" row from a provenance report. Optional: a
|
|
97
|
+
* machine without a running daemon is healthy; the row exists so triage sees
|
|
98
|
+
* identity, supervisor and the real log destination without a second command.
|
|
99
|
+
*/
|
|
100
|
+
export function daemonProvenanceRow(report) {
|
|
101
|
+
const label = "canonical daemon";
|
|
102
|
+
const historical = report.logFiles.filter((f) => f.status === "historical").length;
|
|
103
|
+
const suffix = historical
|
|
104
|
+
? ` — ${historical} historical log file${historical === 1 ? "" : "s"} (see \`tmux-ide daemon info\`)`
|
|
105
|
+
: "";
|
|
106
|
+
const d = report.daemon;
|
|
107
|
+
switch (report.status) {
|
|
108
|
+
case "running":
|
|
109
|
+
return {
|
|
110
|
+
label,
|
|
111
|
+
pass: true,
|
|
112
|
+
detail: `running v${d.productVersion} pid ${d.pid} (${d.supervisor ?? "unknown supervisor"}) · ` +
|
|
113
|
+
`logs → ${d.logDestination}${suffix}`,
|
|
114
|
+
optional: true,
|
|
115
|
+
};
|
|
116
|
+
case "stale-record":
|
|
117
|
+
return {
|
|
118
|
+
label,
|
|
119
|
+
pass: false,
|
|
120
|
+
detail: `record names pid ${d.pid} (v${d.productVersion}) but it is not running${suffix}`,
|
|
121
|
+
optional: true,
|
|
122
|
+
};
|
|
123
|
+
case "record-invalid":
|
|
124
|
+
return {
|
|
125
|
+
label,
|
|
126
|
+
pass: false,
|
|
127
|
+
detail: `record invalid (${report.record.status === "invalid" ? report.record.reason : "unknown"})${suffix}`,
|
|
128
|
+
optional: true,
|
|
129
|
+
};
|
|
130
|
+
default:
|
|
131
|
+
return {
|
|
132
|
+
label,
|
|
133
|
+
pass: true,
|
|
134
|
+
detail: report.record.status === "reserved"
|
|
135
|
+
? `not running (reserved for supervisor ${report.record.supervisionId})${suffix}`
|
|
136
|
+
: `not running${suffix}`,
|
|
137
|
+
optional: true,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
94
141
|
function check(label, fn, { optional = false } = {}) {
|
|
95
142
|
try {
|
|
96
143
|
const result = fn();
|
|
@@ -248,6 +295,21 @@ export async function doctor({ json, } = {}) {
|
|
|
248
295
|
}
|
|
249
296
|
return `in sync (v${installed})`;
|
|
250
297
|
}, { optional: true }));
|
|
298
|
+
// Canonical daemon identity + log provenance (credential-free), so a triage
|
|
299
|
+
// never chases a stale, manually redirected log file.
|
|
300
|
+
checks.push((() => {
|
|
301
|
+
try {
|
|
302
|
+
return daemonProvenanceRow(collectDaemonProvenanceReport());
|
|
303
|
+
}
|
|
304
|
+
catch (e) {
|
|
305
|
+
return {
|
|
306
|
+
label: "canonical daemon",
|
|
307
|
+
pass: false,
|
|
308
|
+
detail: `provenance unavailable: ${e.message}`,
|
|
309
|
+
optional: true,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
})());
|
|
251
313
|
// Native branded sender: only when the macOS channel is actually on.
|
|
252
314
|
if (process.platform === "darwin" && readNotificationPrefs().macos) {
|
|
253
315
|
checks.push(notifierRow(resolveNativeMacosNotifierPath() !== null));
|