@parlor/agent-host 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.
Files changed (155) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +16 -2
  3. package/dist/acp.d.ts +165 -0
  4. package/dist/acp.d.ts.map +1 -0
  5. package/dist/acp.js +626 -0
  6. package/dist/acp.js.map +1 -0
  7. package/dist/adapter-launch.d.ts +7 -0
  8. package/dist/adapter-launch.d.ts.map +1 -0
  9. package/dist/adapter-launch.js +35 -0
  10. package/dist/adapter-launch.js.map +1 -0
  11. package/dist/agents.d.ts +32 -0
  12. package/dist/agents.d.ts.map +1 -0
  13. package/dist/agents.js +132 -0
  14. package/dist/agents.js.map +1 -0
  15. package/dist/assertion.d.ts +9 -0
  16. package/dist/assertion.d.ts.map +1 -0
  17. package/dist/assertion.js +22 -0
  18. package/dist/assertion.js.map +1 -0
  19. package/dist/control.d.ts +106 -0
  20. package/dist/control.d.ts.map +1 -0
  21. package/dist/control.js +32 -0
  22. package/dist/control.js.map +1 -0
  23. package/dist/daemon.d.ts +50 -0
  24. package/dist/daemon.d.ts.map +1 -0
  25. package/dist/daemon.js +543 -0
  26. package/dist/daemon.js.map +1 -0
  27. package/dist/dev-host.d.ts +5 -0
  28. package/dist/dev-host.d.ts.map +1 -0
  29. package/dist/dev-host.js +29 -0
  30. package/dist/dev-host.js.map +1 -0
  31. package/dist/device-list.d.ts +6 -0
  32. package/dist/device-list.d.ts.map +1 -0
  33. package/dist/device-list.js +30 -0
  34. package/dist/device-list.js.map +1 -0
  35. package/dist/embedded.d.ts +6 -0
  36. package/dist/embedded.d.ts.map +1 -0
  37. package/dist/embedded.js +3 -0
  38. package/dist/embedded.js.map +1 -0
  39. package/dist/fold.d.ts +38 -0
  40. package/dist/fold.d.ts.map +1 -0
  41. package/dist/fold.js +165 -0
  42. package/dist/fold.js.map +1 -0
  43. package/dist/grants.d.ts +54 -0
  44. package/dist/grants.d.ts.map +1 -0
  45. package/dist/grants.js +157 -0
  46. package/dist/grants.js.map +1 -0
  47. package/dist/index.d.ts +17 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +17 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/json-file.d.ts +6 -0
  52. package/dist/json-file.d.ts.map +1 -0
  53. package/dist/json-file.js +35 -0
  54. package/dist/json-file.js.map +1 -0
  55. package/dist/lines.d.ts +5 -0
  56. package/dist/lines.d.ts.map +1 -0
  57. package/dist/lines.js +9 -0
  58. package/dist/lines.js.map +1 -0
  59. package/dist/link-device.d.ts +28 -0
  60. package/dist/link-device.d.ts.map +1 -0
  61. package/dist/link-device.js +40 -0
  62. package/dist/link-device.js.map +1 -0
  63. package/dist/link.d.ts +52 -0
  64. package/dist/link.d.ts.map +1 -0
  65. package/dist/link.js +277 -0
  66. package/dist/link.js.map +1 -0
  67. package/dist/lock.d.ts +7 -0
  68. package/dist/lock.d.ts.map +1 -0
  69. package/dist/lock.js +105 -0
  70. package/dist/lock.js.map +1 -0
  71. package/dist/log.d.ts +3 -0
  72. package/dist/log.d.ts.map +1 -0
  73. package/dist/log.js +18 -0
  74. package/dist/log.js.map +1 -0
  75. package/dist/main.d.ts +3 -0
  76. package/dist/main.d.ts.map +1 -0
  77. package/dist/main.js +520 -0
  78. package/dist/main.js.map +1 -0
  79. package/dist/mcp-proxy.d.ts +33 -0
  80. package/dist/mcp-proxy.d.ts.map +1 -0
  81. package/dist/mcp-proxy.js +317 -0
  82. package/dist/mcp-proxy.js.map +1 -0
  83. package/dist/naming.d.ts +7 -0
  84. package/dist/naming.d.ts.map +1 -0
  85. package/dist/naming.js +42 -0
  86. package/dist/naming.js.map +1 -0
  87. package/dist/pairing.d.ts +68 -0
  88. package/dist/pairing.d.ts.map +1 -0
  89. package/dist/pairing.js +312 -0
  90. package/dist/pairing.js.map +1 -0
  91. package/dist/paths.d.ts +19 -0
  92. package/dist/paths.d.ts.map +1 -0
  93. package/dist/paths.js +23 -0
  94. package/dist/paths.js.map +1 -0
  95. package/dist/policy.d.ts +37 -0
  96. package/dist/policy.d.ts.map +1 -0
  97. package/dist/policy.js +60 -0
  98. package/dist/policy.js.map +1 -0
  99. package/dist/pool.d.ts +34 -0
  100. package/dist/pool.d.ts.map +1 -0
  101. package/dist/pool.js +112 -0
  102. package/dist/pool.js.map +1 -0
  103. package/dist/prompter.d.ts +33 -0
  104. package/dist/prompter.d.ts.map +1 -0
  105. package/dist/prompter.js +98 -0
  106. package/dist/prompter.js.map +1 -0
  107. package/dist/release-main.d.ts +2 -0
  108. package/dist/release-main.d.ts.map +1 -0
  109. package/dist/release-main.js +50 -0
  110. package/dist/release-main.js.map +1 -0
  111. package/dist/release.d.ts +22 -0
  112. package/dist/release.d.ts.map +1 -0
  113. package/dist/release.js +81 -0
  114. package/dist/release.js.map +1 -0
  115. package/dist/revoke.d.ts +10 -0
  116. package/dist/revoke.d.ts.map +1 -0
  117. package/dist/revoke.js +23 -0
  118. package/dist/revoke.js.map +1 -0
  119. package/dist/self.d.ts +7 -0
  120. package/dist/self.d.ts.map +1 -0
  121. package/dist/self.js +10 -0
  122. package/dist/self.js.map +1 -0
  123. package/dist/service.d.ts +97 -0
  124. package/dist/service.d.ts.map +1 -0
  125. package/dist/service.js +205 -0
  126. package/dist/service.js.map +1 -0
  127. package/dist/shell-path.d.ts +10 -0
  128. package/dist/shell-path.d.ts.map +1 -0
  129. package/dist/shell-path.js +64 -0
  130. package/dist/shell-path.js.map +1 -0
  131. package/dist/skeleton-kit.d.ts +8 -0
  132. package/dist/skeleton-kit.d.ts.map +1 -0
  133. package/dist/skeleton-kit.js +143 -0
  134. package/dist/skeleton-kit.js.map +1 -0
  135. package/dist/spec-kit.d.ts +102 -0
  136. package/dist/spec-kit.d.ts.map +1 -0
  137. package/dist/spec-kit.js +372 -0
  138. package/dist/spec-kit.js.map +1 -0
  139. package/dist/testing/fake-agent.d.ts +65 -0
  140. package/dist/testing/fake-agent.d.ts.map +1 -0
  141. package/dist/testing/fake-agent.js +238 -0
  142. package/dist/testing/fake-agent.js.map +1 -0
  143. package/dist/trust.d.ts +20 -0
  144. package/dist/trust.d.ts.map +1 -0
  145. package/dist/trust.js +27 -0
  146. package/dist/trust.js.map +1 -0
  147. package/dist/turn.d.ts +32 -0
  148. package/dist/turn.d.ts.map +1 -0
  149. package/dist/turn.js +650 -0
  150. package/dist/turn.js.map +1 -0
  151. package/dist/version.d.ts +2 -0
  152. package/dist/version.d.ts.map +1 -0
  153. package/dist/version.js +6 -0
  154. package/dist/version.js.map +1 -0
  155. package/package.json +57 -3
package/dist/pool.js ADDED
@@ -0,0 +1,112 @@
1
+ import { jsonFile } from "./json-file.js";
2
+ const keyOf = (...parts) => parts.join("\u0000");
3
+ const sessionKeyOf = (grant, threadId, agentId) => keyOf(grant.appOrigin, grant.grantId, threadId, agentId);
4
+ /**
5
+ * Sessions live inside their process: `launch()` hands out the connection with its
6
+ * sessions tracked, so dropping a process forgets every session it held.
7
+ */
8
+ export function createSessionPool(stateFile) {
9
+ const processes = new Map();
10
+ const launching = new Map();
11
+ const owners = new WeakMap();
12
+ const sessions = new Map();
13
+ const remembered = jsonFile(stateFile);
14
+ const discard = (owner) => {
15
+ processes.delete(owner);
16
+ for (const [key, entry] of sessions) {
17
+ if (entry.owner !== owner)
18
+ continue;
19
+ entry.live.proxy.dispose();
20
+ sessions.delete(key);
21
+ }
22
+ };
23
+ const adopt = (owner, connection) => {
24
+ const tracked = {
25
+ capabilities: connection.capabilities,
26
+ exited: connection.exited,
27
+ kill: () => connection.kill(),
28
+ openSession: async (input) => {
29
+ const session = await connection.openSession(input);
30
+ owners.set(session, owner);
31
+ return session;
32
+ },
33
+ };
34
+ processes.set(owner, tracked);
35
+ void connection.exited.then(() => {
36
+ if (processes.get(owner) === tracked)
37
+ discard(owner);
38
+ });
39
+ return tracked;
40
+ };
41
+ const ownedBy = (appOrigin, agentId) => (owner) => agentId === undefined
42
+ ? owner.startsWith(keyOf(appOrigin, ""))
43
+ : owner === keyOf(appOrigin, agentId);
44
+ return {
45
+ process: (appOrigin, agentId) => processes.get(keyOf(appOrigin, agentId)),
46
+ launch(appOrigin, agentId, start) {
47
+ const owner = keyOf(appOrigin, agentId);
48
+ const running = processes.get(owner);
49
+ if (running)
50
+ return Promise.resolve(running);
51
+ const pending = launching.get(owner);
52
+ if (pending)
53
+ return pending.connection;
54
+ const controller = new AbortController();
55
+ const connection = (async () => {
56
+ try {
57
+ const started = await start(controller.signal);
58
+ if (controller.signal.aborted) {
59
+ await started.kill();
60
+ throw new Error("This computer stopped serving the app while the agent started.");
61
+ }
62
+ return adopt(owner, started);
63
+ }
64
+ finally {
65
+ if (launching.get(owner)?.controller === controller)
66
+ launching.delete(owner);
67
+ }
68
+ })();
69
+ launching.set(owner, { controller, connection });
70
+ return connection;
71
+ },
72
+ session: (grant, threadId, agentId) => sessions.get(sessionKeyOf(grant, threadId, agentId))?.live,
73
+ remember(grant, threadId, agentId, live) {
74
+ const owner = keyOf(grant.appOrigin, agentId);
75
+ if (owners.get(live.session) !== owner || processes.get(owner) === undefined) {
76
+ throw new Error("Remember a session opened through the pool's own process.");
77
+ }
78
+ const key = sessionKeyOf(grant, threadId, agentId);
79
+ sessions.set(key, { live, owner });
80
+ void remembered
81
+ .change((all) => void (all[key] = live.session.id))
82
+ .catch(() => undefined);
83
+ },
84
+ resumable: async (grant, threadId, agentId) => {
85
+ const all = await remembered.read().catch(() => ({}));
86
+ return all[sessionKeyOf(grant, threadId, agentId)];
87
+ },
88
+ async drop(appOrigin, agentId) {
89
+ const owned = ownedBy(appOrigin, agentId);
90
+ for (const [owner, pending] of launching) {
91
+ if (!owned(owner))
92
+ continue;
93
+ launching.delete(owner);
94
+ pending.controller.abort();
95
+ }
96
+ const dropped = [...processes].filter(([owner]) => owned(owner));
97
+ for (const [owner] of dropped)
98
+ discard(owner);
99
+ await Promise.all(dropped.map(([, connection]) => connection.kill()));
100
+ },
101
+ async forget(grant) {
102
+ const prefix = keyOf(grant.appOrigin, grant.grantId, "");
103
+ await remembered.change((all) => {
104
+ for (const key of Object.keys(all)) {
105
+ if (key.startsWith(prefix))
106
+ delete all[key];
107
+ }
108
+ });
109
+ },
110
+ };
111
+ }
112
+ //# sourceMappingURL=pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pool.js","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AA4C1C,MAAM,KAAK,GAAG,CAAC,GAAG,KAAwB,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAEpE,MAAM,YAAY,GAAG,CAAC,KAAkB,EAAE,QAAgB,EAAE,OAAe,EAAE,EAAE,CAC7E,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;AAO3D;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IACjD,MAAM,SAAS,GAAG,IAAI,GAAG,EAA2B,CAAC;IACrD,MAAM,SAAS,GAAG,IAAI,GAAG,EAAqB,CAAC;IAC/C,MAAM,MAAM,GAAG,IAAI,OAAO,EAAwB,CAAC;IACnD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAGrB,CAAC;IACJ,MAAM,UAAU,GAAG,QAAQ,CAAyB,SAAS,CAAC,CAAC;IAE/D,MAAM,OAAO,GAAG,CAAC,KAAa,EAAE,EAAE;QAChC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACxB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,QAAQ,EAAE,CAAC;YACpC,IAAI,KAAK,CAAC,KAAK,KAAK,KAAK;gBAAE,SAAS;YACpC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;YAC3B,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACvB,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,KAAK,GAAG,CAAC,KAAa,EAAE,UAA2B,EAAmB,EAAE;QAC5E,MAAM,OAAO,GAAoB;YAC/B,YAAY,EAAE,UAAU,CAAC,YAAY;YACrC,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,IAAI,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE;YAC7B,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;gBAC3B,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;gBACpD,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;gBAC3B,OAAO,OAAO,CAAC;YACjB,CAAC;SACF,CAAC;QACF,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC9B,KAAK,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;YAC/B,IAAI,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,OAAO;gBAAE,OAAO,CAAC,KAAK,CAAC,CAAC;QACvD,CAAC,CAAC,CAAC;QACH,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,CAAC,SAAiB,EAAE,OAA2B,EAAE,EAAE,CAAC,CAAC,KAAa,EAAE,EAAE,CACpF,OAAO,KAAK,SAAS;QACnB,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;QACxC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAE1C,OAAO;QACL,OAAO,EAAE,CAAC,SAAS,EAAE,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACzE,MAAM,CAAC,SAAS,EAAE,OAAO,EAAE,KAAK;YAC9B,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YACxC,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACrC,IAAI,OAAO;gBAAE,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAC7C,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACrC,IAAI,OAAO;gBAAE,OAAO,OAAO,CAAC,UAAU,CAAC;YACvC,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;YACzC,MAAM,UAAU,GAAG,CAAC,KAAK,IAAI,EAAE;gBAC7B,IAAI,CAAC;oBACH,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;oBAC/C,IAAI,UAAU,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;wBAC9B,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;wBACrB,MAAM,IAAI,KAAK,CACb,gEAAgE,CACjE,CAAC;oBACJ,CAAC;oBACD,OAAO,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;gBAC/B,CAAC;wBAAS,CAAC;oBACT,IAAI,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,UAAU,KAAK,UAAU;wBAAE,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBAC/E,CAAC;YACH,CAAC,CAAC,EAAE,CAAC;YACL,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;YACjD,OAAO,UAAU,CAAC;QACpB,CAAC;QACD,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,CACpC,QAAQ,CAAC,GAAG,CAAC,YAAY,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,EAAE,IAAI;QAC5D,QAAQ,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI;YACrC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YAC9C,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,KAAK,IAAI,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,SAAS,EAAE,CAAC;gBAC7E,MAAM,IAAI,KAAK,CAAC,2DAA2D,CAAC,CAAC;YAC/E,CAAC;YACD,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;YACnD,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YACnC,KAAK,UAAU;iBACZ,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;iBAClD,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC5B,CAAC;QACD,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;YAC5C,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAA2B,CAAC,CAAC;YAChF,OAAO,GAAG,CAAC,YAAY,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;QACrD,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,OAAO;YAC3B,MAAM,KAAK,GAAG,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YAC1C,KAAK,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,SAAS,EAAE,CAAC;gBACzC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC;oBAAE,SAAS;gBAC5B,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACxB,OAAO,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;YAC7B,CAAC;YACD,MAAM,OAAO,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;YACjE,KAAK,MAAM,CAAC,KAAK,CAAC,IAAI,OAAO;gBAAE,OAAO,CAAC,KAAK,CAAC,CAAC;YAC9C,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACxE,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,KAAK;YAChB,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YACzD,MAAM,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE;gBAC9B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;oBACnC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC;wBAAE,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;gBAC9C,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { AgentConnection, AgentSession } from \"./acp.js\";\nimport type { AppGrant } from \"./grants.js\";\nimport { jsonFile } from \"./json-file.js\";\nimport type { ProxySession } from \"./mcp-proxy.js\";\n\nexport interface LiveSession {\n readonly session: AgentSession;\n readonly proxy: ProxySession;\n}\n\nexport type PooledGrant = Pick<AppGrant, \"appOrigin\" | \"grantId\">;\n\n/**\n * Processes per (app, agent), so two apps never share an agent process or its MCP\n * servers; sessions per (grant, thread, agent), so a thread keeps its context across\n * turns and never carries it to another account.\n */\nexport interface SessionPool {\n process(appOrigin: string, agentId: string): AgentConnection | undefined;\n /**\n * The running process, or the one `start` launches for every turn that asks meanwhile.\n * A `drop` during the launch aborts `signal` and kills whatever `start` returns.\n */\n launch(\n appOrigin: string,\n agentId: string,\n start: (signal: AbortSignal) => Promise<AgentConnection>,\n ): Promise<AgentConnection>;\n session(grant: PooledGrant, threadId: string, agentId: string): LiveSession | undefined;\n remember(\n grant: PooledGrant,\n threadId: string,\n agentId: string,\n session: LiveSession,\n ): void;\n /** The last session id, from `sessions.json`, for `session/resume` after a restart. */\n resumable(\n grant: PooledGrant,\n threadId: string,\n agentId: string,\n ): Promise<string | undefined>;\n drop(appOrigin: string, agentId?: string): Promise<void>;\n /** Erases the grant's remembered session ids, once the grant is gone. */\n forget(grant: PooledGrant): Promise<void>;\n}\n\nconst keyOf = (...parts: readonly string[]) => parts.join(\"\\u0000\");\n\nconst sessionKeyOf = (grant: PooledGrant, threadId: string, agentId: string) =>\n keyOf(grant.appOrigin, grant.grantId, threadId, agentId);\n\ninterface Launching {\n readonly controller: AbortController;\n readonly connection: Promise<AgentConnection>;\n}\n\n/**\n * Sessions live inside their process: `launch()` hands out the connection with its\n * sessions tracked, so dropping a process forgets every session it held.\n */\nexport function createSessionPool(stateFile: string): SessionPool {\n const processes = new Map<string, AgentConnection>();\n const launching = new Map<string, Launching>();\n const owners = new WeakMap<AgentSession, string>();\n const sessions = new Map<\n string,\n { readonly live: LiveSession; readonly owner: string }\n >();\n const remembered = jsonFile<Record<string, string>>(stateFile);\n\n const discard = (owner: string) => {\n processes.delete(owner);\n for (const [key, entry] of sessions) {\n if (entry.owner !== owner) continue;\n entry.live.proxy.dispose();\n sessions.delete(key);\n }\n };\n\n const adopt = (owner: string, connection: AgentConnection): AgentConnection => {\n const tracked: AgentConnection = {\n capabilities: connection.capabilities,\n exited: connection.exited,\n kill: () => connection.kill(),\n openSession: async (input) => {\n const session = await connection.openSession(input);\n owners.set(session, owner);\n return session;\n },\n };\n processes.set(owner, tracked);\n void connection.exited.then(() => {\n if (processes.get(owner) === tracked) discard(owner);\n });\n return tracked;\n };\n\n const ownedBy = (appOrigin: string, agentId: string | undefined) => (owner: string) =>\n agentId === undefined\n ? owner.startsWith(keyOf(appOrigin, \"\"))\n : owner === keyOf(appOrigin, agentId);\n\n return {\n process: (appOrigin, agentId) => processes.get(keyOf(appOrigin, agentId)),\n launch(appOrigin, agentId, start) {\n const owner = keyOf(appOrigin, agentId);\n const running = processes.get(owner);\n if (running) return Promise.resolve(running);\n const pending = launching.get(owner);\n if (pending) return pending.connection;\n const controller = new AbortController();\n const connection = (async () => {\n try {\n const started = await start(controller.signal);\n if (controller.signal.aborted) {\n await started.kill();\n throw new Error(\n \"This computer stopped serving the app while the agent started.\",\n );\n }\n return adopt(owner, started);\n } finally {\n if (launching.get(owner)?.controller === controller) launching.delete(owner);\n }\n })();\n launching.set(owner, { controller, connection });\n return connection;\n },\n session: (grant, threadId, agentId) =>\n sessions.get(sessionKeyOf(grant, threadId, agentId))?.live,\n remember(grant, threadId, agentId, live) {\n const owner = keyOf(grant.appOrigin, agentId);\n if (owners.get(live.session) !== owner || processes.get(owner) === undefined) {\n throw new Error(\"Remember a session opened through the pool's own process.\");\n }\n const key = sessionKeyOf(grant, threadId, agentId);\n sessions.set(key, { live, owner });\n void remembered\n .change((all) => void (all[key] = live.session.id))\n .catch(() => undefined);\n },\n resumable: async (grant, threadId, agentId) => {\n const all = await remembered.read().catch(() => ({}) as Record<string, string>);\n return all[sessionKeyOf(grant, threadId, agentId)];\n },\n async drop(appOrigin, agentId) {\n const owned = ownedBy(appOrigin, agentId);\n for (const [owner, pending] of launching) {\n if (!owned(owner)) continue;\n launching.delete(owner);\n pending.controller.abort();\n }\n const dropped = [...processes].filter(([owner]) => owned(owner));\n for (const [owner] of dropped) discard(owner);\n await Promise.all(dropped.map(([, connection]) => connection.kill()));\n },\n async forget(grant) {\n const prefix = keyOf(grant.appOrigin, grant.grantId, \"\");\n await remembered.change((all) => {\n for (const key of Object.keys(all)) {\n if (key.startsWith(prefix)) delete all[key];\n }\n });\n },\n };\n}\n"]}
@@ -0,0 +1,33 @@
1
+ /** The computer's own surface, which no app, backend or web page can draw. */
2
+ export interface LocalPrompter {
3
+ /** The chosen option id, or undefined when dismissed. */
4
+ answerHere(input: {
5
+ readonly appName: string;
6
+ readonly agent: string;
7
+ readonly question: string;
8
+ readonly choices: readonly {
9
+ readonly id: string;
10
+ readonly label: string;
11
+ }[];
12
+ readonly signal?: AbortSignal | undefined;
13
+ }): Promise<string | undefined>;
14
+ /** Passive: a turn from another device started here; none for a browser on this computer. */
15
+ turnStarted(input: {
16
+ readonly appName: string;
17
+ readonly device: string;
18
+ readonly agent: string;
19
+ readonly preview: string;
20
+ }): void;
21
+ /** Passive: a device was linked to the agents here. */
22
+ deviceLinked?(input: {
23
+ readonly appName: string;
24
+ readonly device: string;
25
+ }): void;
26
+ }
27
+ export declare const printable: (text: string) => string;
28
+ /** A `[y/N]` question on the terminal that ran a command; only yes is yes. */
29
+ export declare const askYesNo: (question: string) => Promise<boolean>;
30
+ /** Interactive `connect`: asks on the TTY that ran it. */
31
+ export declare function terminalPrompter(): LocalPrompter;
32
+ export declare function macDialogPrompter(): LocalPrompter;
33
+ //# sourceMappingURL=prompter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompter.d.ts","sourceRoot":"","sources":["../src/prompter.ts"],"names":[],"mappings":"AAGA,8EAA8E;AAC9E,MAAM,WAAW,aAAa;IAC5B,yDAAyD;IACzD,UAAU,CAAC,KAAK,EAAE;QAChB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,OAAO,EAAE,SAAS;YAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAC7E,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;KAC3C,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAChC,6FAA6F;IAC7F,WAAW,CAAC,KAAK,EAAE;QACjB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;KAC1B,GAAG,IAAI,CAAC;IACT,uDAAuD;IACvD,YAAY,CAAC,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CACnF;AAID,eAAO,MAAM,SAAS,SAAU,MAAM,WAAkC,CAAC;AAmBzE,8EAA8E;AAC9E,eAAO,MAAM,QAAQ,aAAoB,MAAM,KAAG,OAAO,CAAC,OAAO,CAChB,CAAC;AAElD,0DAA0D;AAC1D,wBAAgB,gBAAgB,IAAI,aAAa,CAsBhD;AAoDD,wBAAgB,iBAAiB,IAAI,aAAa,CAwCjD"}
@@ -0,0 +1,98 @@
1
+ import { execFile } from "node:child_process";
2
+ import { createInterface } from "node:readline";
3
+ const UNPRINTABLE = /[\p{Cc}\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/gu;
4
+ export const printable = (text) => text.replace(UNPRINTABLE, "");
5
+ function ask(question) {
6
+ return new Promise((resolve) => {
7
+ const lines = createInterface({ input: process.stdin, output: process.stdout });
8
+ let answered = false;
9
+ lines.question(question, (answer) => {
10
+ answered = true;
11
+ lines.close();
12
+ resolve(answer);
13
+ });
14
+ lines.once("close", () => {
15
+ if (!answered)
16
+ resolve(undefined);
17
+ });
18
+ });
19
+ }
20
+ const yes = (answer) => /^y(es)?$/i.test(answer?.trim() ?? "");
21
+ /** A `[y/N]` question on the terminal that ran a command; only yes is yes. */
22
+ export const askYesNo = async (question) => yes(await ask(`${printable(question)} [y/N] `));
23
+ /** Interactive `connect`: asks on the TTY that ran it. */
24
+ export function terminalPrompter() {
25
+ return {
26
+ answerHere: async ({ appName, agent, question, choices }) => {
27
+ const menu = choices
28
+ .map((choice, at) => ` ${at + 1}. ${printable(choice.label)}`)
29
+ .join("\n");
30
+ const answer = await ask(`${printable(agent)} for ${printable(appName)} asks: ${printable(question)}\n${menu}\nChoice: `);
31
+ return choices[Number(answer?.trim()) - 1]?.id;
32
+ },
33
+ deviceLinked: ({ appName, device }) => {
34
+ process.stdout.write(`${printable(device)} was linked to your agents for ${printable(appName)}.\n`);
35
+ },
36
+ turnStarted: ({ appName, device, agent, preview }) => {
37
+ process.stdout.write(`${printable(agent)} is working for ${printable(appName)} from ${printable(device)}: ${printable(preview)}\n`);
38
+ },
39
+ };
40
+ }
41
+ const DIALOG_TITLE = "Parlor Host";
42
+ const MAX_DIALOG_BUTTONS = 3;
43
+ const MAX_DIALOG_TEXT = 600;
44
+ const clipped = (text) => printable(text).length > MAX_DIALOG_TEXT
45
+ ? `${printable(text).slice(0, MAX_DIALOG_TEXT)}...`
46
+ : printable(text);
47
+ function osascript(lines, args, signal) {
48
+ return new Promise((resolve) => {
49
+ execFile("osascript", [...lines.flatMap((line) => ["-e", line]), "--", ...args], { signal }, (error, stdout) => resolve(error ? undefined : stdout.trim()));
50
+ });
51
+ }
52
+ function choose(text, labels, signal) {
53
+ const options = labels.map(clipped);
54
+ return osascript(labels.length <= MAX_DIALOG_BUTTONS
55
+ ? [
56
+ "on run argv",
57
+ `set reply to display dialog (item 1 of argv) with title "${DIALOG_TITLE}" buttons (rest of argv)`,
58
+ "return button returned of reply",
59
+ "end run",
60
+ ]
61
+ : [
62
+ "on run argv",
63
+ `set picked to choose from list (rest of argv) with title "${DIALOG_TITLE}" with prompt (item 1 of argv)`,
64
+ "if picked is false then error number -128",
65
+ "return item 1 of picked",
66
+ "end run",
67
+ ], [clipped(text), ...options], signal);
68
+ }
69
+ export function macDialogPrompter() {
70
+ return {
71
+ answerHere: async ({ appName, agent, question, choices, signal }) => {
72
+ const label = await choose(`${agent} for ${appName} asks: ${question}`, choices.map((choice) => choice.label), signal);
73
+ return choices.find((choice) => clipped(choice.label) === label)?.id;
74
+ },
75
+ deviceLinked: ({ appName, device }) => {
76
+ void osascript([
77
+ "on run argv",
78
+ `display notification (item 2 of argv) with title "${DIALOG_TITLE}" subtitle (item 1 of argv)`,
79
+ "end run",
80
+ ], [
81
+ clipped(`${device} was linked`),
82
+ clipped(`It can use your agents for ${appName}. Not you? Remove it under Devices in the app's Agent sidebar.`),
83
+ ]);
84
+ },
85
+ turnStarted: ({ appName, device, agent, preview }) => {
86
+ void osascript([
87
+ "on run argv",
88
+ `display notification (item 3 of argv) with title "${DIALOG_TITLE}" subtitle (item 1 of argv & " from " & item 2 of argv)`,
89
+ "end run",
90
+ ], [
91
+ clipped(`${agent} is working for ${appName}`),
92
+ clipped(device),
93
+ clipped(preview),
94
+ ]);
95
+ },
96
+ };
97
+ }
98
+ //# sourceMappingURL=prompter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompter.js","sourceRoot":"","sources":["../src/prompter.ts"],"names":[],"mappings":"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","sourcesContent":["import { execFile } from \"node:child_process\";\nimport { createInterface } from \"node:readline\";\n\n/** The computer's own surface, which no app, backend or web page can draw. */\nexport interface LocalPrompter {\n /** The chosen option id, or undefined when dismissed. */\n answerHere(input: {\n readonly appName: string;\n readonly agent: string;\n readonly question: string;\n readonly choices: readonly { readonly id: string; readonly label: string }[];\n readonly signal?: AbortSignal | undefined;\n }): Promise<string | undefined>;\n /** Passive: a turn from another device started here; none for a browser on this computer. */\n turnStarted(input: {\n readonly appName: string;\n readonly device: string;\n readonly agent: string;\n readonly preview: string;\n }): void;\n /** Passive: a device was linked to the agents here. */\n deviceLinked?(input: { readonly appName: string; readonly device: string }): void;\n}\n\nconst UNPRINTABLE = /[\\p{Cc}\\u061c\\u200e\\u200f\\u202a-\\u202e\\u2066-\\u2069]/gu;\n\nexport const printable = (text: string) => text.replace(UNPRINTABLE, \"\");\n\nfunction ask(question: string): Promise<string | undefined> {\n return new Promise((resolve) => {\n const lines = createInterface({ input: process.stdin, output: process.stdout });\n let answered = false;\n lines.question(question, (answer) => {\n answered = true;\n lines.close();\n resolve(answer);\n });\n lines.once(\"close\", () => {\n if (!answered) resolve(undefined);\n });\n });\n}\n\nconst yes = (answer: string | undefined) => /^y(es)?$/i.test(answer?.trim() ?? \"\");\n\n/** A `[y/N]` question on the terminal that ran a command; only yes is yes. */\nexport const askYesNo = async (question: string): Promise<boolean> =>\n yes(await ask(`${printable(question)} [y/N] `));\n\n/** Interactive `connect`: asks on the TTY that ran it. */\nexport function terminalPrompter(): LocalPrompter {\n return {\n answerHere: async ({ appName, agent, question, choices }) => {\n const menu = choices\n .map((choice, at) => ` ${at + 1}. ${printable(choice.label)}`)\n .join(\"\\n\");\n const answer = await ask(\n `${printable(agent)} for ${printable(appName)} asks: ${printable(question)}\\n${menu}\\nChoice: `,\n );\n return choices[Number(answer?.trim()) - 1]?.id;\n },\n deviceLinked: ({ appName, device }) => {\n process.stdout.write(\n `${printable(device)} was linked to your agents for ${printable(appName)}.\\n`,\n );\n },\n turnStarted: ({ appName, device, agent, preview }) => {\n process.stdout.write(\n `${printable(agent)} is working for ${printable(appName)} from ${printable(device)}: ${printable(preview)}\\n`,\n );\n },\n };\n}\n\nconst DIALOG_TITLE = \"Parlor Host\";\nconst MAX_DIALOG_BUTTONS = 3;\nconst MAX_DIALOG_TEXT = 600;\n\nconst clipped = (text: string) =>\n printable(text).length > MAX_DIALOG_TEXT\n ? `${printable(text).slice(0, MAX_DIALOG_TEXT)}...`\n : printable(text);\n\nfunction osascript(\n lines: readonly string[],\n args: readonly string[],\n signal?: AbortSignal,\n): Promise<string | undefined> {\n return new Promise((resolve) => {\n execFile(\n \"osascript\",\n [...lines.flatMap((line) => [\"-e\", line]), \"--\", ...args],\n { signal },\n (error, stdout) => resolve(error ? undefined : stdout.trim()),\n );\n });\n}\n\nfunction choose(\n text: string,\n labels: readonly string[],\n signal?: AbortSignal,\n): Promise<string | undefined> {\n const options = labels.map(clipped);\n return osascript(\n labels.length <= MAX_DIALOG_BUTTONS\n ? [\n \"on run argv\",\n `set reply to display dialog (item 1 of argv) with title \"${DIALOG_TITLE}\" buttons (rest of argv)`,\n \"return button returned of reply\",\n \"end run\",\n ]\n : [\n \"on run argv\",\n `set picked to choose from list (rest of argv) with title \"${DIALOG_TITLE}\" with prompt (item 1 of argv)`,\n \"if picked is false then error number -128\",\n \"return item 1 of picked\",\n \"end run\",\n ],\n [clipped(text), ...options],\n signal,\n );\n}\n\nexport function macDialogPrompter(): LocalPrompter {\n return {\n answerHere: async ({ appName, agent, question, choices, signal }) => {\n const label = await choose(\n `${agent} for ${appName} asks: ${question}`,\n choices.map((choice) => choice.label),\n signal,\n );\n return choices.find((choice) => clipped(choice.label) === label)?.id;\n },\n deviceLinked: ({ appName, device }) => {\n void osascript(\n [\n \"on run argv\",\n `display notification (item 2 of argv) with title \"${DIALOG_TITLE}\" subtitle (item 1 of argv)`,\n \"end run\",\n ],\n [\n clipped(`${device} was linked`),\n clipped(\n `It can use your agents for ${appName}. Not you? Remove it under Devices in the app's Agent sidebar.`,\n ),\n ],\n );\n },\n turnStarted: ({ appName, device, agent, preview }) => {\n void osascript(\n [\n \"on run argv\",\n `display notification (item 3 of argv) with title \"${DIALOG_TITLE}\" subtitle (item 1 of argv & \" from \" & item 2 of argv)`,\n \"end run\",\n ],\n [\n clipped(`${agent} is working for ${appName}`),\n clipped(device),\n clipped(preview),\n ],\n );\n },\n };\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=release-main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"release-main.d.ts","sourceRoot":"","sources":["../src/release-main.ts"],"names":[],"mappings":""}
@@ -0,0 +1,50 @@
1
+ import { AGENT_PROFILES } from "./agents.js";
2
+ import { adapterKey, embeddedAdapters } from "./embedded.js";
3
+ async function runClaudeAdapter() {
4
+ const { applyManagedPolicyEnv } = await import("@agentclientprotocol/claude-agent-acp/dist/managed-policy.js");
5
+ const { serveAcp } = await import("@agentclientprotocol/claude-agent-acp/dist/serve.js");
6
+ await applyManagedPolicyEnv();
7
+ console.log = console.error;
8
+ console.info = console.error;
9
+ console.warn = console.error;
10
+ console.debug = console.error;
11
+ const acp = serveAcp(undefined, { experimentalV2: false });
12
+ const shutdown = async () => {
13
+ await acp.dispose().catch(() => undefined);
14
+ process.exit(0);
15
+ };
16
+ void acp.closed.then(shutdown);
17
+ process.on("SIGTERM", shutdown);
18
+ process.on("SIGINT", shutdown);
19
+ process.stdin.resume();
20
+ }
21
+ async function runCodexAdapter() {
22
+ await import("@agentclientprotocol/codex-acp/dist/index.js");
23
+ }
24
+ const ADAPTERS = {
25
+ claude: runClaudeAdapter,
26
+ codex: runCodexAdapter,
27
+ };
28
+ for (const profile of AGENT_PROFILES) {
29
+ if (profile.launch.kind === "adapter" && profile.id in ADAPTERS) {
30
+ embeddedAdapters.set(adapterKey(profile.launch), profile.id);
31
+ }
32
+ }
33
+ async function main() {
34
+ const [command, adapter] = process.argv.slice(2);
35
+ if (command !== "adapter") {
36
+ await import("./main.js");
37
+ return;
38
+ }
39
+ const run = adapter === undefined ? undefined : ADAPTERS[adapter];
40
+ if (!run) {
41
+ process.stderr.write(`parlor-host adapter <${Object.keys(ADAPTERS).join("|")}>\n`);
42
+ process.exit(2);
43
+ }
44
+ await run();
45
+ }
46
+ main().catch((error) => {
47
+ console.error(error);
48
+ process.exit(1);
49
+ });
50
+ //# sourceMappingURL=release-main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"release-main.js","sourceRoot":"","sources":["../src/release-main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAE7D,KAAK,UAAU,gBAAgB;IAC7B,MAAM,EAAE,qBAAqB,EAAE,GAC7B,MAAM,MAAM,CAAC,8DAA8D,CAAC,CAAC;IAC/E,MAAM,EAAE,QAAQ,EAAE,GAChB,MAAM,MAAM,CAAC,qDAAqD,CAAC,CAAC;IACtE,MAAM,qBAAqB,EAAE,CAAC;IAC9B,OAAO,CAAC,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC;IAC5B,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC;IAC7B,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC;IAC7B,OAAO,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;IAC9B,MAAM,GAAG,GAAG,QAAQ,CAAC,SAAS,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,CAAC;IAC3D,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE;QAC1B,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC3C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;IACF,KAAK,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC/B,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;AACzB,CAAC;AAED,KAAK,UAAU,eAAe;IAC5B,MAAM,MAAM,CAAC,8CAA8C,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,QAAQ,GAAkD;IAC9D,MAAM,EAAE,gBAAgB;IACxB,KAAK,EAAE,eAAe;CACvB,CAAC;AAEF,KAAK,MAAM,OAAO,IAAI,cAAc,EAAE,CAAC;IACrC,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,CAAC,EAAE,IAAI,QAAQ,EAAE,CAAC;QAChE,gBAAgB,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IAC/D,CAAC;AACH,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACjD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;QAC1B,OAAO;IACT,CAAC;IACD,MAAM,GAAG,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAClE,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,wBAAwB,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACnF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,GAAG,EAAE,CAAC;AACd,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACrB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC","sourcesContent":["import { AGENT_PROFILES } from \"./agents.js\";\nimport { adapterKey, embeddedAdapters } from \"./embedded.js\";\n\nasync function runClaudeAdapter(): Promise<void> {\n const { applyManagedPolicyEnv } =\n await import(\"@agentclientprotocol/claude-agent-acp/dist/managed-policy.js\");\n const { serveAcp } =\n await import(\"@agentclientprotocol/claude-agent-acp/dist/serve.js\");\n await applyManagedPolicyEnv();\n console.log = console.error;\n console.info = console.error;\n console.warn = console.error;\n console.debug = console.error;\n const acp = serveAcp(undefined, { experimentalV2: false });\n const shutdown = async () => {\n await acp.dispose().catch(() => undefined);\n process.exit(0);\n };\n void acp.closed.then(shutdown);\n process.on(\"SIGTERM\", shutdown);\n process.on(\"SIGINT\", shutdown);\n process.stdin.resume();\n}\n\nasync function runCodexAdapter(): Promise<void> {\n await import(\"@agentclientprotocol/codex-acp/dist/index.js\");\n}\n\nconst ADAPTERS: Readonly<Record<string, () => Promise<void>>> = {\n claude: runClaudeAdapter,\n codex: runCodexAdapter,\n};\n\nfor (const profile of AGENT_PROFILES) {\n if (profile.launch.kind === \"adapter\" && profile.id in ADAPTERS) {\n embeddedAdapters.set(adapterKey(profile.launch), profile.id);\n }\n}\n\nasync function main(): Promise<void> {\n const [command, adapter] = process.argv.slice(2);\n if (command !== \"adapter\") {\n await import(\"./main.js\");\n return;\n }\n const run = adapter === undefined ? undefined : ADAPTERS[adapter];\n if (!run) {\n process.stderr.write(`parlor-host adapter <${Object.keys(ADAPTERS).join(\"|\")}>\\n`);\n process.exit(2);\n }\n await run();\n}\n\nmain().catch((error: unknown) => {\n console.error(error);\n process.exit(1);\n});\n"]}
@@ -0,0 +1,22 @@
1
+ /** The Parlor release public key, base64url, fixed at build time; a build without one trusts no handover. */
2
+ export declare const RELEASE_KEY: string | undefined;
3
+ export declare const RELEASE_PLATFORM: string;
4
+ export interface ReleaseSignature {
5
+ readonly v: 1;
6
+ readonly version: string;
7
+ readonly platform: string;
8
+ readonly sha256: string;
9
+ readonly signature: string;
10
+ }
11
+ export declare const isVersion: (text: string) => boolean;
12
+ export declare function compareVersions(a: string, b: string): number;
13
+ export declare function releaseBytes(signature: Omit<ReleaseSignature, "signature">): Uint8Array;
14
+ export declare function parseReleaseSignature(text: string): ReleaseSignature | undefined;
15
+ export declare function signatureHolds(signature: ReleaseSignature, key: string | undefined): boolean;
16
+ export declare function sha256File(path: string): Promise<string>;
17
+ /**
18
+ * The signed release a binary is: its `.sig` verifies under `key`, names this platform,
19
+ * and its hash is the binary's own. `undefined` for anything else.
20
+ */
21
+ export declare function signedRelease(binary: string, signatureFile: string, key: string | undefined): Promise<ReleaseSignature | undefined>;
22
+ //# sourceMappingURL=release.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"release.d.ts","sourceRoot":"","sources":["../src/release.ts"],"names":[],"mappings":"AAQA,6GAA6G;AAC7G,eAAO,MAAM,WAAW,EAAE,MAAM,GAAG,SACiD,CAAC;AAErF,eAAO,MAAM,gBAAgB,QAAwC,CAAC;AAItE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC;IACd,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,SAAS,SAAU,MAAM,YAAuB,CAAC;AAE9D,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAW5D;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,IAAI,CAAC,gBAAgB,EAAE,WAAW,CAAC,GAAG,UAAU,CAGvF;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAchF;AAED,wBAAgB,cAAc,CAC5B,SAAS,EAAE,gBAAgB,EAC3B,GAAG,EAAE,MAAM,GAAG,SAAS,GACtB,OAAO,CAYT;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQxD;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,EACrB,GAAG,EAAE,MAAM,GAAG,SAAS,GACtB,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAQvC"}
@@ -0,0 +1,81 @@
1
+ import { createHash, createPublicKey, verify } from "node:crypto";
2
+ import { createReadStream } from "node:fs";
3
+ import { readFile } from "node:fs/promises";
4
+ import { canonicalBytes } from "@parlor/capabilities/encoding";
5
+ /** The Parlor release public key, base64url, fixed at build time; a build without one trusts no handover. */
6
+ export const RELEASE_KEY = typeof __PARLOR_RELEASE_KEY__ === "undefined" ? undefined : __PARLOR_RELEASE_KEY__;
7
+ export const RELEASE_PLATFORM = `${process.platform}-${process.arch}`;
8
+ const VERSION = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
9
+ export const isVersion = (text) => VERSION.test(text);
10
+ export function compareVersions(a, b) {
11
+ const [left, right] = [a, b].map((version) => {
12
+ const match = VERSION.exec(version);
13
+ if (!match)
14
+ throw new Error(`"${version}" is not a major.minor.patch version.`);
15
+ return match.slice(1).map(Number);
16
+ });
17
+ for (let at = 0; at < 3; at += 1) {
18
+ const difference = left[at] - right[at];
19
+ if (difference !== 0)
20
+ return difference;
21
+ }
22
+ return 0;
23
+ }
24
+ export function releaseBytes(signature) {
25
+ const { v, version, platform, sha256 } = signature;
26
+ return canonicalBytes({ v, version, platform, sha256 });
27
+ }
28
+ export function parseReleaseSignature(text) {
29
+ let value;
30
+ try {
31
+ value = JSON.parse(text);
32
+ }
33
+ catch {
34
+ return undefined;
35
+ }
36
+ if (typeof value !== "object" || value === null)
37
+ return undefined;
38
+ const { v, version, platform, sha256, signature } = value;
39
+ if (v !== 1)
40
+ return undefined;
41
+ if (typeof version !== "string" || !isVersion(version))
42
+ return undefined;
43
+ if (typeof platform !== "string" || typeof signature !== "string")
44
+ return undefined;
45
+ if (typeof sha256 !== "string" || !/^[0-9a-f]{64}$/.test(sha256))
46
+ return undefined;
47
+ return { v, version, platform, sha256, signature };
48
+ }
49
+ export function signatureHolds(signature, key) {
50
+ if (key === undefined)
51
+ return false;
52
+ try {
53
+ return verify(null, releaseBytes(signature), createPublicKey({ key: { kty: "OKP", crv: "Ed25519", x: key }, format: "jwk" }), Buffer.from(signature.signature, "base64url"));
54
+ }
55
+ catch {
56
+ return false;
57
+ }
58
+ }
59
+ export function sha256File(path) {
60
+ return new Promise((resolve, reject) => {
61
+ const hash = createHash("sha256");
62
+ createReadStream(path)
63
+ .on("data", (chunk) => hash.update(chunk))
64
+ .once("end", () => resolve(hash.digest("hex")))
65
+ .once("error", reject);
66
+ });
67
+ }
68
+ /**
69
+ * The signed release a binary is: its `.sig` verifies under `key`, names this platform,
70
+ * and its hash is the binary's own. `undefined` for anything else.
71
+ */
72
+ export async function signedRelease(binary, signatureFile, key) {
73
+ const signature = parseReleaseSignature(await readFile(signatureFile, "utf8").catch(() => ""));
74
+ if (!signature || signature.platform !== RELEASE_PLATFORM)
75
+ return undefined;
76
+ if (!signatureHolds(signature, key))
77
+ return undefined;
78
+ const actual = await sha256File(binary).catch(() => undefined);
79
+ return actual === signature.sha256 ? signature : undefined;
80
+ }
81
+ //# sourceMappingURL=release.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"release.js","sourceRoot":"","sources":["../src/release.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, createPublicKey, verify } from \"node:crypto\";\nimport { createReadStream } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\n\nimport { canonicalBytes } from \"@parlor/capabilities/encoding\";\n\ndeclare const __PARLOR_RELEASE_KEY__: string | undefined;\n\n/** The Parlor release public key, base64url, fixed at build time; a build without one trusts no handover. */\nexport const RELEASE_KEY: string | undefined =\n typeof __PARLOR_RELEASE_KEY__ === \"undefined\" ? undefined : __PARLOR_RELEASE_KEY__;\n\nexport const RELEASE_PLATFORM = `${process.platform}-${process.arch}`;\n\nconst VERSION = /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$/;\n\nexport interface ReleaseSignature {\n readonly v: 1;\n readonly version: string;\n readonly platform: string;\n readonly sha256: string;\n readonly signature: string;\n}\n\nexport const isVersion = (text: string) => VERSION.test(text);\n\nexport function compareVersions(a: string, b: string): number {\n const [left, right] = [a, b].map((version) => {\n const match = VERSION.exec(version);\n if (!match) throw new Error(`\"${version}\" is not a major.minor.patch version.`);\n return match.slice(1).map(Number);\n }) as [number[], number[]];\n for (let at = 0; at < 3; at += 1) {\n const difference = left[at]! - right[at]!;\n if (difference !== 0) return difference;\n }\n return 0;\n}\n\nexport function releaseBytes(signature: Omit<ReleaseSignature, \"signature\">): Uint8Array {\n const { v, version, platform, sha256 } = signature;\n return canonicalBytes({ v, version, platform, sha256 });\n}\n\nexport function parseReleaseSignature(text: string): ReleaseSignature | undefined {\n let value: unknown;\n try {\n value = JSON.parse(text);\n } catch {\n return undefined;\n }\n if (typeof value !== \"object\" || value === null) return undefined;\n const { v, version, platform, sha256, signature } = value as Record<string, unknown>;\n if (v !== 1) return undefined;\n if (typeof version !== \"string\" || !isVersion(version)) return undefined;\n if (typeof platform !== \"string\" || typeof signature !== \"string\") return undefined;\n if (typeof sha256 !== \"string\" || !/^[0-9a-f]{64}$/.test(sha256)) return undefined;\n return { v, version, platform, sha256, signature };\n}\n\nexport function signatureHolds(\n signature: ReleaseSignature,\n key: string | undefined,\n): boolean {\n if (key === undefined) return false;\n try {\n return verify(\n null,\n releaseBytes(signature),\n createPublicKey({ key: { kty: \"OKP\", crv: \"Ed25519\", x: key }, format: \"jwk\" }),\n Buffer.from(signature.signature, \"base64url\"),\n );\n } catch {\n return false;\n }\n}\n\nexport function sha256File(path: string): Promise<string> {\n return new Promise((resolve, reject) => {\n const hash = createHash(\"sha256\");\n createReadStream(path)\n .on(\"data\", (chunk) => hash.update(chunk))\n .once(\"end\", () => resolve(hash.digest(\"hex\")))\n .once(\"error\", reject);\n });\n}\n\n/**\n * The signed release a binary is: its `.sig` verifies under `key`, names this platform,\n * and its hash is the binary's own. `undefined` for anything else.\n */\nexport async function signedRelease(\n binary: string,\n signatureFile: string,\n key: string | undefined,\n): Promise<ReleaseSignature | undefined> {\n const signature = parseReleaseSignature(\n await readFile(signatureFile, \"utf8\").catch(() => \"\"),\n );\n if (!signature || signature.platform !== RELEASE_PLATFORM) return undefined;\n if (!signatureHolds(signature, key)) return undefined;\n const actual = await sha256File(binary).catch(() => undefined);\n return actual === signature.sha256 ? signature : undefined;\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import type { AssertionRequest } from "./assertion.js";
2
+ export declare const REVOKE_PATH = "/_parlor/auth/hosts/revoke";
3
+ export interface RevokeInput {
4
+ readonly appOrigin: string;
5
+ readonly appName: string;
6
+ readonly assert: (request: AssertionRequest) => Promise<string>;
7
+ readonly fetch?: typeof fetch | undefined;
8
+ }
9
+ export declare function revokeGrant(input: RevokeInput): Promise<void>;
10
+ //# sourceMappingURL=revoke.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revoke.d.ts","sourceRoot":"","sources":["../src/revoke.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,eAAO,MAAM,WAAW,+BAA+B,CAAC;AAIxD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAChE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,GAAG,SAAS,CAAC;CAC3C;AAED,wBAAsB,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBnE"}
package/dist/revoke.js ADDED
@@ -0,0 +1,23 @@
1
+ export const REVOKE_PATH = "/_parlor/auth/hosts/revoke";
2
+ const alreadyRevoked = (response) => response.status === 401;
3
+ export async function revokeGrant(input) {
4
+ const url = `${input.appOrigin}${REVOKE_PATH}`;
5
+ const authorization = `ParlorHost ${await input.assert({ method: "POST", url })}`;
6
+ let response;
7
+ try {
8
+ response = await (input.fetch ?? fetch)(url, {
9
+ method: "POST",
10
+ headers: { authorization, "content-type": "application/json" },
11
+ body: "{}",
12
+ });
13
+ }
14
+ catch (error) {
15
+ throw new Error(`${input.appName} could not be reached to disconnect this computer.`, {
16
+ cause: error,
17
+ });
18
+ }
19
+ if (!response.ok && !alreadyRevoked(response)) {
20
+ throw new Error(`${input.appName} did not disconnect this computer: it answered HTTP ${response.status}.`);
21
+ }
22
+ }
23
+ //# sourceMappingURL=revoke.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revoke.js","sourceRoot":"","sources":["../src/revoke.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,WAAW,GAAG,4BAA4B,CAAC;AAExD,MAAM,cAAc,GAAG,CAAC,QAAkB,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,KAAK,GAAG,CAAC;AASvE,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,KAAkB;IAClD,MAAM,GAAG,GAAG,GAAG,KAAK,CAAC,SAAS,GAAG,WAAW,EAAE,CAAC;IAC/C,MAAM,aAAa,GAAG,cAAc,MAAM,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;IAClF,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,CAAC,GAAG,EAAE;YAC3C,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,aAAa,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC9D,IAAI,EAAE,IAAI;SACX,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,GAAG,KAAK,CAAC,OAAO,oDAAoD,EACpE;YACE,KAAK,EAAE,KAAK;SACb,CACF,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CACb,GAAG,KAAK,CAAC,OAAO,uDAAuD,QAAQ,CAAC,MAAM,GAAG,CAC1F,CAAC;IACJ,CAAC;AACH,CAAC","sourcesContent":["import type { AssertionRequest } from \"./assertion.js\";\n\nexport const REVOKE_PATH = \"/_parlor/auth/hosts/revoke\";\n\nconst alreadyRevoked = (response: Response) => response.status === 401;\n\nexport interface RevokeInput {\n readonly appOrigin: string;\n readonly appName: string;\n readonly assert: (request: AssertionRequest) => Promise<string>;\n readonly fetch?: typeof fetch | undefined;\n}\n\nexport async function revokeGrant(input: RevokeInput): Promise<void> {\n const url = `${input.appOrigin}${REVOKE_PATH}`;\n const authorization = `ParlorHost ${await input.assert({ method: \"POST\", url })}`;\n let response: Response;\n try {\n response = await (input.fetch ?? fetch)(url, {\n method: \"POST\",\n headers: { authorization, \"content-type\": \"application/json\" },\n body: \"{}\",\n });\n } catch (error) {\n throw new Error(\n `${input.appName} could not be reached to disconnect this computer.`,\n {\n cause: error,\n },\n );\n }\n if (!response.ok && !alreadyRevoked(response)) {\n throw new Error(\n `${input.appName} did not disconnect this computer: it answered HTTP ${response.status}.`,\n );\n }\n}\n"]}
package/dist/self.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export declare function selfInvocation(args: readonly string[]): {
2
+ readonly command: string;
3
+ readonly args: readonly string[];
4
+ };
5
+ export declare const runsAsSingleExecutable: () => boolean;
6
+ export declare const selfEntry: () => string | undefined;
7
+ //# sourceMappingURL=self.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"self.d.ts","sourceRoot":"","sources":["../src/self.ts"],"names":[],"mappings":"AAEA,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG;IACvD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC,CAKA;AAED,eAAO,MAAM,sBAAsB,QAAO,OAAkB,CAAC;AAE7D,eAAO,MAAM,SAAS,QAAO,MAAM,GAAG,SACQ,CAAC"}
package/dist/self.js ADDED
@@ -0,0 +1,10 @@
1
+ import { isSea } from "node:sea";
2
+ export function selfInvocation(args) {
3
+ const entry = process.argv[1];
4
+ return isSea() || entry === undefined
5
+ ? { command: process.execPath, args }
6
+ : { command: process.execPath, args: [entry, ...args] };
7
+ }
8
+ export const runsAsSingleExecutable = () => isSea();
9
+ export const selfEntry = () => isSea() ? process.execPath : process.argv[1];
10
+ //# sourceMappingURL=self.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"self.js","sourceRoot":"","sources":["../src/self.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAEjC,MAAM,UAAU,cAAc,CAAC,IAAuB;IAIpD,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9B,OAAO,KAAK,EAAE,IAAI,KAAK,KAAK,SAAS;QACnC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE;QACrC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;AAC5D,CAAC;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAY,EAAE,CAAC,KAAK,EAAE,CAAC;AAE7D,MAAM,CAAC,MAAM,SAAS,GAAG,GAAuB,EAAE,CAChD,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC","sourcesContent":["import { isSea } from \"node:sea\";\n\nexport function selfInvocation(args: readonly string[]): {\n readonly command: string;\n readonly args: readonly string[];\n} {\n const entry = process.argv[1];\n return isSea() || entry === undefined\n ? { command: process.execPath, args }\n : { command: process.execPath, args: [entry, ...args] };\n}\n\nexport const runsAsSingleExecutable = (): boolean => isSea();\n\nexport const selfEntry = (): string | undefined =>\n isSea() ? process.execPath : process.argv[1];\n"]}