@agfpd/iapeer 0.2.78 → 0.2.79
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/LICENSE +1 -1
- package/package.json +1 -1
- package/src/cli/index.ts +51 -33
- package/src/supervisor/daemon.ts +6 -5
package/LICENSE
CHANGED
|
@@ -187,7 +187,7 @@
|
|
|
187
187
|
same "printed page" as the copyright notice for easier
|
|
188
188
|
identification within third-party archives.
|
|
189
189
|
|
|
190
|
-
Copyright
|
|
190
|
+
Copyright 2026 Artur Agafapudov
|
|
191
191
|
|
|
192
192
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
193
|
you may not use this file except in compliance with the License.
|
package/package.json
CHANGED
package/src/cli/index.ts
CHANGED
|
@@ -925,7 +925,7 @@ const USAGE = `usage: iapeer <verb> [args]
|
|
|
925
925
|
it up on the peer's first message); always-on: launchctl bootstrap. --all = every registered peer
|
|
926
926
|
remove <peer> [--force] delete a peer's registry record (locked writer); refuses a LIVE peer unless --force
|
|
927
927
|
send <target> (--message <text> | --message-file <f|->) [--from <id>] [--attachment <p>]… [--topic <t>] manual IAP send (fallback)
|
|
928
|
-
<runtime> launch the cwd's peer (
|
|
928
|
+
<runtime> launch the cwd's peer FRESH; on a TTY drop into it (like attach), else detached
|
|
929
929
|
connect telegram <peer> [--token <t>] attach a telegram bot to a peer (bot add → interface → router restart; asks only the token)
|
|
930
930
|
enable <plugin> [peer] [--no-setup] install + enable an agfpd capability for a peer
|
|
931
931
|
new <peer> [runtime] UNCONDITIONAL fresh restart: canary-clean kill + fresh wake (emergency lever for a hung/dead session — bypasses the peer; exit 0 = fresh session up+ready)
|
|
@@ -1735,36 +1735,8 @@ export async function runCli(argv: string[], env: NodeJS.ProcessEnv = process.en
|
|
|
1735
1735
|
return 1
|
|
1736
1736
|
}
|
|
1737
1737
|
out(`${r.woke ? 'woke + ' : ''}attaching ${r.identity}…\n`)
|
|
1738
|
-
// Attach-window observability (whether a death happened under attach was
|
|
1739
|
-
// answerable only INDIRECTLY via wake cause=attach — i.e. only when the
|
|
1740
|
-
// session was dead at attach time). Bracket the operator's client
|
|
1741
|
-
// window in lifecycle.log: ev=attach at entry, ev=attach-end (with the tmux
|
|
1742
|
-
// client's exit code) when the operator detaches or the server dies under
|
|
1743
|
-
// the client. A death timestamp inside an attach…attach-end window now
|
|
1744
|
-
// reads directly off the log.
|
|
1745
1738
|
const attachCfg = loadLifecycleConfig(env)
|
|
1746
|
-
|
|
1747
|
-
// SPAWN-FLIP Ф0b-3: a supervisor-HOSTED session has no tmux to attach — hand the operator to
|
|
1748
|
-
// the supervisor client (runSupervisorClient: raw passthrough + repaint-on-attach + Ctrl-]
|
|
1749
|
-
// detach + both reattach port-deps). It calls process.exit on every terminal path, so bracket
|
|
1750
|
-
// attach-end via an exit hook (appendFileSync is signal/exit-safe). Flag-off → tmux attach.
|
|
1751
|
-
if (hostSessionAlive(r.identity)) {
|
|
1752
|
-
process.on('exit', () =>
|
|
1753
|
-
appendLifecycleEvent(attachCfg.eventLogDir, { ev: 'attach-end', identity: r.identity, rc: process.exitCode ?? 0 }, { env }),
|
|
1754
|
-
)
|
|
1755
|
-
await runSupervisorClient(hostRunDir(), r.identity)
|
|
1756
|
-
return 0 // unreachable — runSupervisorClient exits on detach / session end
|
|
1757
|
-
}
|
|
1758
|
-
// Drop the operator into the session. `env -u TMUX` so a nested attach from
|
|
1759
|
-
// inside tmux does not error ("sessions should be nested with care").
|
|
1760
|
-
const attachEnv = { ...env }
|
|
1761
|
-
delete attachEnv.TMUX
|
|
1762
|
-
const a = spawnSync('tmux', ['-S', r.socketPath, 'attach', '-t', r.identity], {
|
|
1763
|
-
stdio: 'inherit',
|
|
1764
|
-
env: attachEnv as Record<string, string>,
|
|
1765
|
-
})
|
|
1766
|
-
appendLifecycleEvent(attachCfg.eventLogDir, { ev: 'attach-end', identity: r.identity, rc: a.status ?? -1 }, { env })
|
|
1767
|
-
return a.status ?? 0
|
|
1739
|
+
return await attachIntoSession(r.identity, r.socketPath, env, attachCfg.eventLogDir, r.woke)
|
|
1768
1740
|
}
|
|
1769
1741
|
case 'shadow': {
|
|
1770
1742
|
// Read-only fidelity OBSERVER for the tmux→pty migration burn-in (track «b»). Compares
|
|
@@ -1823,14 +1795,25 @@ export async function runCli(argv: string[], env: NodeJS.ProcessEnv = process.en
|
|
|
1823
1795
|
return await runSupervisorCli(rest)
|
|
1824
1796
|
}
|
|
1825
1797
|
default: {
|
|
1826
|
-
// `iapeer <runtime>` (launch) — folder-launch the cwd's peer, ALWAYS fresh.
|
|
1798
|
+
// `iapeer <runtime>` (launch) — folder-launch the cwd's peer, ALWAYS fresh. On a TTY this is
|
|
1799
|
+
// the human "start a fresh session and work in it" verb (parallel to `attach`, which resumes):
|
|
1800
|
+
// after a successful fresh bring-up it drops the operator straight into the new session.
|
|
1801
|
+
// Non-TTY (scripted / piped) keeps the fire-and-forget behavior — report the launch and exit.
|
|
1827
1802
|
if (verb && isRuntime(verb)) {
|
|
1828
|
-
const
|
|
1803
|
+
const cfg = loadLifecycleConfig(env)
|
|
1804
|
+
const id = resolveIdentity({ env })
|
|
1805
|
+
const r = await folderLaunch({ cwd: process.cwd(), runtime: verb, env, cfg })
|
|
1829
1806
|
if (r.status === 'FAILED') {
|
|
1830
1807
|
errOut(`launch: ${r.reason}\n`)
|
|
1831
1808
|
return 1
|
|
1832
1809
|
}
|
|
1833
|
-
|
|
1810
|
+
const identity = r.process_address ?? buildProcessAddress(verb, id.personality)
|
|
1811
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
1812
|
+
const socketPath = buildSocketPath(verb, id.personality, cfg.sockDir)
|
|
1813
|
+
out(`launched ${identity} (fresh) — attaching…\n`)
|
|
1814
|
+
return await attachIntoSession(identity, socketPath, env, cfg.eventLogDir, true)
|
|
1815
|
+
}
|
|
1816
|
+
out(`launched ${identity} (fresh)\n`)
|
|
1834
1817
|
return 0
|
|
1835
1818
|
}
|
|
1836
1819
|
return usage(errOut)
|
|
@@ -1853,6 +1836,41 @@ function defaultFromIdentity(env: NodeJS.ProcessEnv): string {
|
|
|
1853
1836
|
return resolveIdentity({ env }).address
|
|
1854
1837
|
}
|
|
1855
1838
|
|
|
1839
|
+
/** Drop the operator into a live peer session — the interactive open shared by `attach` (resume)
|
|
1840
|
+
* and a TTY `iapeer <runtime>` (fresh launch). A supervisor-HOSTED session has no tmux to attach,
|
|
1841
|
+
* so the operator is handed to the supervisor client (raw passthrough + repaint-on-attach + Ctrl-]
|
|
1842
|
+
* detach); otherwise `tmux attach`. The operator window is bracketed in lifecycle.log
|
|
1843
|
+
* (ev=attach … ev=attach-end) so a death timestamp inside the window reads directly off the log.
|
|
1844
|
+
* Returns the client exit code. */
|
|
1845
|
+
async function attachIntoSession(
|
|
1846
|
+
identity: string,
|
|
1847
|
+
socketPath: string,
|
|
1848
|
+
env: NodeJS.ProcessEnv,
|
|
1849
|
+
eventLogDir: string,
|
|
1850
|
+
woke: boolean,
|
|
1851
|
+
): Promise<number> {
|
|
1852
|
+
appendLifecycleEvent(eventLogDir, { ev: 'attach', identity, woke: String(woke) }, { env })
|
|
1853
|
+
if (hostSessionAlive(identity)) {
|
|
1854
|
+
// runSupervisorClient calls process.exit on every terminal path → bracket attach-end via an
|
|
1855
|
+
// exit hook (appendFileSync is signal/exit-safe).
|
|
1856
|
+
process.on('exit', () =>
|
|
1857
|
+
appendLifecycleEvent(eventLogDir, { ev: 'attach-end', identity, rc: process.exitCode ?? 0 }, { env }),
|
|
1858
|
+
)
|
|
1859
|
+
await runSupervisorClient(hostRunDir(), identity)
|
|
1860
|
+
return 0 // unreachable — runSupervisorClient exits on detach / session end
|
|
1861
|
+
}
|
|
1862
|
+
// `env -u TMUX` so a nested attach from inside tmux does not error ("sessions should be nested
|
|
1863
|
+
// with care").
|
|
1864
|
+
const attachEnv = { ...env }
|
|
1865
|
+
delete attachEnv.TMUX
|
|
1866
|
+
const a = spawnSync('tmux', ['-S', socketPath, 'attach', '-t', identity], {
|
|
1867
|
+
stdio: 'inherit',
|
|
1868
|
+
env: attachEnv as Record<string, string>,
|
|
1869
|
+
})
|
|
1870
|
+
appendLifecycleEvent(eventLogDir, { ev: 'attach-end', identity, rc: a.status ?? -1 }, { env })
|
|
1871
|
+
return a.status ?? 0
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1856
1874
|
if (import.meta.main) {
|
|
1857
1875
|
runCli(process.argv.slice(2)).then(code => process.exit(code))
|
|
1858
1876
|
}
|
package/src/supervisor/daemon.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// Supervisor DAEMON — holds the pty + child + headless-xterm model + capability responder
|
|
2
|
-
// independent of any terminal; clients attach/detach over a unix socket
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
2
|
+
// independent of any terminal; clients attach/detach over a unix socket. It HOSTS tui/router peers
|
|
3
|
+
// by default (the pty-host launch path; opt-out per-peer via a `.no-pty-host` marker), and is also
|
|
4
|
+
// reachable manually via the `supervisor` CLI verb for throwaway validation. It runs in its OWN
|
|
5
|
+
// detached process, dynamic-imported from the launch path (supervisor/index.ts → daemon.ts), so
|
|
6
|
+
// @xterm never loads in the central router's delivery path nor in the launch/lifecycle process.
|
|
6
7
|
//
|
|
7
8
|
// Loads @xterm/headless + @xterm/addon-serialize (the latter is the repaint source: a reattaching
|
|
8
9
|
// client gets the full current screen even if the child is silent). Both bundle under
|
|
9
|
-
// `bun build --compile` (pure JS; the Bun-native pty has no native addon
|
|
10
|
+
// `bun build --compile` (pure JS; the Bun-native pty has no native addon).
|
|
10
11
|
import { Terminal } from '@xterm/headless'
|
|
11
12
|
import { SerializeAddon } from '@xterm/addon-serialize'
|
|
12
13
|
import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, unlinkSync, writeFileSync, writeSync } from 'node:fs'
|