@huseynsnmz/pi-suite 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/LICENSE +21 -0
- package/README.md +88 -2
- package/SECURITY.md +5 -0
- package/package.json +112 -4
- package/packages/board/CHANGELOG.md +340 -0
- package/packages/board/CONTRIBUTING.md +7 -0
- package/packages/board/LICENSE +21 -0
- package/packages/board/README.md +88 -0
- package/packages/board/SECURITY.md +19 -0
- package/packages/board/docs/how-it-works.md +31 -0
- package/packages/board/docs/reference.md +278 -0
- package/packages/board/package.json +81 -0
- package/packages/board/runner/job-runner-legacy.mjs +68 -0
- package/packages/board/runner/job-runner.mjs +834 -0
- package/packages/board/runner/pty-runner-legacy.mjs +50 -0
- package/packages/board/runner/pty-runner.mjs +2175 -0
- package/packages/board/runner/state-coordinator.mjs +673 -0
- package/packages/board/runner/state-runner.mjs +203 -0
- package/packages/board/runner/title-runner.mjs +80 -0
- package/packages/board/src/commands/agent-board.ts +394 -0
- package/packages/board/src/commands/attach-decision.mjs +66 -0
- package/packages/board/src/commands/attach-flow.ts +251 -0
- package/packages/board/src/commands/bg.ts +103 -0
- package/packages/board/src/commands/board-model.ts +21 -0
- package/packages/board/src/core/atomic.mjs +184 -0
- package/packages/board/src/core/attach-delete.mjs +24 -0
- package/packages/board/src/core/attach-frame-proof.mjs +870 -0
- package/packages/board/src/core/attach-frame-reporter.mjs +648 -0
- package/packages/board/src/core/attach-frame-stream.mjs +266 -0
- package/packages/board/src/core/attach-view-proof.mjs +709 -0
- package/packages/board/src/core/auto-state.mjs +377 -0
- package/packages/board/src/core/background-runs.mjs +273 -0
- package/packages/board/src/core/code-refs-store.mjs +315 -0
- package/packages/board/src/core/code-refs.mjs +913 -0
- package/packages/board/src/core/config.mjs +77 -0
- package/packages/board/src/core/control-protocol.mjs +482 -0
- package/packages/board/src/core/coordinator-client.mjs +633 -0
- package/packages/board/src/core/coordinator-journal.mjs +282 -0
- package/packages/board/src/core/coordinator-protocol.mjs +14 -0
- package/packages/board/src/core/cwd-stats.mjs +114 -0
- package/packages/board/src/core/dashboard-render.mjs +10 -0
- package/packages/board/src/core/derive.mjs +117 -0
- package/packages/board/src/core/diagnostics.mjs +133 -0
- package/packages/board/src/core/double-press.mjs +26 -0
- package/packages/board/src/core/editor-state-reporter.mjs +112 -0
- package/packages/board/src/core/events.mjs +286 -0
- package/packages/board/src/core/evidence.mjs +245 -0
- package/packages/board/src/core/follow-up-queue.mjs +211 -0
- package/packages/board/src/core/foreground-preview-cache.mjs +117 -0
- package/packages/board/src/core/foreground-projection-cache.mjs +99 -0
- package/packages/board/src/core/frame-witness-update.mjs +35 -0
- package/packages/board/src/core/heuristics.mjs +313 -0
- package/packages/board/src/core/hold-input.mjs +27 -0
- package/packages/board/src/core/host-coordination.mjs +300 -0
- package/packages/board/src/core/host-crash.mjs +79 -0
- package/packages/board/src/core/host-probe.mjs +207 -0
- package/packages/board/src/core/host-protocol.mjs +73 -0
- package/packages/board/src/core/ids.mjs +35 -0
- package/packages/board/src/core/ime-cursor-coalesce.mjs +193 -0
- package/packages/board/src/core/invocation.mjs +43 -0
- package/packages/board/src/core/launch-options.mjs +418 -0
- package/packages/board/src/core/launch.mjs +157 -0
- package/packages/board/src/core/load-config.mjs +4 -0
- package/packages/board/src/core/locks.mjs +530 -0
- package/packages/board/src/core/paths.mjs +183 -0
- package/packages/board/src/core/pid.mjs +75 -0
- package/packages/board/src/core/prewarm-schedule.mjs +41 -0
- package/packages/board/src/core/process-identity-probe.mjs +228 -0
- package/packages/board/src/core/prompt-transport.mjs +13 -0
- package/packages/board/src/core/pty-attach-jiggle-controller.mjs +356 -0
- package/packages/board/src/core/pty-attach-jiggle-retry.mjs +109 -0
- package/packages/board/src/core/pty-attach-reconnect.mjs +61 -0
- package/packages/board/src/core/pty-attach-render.mjs +229 -0
- package/packages/board/src/core/pty-links.mjs +81 -0
- package/packages/board/src/core/pty-scroll.mjs +522 -0
- package/packages/board/src/core/pty-support.mjs +327 -0
- package/packages/board/src/core/repo.mjs +103 -0
- package/packages/board/src/core/rows.mjs +414 -0
- package/packages/board/src/core/screen-log-gc.mjs +198 -0
- package/packages/board/src/core/screen-log.mjs +160 -0
- package/packages/board/src/core/session-view.mjs +174 -0
- package/packages/board/src/core/slash-commands.mjs +52 -0
- package/packages/board/src/core/state-commands.mjs +757 -0
- package/packages/board/src/core/status-consistency.mjs +98 -0
- package/packages/board/src/core/steering-prompts.mjs +34 -0
- package/packages/board/src/core/steering.mjs +133 -0
- package/packages/board/src/core/stop-latch.mjs +41 -0
- package/packages/board/src/core/store.mjs +523 -0
- package/packages/board/src/core/suspend.mjs +36 -0
- package/packages/board/src/core/terminal-attach-client.mjs +981 -0
- package/packages/board/src/core/terminal-attach-protocol.mjs +460 -0
- package/packages/board/src/core/terminal-model.mjs +222 -0
- package/packages/board/src/core/terminal-query-sequences.mjs +239 -0
- package/packages/board/src/core/terminal-reply-guard.mjs +222 -0
- package/packages/board/src/core/terminal-snapshot.mjs +440 -0
- package/packages/board/src/core/test-knobs.mjs +28 -0
- package/packages/board/src/core/title.mjs +43 -0
- package/packages/board/src/core/types.mjs +414 -0
- package/packages/board/src/core/warm-host-sweeper.mjs +168 -0
- package/packages/board/src/core/worktree.mjs +65 -0
- package/packages/board/src/index.ts +303 -0
- package/packages/board/src/runtime/service.mjs +3222 -0
- package/packages/board/src/ui/chrome.ts +20 -0
- package/packages/board/src/ui/dashboard-decisions.mjs +55 -0
- package/packages/board/src/ui/dashboard-evidence.mjs +85 -0
- package/packages/board/src/ui/dashboard.ts +2237 -0
- package/packages/board/src/ui/double-left.ts +30 -0
- package/packages/board/src/ui/external-editor.ts +25 -0
- package/packages/board/src/ui/focus.ts +32 -0
- package/packages/board/src/ui/pty-attach.ts +3184 -0
- package/packages/board/src/ui/waiting.ts +30 -0
- package/packages/commands/LICENSE +21 -0
- package/packages/commands/README.md +19 -0
- package/packages/commands/package.json +22 -0
- package/packages/commands/src/index.ts +43 -0
- package/packages/commands/src/security-review.ts +109 -0
- package/packages/frame/CHANGELOG.md +113 -0
- package/packages/frame/CONTRIBUTING.md +7 -0
- package/packages/frame/LICENSE +22 -0
- package/packages/frame/README.md +105 -0
- package/packages/frame/SECURITY.md +16 -0
- package/packages/frame/docs/how-it-works.md +53 -0
- package/packages/frame/docs/reference.md +78 -0
- package/packages/frame/package.json +72 -0
- package/packages/frame/src/data/git.ts +90 -0
- package/packages/frame/src/data/github.ts +140 -0
- package/packages/frame/src/data/gitlab.ts +63 -0
- package/packages/frame/src/data/mode.ts +49 -0
- package/packages/frame/src/data/past-prompts.ts +57 -0
- package/packages/frame/src/data/runner.ts +32 -0
- package/packages/frame/src/data/snapshot.ts +223 -0
- package/packages/frame/src/data/start-notes.ts +104 -0
- package/packages/frame/src/data/telemetry.ts +337 -0
- package/packages/frame/src/data/todos.ts +162 -0
- package/packages/frame/src/data/totals.ts +94 -0
- package/packages/frame/src/data/visits.ts +45 -0
- package/packages/frame/src/index.ts +696 -0
- package/packages/frame/src/kit/LICENSE +21 -0
- package/packages/frame/src/kit/app/attribution.ts +30 -0
- package/packages/frame/src/kit/app/builder.ts +186 -0
- package/packages/frame/src/kit/app/claim.ts +27 -0
- package/packages/frame/src/kit/app/feature.ts +91 -0
- package/packages/frame/src/kit/app/host.ts +214 -0
- package/packages/frame/src/kit/app/renderers.ts +62 -0
- package/packages/frame/src/kit/app/scope.ts +125 -0
- package/packages/frame/src/kit/board/model.ts +337 -0
- package/packages/frame/src/kit/contract.ts +79 -0
- package/packages/frame/src/kit/contracts/accounts.ts +33 -0
- package/packages/frame/src/kit/contracts/ask.ts +120 -0
- package/packages/frame/src/kit/contracts/board.ts +6 -0
- package/packages/frame/src/kit/contracts/by.ts +22 -0
- package/packages/frame/src/kit/contracts/calls.ts +46 -0
- package/packages/frame/src/kit/contracts/events.ts +89 -0
- package/packages/frame/src/kit/contracts/mode.ts +26 -0
- package/packages/frame/src/kit/contracts/names.ts +12 -0
- package/packages/frame/src/kit/contracts/peers.ts +77 -0
- package/packages/frame/src/kit/contracts/prompts.ts +8 -0
- package/packages/frame/src/kit/contracts/screen.ts +75 -0
- package/packages/frame/src/kit/contracts/skills.ts +81 -0
- package/packages/frame/src/kit/contracts/sources.ts +81 -0
- package/packages/frame/src/kit/contracts/views.ts +42 -0
- package/packages/frame/src/kit/control.ts +59 -0
- package/packages/frame/src/kit/dark-theme.ts +41 -0
- package/packages/frame/src/kit/decode.ts +210 -0
- package/packages/frame/src/kit/events/log.ts +313 -0
- package/packages/frame/src/kit/format.ts +25 -0
- package/packages/frame/src/kit/host.ts +4 -0
- package/packages/frame/src/kit/index.ts +25 -0
- package/packages/frame/src/kit/legacy-tasks.ts +39 -0
- package/packages/frame/src/kit/overlay-images.ts +36 -0
- package/packages/frame/src/kit/paths.ts +16 -0
- package/packages/frame/src/kit/screen/client.ts +74 -0
- package/packages/frame/src/kit/screen/feature.ts +64 -0
- package/packages/frame/src/kit/screen/model.ts +182 -0
- package/packages/frame/src/kit/screen/view.ts +838 -0
- package/packages/frame/src/kit/settings/files.ts +90 -0
- package/packages/frame/src/kit/settings/setting.ts +57 -0
- package/packages/frame/src/kit/settings/store.ts +192 -0
- package/packages/frame/src/kit/sources/file.ts +129 -0
- package/packages/frame/src/kit/sources/follow.ts +155 -0
- package/packages/frame/src/kit/testing.ts +260 -0
- package/packages/frame/src/kit/totals.ts +72 -0
- package/packages/frame/src/kit/ui/entry.ts +75 -0
- package/packages/frame/src/kit/ui/frame.ts +212 -0
- package/packages/frame/src/kit/ui/glyphs.ts +132 -0
- package/packages/frame/src/kit/ui/scroll.ts +14 -0
- package/packages/frame/src/render/diff.ts +157 -0
- package/packages/frame/src/render/fit.ts +86 -0
- package/packages/frame/src/render/format.ts +108 -0
- package/packages/frame/src/render/frame.ts +106 -0
- package/packages/frame/src/render/glyphs.ts +189 -0
- package/packages/frame/src/render/keys.ts +43 -0
- package/packages/frame/src/render/lead.ts +37 -0
- package/packages/frame/src/render/paint.ts +135 -0
- package/packages/frame/src/render/safe.ts +19 -0
- package/packages/frame/src/render/segments.ts +735 -0
- package/packages/frame/src/render/streaming.ts +223 -0
- package/packages/frame/src/render/timing.ts +106 -0
- package/packages/frame/src/render/tools.ts +167 -0
- package/packages/frame/src/settings.ts +105 -0
- package/packages/frame/src/ui/editor.ts +272 -0
- package/packages/frame/src/ui/effort.ts +36 -0
- package/packages/frame/src/ui/footer.ts +165 -0
- package/packages/frame/src/ui/header.ts +362 -0
- package/packages/frame/src/ui/history-search.ts +23 -0
- package/packages/frame/src/ui/interrupt.ts +21 -0
- package/packages/frame/src/ui/outputs.ts +170 -0
- package/packages/frame/src/ui/pi-entries.ts +298 -0
- package/packages/frame/src/ui/previews.ts +72 -0
- package/packages/frame/src/ui/queue.ts +75 -0
- package/packages/frame/src/ui/screen.ts +59 -0
- package/packages/frame/src/ui/timings.ts +48 -0
- package/packages/frame/src/ui/todos.ts +205 -0
- package/packages/frame/src/ui/tool-frame.ts +595 -0
- package/packages/frame/src/ui/working.ts +25 -0
- package/packages/frame/src/view/state.ts +97 -0
- package/packages/frame/src/view/transcript.ts +175 -0
- package/packages/frame/src/view/view.ts +162 -0
- package/packages/memory/CONTRIBUTING.md +7 -0
- package/packages/memory/LICENSE +21 -0
- package/packages/memory/README.md +44 -0
- package/packages/memory/SECURITY.md +20 -0
- package/packages/memory/package.json +55 -0
- package/packages/memory/skills/review-memory/SKILL.md +48 -0
- package/packages/memory/src/index.ts +163 -0
- package/packages/memory/src/prompt.ts +25 -0
- package/packages/memory/src/review.ts +150 -0
- package/packages/memory/src/secrets.ts +22 -0
- package/packages/memory/src/store.ts +173 -0
- package/packages/modes/CHANGELOG.md +104 -0
- package/packages/modes/CONTRIBUTING.md +7 -0
- package/packages/modes/LICENSE +21 -0
- package/packages/modes/README.md +174 -0
- package/packages/modes/SECURITY.md +17 -0
- package/packages/modes/package.json +55 -0
- package/packages/modes/skills/review-permissions/SKILL.md +54 -0
- package/packages/modes/src/bash.ts +218 -0
- package/packages/modes/src/deny-off.ts +62 -0
- package/packages/modes/src/git-safety.ts +72 -0
- package/packages/modes/src/index.ts +338 -0
- package/packages/modes/src/interview.ts +206 -0
- package/packages/modes/src/judge-gate.ts +113 -0
- package/packages/modes/src/modes.ts +121 -0
- package/packages/modes/src/review/apply.mjs +156 -0
- package/packages/modes/src/review/collect.mjs +208 -0
- package/packages/modes/src/review/tools.ts +71 -0
- package/packages/modes/src/sandbox.ts +37 -0
- package/packages/router/CHANGELOG.md +8 -0
- package/packages/router/LICENSE +21 -0
- package/packages/router/README.md +90 -0
- package/packages/router/package.json +51 -0
- package/packages/router/src/config.ts +65 -0
- package/packages/router/src/index.ts +172 -0
- package/packages/router/src/judge.ts +51 -0
- package/packages/router/src/route.ts +67 -0
- package/packages/web/CHANGELOG.md +36 -0
- package/packages/web/LICENSE +21 -0
- package/packages/web/README.md +87 -0
- package/packages/web/SECURITY.md +13 -0
- package/packages/web/package.json +59 -0
- package/packages/web/src/config.ts +109 -0
- package/packages/web/src/dnsfilter.ts +97 -0
- package/packages/web/src/extract.ts +65 -0
- package/packages/web/src/index.ts +217 -0
- package/packages/web/src/markdown.ts +122 -0
- package/packages/web/src/net.ts +109 -0
- package/packages/web/src/reader.ts +73 -0
- package/packages/web/src/sanitize.ts +50 -0
- package/packages/web/src/search.ts +76 -0
- package/packages/web/src/secrets.ts +22 -0
- package/packages/web/src/validate.ts +186 -0
- package/src/index.ts +156 -0
- package/src/settings.ts +79 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared node-pty support helpers:
|
|
3
|
+
* - best-effort self-heal for macOS spawn-helper permissions
|
|
4
|
+
* - user-facing diagnosis for common PTY startup failures
|
|
5
|
+
*/
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
import { chmodSync, existsSync, statSync } from "node:fs";
|
|
8
|
+
|
|
9
|
+
/** @param {import("node:module").Require} requireForPty */
|
|
10
|
+
export function resolveNodePtyPackageRoot(requireForPty) {
|
|
11
|
+
try {
|
|
12
|
+
const pkg = requireForPty.resolve("node-pty/package.json");
|
|
13
|
+
return pkg.slice(0, -"package.json".length);
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {import("node:module").Require} requireForPty
|
|
21
|
+
* @param {string} [platform]
|
|
22
|
+
* @param {string} [arch]
|
|
23
|
+
*/
|
|
24
|
+
export function nodePtySpawnHelperPaths(requireForPty, platform = process.platform, arch = process.arch) {
|
|
25
|
+
const root = resolveNodePtyPackageRoot(requireForPty);
|
|
26
|
+
if (!root) return [];
|
|
27
|
+
return [`${root}prebuilds/${platform}-${arch}/spawn-helper`, `${root}build/Release/spawn-helper`];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Best-effort chmod for node-pty's spawn-helper. This specifically heals the macOS/npm
|
|
32
|
+
* packaging issue where the helper lands as 0644 instead of 0755.
|
|
33
|
+
*
|
|
34
|
+
* Important: this must never create a false PTY failure for an already-working install.
|
|
35
|
+
* If the helper already has an execute bit, leave it alone. If chmod itself fails because
|
|
36
|
+
* the install directory is read-only, swallow that and let the real node-pty probe decide.
|
|
37
|
+
* @param {import("node:module").Require} requireForPty
|
|
38
|
+
* @param {string} [platform]
|
|
39
|
+
* @param {string} [arch]
|
|
40
|
+
* @returns {string[]} touched helper paths
|
|
41
|
+
*/
|
|
42
|
+
export function ensureNodePtySpawnHelperExecutable(requireForPty, platform = process.platform, arch = process.arch) {
|
|
43
|
+
const touched = [];
|
|
44
|
+
for (const helper of nodePtySpawnHelperPaths(requireForPty, platform, arch)) {
|
|
45
|
+
if (!existsSync(helper)) continue;
|
|
46
|
+
try {
|
|
47
|
+
if (Boolean(statSync(helper).mode & 0o111)) continue;
|
|
48
|
+
} catch {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
chmodSync(helper, 0o755);
|
|
53
|
+
touched.push(helper);
|
|
54
|
+
} catch {
|
|
55
|
+
/* best effort: a read-only install should not disable PTY if the helper already works */
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return touched;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const RESOLVE_HELPER_COMMAND =
|
|
62
|
+
`helper=$(node -p "const path=require('path'); const pkg=require.resolve('node-pty/package.json'); path.join(path.dirname(pkg),'prebuilds',process.platform+'-'+process.arch,'spawn-helper')")`;
|
|
63
|
+
const VERIFY_COMMAND =
|
|
64
|
+
`node -e "const p=require('node-pty').spawn('/bin/echo',['ok'],{name:'xterm-256color',cols:20,rows:5,cwd:process.cwd(),env:process.env}); console.log('node-pty OK'); p.kill()"`;
|
|
65
|
+
const TEMP_WORKAROUND = "AGENT_BOARD_DISABLE_PTY=1 pi /agent-board";
|
|
66
|
+
|
|
67
|
+
/** @param {string} value */
|
|
68
|
+
function shellQuote(value) {
|
|
69
|
+
return `'${String(value).replace(/'/g, `'"'"'`)}'`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** @param {string|null|undefined} helperPath @param {string} [label] */
|
|
73
|
+
function helperPathSteps(helperPath, label = "Detected helper path") {
|
|
74
|
+
if (!helperPath) return ["Resolve the helper path:", RESOLVE_HELPER_COMMAND];
|
|
75
|
+
return [`${label}:`, helperPath];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** @param {string|null|undefined} helperPath */
|
|
79
|
+
function chmodHelperCommand(helperPath) {
|
|
80
|
+
return helperPath ? `chmod +x ${shellQuote(helperPath)}` : `chmod +x "$helper"`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** @param {string|null|undefined} helperPath */
|
|
84
|
+
function quarantineHelperCommand(helperPath) {
|
|
85
|
+
return helperPath ? `xattr -dr com.apple.quarantine ${shellQuote(helperPath)}` : `xattr -dr com.apple.quarantine "$helper"`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** @param {string|null|undefined} helperPath */
|
|
89
|
+
function quarantineHelperCommandBestEffort(helperPath) {
|
|
90
|
+
return helperPath
|
|
91
|
+
? `xattr -dr com.apple.quarantine ${shellQuote(helperPath)} 2>/dev/null || true`
|
|
92
|
+
: `xattr -dr com.apple.quarantine "$helper" 2>/dev/null || true`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @typedef {Object} PtyProbe
|
|
97
|
+
* @property {string|null} helperPath
|
|
98
|
+
* @property {boolean|null} helperExists
|
|
99
|
+
* @property {boolean|null} helperExecutable
|
|
100
|
+
* @property {boolean|null} helperQuarantined
|
|
101
|
+
*/
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @typedef {Object} PtyIssue
|
|
105
|
+
* @property {string} id
|
|
106
|
+
* @property {string} title
|
|
107
|
+
* @property {string} statusLabel
|
|
108
|
+
* @property {string} summary
|
|
109
|
+
* @property {string} fixHint
|
|
110
|
+
* @property {string[]} steps
|
|
111
|
+
* @property {string} rawReason
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Inspect the installed node-pty helper on disk so we can separate "missing exec bit"
|
|
116
|
+
* from "quarantined by Gatekeeper" from "helper missing altogether".
|
|
117
|
+
* @param {import("node:module").Require} requireForPty
|
|
118
|
+
* @param {string} [platform]
|
|
119
|
+
* @param {string} [arch]
|
|
120
|
+
* @returns {PtyProbe}
|
|
121
|
+
*/
|
|
122
|
+
export function probeNodePtyEnvironment(requireForPty, platform = process.platform, arch = process.arch) {
|
|
123
|
+
const candidates = nodePtySpawnHelperPaths(requireForPty, platform, arch);
|
|
124
|
+
const helperPath = candidates.find((p) => existsSync(p)) ?? candidates[0] ?? null;
|
|
125
|
+
if (!helperPath) {
|
|
126
|
+
return { helperPath: null, helperExists: null, helperExecutable: null, helperQuarantined: null };
|
|
127
|
+
}
|
|
128
|
+
const helperExists = existsSync(helperPath);
|
|
129
|
+
let helperExecutable = null;
|
|
130
|
+
let helperQuarantined = null;
|
|
131
|
+
if (helperExists) {
|
|
132
|
+
try {
|
|
133
|
+
helperExecutable = Boolean(statSync(helperPath).mode & 0o111);
|
|
134
|
+
} catch {
|
|
135
|
+
helperExecutable = null;
|
|
136
|
+
}
|
|
137
|
+
if (platform === "darwin") {
|
|
138
|
+
try {
|
|
139
|
+
const res = spawnSync("xattr", ["-p", "com.apple.quarantine", helperPath], { encoding: "utf8" });
|
|
140
|
+
helperQuarantined = res.status === 0 && Boolean(res.stdout?.trim());
|
|
141
|
+
} catch {
|
|
142
|
+
helperQuarantined = null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return { helperPath, helperExists, helperExecutable, helperQuarantined };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** @param {string|null|undefined} reason */
|
|
150
|
+
function cleanReason(reason) {
|
|
151
|
+
return String(reason || "").replace(/\s+/g, " ").trim();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Turn a raw node-pty startup failure into a user-facing explanation + fix steps.
|
|
156
|
+
* @param {string|null|undefined} reason
|
|
157
|
+
* @param {{ platform?: string, arch?: string, probe?: PtyProbe|null }} [opts]
|
|
158
|
+
* @returns {PtyIssue}
|
|
159
|
+
*/
|
|
160
|
+
export function diagnoseNodePtyFailure(reason, opts = {}) {
|
|
161
|
+
const platform = opts.platform ?? process.platform;
|
|
162
|
+
const arch = opts.arch ?? process.arch;
|
|
163
|
+
const probe = opts.probe ?? null;
|
|
164
|
+
const helperPath = probe?.helperPath ?? null;
|
|
165
|
+
const rawReason = cleanReason(reason) || "unknown error";
|
|
166
|
+
|
|
167
|
+
if (/AGENT_BOARD_DISABLE_PTY=1|AGENT_VIEW_DISABLE_PTY=1/.test(rawReason)) {
|
|
168
|
+
return {
|
|
169
|
+
id: "disabled-env",
|
|
170
|
+
title: "PTY disabled by environment",
|
|
171
|
+
statusLabel: "disabled by env",
|
|
172
|
+
summary: "Live PTY is disabled because AGENT_BOARD_DISABLE_PTY=1 is set.",
|
|
173
|
+
fixHint: "Unset AGENT_BOARD_DISABLE_PTY and retry Agent Board.",
|
|
174
|
+
steps: ["Unset the env override.", "Retry /agent-board or attach again."],
|
|
175
|
+
rawReason,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (/Cannot find module ['\"]node-pty['\"]|Cannot find package ['\"]node-pty['\"]|ERR_MODULE_NOT_FOUND/i.test(rawReason)) {
|
|
180
|
+
return {
|
|
181
|
+
id: "missing-module",
|
|
182
|
+
title: "node-pty dependency missing",
|
|
183
|
+
statusLabel: "node-pty missing",
|
|
184
|
+
summary: "Live PTY is disabled because the node-pty dependency is missing.",
|
|
185
|
+
fixHint: "Reinstall the package so production dependencies are present.",
|
|
186
|
+
steps: [
|
|
187
|
+
"For a local checkout, run: npm install",
|
|
188
|
+
"For an installed package, reinstall agent-board so node-pty is present.",
|
|
189
|
+
`Temporary workaround: ${TEMP_WORKAROUND}`,
|
|
190
|
+
],
|
|
191
|
+
rawReason,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (/(Could not locate the bindings file|No native build was found|Cannot find module ['\"].*pty\.node['\"]|No prebuilds found)/i.test(rawReason)) {
|
|
196
|
+
return {
|
|
197
|
+
id: "native-missing",
|
|
198
|
+
title: "node-pty native binary missing",
|
|
199
|
+
statusLabel: "native binary missing",
|
|
200
|
+
summary: "Live PTY is disabled because node-pty is installed but its native binary is missing for this runtime.",
|
|
201
|
+
fixHint: `Reinstall or rebuild node-pty under the same Node version and architecture (${platform}-${arch}) that Pi is using.`,
|
|
202
|
+
steps: [
|
|
203
|
+
`Confirm runtime: node -p \"process.version + ' ' + process.platform + ' ' + process.arch\"`,
|
|
204
|
+
"Reinstall dependencies or rebuild native modules under that same runtime.",
|
|
205
|
+
"For source installs, run npm rebuild node-pty (or npm install) in the package directory.",
|
|
206
|
+
`Temporary workaround: ${TEMP_WORKAROUND}`,
|
|
207
|
+
],
|
|
208
|
+
rawReason,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (
|
|
213
|
+
platform === "darwin" &&
|
|
214
|
+
/(posix_spawnp failed|spawn-helper|permission denied|operation not permitted|eacces|eperm)/i.test(rawReason)
|
|
215
|
+
) {
|
|
216
|
+
if (probe?.helperExists === false) {
|
|
217
|
+
return {
|
|
218
|
+
id: "macos-spawn-helper-missing",
|
|
219
|
+
title: "node-pty helper missing",
|
|
220
|
+
statusLabel: "spawn-helper missing",
|
|
221
|
+
summary: "Live PTY is disabled because node-pty's macOS spawn-helper file is missing.",
|
|
222
|
+
fixHint: "Reinstall the package so the darwin helper is restored.",
|
|
223
|
+
steps: [
|
|
224
|
+
...helperPathSteps(helperPath, "Expected helper path"),
|
|
225
|
+
"Reinstall agent-board or run npm install again so node-pty's darwin prebuilds are present.",
|
|
226
|
+
`Temporary workaround: ${TEMP_WORKAROUND}`,
|
|
227
|
+
],
|
|
228
|
+
rawReason,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
if (probe?.helperExecutable === false) {
|
|
232
|
+
return {
|
|
233
|
+
id: "macos-spawn-helper-mode",
|
|
234
|
+
title: "spawn-helper is not executable",
|
|
235
|
+
statusLabel: "spawn-helper not executable",
|
|
236
|
+
summary: "Live PTY is disabled because node-pty's spawn-helper does not have execute permission.",
|
|
237
|
+
fixHint: "Run chmod +x on spawn-helper.",
|
|
238
|
+
steps: [
|
|
239
|
+
...helperPathSteps(helperPath),
|
|
240
|
+
"Grant execute permission:",
|
|
241
|
+
chmodHelperCommand(helperPath),
|
|
242
|
+
"Validate node-pty after the fix:",
|
|
243
|
+
VERIFY_COMMAND,
|
|
244
|
+
`Temporary workaround: ${TEMP_WORKAROUND}`,
|
|
245
|
+
],
|
|
246
|
+
rawReason,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
if (probe?.helperQuarantined) {
|
|
250
|
+
return {
|
|
251
|
+
id: "macos-spawn-helper-quarantine",
|
|
252
|
+
title: "spawn-helper is quarantined",
|
|
253
|
+
statusLabel: "spawn-helper quarantined",
|
|
254
|
+
summary: "Live PTY is disabled because macOS Gatekeeper quarantined node-pty's spawn-helper.",
|
|
255
|
+
fixHint: "Clear the quarantine xattr from spawn-helper.",
|
|
256
|
+
steps: [
|
|
257
|
+
...helperPathSteps(helperPath),
|
|
258
|
+
"Remove the quarantine attribute:",
|
|
259
|
+
quarantineHelperCommand(helperPath),
|
|
260
|
+
"Validate node-pty after the fix:",
|
|
261
|
+
VERIFY_COMMAND,
|
|
262
|
+
`Temporary workaround: ${TEMP_WORKAROUND}`,
|
|
263
|
+
],
|
|
264
|
+
rawReason,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
id: "macos-spawn-helper",
|
|
269
|
+
title: "macOS blocked spawn-helper",
|
|
270
|
+
statusLabel: "spawn-helper blocked",
|
|
271
|
+
summary: "Live PTY is disabled because macOS could not execute node-pty's spawn-helper.",
|
|
272
|
+
fixHint: "Run chmod +x on spawn-helper and clear quarantine if needed.",
|
|
273
|
+
steps: [
|
|
274
|
+
...helperPathSteps(helperPath),
|
|
275
|
+
"Fix permissions and quarantine:",
|
|
276
|
+
chmodHelperCommand(helperPath),
|
|
277
|
+
quarantineHelperCommandBestEffort(helperPath),
|
|
278
|
+
"Validate node-pty after the fix:",
|
|
279
|
+
VERIFY_COMMAND,
|
|
280
|
+
`Temporary workaround: ${TEMP_WORKAROUND}`,
|
|
281
|
+
],
|
|
282
|
+
rawReason,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (
|
|
287
|
+
/(NODE_MODULE_VERSION|module version mismatch|module did not self-register|dlopen\(|mach-o|wrong architecture|incompatible architecture|no suitable image|invalid elf|symbol not found)/i.test(
|
|
288
|
+
rawReason,
|
|
289
|
+
)
|
|
290
|
+
) {
|
|
291
|
+
return {
|
|
292
|
+
id: "native-mismatch",
|
|
293
|
+
title: "node-pty native binary mismatch",
|
|
294
|
+
statusLabel: "native binary mismatch",
|
|
295
|
+
summary: "Live PTY is disabled because node-pty's native binary does not match this Node or CPU architecture.",
|
|
296
|
+
fixHint: `Reinstall node-pty under the same Node version and architecture (${platform}-${arch}) that Pi is using.`,
|
|
297
|
+
steps: [
|
|
298
|
+
`Confirm runtime: node -p \"process.version + ' ' + process.platform + ' ' + process.arch\"`,
|
|
299
|
+
"Reinstall dependencies under that same runtime.",
|
|
300
|
+
"Avoid mixing Rosetta x64 and native arm64 installs on macOS.",
|
|
301
|
+
`Temporary workaround: ${TEMP_WORKAROUND}`,
|
|
302
|
+
],
|
|
303
|
+
rawReason,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
id: "generic",
|
|
309
|
+
title: "node-pty startup probe failed",
|
|
310
|
+
statusLabel: "startup probe failed",
|
|
311
|
+
summary: "Live PTY is disabled because node-pty failed its startup probe.",
|
|
312
|
+
fixHint: "Inspect the raw error, reinstall node-pty, or temporarily disable PTY.",
|
|
313
|
+
steps: [
|
|
314
|
+
"Inspect the raw reason below for the underlying system error.",
|
|
315
|
+
"Reinstall the package dependencies if the problem persists.",
|
|
316
|
+
`Temporary workaround: ${TEMP_WORKAROUND}`,
|
|
317
|
+
],
|
|
318
|
+
rawReason,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** @param {{ ok: boolean, reason?: string|null, issue?: PtyIssue|null }} support */
|
|
323
|
+
export function nodePtyFallbackMessage(support) {
|
|
324
|
+
if (support.ok) return undefined;
|
|
325
|
+
const issue = support.issue ?? diagnoseNodePtyFailure(support.reason ?? null);
|
|
326
|
+
return `${issue.summary} Fix: ${issue.fixHint} Press ! for exact steps.`;
|
|
327
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/** Git repository identity. Pure node (shells out to `git`); returns null off-repo. */
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { parseRemoteHost } from "./code-refs.mjs";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the git repo root containing `cwd`, or null if not in a repo.
|
|
7
|
+
* @param {string} cwd
|
|
8
|
+
* @returns {string|null}
|
|
9
|
+
*/
|
|
10
|
+
export function gitRepoRoot(cwd) {
|
|
11
|
+
try {
|
|
12
|
+
const out = execFileSync("git", ["-C", cwd, "rev-parse", "--show-toplevel"], {
|
|
13
|
+
encoding: "utf8",
|
|
14
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
15
|
+
windowsHide: true,
|
|
16
|
+
});
|
|
17
|
+
const root = out.trim();
|
|
18
|
+
return root || null;
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Whether two directories resolve to the same git repo root.
|
|
26
|
+
* @param {string|null} rootA
|
|
27
|
+
* @param {string|null} rootB
|
|
28
|
+
* @returns {boolean}
|
|
29
|
+
*/
|
|
30
|
+
export function sameRepo(rootA, rootB) {
|
|
31
|
+
return Boolean(rootA && rootB && rootA === rootB);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Whether the repo has uncommitted changes (dirty). Best-effort; false on error/off-repo.
|
|
36
|
+
* @param {string} repoRoot
|
|
37
|
+
* @returns {boolean}
|
|
38
|
+
*/
|
|
39
|
+
export function isDirty(repoRoot) {
|
|
40
|
+
try {
|
|
41
|
+
const out = execFileSync("git", ["-C", repoRoot, "status", "--porcelain"], {
|
|
42
|
+
encoding: "utf8",
|
|
43
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
44
|
+
windowsHide: true,
|
|
45
|
+
});
|
|
46
|
+
return out.trim().length > 0;
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Cached origin-remote URL per repo root. Failures are cached as null so each
|
|
54
|
+
* repo root is only ever queried once per process.
|
|
55
|
+
* @type {Map<string, string|null>}
|
|
56
|
+
*/
|
|
57
|
+
const remoteUrlCache = new Map();
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Raw URL of the `origin` remote of `repoRoot`, or null when the repo has no
|
|
61
|
+
* origin remote or is not a repo. The result (including misses) is cached per
|
|
62
|
+
* repoRoot.
|
|
63
|
+
* @param {string} repoRoot
|
|
64
|
+
* @returns {string|null}
|
|
65
|
+
*/
|
|
66
|
+
export function gitRemoteUrl(repoRoot) {
|
|
67
|
+
const cached = remoteUrlCache.get(repoRoot);
|
|
68
|
+
if (cached !== undefined) return cached;
|
|
69
|
+
let url = null;
|
|
70
|
+
try {
|
|
71
|
+
const out = execFileSync("git", ["-C", repoRoot, "remote", "get-url", "origin"], {
|
|
72
|
+
encoding: "utf8",
|
|
73
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
74
|
+
timeout: 2000,
|
|
75
|
+
windowsHide: true,
|
|
76
|
+
});
|
|
77
|
+
url = out.trim() || null;
|
|
78
|
+
} catch {
|
|
79
|
+
// not a repo or no origin remote
|
|
80
|
+
}
|
|
81
|
+
remoteUrlCache.set(repoRoot, url);
|
|
82
|
+
return url;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Host of the `origin` remote of `repoRoot`, lowercased and without port, or
|
|
87
|
+
* null when the repo has no origin remote or is not a repo. Supports
|
|
88
|
+
* `https://host/owner/repo(.git)` and `git@host:owner/repo(.git)`; parsed from
|
|
89
|
+
* the cached raw remote URL.
|
|
90
|
+
* @param {string} repoRoot
|
|
91
|
+
* @returns {string|null}
|
|
92
|
+
*/
|
|
93
|
+
export function gitRemoteHost(repoRoot) {
|
|
94
|
+
return parseRemoteHost(gitRemoteUrl(repoRoot));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Clear the gitRemoteHost cache. Test-only helper.
|
|
99
|
+
* @returns {void}
|
|
100
|
+
*/
|
|
101
|
+
export function clearRemoteHostCacheForTests() {
|
|
102
|
+
remoteUrlCache.clear();
|
|
103
|
+
}
|