@phigon/observer 8.0.1

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 (284) hide show
  1. package/README.md +303 -0
  2. package/dist/bin/guardian-runner-darwin-amd64 +0 -0
  3. package/dist/bin/guardian-runner-darwin-arm64 +0 -0
  4. package/dist/bin/guardian-runner-linux-amd64 +0 -0
  5. package/dist/bin/guardian-runner-linux-arm64 +0 -0
  6. package/dist/bin/guardian-runner-windows-amd64.exe +0 -0
  7. package/dist/bin/guardian-runner-windows-arm64.exe +0 -0
  8. package/dist/src/action-policy.d.ts +38 -0
  9. package/dist/src/action-policy.d.ts.map +1 -0
  10. package/dist/src/action-policy.js +269 -0
  11. package/dist/src/action-policy.js.map +1 -0
  12. package/dist/src/activity/adapters/claude.d.ts +3 -0
  13. package/dist/src/activity/adapters/claude.d.ts.map +1 -0
  14. package/dist/src/activity/adapters/claude.js +89 -0
  15. package/dist/src/activity/adapters/claude.js.map +1 -0
  16. package/dist/src/activity/adapters/codex.d.ts +3 -0
  17. package/dist/src/activity/adapters/codex.d.ts.map +1 -0
  18. package/dist/src/activity/adapters/codex.js +277 -0
  19. package/dist/src/activity/adapters/codex.js.map +1 -0
  20. package/dist/src/activity/adapters/copilot.d.ts +2 -0
  21. package/dist/src/activity/adapters/copilot.d.ts.map +1 -0
  22. package/dist/src/activity/adapters/copilot.js +4 -0
  23. package/dist/src/activity/adapters/copilot.js.map +1 -0
  24. package/dist/src/activity/adapters/generic.d.ts +2 -0
  25. package/dist/src/activity/adapters/generic.d.ts.map +1 -0
  26. package/dist/src/activity/adapters/generic.js +4 -0
  27. package/dist/src/activity/adapters/generic.js.map +1 -0
  28. package/dist/src/activity/adapters/hermes.d.ts +2 -0
  29. package/dist/src/activity/adapters/hermes.d.ts.map +1 -0
  30. package/dist/src/activity/adapters/hermes.js +4 -0
  31. package/dist/src/activity/adapters/hermes.js.map +1 -0
  32. package/dist/src/activity/adapters/openai-compatible.d.ts +4 -0
  33. package/dist/src/activity/adapters/openai-compatible.d.ts.map +1 -0
  34. package/dist/src/activity/adapters/openai-compatible.js +116 -0
  35. package/dist/src/activity/adapters/openai-compatible.js.map +1 -0
  36. package/dist/src/activity/adapters/openclaw.d.ts +2 -0
  37. package/dist/src/activity/adapters/openclaw.d.ts.map +1 -0
  38. package/dist/src/activity/adapters/openclaw.js +4 -0
  39. package/dist/src/activity/adapters/openclaw.js.map +1 -0
  40. package/dist/src/activity/adapters/opencode.d.ts +3 -0
  41. package/dist/src/activity/adapters/opencode.d.ts.map +1 -0
  42. package/dist/src/activity/adapters/opencode.js +137 -0
  43. package/dist/src/activity/adapters/opencode.js.map +1 -0
  44. package/dist/src/activity/bridges.d.ts +7 -0
  45. package/dist/src/activity/bridges.d.ts.map +1 -0
  46. package/dist/src/activity/bridges.js +17 -0
  47. package/dist/src/activity/bridges.js.map +1 -0
  48. package/dist/src/activity/collector.d.ts +90 -0
  49. package/dist/src/activity/collector.d.ts.map +1 -0
  50. package/dist/src/activity/collector.js +706 -0
  51. package/dist/src/activity/collector.js.map +1 -0
  52. package/dist/src/activity/index.d.ts +14 -0
  53. package/dist/src/activity/index.d.ts.map +1 -0
  54. package/dist/src/activity/index.js +14 -0
  55. package/dist/src/activity/index.js.map +1 -0
  56. package/dist/src/activity/ingest.d.ts +15 -0
  57. package/dist/src/activity/ingest.d.ts.map +1 -0
  58. package/dist/src/activity/ingest.js +103 -0
  59. package/dist/src/activity/ingest.js.map +1 -0
  60. package/dist/src/activity/native-capabilities.d.ts +14 -0
  61. package/dist/src/activity/native-capabilities.d.ts.map +1 -0
  62. package/dist/src/activity/native-capabilities.js +85 -0
  63. package/dist/src/activity/native-capabilities.js.map +1 -0
  64. package/dist/src/activity/normalize.d.ts +17 -0
  65. package/dist/src/activity/normalize.d.ts.map +1 -0
  66. package/dist/src/activity/normalize.js +416 -0
  67. package/dist/src/activity/normalize.js.map +1 -0
  68. package/dist/src/activity/opencode-sqlite-source.d.ts +19 -0
  69. package/dist/src/activity/opencode-sqlite-source.d.ts.map +1 -0
  70. package/dist/src/activity/opencode-sqlite-source.js +305 -0
  71. package/dist/src/activity/opencode-sqlite-source.js.map +1 -0
  72. package/dist/src/activity/policy.d.ts +48 -0
  73. package/dist/src/activity/policy.d.ts.map +1 -0
  74. package/dist/src/activity/policy.js +436 -0
  75. package/dist/src/activity/policy.js.map +1 -0
  76. package/dist/src/activity/record.d.ts +7 -0
  77. package/dist/src/activity/record.d.ts.map +1 -0
  78. package/dist/src/activity/record.js +61 -0
  79. package/dist/src/activity/record.js.map +1 -0
  80. package/dist/src/activity/source-reader.d.ts +46 -0
  81. package/dist/src/activity/source-reader.d.ts.map +1 -0
  82. package/dist/src/activity/source-reader.js +594 -0
  83. package/dist/src/activity/source-reader.js.map +1 -0
  84. package/dist/src/activity/types.d.ts +21 -0
  85. package/dist/src/activity/types.d.ts.map +1 -0
  86. package/dist/src/activity/types.js +2 -0
  87. package/dist/src/activity/types.js.map +1 -0
  88. package/dist/src/agent-action-executor.d.ts +21 -0
  89. package/dist/src/agent-action-executor.d.ts.map +1 -0
  90. package/dist/src/agent-action-executor.js +248 -0
  91. package/dist/src/agent-action-executor.js.map +1 -0
  92. package/dist/src/agentic-guardian.d.ts +50 -0
  93. package/dist/src/agentic-guardian.d.ts.map +1 -0
  94. package/dist/src/agentic-guardian.js +220 -0
  95. package/dist/src/agentic-guardian.js.map +1 -0
  96. package/dist/src/atomic-replace.d.ts +4 -0
  97. package/dist/src/atomic-replace.d.ts.map +1 -0
  98. package/dist/src/atomic-replace.js +18 -0
  99. package/dist/src/atomic-replace.js.map +1 -0
  100. package/dist/src/cli.d.ts +11 -0
  101. package/dist/src/cli.d.ts.map +1 -0
  102. package/dist/src/cli.js +1244 -0
  103. package/dist/src/cli.js.map +1 -0
  104. package/dist/src/config.d.ts +68 -0
  105. package/dist/src/config.d.ts.map +1 -0
  106. package/dist/src/config.js +260 -0
  107. package/dist/src/config.js.map +1 -0
  108. package/dist/src/credential-store.d.ts +22 -0
  109. package/dist/src/credential-store.d.ts.map +1 -0
  110. package/dist/src/credential-store.js +91 -0
  111. package/dist/src/credential-store.js.map +1 -0
  112. package/dist/src/custom-agent-registry.d.ts +26 -0
  113. package/dist/src/custom-agent-registry.d.ts.map +1 -0
  114. package/dist/src/custom-agent-registry.js +288 -0
  115. package/dist/src/custom-agent-registry.js.map +1 -0
  116. package/dist/src/discovery/adapters/claude.d.ts +3 -0
  117. package/dist/src/discovery/adapters/claude.d.ts.map +1 -0
  118. package/dist/src/discovery/adapters/claude.js +26 -0
  119. package/dist/src/discovery/adapters/claude.js.map +1 -0
  120. package/dist/src/discovery/adapters/codex.d.ts +3 -0
  121. package/dist/src/discovery/adapters/codex.d.ts.map +1 -0
  122. package/dist/src/discovery/adapters/codex.js +23 -0
  123. package/dist/src/discovery/adapters/codex.js.map +1 -0
  124. package/dist/src/discovery/adapters/custom.d.ts +4 -0
  125. package/dist/src/discovery/adapters/custom.d.ts.map +1 -0
  126. package/dist/src/discovery/adapters/custom.js +24 -0
  127. package/dist/src/discovery/adapters/custom.js.map +1 -0
  128. package/dist/src/discovery/adapters/generic.d.ts +3 -0
  129. package/dist/src/discovery/adapters/generic.d.ts.map +1 -0
  130. package/dist/src/discovery/adapters/generic.js +126 -0
  131. package/dist/src/discovery/adapters/generic.js.map +1 -0
  132. package/dist/src/discovery/adapters/hermes.d.ts +3 -0
  133. package/dist/src/discovery/adapters/hermes.d.ts.map +1 -0
  134. package/dist/src/discovery/adapters/hermes.js +22 -0
  135. package/dist/src/discovery/adapters/hermes.js.map +1 -0
  136. package/dist/src/discovery/adapters/openclaw.d.ts +3 -0
  137. package/dist/src/discovery/adapters/openclaw.d.ts.map +1 -0
  138. package/dist/src/discovery/adapters/openclaw.js +22 -0
  139. package/dist/src/discovery/adapters/openclaw.js.map +1 -0
  140. package/dist/src/discovery/engine.d.ts +17 -0
  141. package/dist/src/discovery/engine.d.ts.map +1 -0
  142. package/dist/src/discovery/engine.js +164 -0
  143. package/dist/src/discovery/engine.js.map +1 -0
  144. package/dist/src/discovery/evidence.d.ts +29 -0
  145. package/dist/src/discovery/evidence.d.ts.map +1 -0
  146. package/dist/src/discovery/evidence.js +2 -0
  147. package/dist/src/discovery/evidence.js.map +1 -0
  148. package/dist/src/discovery/index.d.ts +11 -0
  149. package/dist/src/discovery/index.d.ts.map +1 -0
  150. package/dist/src/discovery/index.js +11 -0
  151. package/dist/src/discovery/index.js.map +1 -0
  152. package/dist/src/discovery/known-agent.d.ts +17 -0
  153. package/dist/src/discovery/known-agent.d.ts.map +1 -0
  154. package/dist/src/discovery/known-agent.js +193 -0
  155. package/dist/src/discovery/known-agent.js.map +1 -0
  156. package/dist/src/discovery/process-identity.d.ts +19 -0
  157. package/dist/src/discovery/process-identity.d.ts.map +1 -0
  158. package/dist/src/discovery/process-identity.js +73 -0
  159. package/dist/src/discovery/process-identity.js.map +1 -0
  160. package/dist/src/discovery/system-evidence.d.ts +9 -0
  161. package/dist/src/discovery/system-evidence.d.ts.map +1 -0
  162. package/dist/src/discovery/system-evidence.js +270 -0
  163. package/dist/src/discovery/system-evidence.js.map +1 -0
  164. package/dist/src/discovery/types.d.ts +21 -0
  165. package/dist/src/discovery/types.d.ts.map +1 -0
  166. package/dist/src/discovery/types.js +2 -0
  167. package/dist/src/discovery/types.js.map +1 -0
  168. package/dist/src/event-queue.d.ts +32 -0
  169. package/dist/src/event-queue.d.ts.map +1 -0
  170. package/dist/src/event-queue.js +125 -0
  171. package/dist/src/event-queue.js.map +1 -0
  172. package/dist/src/event-spool.d.ts +19 -0
  173. package/dist/src/event-spool.d.ts.map +1 -0
  174. package/dist/src/event-spool.js +350 -0
  175. package/dist/src/event-spool.js.map +1 -0
  176. package/dist/src/generated/phigon/v1/action_pb.d.ts +223 -0
  177. package/dist/src/generated/phigon/v1/action_pb.d.ts.map +1 -0
  178. package/dist/src/generated/phigon/v1/action_pb.js +96 -0
  179. package/dist/src/generated/phigon/v1/action_pb.js.map +1 -0
  180. package/dist/src/generated/phigon/v1/common_pb.d.ts +255 -0
  181. package/dist/src/generated/phigon/v1/common_pb.d.ts.map +1 -0
  182. package/dist/src/generated/phigon/v1/common_pb.js +242 -0
  183. package/dist/src/generated/phigon/v1/common_pb.js.map +1 -0
  184. package/dist/src/generated/phigon/v1/history_sync_pb.d.ts +119 -0
  185. package/dist/src/generated/phigon/v1/history_sync_pb.d.ts.map +1 -0
  186. package/dist/src/generated/phigon/v1/history_sync_pb.js +46 -0
  187. package/dist/src/generated/phigon/v1/history_sync_pb.js.map +1 -0
  188. package/dist/src/generated/phigon/v1/identity_pb.d.ts +235 -0
  189. package/dist/src/generated/phigon/v1/identity_pb.d.ts.map +1 -0
  190. package/dist/src/generated/phigon/v1/identity_pb.js +31 -0
  191. package/dist/src/generated/phigon/v1/identity_pb.js.map +1 -0
  192. package/dist/src/generated/phigon/v1/observer_service_pb.d.ts +362 -0
  193. package/dist/src/generated/phigon/v1/observer_service_pb.d.ts.map +1 -0
  194. package/dist/src/generated/phigon/v1/observer_service_pb.js +120 -0
  195. package/dist/src/generated/phigon/v1/observer_service_pb.js.map +1 -0
  196. package/dist/src/generated/phigon/v1/policy_pb.d.ts +456 -0
  197. package/dist/src/generated/phigon/v1/policy_pb.d.ts.map +1 -0
  198. package/dist/src/generated/phigon/v1/policy_pb.js +161 -0
  199. package/dist/src/generated/phigon/v1/policy_pb.js.map +1 -0
  200. package/dist/src/generated/phigon/v1/supervision_pb.d.ts +849 -0
  201. package/dist/src/generated/phigon/v1/supervision_pb.d.ts.map +1 -0
  202. package/dist/src/generated/phigon/v1/supervision_pb.js +384 -0
  203. package/dist/src/generated/phigon/v1/supervision_pb.js.map +1 -0
  204. package/dist/src/generated/phigon/v1/telemetry_pb.d.ts +761 -0
  205. package/dist/src/generated/phigon/v1/telemetry_pb.d.ts.map +1 -0
  206. package/dist/src/generated/phigon/v1/telemetry_pb.js +309 -0
  207. package/dist/src/generated/phigon/v1/telemetry_pb.js.map +1 -0
  208. package/dist/src/guardian-containment.d.ts +30 -0
  209. package/dist/src/guardian-containment.d.ts.map +1 -0
  210. package/dist/src/guardian-containment.js +113 -0
  211. package/dist/src/guardian-containment.js.map +1 -0
  212. package/dist/src/guardian-decision-journal.d.ts +29 -0
  213. package/dist/src/guardian-decision-journal.d.ts.map +1 -0
  214. package/dist/src/guardian-decision-journal.js +55 -0
  215. package/dist/src/guardian-decision-journal.js.map +1 -0
  216. package/dist/src/guardian-execution-manifest.d.ts +29 -0
  217. package/dist/src/guardian-execution-manifest.d.ts.map +1 -0
  218. package/dist/src/guardian-execution-manifest.js +95 -0
  219. package/dist/src/guardian-execution-manifest.js.map +1 -0
  220. package/dist/src/guardian-hosted-client.d.ts +10 -0
  221. package/dist/src/guardian-hosted-client.d.ts.map +1 -0
  222. package/dist/src/guardian-hosted-client.js +45 -0
  223. package/dist/src/guardian-hosted-client.js.map +1 -0
  224. package/dist/src/guardian-managed-exec.d.ts +21 -0
  225. package/dist/src/guardian-managed-exec.d.ts.map +1 -0
  226. package/dist/src/guardian-managed-exec.js +249 -0
  227. package/dist/src/guardian-managed-exec.js.map +1 -0
  228. package/dist/src/guardian-model-bridge.d.ts +13 -0
  229. package/dist/src/guardian-model-bridge.d.ts.map +1 -0
  230. package/dist/src/guardian-model-bridge.js +87 -0
  231. package/dist/src/guardian-model-bridge.js.map +1 -0
  232. package/dist/src/guardian-native.d.ts +3 -0
  233. package/dist/src/guardian-native.d.ts.map +1 -0
  234. package/dist/src/guardian-native.js +13 -0
  235. package/dist/src/guardian-native.js.map +1 -0
  236. package/dist/src/guardian-process-monitor.d.ts +20 -0
  237. package/dist/src/guardian-process-monitor.d.ts.map +1 -0
  238. package/dist/src/guardian-process-monitor.js +98 -0
  239. package/dist/src/guardian-process-monitor.js.map +1 -0
  240. package/dist/src/health.d.ts +35 -0
  241. package/dist/src/health.d.ts.map +1 -0
  242. package/dist/src/health.js +41 -0
  243. package/dist/src/health.js.map +1 -0
  244. package/dist/src/historical-identity-store.d.ts +20 -0
  245. package/dist/src/historical-identity-store.d.ts.map +1 -0
  246. package/dist/src/historical-identity-store.js +391 -0
  247. package/dist/src/historical-identity-store.js.map +1 -0
  248. package/dist/src/index.d.ts +14 -0
  249. package/dist/src/index.d.ts.map +1 -0
  250. package/dist/src/index.js +14 -0
  251. package/dist/src/index.js.map +1 -0
  252. package/dist/src/installation.d.ts +2 -0
  253. package/dist/src/installation.d.ts.map +1 -0
  254. package/dist/src/installation.js +44 -0
  255. package/dist/src/installation.js.map +1 -0
  256. package/dist/src/machine-command-executor.d.ts +28 -0
  257. package/dist/src/machine-command-executor.d.ts.map +1 -0
  258. package/dist/src/machine-command-executor.js +166 -0
  259. package/dist/src/machine-command-executor.js.map +1 -0
  260. package/dist/src/pair.d.ts +21 -0
  261. package/dist/src/pair.d.ts.map +1 -0
  262. package/dist/src/pair.js +125 -0
  263. package/dist/src/pair.js.map +1 -0
  264. package/dist/src/remove-machine.d.ts +15 -0
  265. package/dist/src/remove-machine.d.ts.map +1 -0
  266. package/dist/src/remove-machine.js +77 -0
  267. package/dist/src/remove-machine.js.map +1 -0
  268. package/dist/src/stream-client.d.ts +42 -0
  269. package/dist/src/stream-client.d.ts.map +1 -0
  270. package/dist/src/stream-client.js +867 -0
  271. package/dist/src/stream-client.js.map +1 -0
  272. package/dist/src/stream-state.d.ts +13 -0
  273. package/dist/src/stream-state.d.ts.map +1 -0
  274. package/dist/src/stream-state.js +128 -0
  275. package/dist/src/stream-state.js.map +1 -0
  276. package/dist/src/windows-process.d.ts +7 -0
  277. package/dist/src/windows-process.d.ts.map +1 -0
  278. package/dist/src/windows-process.js +17 -0
  279. package/dist/src/windows-process.js.map +1 -0
  280. package/package.json +78 -0
  281. package/scripts/install.mjs +308 -0
  282. package/scripts/managed-service.mjs +132 -0
  283. package/scripts/verify-guardian-release.mjs +42 -0
  284. package/scripts/windows-app-control.mjs +117 -0
@@ -0,0 +1,1244 @@
1
+ #!/usr/bin/env node
2
+ import { create, toJsonString } from "@bufbuild/protobuf";
3
+ import { timestampDate, timestampFromDate } from "@bufbuild/protobuf/wkt";
4
+ import { constants, realpathSync } from "node:fs";
5
+ import { access, readFile } from "node:fs/promises";
6
+ import { createInterface } from "node:readline";
7
+ import { fileURLToPath } from "node:url";
8
+ import { FileActionPolicyStore, managedActionNames, } from "./action-policy.js";
9
+ import { GuardianDecisionUnavailableError, HTTPGuardianDecisionClient, readGuardianActionContext, } from "./agentic-guardian.js";
10
+ import { AgentActionExecutor } from "./agent-action-executor.js";
11
+ import { ActivityCollector, historySyncQueueHeadroom, mergeInventoryWithSourceIdentities, registeredAgentActivitySources, } from "./activity/collector.js";
12
+ import { ingestActivityJSONL } from "./activity/ingest.js";
13
+ import { loadConfig, } from "./config.js";
14
+ import { FileCredentialStore } from "./credential-store.js";
15
+ import { CustomAgentRegistry, customAgentRegistration, } from "./custom-agent-registry.js";
16
+ import { DiscoveryEngine, runDiscoveryLoop, } from "./discovery/index.js";
17
+ import { AgentState } from "./generated/phigon/v1/common_pb.js";
18
+ import { AgentInventorySchema } from "./generated/phigon/v1/identity_pb.js";
19
+ import { HistorySyncResultSchema, HistorySyncResultState, } from "./generated/phigon/v1/history_sync_pb.js";
20
+ import { HistoricalIdentityStore, mergeInventoryWithHistoricalIdentities, } from "./historical-identity-store.js";
21
+ import { createHealthSnapshot, } from "./health.js";
22
+ import { OBSERVER_CONSENT_POLICY_VERSION, pairMachine, } from "./pair.js";
23
+ import { removeMachine } from "./remove-machine.js";
24
+ import { clearEventSpool } from "./event-spool.js";
25
+ import { GuardianDecisionJournal } from "./guardian-decision-journal.js";
26
+ import { GuardianContainmentExecutor, } from "./guardian-containment.js";
27
+ import { ObserverStreamClient } from "./stream-client.js";
28
+ import { executeManagedCommand } from "./guardian-managed-exec.js";
29
+ import { createHash } from "node:crypto";
30
+ import { createExecutionManifest, executionEnvironment, verifyExecutionManifest } from "./guardian-execution-manifest.js";
31
+ import { GuardianHostedClient } from "./guardian-hosted-client.js";
32
+ import { startGuardianModelBridge } from "./guardian-model-bridge.js";
33
+ import { semanticDecision } from "./agentic-guardian.js";
34
+ import { guardianRunnerPath } from "./guardian-native.js";
35
+ import { GuardianProcessMonitor } from "./guardian-process-monitor.js";
36
+ import { setTimeout as delay } from "node:timers/promises";
37
+ import { StreamStateStore } from "./stream-state.js";
38
+ const defaultOutput = {
39
+ stdout: (value) => process.stdout.write(value),
40
+ stderr: (value) => process.stderr.write(value),
41
+ };
42
+ export async function run(argv, environment = process.env, output = defaultOutput, input = process.stdin) {
43
+ const [command, ...flags] = argv;
44
+ if (command === undefined || command === "help" || command === "--help" || command === "-h" || flags.includes("--help")) {
45
+ output.stdout(helpText());
46
+ return 0;
47
+ }
48
+ if (command !== "doctor" &&
49
+ command !== "agent" &&
50
+ command !== "authorize-action" &&
51
+ command !== "guardian" &&
52
+ command !== "discover" &&
53
+ command !== "ingest" &&
54
+ command !== "pair" &&
55
+ command !== "remove-machine" &&
56
+ command !== "unpair" &&
57
+ command !== "run") {
58
+ output.stderr(`Unknown command: ${command}\n\n${helpText()}`);
59
+ return 2;
60
+ }
61
+ try {
62
+ const config = loadConfig(observabilityEnvironment(command, flags, environment));
63
+ const version = await readVersion();
64
+ const json = flags.includes("--json");
65
+ const customAgentRegistry = new CustomAgentRegistry(config.dataDirectory);
66
+ if (command === "guardian") {
67
+ const subcommand = flags[0] ?? "status";
68
+ if (subcommand === "exec") {
69
+ return await runGuardianExec(flags.slice(1), environment, output, input, config.dataDirectory);
70
+ }
71
+ if (subcommand === "launch") {
72
+ return await runGuardianLaunch(flags.slice(1), environment, output, input, config.dataDirectory);
73
+ }
74
+ if (subcommand !== "status") {
75
+ throw new Error("Local Guardian controls have been removed. Administrators manage policies and containment in the Phigon dashboard.");
76
+ }
77
+ const credential = await new FileCredentialStore(config.dataDirectory).load();
78
+ const policy = credential ? await new FileActionPolicyStore(config.dataDirectory).load(credential.machineId) : null;
79
+ let nativeRunner = false;
80
+ try {
81
+ await access(guardianRunnerPath(), constants.X_OK);
82
+ nativeRunner = true;
83
+ }
84
+ catch { /* Missing or non-executable binaries are a coverage gap. */ }
85
+ const status = { enabled: true, decisionAuthority: "control_plane", containmentAuthority: "admin_policy", localOverrides: false,
86
+ paired: credential !== null, policyDelivered: policy !== null, nativeRunner, machineWidePrevention: "not_attested",
87
+ managedExecution: credential && policy && nativeRunner ? "available_requires_online_grant" : "unavailable" };
88
+ output.stdout(json ? `${JSON.stringify(status, null, 2)}\n`
89
+ : `Phigon Guardian: centrally managed.\nManaged execution: ${status.managedExecution}.\nMachine-wide prevention: not verified.\nLocal policy overrides are unavailable.\n`);
90
+ return 0;
91
+ }
92
+ if (command === "authorize-action") {
93
+ const action = parseManagedAction(flagValue(flags, "--action"));
94
+ const resource = flagValue(flags, "--resource");
95
+ const agentID = flagValue(flags, "--agent") ?? "";
96
+ const processID = parseOptionalProcessID(flagValue(flags, "--process-id"));
97
+ const processStartedAt = flagValue(flags, "--process-started-at");
98
+ if ((processID === undefined) !== (processStartedAt === undefined)) {
99
+ throw new Error("--process-id and --process-started-at must be supplied together.");
100
+ }
101
+ if (processStartedAt !== undefined &&
102
+ !Number.isFinite(Date.parse(processStartedAt))) {
103
+ throw new Error("--process-started-at must be an RFC 3339 timestamp.");
104
+ }
105
+ const context = await readGuardianActionContext(flagValue(flags, "--context-file"));
106
+ if (resource === undefined) {
107
+ throw new Error("authorize-action requires --resource <normalized-resource>.");
108
+ }
109
+ const credential = await new FileCredentialStore(config.dataDirectory).load();
110
+ if (credential === null) {
111
+ throw new Error("Observer is not paired; the managed gateway failed closed.");
112
+ }
113
+ if (credential.controlPlaneOrigin !== config.controlPlaneUrl.origin) {
114
+ throw new Error(`Stored machine credential belongs to ${credential.controlPlaneOrigin}, ` +
115
+ `but the observer is configured for ${config.controlPlaneUrl.origin}. ` +
116
+ "The managed gateway failed closed.");
117
+ }
118
+ const policy = await new FileActionPolicyStore(config.dataDirectory).load(credential.machineId);
119
+ const started = performance.now();
120
+ let decision = {
121
+ allowed: false,
122
+ outcome: "policy_unavailable",
123
+ action,
124
+ reason: "No delivered policy revision is available; the managed gateway failed closed.",
125
+ agentMessage: "Phigon Guardian blocked this action because its administrator policy is unavailable.",
126
+ decisionSource: "fail_closed",
127
+ intervention: "block_action",
128
+ latencyMs: 0,
129
+ };
130
+ if (policy !== null) {
131
+ try {
132
+ const semantic = await new HTTPGuardianDecisionClient({
133
+ controlPlaneURL: config.controlPlaneUrl,
134
+ credential: credential.credential,
135
+ timeoutMs: config.guardianDecisionTimeoutMs,
136
+ }).decide({
137
+ policyDigest: policy.policyDigest,
138
+ agentId: agentID,
139
+ ...(processID === undefined ? {} : { processId: processID }),
140
+ ...(processStartedAt === undefined ? {} : { processStartedAt }),
141
+ action,
142
+ resource,
143
+ context,
144
+ });
145
+ const allowed = semantic.decision === "allow";
146
+ decision = {
147
+ allowed,
148
+ outcome: allowed
149
+ ? "allowed"
150
+ : semantic.decision === "deny"
151
+ ? "denied"
152
+ : "approval_required",
153
+ action,
154
+ reason: semantic.reason,
155
+ ...(semantic.matchedContractId === undefined
156
+ ? {}
157
+ : { matchedRuleId: semantic.matchedContractId }),
158
+ ...(semantic.adminContract === undefined
159
+ ? {}
160
+ : { adminContract: semantic.adminContract }),
161
+ agentMessage: guardianAgentMessage(action, allowed, semantic.decision === "require_approval", semantic.reason, semantic.adminContract, semantic.approvalState),
162
+ decisionSource: semantic.model === "policy-gateway" ? "central_policy"
163
+ : semantic.model === "admin-approval" ? "administrator_approval" : "agentic_guardian",
164
+ model: semantic.model,
165
+ risk: semantic.risk,
166
+ confidence: semantic.confidence,
167
+ ...(semantic.intervention === "none"
168
+ ? {}
169
+ : { intervention: semantic.intervention }),
170
+ cached: semantic.cached,
171
+ latencyMs: semantic.latencyMs,
172
+ policyDigest: policy.policyDigest,
173
+ ...(semantic.approvalId === undefined
174
+ ? {}
175
+ : { approvalId: semantic.approvalId }),
176
+ ...(semantic.approvalState === undefined
177
+ ? {}
178
+ : { approvalState: semantic.approvalState }),
179
+ ...(semantic.approvalExpiresAt === undefined
180
+ ? {}
181
+ : { approvalExpiresAt: semantic.approvalExpiresAt }),
182
+ };
183
+ }
184
+ catch (error) {
185
+ if (!(error instanceof GuardianDecisionUnavailableError))
186
+ throw error;
187
+ decision = {
188
+ allowed: false,
189
+ outcome: "guardian_unavailable",
190
+ action,
191
+ reason: "The semantic Guardian could not return a verified decision; the managed gateway failed closed.",
192
+ agentMessage: guardianAgentMessage(action, false, false, "The semantic Guardian was unavailable, so Phigon stopped the action instead of guessing."),
193
+ decisionSource: "fail_closed",
194
+ intervention: "block_action",
195
+ latencyMs: Math.max(0, Math.round(performance.now() - started)),
196
+ policyDigest: policy.policyDigest,
197
+ };
198
+ }
199
+ }
200
+ if (!decision.allowed && decision.intervention !== undefined &&
201
+ decision.intervention !== "block_action") {
202
+ const containment = await new GuardianContainmentExecutor({
203
+ enabled: true,
204
+ inventory: async () => (await new DiscoveryEngine({
205
+ customAgentRegistry,
206
+ }).discover(credential.machineId)).inventory,
207
+ }).execute({
208
+ intervention: decision.intervention,
209
+ agentId: agentID,
210
+ ...(processID === undefined ? {} : { processId: processID }),
211
+ ...(processStartedAt === undefined
212
+ ? {}
213
+ : { expectedStartedAt: processStartedAt }),
214
+ });
215
+ decision = Object.freeze({
216
+ ...decision,
217
+ containment,
218
+ agentMessage: `${decision.agentMessage} Containment: ${containment.detail}`,
219
+ });
220
+ }
221
+ await new GuardianDecisionJournal(config.dataDirectory).append({
222
+ agentId: agentID,
223
+ action,
224
+ resource,
225
+ outcome: decision.outcome,
226
+ reason: decision.reason,
227
+ decisionSource: decision.decisionSource,
228
+ latencyMs: decision.latencyMs,
229
+ context,
230
+ ...(decision.adminContract === undefined
231
+ ? {}
232
+ : { adminContract: decision.adminContract }),
233
+ ...(decision.model === undefined ? {} : { model: decision.model }),
234
+ ...(decision.risk === undefined ? {} : { risk: decision.risk }),
235
+ ...(decision.confidence === undefined
236
+ ? {}
237
+ : { confidence: decision.confidence }),
238
+ ...(decision.policyDigest === undefined
239
+ ? {}
240
+ : { policyDigest: decision.policyDigest }),
241
+ ...(decision.intervention === undefined
242
+ ? {}
243
+ : { intervention: decision.intervention }),
244
+ ...(decision.containment === undefined
245
+ ? {}
246
+ : { containment: decision.containment }),
247
+ ...(decision.approvalId === undefined
248
+ ? {}
249
+ : { approvalId: decision.approvalId }),
250
+ ...(decision.approvalState === undefined
251
+ ? {}
252
+ : { approvalState: decision.approvalState }),
253
+ ...(decision.approvalExpiresAt === undefined
254
+ ? {}
255
+ : { approvalExpiresAt: decision.approvalExpiresAt }),
256
+ });
257
+ output.stdout(json
258
+ ? `${JSON.stringify(decision, null, 2)}\n`
259
+ : [
260
+ `Decision: ${decision.outcome}`,
261
+ `Action: ${decision.action}`,
262
+ `Message for agent: ${decision.agentMessage}`,
263
+ `Reason: ${decision.reason}`,
264
+ ...(decision.adminContract === undefined
265
+ ? []
266
+ : [`Admin contract: ${decision.adminContract}`]),
267
+ `Decision source: ${decision.decisionSource}${decision.model ? ` (${decision.model})` : ""}`,
268
+ `Latency: ${decision.latencyMs}ms`,
269
+ ...(decision.intervention === undefined
270
+ ? []
271
+ : [`Authorized intervention: ${decision.intervention}`]),
272
+ ...(decision.containment === undefined
273
+ ? []
274
+ : [`Containment: ${decision.containment.state} (${decision.containment.code}) — ${decision.containment.detail}`]),
275
+ ...(decision.matchedRuleId === undefined
276
+ ? []
277
+ : [`Matched rule: ${decision.matchedRuleId}`]),
278
+ ...(decision.approvalId === undefined
279
+ ? []
280
+ : [`Approval: ${decision.approvalId} (${decision.approvalState ?? "pending"})`]),
281
+ ...(decision.approvalExpiresAt === undefined
282
+ ? []
283
+ : [`Approval expires: ${decision.approvalExpiresAt}`]),
284
+ "Boundary: Phigon-managed gateway only",
285
+ "",
286
+ ].join("\n"));
287
+ return decision.allowed ? 0 : 77;
288
+ }
289
+ if (command === "agent") {
290
+ return await runAgentCommand(flags, customAgentRegistry, json, output, input);
291
+ }
292
+ if (command === "ingest") {
293
+ const agent = flagValue(flags, "--agent");
294
+ if (agent === undefined) {
295
+ throw new Error("ingest requires --agent <agent-id>.");
296
+ }
297
+ const agentName = flagValue(flags, "--name");
298
+ const result = await ingestActivityJSONL({
299
+ agent,
300
+ ...(agentName === undefined ? {} : { agentName }),
301
+ dataDirectory: config.dataDirectory,
302
+ input,
303
+ });
304
+ if (!flags.includes("--quiet")) {
305
+ output.stdout(json
306
+ ? `${JSON.stringify(result, null, 2)}\n`
307
+ : [
308
+ `Activity bridge: ${result.agent}`,
309
+ ...(result.agentName === undefined
310
+ ? []
311
+ : [`Agent name: ${result.agentName}`]),
312
+ `Accepted records: ${result.records}`,
313
+ `Rejected malformed records: ${result.malformed}`,
314
+ `Rejected oversized records: ${result.oversized}`,
315
+ "",
316
+ ].join("\n"));
317
+ }
318
+ return result.records > 0 ? 0 : 1;
319
+ }
320
+ if (command === "remove-machine" || command === "unpair") {
321
+ const credentialStore = new FileCredentialStore(config.dataDirectory);
322
+ const credential = await credentialStore.load();
323
+ if (credential === null) {
324
+ throw new Error("Observer is not paired; there is no machine to remove.");
325
+ }
326
+ if (json && !flags.includes("--yes")) {
327
+ throw new Error("JSON machine removal requires --yes after reviewing the removal scope.");
328
+ }
329
+ if (!flags.includes("--yes")) {
330
+ output.stdout(removeMachinePrompt(credential.machineId));
331
+ const confirmed = await readConfirmation(input);
332
+ if (confirmed !== "remove") {
333
+ throw new Error("Machine removal was not confirmed. No request was sent.");
334
+ }
335
+ }
336
+ const result = await removeMachine({
337
+ config,
338
+ credential,
339
+ clearCredential: async () => {
340
+ clearEventSpool(config.dataDirectory);
341
+ return await credentialStore.clear();
342
+ },
343
+ });
344
+ output.stdout(json
345
+ ? `${JSON.stringify(result, null, 2)}\n`
346
+ : formatRemoveMachineResult(result));
347
+ return 0;
348
+ }
349
+ if (command === "discover") {
350
+ const storedCredential = await new FileCredentialStore(config.dataDirectory).load();
351
+ const snapshot = await new DiscoveryEngine({
352
+ customAgentRegistry,
353
+ }).discover(storedCredential?.machineId ?? "");
354
+ output.stdout(json
355
+ ? formatDiscoveryJSON(snapshot)
356
+ : formatDiscovery(snapshot));
357
+ return 0;
358
+ }
359
+ if (command === "run") {
360
+ const controller = new AbortController();
361
+ const stop = () => controller.abort();
362
+ process.once("SIGINT", stop);
363
+ process.once("SIGTERM", stop);
364
+ try {
365
+ const credential = await new FileCredentialStore(config.dataDirectory).load();
366
+ if (credential === null) {
367
+ throw new Error("Observer is not paired. Run the pair command first.");
368
+ }
369
+ if (credential.controlPlaneOrigin !== config.controlPlaneUrl.origin) {
370
+ throw new Error(`Stored machine credential belongs to ${credential.controlPlaneOrigin}, ` +
371
+ `but the observer is configured for ${config.controlPlaneUrl.origin}. ` +
372
+ "Refusing to send the credential to a different control plane.");
373
+ }
374
+ if (credential.consentPolicyVersion !== OBSERVER_CONSENT_POLICY_VERSION ||
375
+ credential.consentProfile === undefined) {
376
+ throw new Error("Current machine-owner consent is missing. Pair again with an explicit consent profile before starting collection.");
377
+ }
378
+ if (config.observabilityProfile === "full" &&
379
+ credential.consentProfile !== "full") {
380
+ throw new Error("Full-content collection is outside the consented metadata profile.");
381
+ }
382
+ let effectivePolicy = intersectCollectionPolicy(config.policy);
383
+ let collector;
384
+ let resolveCollector;
385
+ const collectorReady = new Promise((resolve) => {
386
+ resolveCollector = resolve;
387
+ });
388
+ let fatalPersistenceError;
389
+ const discoveryEngine = new DiscoveryEngine({
390
+ customAgentRegistry,
391
+ });
392
+ const actionExecutor = new AgentActionExecutor({
393
+ enabled: config.remoteActionsEnabled,
394
+ commandsEnabled: config.remoteCommandsEnabled,
395
+ machineID: credential.machineId,
396
+ dataDirectory: config.dataDirectory,
397
+ inventory: async () => (await discoveryEngine.discover(credential.machineId)).inventory,
398
+ });
399
+ const client = new ObserverStreamClient({
400
+ config,
401
+ actionExecutor,
402
+ onHistorySync: async (syncCommand) => {
403
+ const rejected = (detail) => create(HistorySyncResultSchema, {
404
+ jobId: syncCommand.jobId,
405
+ machineId: credential.machineId,
406
+ idempotencyKey: syncCommand.idempotencyKey,
407
+ state: HistorySyncResultState.REJECTED,
408
+ detail,
409
+ observedAt: timestampFromDate(new Date()),
410
+ });
411
+ if (syncCommand.machineId !== credential.machineId ||
412
+ syncCommand.expiresAt === undefined) {
413
+ return rejected("History sync command identity or expiry is invalid.");
414
+ }
415
+ if (config.observabilityProfile !== "full" ||
416
+ credential.consentProfile !== "full") {
417
+ return rejected("Machine-owner full-activity consent is required before history can be uploaded.");
418
+ }
419
+ if (!fullHistoryPolicyEnabled(effectivePolicy)) {
420
+ return rejected("Workspace collection policy does not allow readable prompts, replies, and tool activity.");
421
+ }
422
+ const expiresAt = timestampDate(syncCommand.expiresAt).getTime();
423
+ const remainingMs = expiresAt - Date.now();
424
+ if (!Number.isFinite(remainingMs) || remainingMs <= 0) {
425
+ return rejected("History sync command expired before execution.");
426
+ }
427
+ const activeCollector = collector ?? await collectorReady;
428
+ const summary = await activeCollector.syncHistory(AbortSignal.timeout(Math.max(1, remainingMs)));
429
+ return create(HistorySyncResultSchema, {
430
+ jobId: syncCommand.jobId,
431
+ machineId: credential.machineId,
432
+ idempotencyKey: syncCommand.idempotencyKey,
433
+ state: HistorySyncResultState.SUCCEEDED,
434
+ filesDiscovered: Math.min(summary.filesDiscovered, 0xffff_ffff),
435
+ recordsScanned: BigInt(summary.recordsScanned),
436
+ eventsUploaded: BigInt(summary.eventsUploaded),
437
+ throughSequence: summary.throughSequence,
438
+ detail: summary.recordsScanned === 0
439
+ ? "All supported history was already synchronized."
440
+ : "All supported history was queued and acknowledged by the control plane.",
441
+ observedAt: timestampFromDate(new Date()),
442
+ });
443
+ },
444
+ onStatus: (message) => output.stdout(`${message}\n`),
445
+ onPolicy: (policy) => {
446
+ effectivePolicy = intersectCollectionPolicy(config.policy, policy);
447
+ collector?.updatePolicy(effectivePolicy);
448
+ },
449
+ onCursorReset: (acknowledgedSequence, allocationSequence) => {
450
+ collector?.resetSequence(acknowledgedSequence, allocationSequence);
451
+ },
452
+ onAcknowledgement: (sequence) => {
453
+ const activeCollector = collector;
454
+ if (activeCollector === undefined)
455
+ return;
456
+ return activeCollector.acknowledge(sequence).catch((error) => {
457
+ fatalPersistenceError = error instanceof Error
458
+ ? error
459
+ : new Error(String(error));
460
+ output.stderr(`Unable to persist acknowledged activity source state: ${fatalPersistenceError.message}\n`);
461
+ controller.abort();
462
+ throw fatalPersistenceError;
463
+ });
464
+ },
465
+ });
466
+ const historicalStore = new HistoricalIdentityStore(config.dataDirectory, {
467
+ tenantId: credential.tenantId,
468
+ machineId: credential.machineId,
469
+ });
470
+ let historicalIdentities = await historicalStore.load();
471
+ const initialDiscovery = await discoveryEngine.discover(credential.machineId);
472
+ historicalIdentities = await historicalStore.remember(initialDiscovery.inventory.agents);
473
+ let latestDiscoveryInventory = initialDiscovery.inventory;
474
+ const publishInventory = () => {
475
+ const withHistory = mergeInventoryWithHistoricalIdentities(latestDiscoveryInventory, historicalIdentities);
476
+ client.updateInventory(mergeInventoryWithSourceIdentities(withHistory, collector?.sourceIdentities() ?? []));
477
+ };
478
+ publishInventory();
479
+ output.stdout("Starting Phigon observer telemetry.\n");
480
+ const streaming = client.run(controller.signal);
481
+ const discovering = runDiscoveryLoop(discoveryEngine, credential.machineId, config.discoveryIntervalMs, controller.signal, async (inventory) => {
482
+ latestDiscoveryInventory = inventory;
483
+ collector?.updateInventory(inventory);
484
+ historicalIdentities = await historicalStore.remember(inventory.agents);
485
+ publishInventory();
486
+ });
487
+ const processMonitor = new GuardianProcessMonitor({
488
+ coverageClient: new GuardianHostedClient({ controlPlaneURL: config.controlPlaneUrl, credential: credential.credential }),
489
+ machineId: credential.machineId,
490
+ policyStore: new FileActionPolicyStore(config.dataDirectory),
491
+ decisionClient: new HTTPGuardianDecisionClient({
492
+ controlPlaneURL: config.controlPlaneUrl,
493
+ credential: credential.credential,
494
+ timeoutMs: config.guardianDecisionTimeoutMs,
495
+ }),
496
+ containment: new GuardianContainmentExecutor({
497
+ enabled: true,
498
+ inventory: async () => (await discoveryEngine.discover(credential.machineId)).inventory,
499
+ }),
500
+ journal: new GuardianDecisionJournal(config.dataDirectory),
501
+ onStatus: (message) => output.stdout(`${message}\n`),
502
+ });
503
+ const monitoring = (async () => {
504
+ await client.waitUntilReady(controller.signal);
505
+ while (!controller.signal.aborted) {
506
+ try {
507
+ await processMonitor.review(latestDiscoveryInventory, controller.signal);
508
+ }
509
+ catch {
510
+ output.stderr("Guardian process monitoring could not read verified policy state.\n");
511
+ }
512
+ try {
513
+ await delay(config.discoveryIntervalMs, undefined, { signal: controller.signal });
514
+ }
515
+ catch {
516
+ break;
517
+ }
518
+ }
519
+ })();
520
+ const collecting = (async () => {
521
+ const allocationSequence = await client.waitUntilReady(controller.signal);
522
+ collector = new ActivityCollector({
523
+ config,
524
+ credential,
525
+ initialSequence: allocationSequence,
526
+ initialAcknowledgedSequence: client.metrics().lastAcknowledgedSequence,
527
+ durableDelivery: true,
528
+ policy: effectivePolicy,
529
+ emit: (event) => client.enqueue(event),
530
+ canCollect: () => {
531
+ const queue = client.metrics().queue;
532
+ return (queue.capacity - queue.depth >=
533
+ Math.max(32, config.activityRateLimitPerMinute));
534
+ },
535
+ canSyncHistory: () => {
536
+ const queue = client.metrics().queue;
537
+ return queue.capacity - queue.depth >= Math.min(queue.capacity, historySyncQueueHeadroom);
538
+ },
539
+ additionalSources: async () => registeredAgentActivitySources(await customAgentRegistry.list()),
540
+ onSourceIdentity: async (identity) => {
541
+ // Make the source target addressable immediately, then durably
542
+ // record only its sanitized inventory metadata.
543
+ publishInventory();
544
+ historicalIdentities = await historicalStore.remember([
545
+ identity,
546
+ ]);
547
+ publishInventory();
548
+ },
549
+ });
550
+ resolveCollector?.(collector);
551
+ resolveCollector = undefined;
552
+ collector.updateInventory(latestDiscoveryInventory);
553
+ publishInventory();
554
+ const historyCollector = collector;
555
+ const initialHistory = async () => {
556
+ if (config.observabilityProfile !== "full" || credential.consentProfile !== "full")
557
+ return;
558
+ while (!controller.signal.aborted) {
559
+ if (!fullHistoryPolicyEnabled(effectivePolicy)) {
560
+ output.stdout("Saved-history sync is waiting for the workspace full-activity policy.\n");
561
+ }
562
+ else {
563
+ try {
564
+ output.stdout("Synchronizing saved agent history in the background. Live collection remains active.\n");
565
+ const summary = await historyCollector.syncHistory(controller.signal);
566
+ output.stdout(`History scan complete: ${summary.recordsScanned} records scanned, ${summary.eventsUploaded} events queued for server delivery.\n`);
567
+ return;
568
+ }
569
+ catch {
570
+ if (controller.signal.aborted)
571
+ return;
572
+ output.stderr("Saved-history sync was interrupted. It will retry from its durable cursor.\n");
573
+ }
574
+ }
575
+ try {
576
+ await delay(30_000, undefined, { signal: controller.signal });
577
+ }
578
+ catch {
579
+ return;
580
+ }
581
+ }
582
+ };
583
+ await Promise.all([collector.run(controller.signal), initialHistory()]);
584
+ })();
585
+ await settleObserverTasks([streaming, discovering, collecting, monitoring], controller, () => fatalPersistenceError);
586
+ output.stdout("Observer stopped.\n");
587
+ }
588
+ finally {
589
+ process.removeListener("SIGINT", stop);
590
+ process.removeListener("SIGTERM", stop);
591
+ }
592
+ return 0;
593
+ }
594
+ if (command === "pair") {
595
+ const code = flagValue(flags, "--code");
596
+ if (code === undefined) {
597
+ throw new Error("pair requires --code <one-time-code>.");
598
+ }
599
+ const displayName = flagValue(flags, "--name");
600
+ const consentProfile = parseConsentProfile(flagValue(flags, "--consent-profile"));
601
+ if (json && !flags.includes("--accept-consent")) {
602
+ throw new Error("JSON pairing requires --accept-consent after reviewing the selected profile.");
603
+ }
604
+ if (!flags.includes("--accept-consent")) {
605
+ output.stdout(consentPrompt(consentProfile));
606
+ const confirmed = await readConfirmation(input);
607
+ if (confirmed !== "yes") {
608
+ throw new Error("Consent was not granted. No pairing request was sent.");
609
+ }
610
+ }
611
+ const result = await pairMachine({
612
+ config,
613
+ code,
614
+ consentProfile,
615
+ ...(displayName === undefined ? {} : { displayName }),
616
+ });
617
+ output.stdout(json
618
+ ? `${JSON.stringify(result, null, 2)}\n`
619
+ : formatPairResult(result));
620
+ return 0;
621
+ }
622
+ const storedCredential = await new FileCredentialStore(config.dataDirectory).load();
623
+ let streamCursor;
624
+ let streamCursorIssue;
625
+ if (storedCredential !== null) {
626
+ try {
627
+ const state = await new StreamStateStore(config.dataDirectory).load(storedCredential.machineId);
628
+ streamCursor = {
629
+ machineId: state.machineId,
630
+ acknowledgedSequence: state.acknowledgedSequence.toString(),
631
+ updatedAt: state.updatedAt,
632
+ };
633
+ }
634
+ catch {
635
+ streamCursorIssue = "Stored stream cursor could not be read.";
636
+ }
637
+ }
638
+ const health = createHealthSnapshot({
639
+ ...config,
640
+ machineTokenConfigured: config.machineTokenConfigured || storedCredential !== null,
641
+ }, version, new Date(), streamCursor, streamCursorIssue);
642
+ output.stdout(json ? `${JSON.stringify(health, null, 2)}\n` : formatHealth(health));
643
+ return 0;
644
+ }
645
+ catch (error) {
646
+ const message = error instanceof Error ? error.message : "Unknown error.";
647
+ output.stderr(`Observer command failed: ${message}\n`);
648
+ return 1;
649
+ }
650
+ }
651
+ function guardianAgentMessage(action, allowed, approvalRequired, reason, adminContract, approvalState) {
652
+ const operation = action.replace(".", " ");
653
+ if (allowed) {
654
+ return approvalState === "consumed"
655
+ ? `${operation} allowed by a one-time Phigon administrator approval.`
656
+ : `${operation} allowed by Phigon Guardian.`;
657
+ }
658
+ const result = !approvalRequired
659
+ ? `${operation} blocked by Phigon Guardian.`
660
+ : approvalState === "rejected"
661
+ ? `${operation} blocked because an administrator rejected the exact proposal.`
662
+ : approvalState === "consumed"
663
+ ? `${operation} blocked because its one-time administrator approval was already consumed.`
664
+ : approvalState === "expired"
665
+ ? `${operation} blocked because its administrator approval expired.`
666
+ : `${operation} held by Phigon Guardian for administrator approval.`;
667
+ const contract = adminContract
668
+ ? ` Admin contract: ${adminContract}`
669
+ : "";
670
+ return `${result} Reason: ${reason}${contract}`;
671
+ }
672
+ async function runGuardianExec(flags, environment, output, input, dataDirectory) {
673
+ const separator = flags.indexOf("--");
674
+ if (separator < 0 || separator === flags.length - 1) {
675
+ throw new Error("guardian exec requires -- <executable> [arguments...].");
676
+ }
677
+ const gatewayFlags = flags.slice(0, separator);
678
+ validateGuardianExecFlags(gatewayFlags);
679
+ if (gatewayFlags.includes("--json")) {
680
+ throw new Error("guardian exec does not support --json because command output is streamed directly.");
681
+ }
682
+ const executable = flags[separator + 1];
683
+ const commandArguments = flags.slice(separator + 2);
684
+ if (executable.trim() === "" || /[\u0000\r\n]/u.test(executable) ||
685
+ commandArguments.some((argument) => /[\u0000]/u.test(argument))) {
686
+ throw new Error("guardian exec executable or arguments are invalid.");
687
+ }
688
+ return await runGuardedCommand(executable, commandArguments, gatewayFlags, undefined, environment, output, dataDirectory);
689
+ }
690
+ async function runGuardianLaunch(flags, environment, output, input, dataDirectory) {
691
+ const separator = flags.indexOf("--");
692
+ if (separator < 0 || separator === flags.length - 1) {
693
+ throw new Error("guardian launch requires -- <executable> [arguments...].");
694
+ }
695
+ const gatewayFlags = flags.slice(0, separator);
696
+ validateGuardianLaunchFlags(gatewayFlags);
697
+ if (gatewayFlags.includes("--json")) {
698
+ throw new Error("guardian launch does not support --json because agent output is streamed directly.");
699
+ }
700
+ const agentID = flagValue(gatewayFlags, "--agent");
701
+ if (agentID === undefined) {
702
+ throw new Error("guardian launch requires --agent <agent-id>.");
703
+ }
704
+ const model = validatedModelResource(flagValue(gatewayFlags, "--model"));
705
+ const executable = flags[separator + 1];
706
+ const commandArguments = flags.slice(separator + 2);
707
+ if (executable.trim() === "" || /[\u0000\r\n]/u.test(executable) ||
708
+ commandArguments.some((argument) => /[\u0000]/u.test(argument))) {
709
+ throw new Error("guardian launch executable or arguments are invalid.");
710
+ }
711
+ return await runGuardedCommand(executable, commandArguments, gatewayFlags, model, environment, output, dataDirectory);
712
+ }
713
+ function validateGuardianExecFlags(flags) {
714
+ const valued = new Set([
715
+ "--agent",
716
+ "--context-file",
717
+ ]);
718
+ for (let index = 0; index < flags.length; index += 1) {
719
+ const flag = flags[index];
720
+ if (flag === "--json")
721
+ continue;
722
+ if (!valued.has(flag) || index + 1 >= flags.length) {
723
+ throw new Error(`guardian exec does not support ${flag}.`);
724
+ }
725
+ index += 1;
726
+ }
727
+ }
728
+ function validateGuardianLaunchFlags(flags) {
729
+ const valued = new Set([
730
+ "--agent",
731
+ "--model",
732
+ "--context-file",
733
+ ]);
734
+ for (let index = 0; index < flags.length; index += 1) {
735
+ const flag = flags[index];
736
+ if (flag === "--json")
737
+ continue;
738
+ if (!valued.has(flag) || index + 1 >= flags.length) {
739
+ throw new Error(`guardian launch does not support ${flag}.`);
740
+ }
741
+ index += 1;
742
+ }
743
+ }
744
+ function validatedModelResource(value) {
745
+ if (value === undefined) {
746
+ throw new Error("guardian launch requires --model <provider:model> so model policy cannot be skipped.");
747
+ }
748
+ const normalized = value.trim();
749
+ if (Buffer.byteLength(normalized, "utf8") > 256 ||
750
+ !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}:[A-Za-z0-9][A-Za-z0-9._:/-]{0,190}$/u.test(normalized)) {
751
+ throw new Error("--model must use the stable provider:model identifier format.");
752
+ }
753
+ return normalized;
754
+ }
755
+ async function runGuardedCommand(executable, arguments_, flags, model, environment, output, dataDirectory) {
756
+ const config = loadConfig(environment);
757
+ const credential = await new FileCredentialStore(dataDirectory).load();
758
+ if (!credential || credential.controlPlaneOrigin !== config.controlPlaneUrl.origin)
759
+ throw new Error("Guardian execution requires a paired machine and its configured control plane.");
760
+ const policy = await new FileActionPolicyStore(dataDirectory).load(credential.machineId);
761
+ if (!policy) {
762
+ output.stdout("Phigon Guardian blocked execution: administrator policy is unavailable.\n");
763
+ return 77;
764
+ }
765
+ const agentId = flagValue(flags, "--agent") ?? "managed-agent";
766
+ const context = await readGuardianActionContext(flagValue(flags, "--context-file"));
767
+ const hosted = new GuardianHostedClient({ controlPlaneURL: config.controlPlaneUrl, credential: credential.credential });
768
+ const childEnvironment = executionEnvironment({ ...process.env, ...environment });
769
+ const bridge = model === undefined ? undefined : await startGuardianModelBridge({
770
+ controlPlaneURL: config.controlPlaneUrl, credential: credential.credential, agentId, provider: model.split(":")[0].toLowerCase(),
771
+ });
772
+ const lifetime = new AbortController();
773
+ try {
774
+ const assigned = await hosted.post("/v1/machine/guardian/execution-profile", {}, lifetime.signal, 10_000);
775
+ if (assigned["policy_digest"] !== policy.policyDigest || typeof assigned["sandbox"] !== "string" || Buffer.byteLength(assigned["sandbox"]) > 24 << 10)
776
+ throw new GuardianDecisionUnavailableError("Guardian could not bind the current OS restrictions.");
777
+ if (bridge) {
778
+ for (const key of Object.keys(childEnvironment)) {
779
+ if (/^(OPENAI_API_KEY|OPENAI_BASE_URL|OPENAI_ORG_ID|OPENAI_PROJECT_ID|ANTHROPIC_API_KEY|ANTHROPIC_AUTH_TOKEN|ANTHROPIC_BASE_URL)$/iu.test(key))
780
+ delete childEnvironment[key];
781
+ }
782
+ childEnvironment["OPENAI_API_KEY"] = bridge.token;
783
+ childEnvironment["OPENAI_BASE_URL"] = bridge.baseURL;
784
+ childEnvironment["ANTHROPIC_API_KEY"] = bridge.token;
785
+ childEnvironment["ANTHROPIC_AUTH_TOKEN"] = bridge.token;
786
+ childEnvironment["ANTHROPIC_BASE_URL"] = bridge.baseURL.replace(/\/v1$/u, "");
787
+ }
788
+ const manifest = await createExecutionManifest({ executable, arguments: arguments_, agentId, ...(model === undefined ? {} : { model }), environment: childEnvironment,
789
+ sandbox: assigned["sandbox"],
790
+ cgroupRoot: environment["PHIGON_GUARDIAN_CGROUP_ROOT"] ?? process.env["PHIGON_GUARDIAN_CGROUP_ROOT"] ?? "" });
791
+ const serialized = JSON.stringify(manifest);
792
+ const manifestDigest = createHash("sha256").update(serialized).digest("hex");
793
+ const request = { policy_digest: policy.policyDigest, manifest: serialized, context: {
794
+ task_id: context.taskId, proposal_id: context.proposalId, plan_step_id: context.planStepId,
795
+ user_prompt: context.userPrompt, agent_plan: context.agentPlan, justification: context.justification,
796
+ expected_effect: context.expectedEffect, recent_activity: context.recentActivity,
797
+ } };
798
+ let grant = await hosted.post("/v1/machine/guardian/executions", request);
799
+ const approvalDeadline = Date.now() + 5 * 60_000;
800
+ let waitingMessage = false;
801
+ while (grant["decision"] !== undefined) {
802
+ const review = semanticDecision(grant["decision"], 0);
803
+ if (review.decision !== "require_approval" || review.approvalState !== "pending" || Date.now() >= approvalDeadline)
804
+ break;
805
+ if (!waitingMessage) {
806
+ output.stdout("Phigon Guardian is waiting for your administrator to approve this complete launch.\n");
807
+ waitingMessage = true;
808
+ }
809
+ await delay(5_000);
810
+ await verifyExecutionManifest(manifest, childEnvironment);
811
+ grant = await hosted.post("/v1/machine/guardian/executions", request);
812
+ }
813
+ if (grant["decision"] !== undefined) {
814
+ const decision = semanticDecision(grant["decision"], 0);
815
+ if (decision.decision === "allow")
816
+ throw new GuardianDecisionUnavailableError("Guardian omitted the execution grant.");
817
+ const action = parseManagedAction(typeof grant["action"] === "string" ? grant["action"] : undefined);
818
+ output.stdout(`Action: ${action}\n${guardianAgentMessage(action, false, decision.decision === "require_approval", decision.reason, decision.adminContract, decision.approvalState)}\n`);
819
+ await new GuardianDecisionJournal(dataDirectory).append({ agentId, action: String(grant["action"]), resource: manifest.executable,
820
+ outcome: decision.decision, reason: decision.reason, decisionSource: "execution_grant", latencyMs: decision.latencyMs, policyDigest: policy.policyDigest, context });
821
+ return 77;
822
+ }
823
+ if (typeof grant["token"] !== "string" || !/^[a-f0-9]{64}$/u.test(grant["token"]) || grant["manifest_digest"] !== manifestDigest || grant["policy_digest"] !== policy.policyDigest ||
824
+ typeof grant["expires_at"] !== "string" || !Number.isFinite(Date.parse(grant["expires_at"])) || Date.parse(grant["expires_at"]) <= Date.now() || Date.parse(grant["expires_at"]) > Date.now() + 30_000) {
825
+ throw new GuardianDecisionUnavailableError("Guardian returned an invalid execution grant.");
826
+ }
827
+ await verifyExecutionManifest(manifest, childEnvironment);
828
+ const consumed = await hosted.post("/v1/machine/guardian/executions/consume", { ...request, token: grant["token"] }, undefined, 10_000);
829
+ if (consumed["authorized"] !== true || consumed["manifest_digest"] !== manifestDigest || consumed["policy_digest"] !== policy.policyDigest)
830
+ throw new GuardianDecisionUnavailableError("Guardian did not authorize this exact execution.");
831
+ if (consumed["lease_seconds"] !== 30)
832
+ throw new GuardianDecisionUnavailableError("Guardian did not establish an execution lease.");
833
+ const leaseBody = { ...request, token: grant["token"] };
834
+ try {
835
+ const execution = await executeManagedCommand({ executable: manifest.executable, arguments: manifest.arguments, agentId,
836
+ sessionId: context.taskId ?? context.proposalId ?? `managed:${agentId}`, dataDirectory, manifest, environment: childEnvironment, signal: lifetime.signal,
837
+ lease: { origin: config.controlPlaneUrl.origin, credential: credential.credential, request: leaseBody } });
838
+ let reported = false;
839
+ for (let attempt = 0; attempt < 3; attempt++) {
840
+ try {
841
+ const receipt = await hosted.post("/v1/machine/guardian/executions/finish", leaseBody, undefined, 3_000);
842
+ if (receipt["finished"] !== true)
843
+ throw new Error("Invalid completion receipt.");
844
+ reported = true;
845
+ break;
846
+ }
847
+ catch {
848
+ if (attempt < 2)
849
+ await delay(200 * (attempt + 1));
850
+ }
851
+ }
852
+ if (!reported)
853
+ output.stderr(`Phigon Guardian: the command executed and exited with code ${execution.exitCode}. Its completion report could not be acknowledged; the observed result is journaled locally. Do not rerun the command to retry the report.\n`);
854
+ return execution.exitCode;
855
+ }
856
+ finally {
857
+ lifetime.abort();
858
+ }
859
+ }
860
+ catch (error) {
861
+ output.stdout(`${error instanceof Error ? error.message : "Phigon Guardian could not verify execution."}\n`);
862
+ return 77;
863
+ }
864
+ finally {
865
+ lifetime.abort();
866
+ await bridge?.close();
867
+ }
868
+ }
869
+ export async function settleObserverTasks(tasks, controller, fatalError) {
870
+ try {
871
+ await Promise.all(tasks);
872
+ const error = fatalError();
873
+ if (error !== undefined)
874
+ throw error;
875
+ }
876
+ finally {
877
+ controller.abort();
878
+ await Promise.allSettled(tasks);
879
+ }
880
+ }
881
+ function intersectCollectionPolicy(local, server) {
882
+ return Object.freeze({
883
+ promptContent: local.promptContent &&
884
+ server?.promptContent === true,
885
+ responseContent: local.responseContent &&
886
+ server?.responseContent === true,
887
+ reasoningContent: local.reasoningContent &&
888
+ server?.reasoningContent === true,
889
+ commandArguments: local.commandArguments &&
890
+ server?.commandArguments === true,
891
+ terminalOutput: local.terminalOutput &&
892
+ server?.terminalOutput === true,
893
+ filePaths: local.filePaths && server?.filePaths === true,
894
+ fileContent: local.fileContent && server?.fileContent === true,
895
+ browserUrls: local.browserUrls && server?.browserUrls === true,
896
+ browserContent: local.browserContent &&
897
+ server?.browserContent === true,
898
+ networkEndpoints: local.networkEndpoints &&
899
+ server?.networkEndpoints === true,
900
+ gitPatchContent: local.gitPatchContent &&
901
+ server?.gitPatchContent === true,
902
+ environmentValues: local.environmentValues &&
903
+ server?.environmentValues === true,
904
+ toolOutput: local.toolOutput && server?.toolOutput === true,
905
+ vendorPayload: local.vendorPayload &&
906
+ server?.vendorPayload === true,
907
+ processMetadata: local.processMetadata &&
908
+ server?.processMetadata === true,
909
+ processArguments: local.processArguments &&
910
+ server?.processArguments === true,
911
+ fileMetadata: local.fileMetadata && server?.fileMetadata === true,
912
+ fileDiffs: local.fileDiffs && server?.fileDiffs === true,
913
+ networkMetadata: local.networkMetadata &&
914
+ server?.networkMetadata === true,
915
+ networkContent: local.networkContent &&
916
+ server?.networkContent === true,
917
+ browserMetadata: local.browserMetadata &&
918
+ server?.browserMetadata === true,
919
+ mcpArguments: local.mcpArguments && server?.mcpArguments === true,
920
+ mcpOutput: local.mcpOutput && server?.mcpOutput === true,
921
+ modelPrompts: local.modelPrompts && server?.modelPrompts === true,
922
+ modelResponses: local.modelResponses &&
923
+ server?.modelResponses === true,
924
+ });
925
+ }
926
+ function fullHistoryPolicyEnabled(policy) {
927
+ return policy.promptContent &&
928
+ policy.responseContent &&
929
+ policy.commandArguments &&
930
+ policy.toolOutput &&
931
+ policy.vendorPayload &&
932
+ policy.modelPrompts &&
933
+ policy.modelResponses;
934
+ }
935
+ function helpText() {
936
+ return [
937
+ "Phigon observer",
938
+ "",
939
+ "Usage:",
940
+ " phigon-observer authorize-action --action <action> --resource <normalized-resource> [--agent <agent-id>] [--process-id <pid> --process-started-at <RFC3339>] [--context-file <path>] [--json]",
941
+ " phigon-observer guardian status [--json]",
942
+ " phigon-observer guardian exec [--agent <agent-id>] [--context-file <path>] -- <executable> [arguments...]",
943
+ " phigon-observer guardian launch --agent <agent-id> --model <provider:model> [--context-file <path>] -- <executable> [arguments...]",
944
+ " phigon-observer agent add --id <id> --name <name> [--executable <name>] [--process <name>] [--config-path <path>] [--activity-dir <path>] [--capability <name>] [--json]",
945
+ " phigon-observer agent list [--json]",
946
+ " phigon-observer agent remove --id <id> [--yes] [--json]",
947
+ " phigon-observer doctor [--profile <metadata|full>] [--json]",
948
+ " phigon-observer discover [--json]",
949
+ " phigon-observer ingest --agent <agent-id|custom> [--name <agent-name>] [--quiet] [--json]",
950
+ " phigon-observer pair --code <one-time-code> --consent-profile <metadata|full> [--name <machine-name>] [--accept-consent] [--json]",
951
+ " phigon-observer remove-machine [--yes] [--json]",
952
+ " phigon-observer run [--profile <metadata|full>]",
953
+ " phigon-observer help",
954
+ "",
955
+ "The doctor command validates local configuration without transmitting data.",
956
+ "The agent commands manage dynamic local monitoring definitions for custom agents without changing observer code.",
957
+ "The authorize-action command is a fail-closed decision point for managed gateways; it does not intercept actions performed outside that boundary. Stop/force-stop requires an administrator policy and exact process identity.",
958
+ "The optional Guardian context file supplies bounded task_id, user_prompt, agent_plan, justification, and recent_activity fields for semantic review.",
959
+ "The guardian status command reports central management. Only administrator policies authorize blocking and containment; local overrides are unavailable.",
960
+ "The guardian exec command checks agent.start and command.execute, launches without a shell after authorization, and journals command input/output and its exit code for hosted telemetry delivery.",
961
+ "The guardian launch command requires a model claim and a single-use grant covering agent.start, model.use, and command.execute. The native supervisor stops the managed process tree when its online lease is revoked or unavailable.",
962
+ "The discover command reports a sanitized one-time local inventory.",
963
+ "The ingest command accepts NDJSON on stdin for every supported bridge; use --agent custom --name <agent-name> for any other agent.",
964
+ "The pair command shows the selected collection scope, requires machine-owner acceptance, and stores that evidence with the mode-0600 credential.",
965
+ "The remove-machine command unregisters only this paired machine, revokes its credential, and requires local confirmation.",
966
+ "The run command maintains an authenticated, resumable telemetry stream; --profile applies the same portable local collection profile on Windows, macOS, and Linux.",
967
+ "",
968
+ ].join("\n");
969
+ }
970
+ function parseManagedAction(value) {
971
+ if (value !== undefined &&
972
+ managedActionNames.includes(value)) {
973
+ return value;
974
+ }
975
+ throw new Error(`authorize-action requires --action ${managedActionNames.join(" | ")}.`);
976
+ }
977
+ function parseOptionalProcessID(value) {
978
+ if (value === undefined)
979
+ return undefined;
980
+ if (!/^[1-9][0-9]{0,14}$/u.test(value)) {
981
+ throw new Error("--process-id must be a positive safe integer.");
982
+ }
983
+ const parsed = Number(value);
984
+ if (!Number.isSafeInteger(parsed)) {
985
+ throw new Error("--process-id must be a positive safe integer.");
986
+ }
987
+ return parsed;
988
+ }
989
+ function formatDiscoveryJSON(snapshot) {
990
+ return `${JSON.stringify({
991
+ inventory: JSON.parse(toJsonString(AgentInventorySchema, snapshot.inventory)),
992
+ adapterHealth: snapshot.adapterHealth,
993
+ }, null, 2)}\n`;
994
+ }
995
+ function formatDiscovery(snapshot) {
996
+ const agents = snapshot.inventory.agents.length === 0
997
+ ? ["No supported agent installations or processes were detected."]
998
+ : snapshot.inventory.agents.map((agent) => {
999
+ const state = AgentState[agent.state]?.toLowerCase() ?? "unknown";
1000
+ const version = agent.version.length > 0 ? ` ${agent.version}` : "";
1001
+ return `${agent.displayName}${version}: ${state}`;
1002
+ });
1003
+ return [
1004
+ `Observed agents: ${snapshot.inventory.agents.length}`,
1005
+ `Inventory complete: ${snapshot.inventory.partial ? "no" : "yes"}`,
1006
+ ...agents,
1007
+ "",
1008
+ ].join("\n");
1009
+ }
1010
+ function formatPairResult(result) {
1011
+ return [
1012
+ "Machine paired successfully.",
1013
+ `Machine: ${result.displayName} (${result.machineId})`,
1014
+ `Control plane: ${result.controlPlaneOrigin}`,
1015
+ "Machine credential: stored securely",
1016
+ "",
1017
+ ].join("\n");
1018
+ }
1019
+ function removeMachinePrompt(machineId) {
1020
+ return [
1021
+ "Remove this machine from Phigon?",
1022
+ `Machine: ${machineId}`,
1023
+ "This unregisters the machine and immediately revokes its observer credential.",
1024
+ "Its stored telemetry and machine-scoped data will be durably purged; a minimal deletion tombstone plus consent and audit evidence remain according to retention policy.",
1025
+ 'Type "remove" to continue: ',
1026
+ ].join("\n");
1027
+ }
1028
+ function formatRemoveMachineResult(result) {
1029
+ return [
1030
+ "Machine removed successfully.",
1031
+ `Machine: ${result.machineId}`,
1032
+ `Control plane: ${result.controlPlaneOrigin}`,
1033
+ "Machine credential: removed from this device",
1034
+ "Stored machine telemetry: deletion scheduled",
1035
+ "Next step: pair again to reconnect this observer.",
1036
+ "",
1037
+ ].join("\n");
1038
+ }
1039
+ async function runAgentCommand(flags, registry, json, output, input) {
1040
+ const subcommand = flags[0];
1041
+ if (subcommand === "list") {
1042
+ const agents = await registry.list();
1043
+ output.stdout(json
1044
+ ? `${JSON.stringify({ agents }, null, 2)}\n`
1045
+ : formatCustomAgentList(agents));
1046
+ return 0;
1047
+ }
1048
+ if (subcommand === "add") {
1049
+ const id = flagValue(flags, "--id");
1050
+ const displayName = flagValue(flags, "--name");
1051
+ if (id === undefined || displayName === undefined) {
1052
+ throw new Error("agent add requires --id <id> and --name <name>.");
1053
+ }
1054
+ const registration = customAgentRegistration({
1055
+ id,
1056
+ displayName,
1057
+ executableNames: flagValues(flags, "--executable"),
1058
+ processNames: flagValues(flags, "--process"),
1059
+ configurationPaths: flagValues(flags, "--config-path"),
1060
+ activityDirectories: flagValues(flags, "--activity-dir"),
1061
+ capabilities: flagValues(flags, "--capability"),
1062
+ });
1063
+ const added = await registry.add(registration);
1064
+ output.stdout(json
1065
+ ? `${JSON.stringify({ status: "added", agent: added }, null, 2)}\n`
1066
+ : formatCustomAgentChange("added", added));
1067
+ return 0;
1068
+ }
1069
+ if (subcommand === "remove") {
1070
+ const id = flagValue(flags, "--id");
1071
+ if (id === undefined) {
1072
+ throw new Error("agent remove requires --id <id>.");
1073
+ }
1074
+ if (json && !flags.includes("--yes")) {
1075
+ throw new Error("JSON custom-agent removal requires --yes after reviewing the target.");
1076
+ }
1077
+ if (!flags.includes("--yes")) {
1078
+ output.stdout(`Remove custom monitoring definition ${id}?\n` +
1079
+ 'Type "remove" to continue: ');
1080
+ if (await readConfirmation(input) !== "remove") {
1081
+ throw new Error("Custom-agent removal was not confirmed. No change was made.");
1082
+ }
1083
+ }
1084
+ const removed = await registry.remove(id);
1085
+ if (removed === undefined) {
1086
+ throw new Error(`Custom agent ${id} is not registered.`);
1087
+ }
1088
+ output.stdout(json
1089
+ ? `${JSON.stringify({ status: "removed", agent: removed }, null, 2)}\n`
1090
+ : formatCustomAgentChange("removed", removed));
1091
+ return 0;
1092
+ }
1093
+ throw new Error("agent requires one of: add, list, or remove. Run phigon-observer help for usage.");
1094
+ }
1095
+ function formatCustomAgentList(agents) {
1096
+ if (agents.length === 0) {
1097
+ return "Custom agents: 0\nNo dynamic monitoring definitions are registered.\n";
1098
+ }
1099
+ return [
1100
+ `Custom agents: ${agents.length}`,
1101
+ ...agents.map((agent) => {
1102
+ const signals = [
1103
+ agent.executableNames.length > 0 ? "executable" : "",
1104
+ agent.processNames.length > 0 ? "process" : "",
1105
+ agent.configurationPaths.length > 0 ? "configuration" : "",
1106
+ agent.activityDirectories.length > 0 ? "activity" : "",
1107
+ ].filter(Boolean).join(", ");
1108
+ return `${agent.displayName} (${agent.id}): ${signals}`;
1109
+ }),
1110
+ "",
1111
+ ].join("\n");
1112
+ }
1113
+ function formatCustomAgentChange(status, agent) {
1114
+ return [
1115
+ `Custom agent ${status}.`,
1116
+ `Agent: ${agent.displayName} (${agent.id})`,
1117
+ `Discovery signals: ${agent.executableNames.length +
1118
+ agent.processNames.length +
1119
+ agent.configurationPaths.length}`,
1120
+ `Activity directories: ${agent.activityDirectories.length}`,
1121
+ status === "added"
1122
+ ? "Monitoring definition is active on the next discovery and activity scan."
1123
+ : "The observer will stop using this definition on the next scan.",
1124
+ "",
1125
+ ].join("\n");
1126
+ }
1127
+ function flagValue(flags, name) {
1128
+ const equalsPrefix = `${name}=`;
1129
+ for (let index = 0; index < flags.length; index++) {
1130
+ const flag = flags[index];
1131
+ if (flag === name) {
1132
+ return flags[index + 1];
1133
+ }
1134
+ if (flag?.startsWith(equalsPrefix)) {
1135
+ return flag.slice(equalsPrefix.length);
1136
+ }
1137
+ }
1138
+ return undefined;
1139
+ }
1140
+ function flagValues(flags, name) {
1141
+ const values = [];
1142
+ const equalsPrefix = `${name}=`;
1143
+ for (let index = 0; index < flags.length; index++) {
1144
+ const flag = flags[index];
1145
+ if (flag === name) {
1146
+ const value = flags[index + 1];
1147
+ if (value !== undefined && !value.startsWith("--"))
1148
+ values.push(value);
1149
+ continue;
1150
+ }
1151
+ if (flag?.startsWith(equalsPrefix)) {
1152
+ values.push(flag.slice(equalsPrefix.length));
1153
+ }
1154
+ }
1155
+ return values;
1156
+ }
1157
+ function observabilityEnvironment(command, flags, environment) {
1158
+ const profile = flagValue(flags, "--profile");
1159
+ if (profile === undefined)
1160
+ return environment;
1161
+ if (command !== "doctor" && command !== "run") {
1162
+ throw new Error("--profile is supported only by doctor and run.");
1163
+ }
1164
+ if (profile !== "metadata" && profile !== "full") {
1165
+ throw new Error("--profile must be metadata or full.");
1166
+ }
1167
+ return {
1168
+ ...environment,
1169
+ PHIGON_OBSERVABILITY_PROFILE: profile,
1170
+ };
1171
+ }
1172
+ function parseConsentProfile(value) {
1173
+ if (value === "metadata" || value === "full")
1174
+ return value;
1175
+ throw new Error("pair requires --consent-profile metadata or --consent-profile full.");
1176
+ }
1177
+ function consentPrompt(profile) {
1178
+ const scope = profile === "metadata"
1179
+ ? "process, file, network, and browser metadata; no prompts, responses, file contents, terminal output, or browser contents"
1180
+ : "metadata plus prompts, responses, reasoning, command arguments and output, file/browser/network content, tool output, and model content; environment values remain excluded";
1181
+ return [
1182
+ `Consent profile: ${profile}`,
1183
+ `Collection scope: ${scope}.`,
1184
+ "The machine owner can stop the observer at any time.",
1185
+ 'Type "yes" to grant this scope and pair the machine: ',
1186
+ ].join("\n");
1187
+ }
1188
+ async function readConfirmation(input) {
1189
+ const lines = createInterface({ input, crlfDelay: Infinity });
1190
+ try {
1191
+ for await (const line of lines)
1192
+ return line.trim().toLowerCase();
1193
+ return "";
1194
+ }
1195
+ finally {
1196
+ lines.close();
1197
+ }
1198
+ }
1199
+ function formatHealth(health) {
1200
+ return [
1201
+ `Status: ${health.status}`,
1202
+ `Observer: ${health.observerVersion}`,
1203
+ `Control plane: ${health.controlPlaneOrigin}`,
1204
+ `Telemetry stream: ${health.grpcOrigin}`,
1205
+ `Host: ${health.host.platform}/${health.host.architecture}`,
1206
+ `Machine credential configured: ${health.machineTokenConfigured ? "yes" : "no"}`,
1207
+ ...(health.streamCursor === undefined
1208
+ ? health.streamCursorIssue === undefined
1209
+ ? []
1210
+ : [`Stream cursor: unavailable (${health.streamCursorIssue})`]
1211
+ : [
1212
+ `Stream cursor: ${health.streamCursor.acknowledgedSequence} for ${health.streamCursor.machineId}`,
1213
+ ]),
1214
+ `Content collection: ${health.safeCollectionDefaults ? "metadata only" : "explicit categories enabled"}`,
1215
+ "",
1216
+ ].join("\n");
1217
+ }
1218
+ async function readVersion() {
1219
+ const packagePath = new URL("../../package.json", import.meta.url);
1220
+ const raw = await readFile(packagePath, "utf8");
1221
+ const value = JSON.parse(raw);
1222
+ if (typeof value === "object" &&
1223
+ value !== null &&
1224
+ "version" in value &&
1225
+ typeof value.version === "string") {
1226
+ return value.version;
1227
+ }
1228
+ throw new Error("Observer package version is unavailable.");
1229
+ }
1230
+ export function isCliEntryPoint(candidate) {
1231
+ if (candidate === undefined)
1232
+ return false;
1233
+ try {
1234
+ return realpathSync(fileURLToPath(import.meta.url)) ===
1235
+ realpathSync(candidate);
1236
+ }
1237
+ catch {
1238
+ return false;
1239
+ }
1240
+ }
1241
+ if (isCliEntryPoint(process.argv[1])) {
1242
+ process.exitCode = await run(process.argv.slice(2));
1243
+ }
1244
+ //# sourceMappingURL=cli.js.map