@fitlab-ai/agent-infra 0.9.14 → 0.9.15

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 (250) hide show
  1. package/README.md +10 -0
  2. package/README.zh-CN.md +10 -0
  3. package/bin/internal-cli.sh +45 -0
  4. package/dist/bin/internal-cli.js +93 -30
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -2
  7. package/dist/lib/defaults.json +103 -103
  8. package/dist/lib/fs/durable-write.js +37 -0
  9. package/dist/lib/fs/file-lock.js +111 -0
  10. package/dist/lib/host-control/audit.js +19 -0
  11. package/dist/lib/host-control/client.js +142 -0
  12. package/dist/lib/host-control/command.js +49 -0
  13. package/dist/lib/host-control/path.js +160 -0
  14. package/dist/lib/host-control/secure-fs.js +140 -0
  15. package/dist/lib/host-control/server.js +159 -0
  16. package/dist/lib/host-control/service.js +112 -0
  17. package/dist/lib/init.js +11 -2
  18. package/dist/lib/internal/cli-route-inventory.js +3 -1
  19. package/dist/lib/internal/codex-lifecycle.js +6 -6
  20. package/dist/lib/internal/demo-promotion.js +322 -0
  21. package/dist/lib/internal/demo-transcript.js +454 -0
  22. package/dist/lib/internal/host-control.js +28 -0
  23. package/dist/lib/internal/manual-validation.js +322 -0
  24. package/dist/lib/internal/platform-pr.js +18 -4
  25. package/dist/lib/internal/release-workflow.js +63 -84
  26. package/dist/lib/internal/sandbox-control.js +149 -2
  27. package/dist/lib/internal/task-artifact.js +15 -262
  28. package/dist/lib/internal/task-create.js +6 -5
  29. package/dist/lib/internal/task-event.js +37 -37
  30. package/dist/lib/internal/task-operation-registry.js +101 -3
  31. package/dist/lib/internal/task-review.js +8 -53
  32. package/dist/lib/internal/task-validate.js +30 -10
  33. package/dist/lib/owned-path.js +33 -0
  34. package/dist/lib/platform/check-status.js +11 -0
  35. package/dist/lib/platform/comment-safety.js +4 -1
  36. package/dist/lib/platform/completion-backfill.js +1 -1
  37. package/dist/lib/platform/github-data.js +241 -0
  38. package/dist/lib/platform/github-provider.js +15 -20
  39. package/dist/lib/platform/issue-comments.js +2 -2
  40. package/dist/lib/platform/issues.js +1 -179
  41. package/dist/lib/platform/pr-checks.js +12 -65
  42. package/dist/lib/platform/pr-summary.js +55 -3
  43. package/dist/lib/platform/pull-requests.js +2 -105
  44. package/dist/lib/platform/snapshots.js +2 -0
  45. package/dist/lib/platform/verification-sync.js +127 -142
  46. package/dist/lib/release/workflow.js +1 -1
  47. package/dist/lib/sandbox/commands/create.js +7 -2
  48. package/dist/lib/sandbox/commands/rm.js +3 -1514
  49. package/dist/lib/sandbox/control/audit.js +203 -0
  50. package/dist/lib/sandbox/control/client.js +93 -54
  51. package/dist/lib/sandbox/control/executor.js +129 -65
  52. package/dist/lib/sandbox/control/finalization-response.js +27 -0
  53. package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
  54. package/dist/lib/sandbox/control/lifecycle.js +36 -2
  55. package/dist/lib/sandbox/control/native-file-lock.js +6 -98
  56. package/dist/lib/sandbox/control/protocol.js +55 -4
  57. package/dist/lib/sandbox/control/server.js +491 -120
  58. package/dist/lib/sandbox/control/state.js +58 -0
  59. package/dist/lib/sandbox/control/task-workflow.js +74 -0
  60. package/dist/lib/sandbox/control/workflow-executor.js +99 -0
  61. package/dist/lib/sandbox/index.js +2 -2
  62. package/dist/lib/sandbox/recovery.js +28 -4
  63. package/dist/lib/sandbox/removal.js +1717 -0
  64. package/dist/lib/sandbox/task-cleanup.js +217 -0
  65. package/dist/lib/sandbox/workspace-view.js +13 -14
  66. package/dist/lib/task/activity-intent.js +2 -1
  67. package/dist/lib/task/activity-log.js +30 -24
  68. package/dist/lib/task/artifact-command.js +101 -0
  69. package/dist/lib/task/artifact-lifecycle.js +30 -28
  70. package/dist/lib/task/artifact-name.js +36 -0
  71. package/dist/lib/task/artifact-operations.js +23 -69
  72. package/dist/lib/task/artifact-receipts.js +3 -15
  73. package/dist/lib/task/artifact-repair-intent.js +87 -16
  74. package/dist/lib/task/artifact-schema.js +6 -14
  75. package/dist/lib/task/capabilities.js +12 -19
  76. package/dist/lib/task/codex-orchestration.js +71 -25
  77. package/dist/lib/task/commit-identity.js +28 -0
  78. package/dist/lib/task/commit-operation.js +2 -16
  79. package/dist/lib/task/control-authority.js +371 -5
  80. package/dist/lib/task/control-recovery.js +128 -0
  81. package/dist/lib/task/create.js +62 -9
  82. package/dist/lib/task/decision-details.js +2 -46
  83. package/dist/lib/task/events.js +120 -9
  84. package/dist/lib/task/finalization-state.js +9 -0
  85. package/dist/lib/task/human-override.js +3 -6
  86. package/dist/lib/task/implementation-inputs.js +3 -3
  87. package/dist/lib/task/intermediate-cleanup.js +383 -0
  88. package/dist/lib/task/invalidation.js +18 -65
  89. package/dist/lib/task/ledger-intents.js +7 -22
  90. package/dist/lib/task/ledger.js +1 -3
  91. package/dist/lib/task/lifecycle.js +19 -1
  92. package/dist/lib/task/local-artifact-finalization.js +132 -157
  93. package/dist/lib/task/manual-validation-completion.js +26 -0
  94. package/dist/lib/task/manual-validation-evidence.js +130 -0
  95. package/dist/lib/task/manual-validation-receipt.js +101 -0
  96. package/dist/lib/task/manual-validation-shared.js +40 -0
  97. package/dist/lib/task/manual-validation-transaction.js +243 -0
  98. package/dist/lib/task/markdown.js +47 -0
  99. package/dist/lib/task/orchestration.js +14 -17
  100. package/dist/lib/task/qualification-audit.js +20 -34
  101. package/dist/lib/task/review-artifacts.js +2 -18
  102. package/dist/lib/task/review-command.js +25 -0
  103. package/dist/lib/task/review-finalization.js +125 -193
  104. package/dist/lib/task/review-fingerprint.js +2 -2
  105. package/dist/lib/task/sections.js +34 -56
  106. package/dist/lib/task/verification-engine.js +116 -323
  107. package/dist/lib/task/verification.js +1 -1
  108. package/dist/lib/task/workflow-command.js +17 -0
  109. package/dist/lib/task/workflow-warning-intents.js +0 -16
  110. package/dist/lib/task/workflow-warnings.js +28 -2
  111. package/dist/lib/update.js +2 -0
  112. package/dist/lifecycle-build-manifest.json +3 -3
  113. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
  114. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -2
  115. package/lib/fs/durable-write.ts +29 -0
  116. package/lib/fs/file-lock.ts +113 -0
  117. package/lib/host-control/audit.ts +22 -0
  118. package/lib/host-control/client.ts +172 -0
  119. package/lib/host-control/command.ts +60 -0
  120. package/lib/host-control/path.ts +167 -0
  121. package/lib/host-control/secure-fs.ts +135 -0
  122. package/lib/host-control/server.ts +180 -0
  123. package/lib/host-control/service.ts +133 -0
  124. package/lib/init.ts +15 -2
  125. package/lib/internal/cli-route-inventory.ts +3 -1
  126. package/lib/internal/codex-lifecycle.ts +6 -6
  127. package/lib/internal/demo-promotion.ts +354 -0
  128. package/lib/internal/demo-transcript.ts +419 -0
  129. package/lib/internal/host-control.ts +28 -0
  130. package/lib/internal/manual-validation.ts +306 -0
  131. package/lib/internal/platform-pr.ts +15 -4
  132. package/lib/internal/release-workflow.ts +74 -82
  133. package/lib/internal/sandbox-control.ts +162 -1
  134. package/lib/internal/task-artifact.ts +13 -216
  135. package/lib/internal/task-create.ts +5 -5
  136. package/lib/internal/task-event.ts +20 -11
  137. package/lib/internal/task-operation-registry.ts +111 -3
  138. package/lib/internal/task-review.ts +6 -51
  139. package/lib/internal/task-validate.ts +26 -10
  140. package/lib/owned-path.ts +26 -0
  141. package/lib/platform/check-status.ts +9 -0
  142. package/lib/platform/comment-safety.ts +5 -0
  143. package/lib/platform/completion-backfill.ts +1 -1
  144. package/lib/platform/github-data.ts +322 -0
  145. package/lib/platform/github-provider.ts +15 -20
  146. package/lib/platform/issue-comments.ts +3 -4
  147. package/lib/platform/issues.ts +4 -245
  148. package/lib/platform/pr-checks.ts +13 -77
  149. package/lib/platform/pr-summary.ts +61 -5
  150. package/lib/platform/pull-requests.ts +3 -170
  151. package/lib/platform/snapshots.ts +53 -0
  152. package/lib/platform/verification-sync.ts +145 -155
  153. package/lib/release/workflow.ts +3 -3
  154. package/lib/sandbox/commands/create.ts +7 -6
  155. package/lib/sandbox/commands/rm.ts +3 -1909
  156. package/lib/sandbox/control/audit.ts +253 -0
  157. package/lib/sandbox/control/client.ts +109 -66
  158. package/lib/sandbox/control/executor.ts +134 -74
  159. package/lib/sandbox/control/finalization-response.ts +29 -0
  160. package/lib/sandbox/control/identity-sentinel.ts +137 -0
  161. package/lib/sandbox/control/lifecycle.ts +45 -3
  162. package/lib/sandbox/control/native-file-lock.ts +6 -107
  163. package/lib/sandbox/control/protocol.ts +75 -6
  164. package/lib/sandbox/control/server.ts +538 -123
  165. package/lib/sandbox/control/state.ts +78 -0
  166. package/lib/sandbox/control/task-workflow.ts +95 -0
  167. package/lib/sandbox/control/workflow-executor.ts +102 -0
  168. package/lib/sandbox/index.ts +2 -2
  169. package/lib/sandbox/recovery.ts +28 -5
  170. package/lib/sandbox/removal.ts +2144 -0
  171. package/lib/sandbox/task-cleanup.ts +209 -0
  172. package/lib/sandbox/workspace-view.ts +17 -15
  173. package/lib/task/activity-intent.ts +2 -2
  174. package/lib/task/activity-log.ts +27 -19
  175. package/lib/task/artifact-command.ts +104 -0
  176. package/lib/task/artifact-lifecycle.ts +31 -26
  177. package/lib/task/artifact-name.ts +55 -0
  178. package/lib/task/artifact-operations.ts +22 -68
  179. package/lib/task/artifact-receipts.ts +3 -14
  180. package/lib/task/artifact-repair-intent.ts +99 -18
  181. package/lib/task/artifact-schema.ts +5 -34
  182. package/lib/task/capabilities.ts +13 -22
  183. package/lib/task/codex-orchestration.ts +85 -24
  184. package/lib/task/commit-identity.ts +31 -0
  185. package/lib/task/commit-operation.ts +2 -16
  186. package/lib/task/control-authority.ts +502 -5
  187. package/lib/task/control-recovery.ts +174 -0
  188. package/lib/task/create.ts +63 -8
  189. package/lib/task/decision-details.ts +3 -71
  190. package/lib/task/events.ts +131 -10
  191. package/lib/task/finalization-state.ts +11 -0
  192. package/lib/task/human-override.ts +3 -8
  193. package/lib/task/implementation-inputs.ts +3 -3
  194. package/lib/task/intermediate-cleanup.ts +455 -0
  195. package/lib/task/invalidation.ts +18 -62
  196. package/lib/task/ledger-intents.ts +7 -24
  197. package/lib/task/ledger.ts +1 -3
  198. package/lib/task/lifecycle.ts +17 -1
  199. package/lib/task/local-artifact-finalization.ts +148 -182
  200. package/lib/task/manual-validation-completion.ts +40 -0
  201. package/lib/task/manual-validation-evidence.ts +209 -0
  202. package/lib/task/manual-validation-receipt.ts +152 -0
  203. package/lib/task/manual-validation-shared.ts +50 -0
  204. package/lib/task/manual-validation-transaction.ts +304 -0
  205. package/lib/task/markdown.ts +70 -0
  206. package/lib/task/orchestration.ts +19 -19
  207. package/lib/task/qualification-audit.ts +22 -33
  208. package/lib/task/review-artifacts.ts +1 -20
  209. package/lib/task/review-command.ts +23 -0
  210. package/lib/task/review-finalization.ts +131 -259
  211. package/lib/task/review-fingerprint.ts +2 -2
  212. package/lib/task/sections.ts +33 -54
  213. package/lib/task/verification-engine.ts +110 -366
  214. package/lib/task/verification-types.ts +17 -0
  215. package/lib/task/verification.ts +1 -1
  216. package/lib/task/workflow-command.ts +18 -0
  217. package/lib/task/workflow-warning-intents.ts +0 -13
  218. package/lib/task/workflow-warnings.ts +21 -2
  219. package/lib/update.ts +6 -0
  220. package/package.json +6 -6
  221. package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
  222. package/templates/.agents/rules/testing-discipline.en.md +2 -0
  223. package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
  224. package/templates/.agents/scripts/package.json +3 -0
  225. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +32 -20
  226. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +32 -20
  227. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
  228. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
  229. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
  230. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
  231. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
  232. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
  233. package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
  234. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
  235. package/templates/.agents/skills/release/SKILL.en.md +7 -3
  236. package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
  237. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
  238. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
  239. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
  240. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
  241. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
  242. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
  243. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  244. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  245. package/templates/.agents/skills/test/SKILL.en.md +17 -6
  246. package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
  247. package/templates/.claude/commands/run-manual-validation.en.md +1 -1
  248. package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
  249. package/dist/lib/platform/adapters.js +0 -56
  250. package/lib/platform/adapters.ts +0 -197
@@ -0,0 +1,419 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+
6
+ import { loadNodePty } from '../sandbox/clipboard/node-pty.ts';
7
+
8
+ const DEMO_COLUMNS = 128;
9
+ const DEMO_ROWS = 40;
10
+ const DEMO_TIMEOUT_MS = 30_000;
11
+ const CHECKPOINT = '\u001b]9;agent-infra-demo-checkpoint\u0007';
12
+ const TREE_CHECKPOINT = '\u001b]9;agent-infra-demo-tree-checkpoint\u0007';
13
+ const TEMP_PROJECT_PATTERN = /(^|[\s"'`=])((?:[A-Za-z]:[\\/]|[\\/])(?:[^\\/\s"'`]+[\\/])*(?:agent-infra-demo-project-|agent-infra-demo-|agent-infra-)[^\s"'`]+)/g;
14
+ const QUOTED_TEMP_PROJECT_PATTERN = /(^|[\s"'`=])('(?:[^']|'\\'')*\/(?:agent-infra-demo-project-|agent-infra-demo-|agent-infra-)(?:[^']|'\\'')*')(?=[\s"'`=]|$)/g;
15
+ const QUOTED_RELATIVE_TEMP_PROJECT_PATTERN = /(^|[\s"'`=])('(?:[^']|'\\'')*(?:agent-infra-demo-project-|agent-infra-demo-|agent-infra-)(?:[^']|'\\'')*')(?=[\s"'`=]|$)/g;
16
+
17
+ const DEMO_PROJECT_PATH = '/tmp/my-awesome-project';
18
+ const DEMO_VISIBLE_COMMANDS = Object.freeze({
19
+ prepare: `rm -rf ${DEMO_PROJECT_PATH} && mkdir -p ${DEMO_PROJECT_PATH} && cd ${DEMO_PROJECT_PATH}`,
20
+ git: 'git init -q && git remote add origin git@github.com:acme-corp/my-awesome-project.git',
21
+ init: 'ai init',
22
+ language: 'en',
23
+ clients: '1,2,3,4',
24
+ tree: 'tree .agents/ .claude/ .opencode/ -L 2 --dirsfirst'
25
+ });
26
+
27
+ type TranscriptSuccess = { status: 'ok'; transcript: string; sha256: string };
28
+ type TranscriptFailure = { status: 'failed'; reasonCode: 'DEMO_TRANSCRIPT_UNAVAILABLE' | 'DEMO_TRANSCRIPT_FAILED'; message: string };
29
+ type TranscriptResult = TranscriptSuccess | TranscriptFailure;
30
+ type TranscriptCollector = (cwd: string) => Promise<TranscriptResult>;
31
+
32
+ type Cell = string;
33
+
34
+ function blankRow(): Cell[] {
35
+ return Array.from({ length: DEMO_COLUMNS }, () => ' ');
36
+ }
37
+
38
+ function blankScreen(): Cell[][] {
39
+ return Array.from({ length: DEMO_ROWS }, blankRow);
40
+ }
41
+
42
+ function clamp(value: number, minimum: number, maximum: number): number {
43
+ return Math.max(minimum, Math.min(maximum, value));
44
+ }
45
+
46
+ class TerminalDisplay {
47
+ private screen = blankScreen();
48
+ private history: string[] = [];
49
+ private cursorX = 0;
50
+ private cursorY = 0;
51
+ private savedCursor: { x: number; y: number } = { x: 0, y: 0 };
52
+ private mainScreen: { screen: Cell[][]; x: number; y: number } | null = null;
53
+
54
+ feed(value: string): void {
55
+ for (let index = 0; index < value.length; index += 1) {
56
+ const character = value[index]!;
57
+ if (character === '\u001b') {
58
+ index = this.escape(value, index);
59
+ } else if (character === '\n') {
60
+ this.lineFeed();
61
+ } else if (character === '\r') {
62
+ this.cursorX = 0;
63
+ } else if (character === '\b') {
64
+ this.cursorX = Math.max(0, this.cursorX - 1);
65
+ } else if (character === '\t') {
66
+ this.cursorX = Math.min(DEMO_COLUMNS - 1, Math.floor(this.cursorX / 8 + 1) * 8);
67
+ } else if (character.charCodeAt(0) < 0x20 || character === '\u007f') {
68
+ continue;
69
+ } else {
70
+ this.print(character);
71
+ }
72
+ }
73
+ }
74
+
75
+ render(): string {
76
+ const rendered = [...this.history, ...this.screen.map((row) => row.join(''))]
77
+ .map((line) => line.replace(/\s+$/u, ''))
78
+ .join('\n')
79
+ .replace(/\n+$/u, '');
80
+ return rendered ? `${rendered}\n` : '';
81
+ }
82
+
83
+ private print(character: string): void {
84
+ if (this.cursorX >= DEMO_COLUMNS) {
85
+ this.cursorX = 0;
86
+ this.lineFeed();
87
+ }
88
+ this.screen[this.cursorY]![this.cursorX] = character;
89
+ this.cursorX += 1;
90
+ }
91
+
92
+ private lineFeed(): void {
93
+ if (this.cursorY === DEMO_ROWS - 1) {
94
+ this.history.push(this.screen[0]!.join(''));
95
+ this.screen = [...this.screen.slice(1), blankRow()];
96
+ } else {
97
+ this.cursorY += 1;
98
+ }
99
+ }
100
+
101
+ private escape(value: string, index: number): number {
102
+ const next = value[index + 1];
103
+ if (next === '[') return this.csi(value, index + 2);
104
+ if (next === ']') return this.osc(value, index + 2);
105
+ if (next === '7') {
106
+ this.saveCursor();
107
+ return index + 1;
108
+ }
109
+ if (next === '8') {
110
+ this.restoreCursor();
111
+ return index + 1;
112
+ }
113
+ if (next === 'c') {
114
+ this.screen = blankScreen();
115
+ this.history = [];
116
+ this.cursorX = 0;
117
+ this.cursorY = 0;
118
+ return index + 1;
119
+ }
120
+ throw new Error(`Unknown terminal escape sequence at offset ${index}`);
121
+ }
122
+
123
+ private osc(value: string, start: number): number {
124
+ for (let index = start; index < value.length; index += 1) {
125
+ if (value[index] === '\u0007') return index;
126
+ if (value[index] === '\u001b' && value[index + 1] === '\\') return index + 1;
127
+ }
128
+ throw new Error(`Unterminated terminal OSC sequence at offset ${start - 2}`);
129
+ }
130
+
131
+ private csi(value: string, start: number): number {
132
+ let end = start;
133
+ while (end < value.length && !/[\x40-\x7e]/u.test(value[end]!)) end += 1;
134
+ if (end >= value.length) throw new Error(`Unterminated terminal CSI sequence at offset ${start - 2}`);
135
+ const body = value.slice(start, end);
136
+ const final = value[end]!;
137
+ const privateMode = body.startsWith('?');
138
+ const parameters = (privateMode ? body.slice(1) : body)
139
+ .split(';')
140
+ .map((part) => part === '' ? 0 : Number(part));
141
+ if (parameters.some((parameter) => !Number.isInteger(parameter) || parameter < 0)) {
142
+ throw new Error(`Invalid terminal CSI parameters at offset ${start - 2}`);
143
+ }
144
+ this.applyCsi(final, parameters, privateMode);
145
+ return end;
146
+ }
147
+
148
+ private applyCsi(final: string, parameters: number[], privateMode: boolean): void {
149
+ const first = (fallback: number): number => parameters[0] || fallback;
150
+ switch (final) {
151
+ case 'A': this.cursorY = clamp(this.cursorY - first(1), 0, DEMO_ROWS - 1); return;
152
+ case 'B': this.cursorY = clamp(this.cursorY + first(1), 0, DEMO_ROWS - 1); return;
153
+ case 'C': this.cursorX = clamp(this.cursorX + first(1), 0, DEMO_COLUMNS - 1); return;
154
+ case 'D': this.cursorX = clamp(this.cursorX - first(1), 0, DEMO_COLUMNS - 1); return;
155
+ case 'E': this.cursorY = clamp(this.cursorY + first(1), 0, DEMO_ROWS - 1); this.cursorX = 0; return;
156
+ case 'F': this.cursorY = clamp(this.cursorY - first(1), 0, DEMO_ROWS - 1); this.cursorX = 0; return;
157
+ case 'G': this.cursorX = clamp(first(1) - 1, 0, DEMO_COLUMNS - 1); return;
158
+ case 'd': this.cursorY = clamp(first(1) - 1, 0, DEMO_ROWS - 1); return;
159
+ case 'H':
160
+ case 'f':
161
+ this.cursorY = clamp((parameters[0] || 1) - 1, 0, DEMO_ROWS - 1);
162
+ this.cursorX = clamp((parameters[1] || 1) - 1, 0, DEMO_COLUMNS - 1);
163
+ return;
164
+ case 'J': this.eraseDisplay(first(0)); return;
165
+ case 'K': this.eraseLine(first(0)); return;
166
+ case 'P': this.deleteCharacters(first(1)); return;
167
+ case '@': this.insertCharacters(first(1)); return;
168
+ case 'X': this.eraseCharacters(first(1)); return;
169
+ case 'L': this.insertLines(first(1)); return;
170
+ case 'M': this.deleteLines(first(1)); return;
171
+ case 'S': for (let count = 0; count < first(1); count += 1) this.lineFeed(); return;
172
+ case 'T': for (let count = 0; count < first(1); count += 1) this.reverseLineFeed(); return;
173
+ case 's': this.saveCursor(); return;
174
+ case 'u': this.restoreCursor(); return;
175
+ case 'm': return;
176
+ case 'r': return;
177
+ case 'h':
178
+ case 'l':
179
+ if (!privateMode || parameters.some((parameter) => ![25, 1049, 2004].includes(parameter))) {
180
+ throw new Error(`Unknown terminal mode sequence: ${final}`);
181
+ }
182
+ if (parameters.includes(1049)) {
183
+ if (final === 'h') this.enterAlternateScreen();
184
+ else this.leaveAlternateScreen();
185
+ }
186
+ return;
187
+ default:
188
+ throw new Error(`Unknown terminal CSI sequence: ${final}`);
189
+ }
190
+ }
191
+
192
+ private eraseDisplay(mode: number): void {
193
+ if (mode === 2) {
194
+ this.screen = blankScreen();
195
+ this.history = [];
196
+ return;
197
+ }
198
+ if (mode === 0) {
199
+ for (let y = this.cursorY; y < DEMO_ROWS; y += 1) {
200
+ const start = y === this.cursorY ? this.cursorX : 0;
201
+ this.screen[y]!.fill(' ', start);
202
+ }
203
+ return;
204
+ }
205
+ if (mode === 1) {
206
+ for (let y = 0; y <= this.cursorY; y += 1) {
207
+ const end = y === this.cursorY ? this.cursorX + 1 : DEMO_COLUMNS;
208
+ this.screen[y]!.fill(' ', 0, end);
209
+ }
210
+ return;
211
+ }
212
+ throw new Error(`Unknown erase-display mode: ${mode}`);
213
+ }
214
+
215
+ private eraseLine(mode: number): void {
216
+ if (mode === 0) this.screen[this.cursorY]!.fill(' ', this.cursorX);
217
+ else if (mode === 1) this.screen[this.cursorY]!.fill(' ', 0, this.cursorX + 1);
218
+ else if (mode === 2) this.screen[this.cursorY]!.fill(' ');
219
+ else throw new Error(`Unknown erase-line mode: ${mode}`);
220
+ }
221
+
222
+ private deleteCharacters(count: number): void {
223
+ const row = this.screen[this.cursorY]!;
224
+ row.splice(this.cursorX, count);
225
+ row.push(...Array.from({ length: count }, () => ' '));
226
+ }
227
+
228
+ private insertCharacters(count: number): void {
229
+ const row = this.screen[this.cursorY]!;
230
+ row.splice(this.cursorX, 0, ...Array.from({ length: count }, () => ' '));
231
+ row.length = DEMO_COLUMNS;
232
+ }
233
+
234
+ private eraseCharacters(count: number): void {
235
+ this.screen[this.cursorY]!.fill(' ', this.cursorX, Math.min(DEMO_COLUMNS, this.cursorX + count));
236
+ }
237
+
238
+ private insertLines(count: number): void {
239
+ this.screen.splice(this.cursorY, 0, ...Array.from({ length: count }, blankRow));
240
+ this.screen.length = DEMO_ROWS;
241
+ }
242
+
243
+ private deleteLines(count: number): void {
244
+ this.screen.splice(this.cursorY, count);
245
+ this.screen.push(...Array.from({ length: count }, blankRow));
246
+ this.screen.length = DEMO_ROWS;
247
+ }
248
+
249
+ private reverseLineFeed(): void {
250
+ if (this.cursorY === 0) this.screen.unshift(blankRow());
251
+ else this.cursorY -= 1;
252
+ this.screen.length = DEMO_ROWS;
253
+ }
254
+
255
+ private saveCursor(): void {
256
+ this.savedCursor = { x: this.cursorX, y: this.cursorY };
257
+ }
258
+
259
+ private restoreCursor(): void {
260
+ this.cursorX = clamp(this.savedCursor.x, 0, DEMO_COLUMNS - 1);
261
+ this.cursorY = clamp(this.savedCursor.y, 0, DEMO_ROWS - 1);
262
+ }
263
+
264
+ private enterAlternateScreen(): void {
265
+ if (this.mainScreen) return;
266
+ this.mainScreen = { screen: this.screen, x: this.cursorX, y: this.cursorY };
267
+ this.screen = blankScreen();
268
+ this.history = [];
269
+ this.cursorX = 0;
270
+ this.cursorY = 0;
271
+ }
272
+
273
+ private leaveAlternateScreen(): void {
274
+ if (!this.mainScreen) return;
275
+ this.screen = this.mainScreen.screen;
276
+ this.cursorX = this.mainScreen.x;
277
+ this.cursorY = this.mainScreen.y;
278
+ this.mainScreen = null;
279
+ }
280
+ }
281
+
282
+ function normalizeDemoPaths(value: string): string {
283
+ return value
284
+ .replace(QUOTED_TEMP_PROJECT_PATTERN, '$1/tmp/my-awesome-project')
285
+ .replace(QUOTED_RELATIVE_TEMP_PROJECT_PATTERN, '$1/tmp/my-awesome-project')
286
+ .replace(TEMP_PROJECT_PATTERN, '$1/tmp/my-awesome-project');
287
+ }
288
+
289
+ function normalizeVisibleTranscript(value: string): string {
290
+ const display = new TerminalDisplay();
291
+ display.feed(normalizeDemoPaths(value));
292
+ return normalizeDemoPaths(display.render());
293
+ }
294
+
295
+ function sha256Transcript(value: string): string {
296
+ return crypto.createHash('sha256').update(value, 'utf8').digest('hex');
297
+ }
298
+
299
+ function waitFor(output: () => string, pattern: RegExp, timeoutMs: number): Promise<void> {
300
+ return new Promise((resolve, reject) => {
301
+ const deadline = Date.now() + timeoutMs;
302
+ const check = () => {
303
+ if (pattern.test(output())) { resolve(); return; }
304
+ if (Date.now() >= deadline) { reject(new Error(`Timed out waiting for terminal output matching ${pattern}`)); return; }
305
+ setTimeout(check, 25);
306
+ };
307
+ check();
308
+ });
309
+ }
310
+
311
+ function shellQuote(value: string): string {
312
+ return `'${value.replaceAll("'", "'\\''")}'`;
313
+ }
314
+
315
+ function buildDemoPrepareCommand(project: string): string {
316
+ return DEMO_VISIBLE_COMMANDS.prepare.replaceAll(DEMO_PROJECT_PATH, shellQuote(project));
317
+ }
318
+
319
+ async function collectDemoTranscript(cwd: string): Promise<TranscriptResult> {
320
+ const ptyModule = await loadNodePty();
321
+ if (!ptyModule) return { status: 'failed', reasonCode: 'DEMO_TRANSCRIPT_UNAVAILABLE', message: '@lydell/node-pty is unavailable' };
322
+
323
+ const project = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-infra-'));
324
+ const projectName = path.basename(project);
325
+ const shimDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-infra-demo-shim-'));
326
+ const home = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-infra-demo-home-'));
327
+ const globalGitConfig = path.join(home, 'empty-gitconfig');
328
+ const localCli = path.join(cwd, 'dist', 'bin', 'cli.js');
329
+ const basePath = process.env.PATH ?? '';
330
+ let processHandle: ReturnType<typeof ptyModule.spawn> | null = null;
331
+ let output = '';
332
+ let exited: { exitCode: number; signal?: number | string } | null = null;
333
+
334
+ try {
335
+ if (!fs.existsSync(localCli)) throw new Error(`${localCli} not found; run npm run build first`);
336
+ fs.writeFileSync(globalGitConfig, '');
337
+ for (const name of ['ai', 'agent-infra']) {
338
+ const shim = path.join(shimDir, name);
339
+ const checkpoint = name === 'ai' ? `\nprintf '${CHECKPOINT}'\n` : '';
340
+ fs.writeFileSync(shim, `#!/bin/sh\n"$AGENT_INFRA_DEMO_NODE" "$AGENT_INFRA_DEMO_CLI" "$@"\nstatus=$?${checkpoint}\nexit $status\n`);
341
+ fs.chmodSync(shim, 0o755);
342
+ }
343
+ const treeShim = path.join(shimDir, 'tree');
344
+ fs.writeFileSync(
345
+ treeShim,
346
+ `#!/bin/sh\nPATH=${shellQuote(basePath)} command tree "$@"\nstatus=$?\nprintf '${TREE_CHECKPOINT}'\nexit $status\n`
347
+ );
348
+ fs.chmodSync(treeShim, 0o755);
349
+ const environment = {
350
+ ...process.env,
351
+ HOME: home,
352
+ PATH: `${shimDir}${path.delimiter}${process.env.PATH ?? ''}`,
353
+ TERM: 'xterm-256color',
354
+ LANG: 'C.UTF-8',
355
+ LC_ALL: 'C.UTF-8',
356
+ GIT_CONFIG_GLOBAL: globalGitConfig,
357
+ AGENT_INFRA_DEMO_NODE: process.execPath,
358
+ AGENT_INFRA_DEMO_CLI: localCli,
359
+ AGENT_INFRA_DEMO_PLATFORM: 'linux',
360
+ TERM_PROGRAM: '',
361
+ PS1: 'demo$ ',
362
+ PROMPT: 'demo$ ',
363
+ RPS1: '',
364
+ RPROMPT: '',
365
+ PS2: 'demo> ',
366
+ PROMPT2: 'demo> ',
367
+ PS3: 'demo? ',
368
+ PROMPT3: 'demo? ',
369
+ PS4: '+ '
370
+ };
371
+ processHandle = ptyModule.spawn('zsh', ['-f', '-i'], {
372
+ name: 'xterm-256color', cols: DEMO_COLUMNS, rows: DEMO_ROWS, cwd: path.dirname(project), env: environment
373
+ });
374
+ processHandle.onData((data) => { output += data; });
375
+ processHandle.onExit((event) => { exited = event; });
376
+ await waitFor(() => output, /demo\$ /, DEMO_TIMEOUT_MS);
377
+
378
+ const send = async (command: string, expected?: RegExp) => {
379
+ processHandle!.write(`${command}\r`);
380
+ if (expected) await waitFor(() => output, expected, DEMO_TIMEOUT_MS);
381
+ else await new Promise((resolve) => setTimeout(resolve, 150));
382
+ };
383
+
384
+ await send(buildDemoPrepareCommand(projectName));
385
+ await send(DEMO_VISIBLE_COMMANDS.git);
386
+ await send(DEMO_VISIBLE_COMMANDS.init, /Project name/);
387
+ await send('', /Organization/);
388
+ await send('', /Language/);
389
+ await send(DEMO_VISIBLE_COMMANDS.language, /Sandbox engine/);
390
+ await send('', /Platform/);
391
+ await send('', /Agent Client project integrations/);
392
+ await send(DEMO_VISIBLE_COMMANDS.clients, /Template sources/);
393
+ await send('', /Skill sources/);
394
+ await send('', /(?:initialized|success|created)/i);
395
+ await waitFor(() => output, /agent-infra-demo-checkpoint/, DEMO_TIMEOUT_MS);
396
+ await send(DEMO_VISIBLE_COMMANDS.tree, /agent-infra-demo-tree-checkpoint/);
397
+ processHandle.write('\u0004');
398
+ await waitFor(() => exited ? 'exited' : '', /exited/, 5_000);
399
+
400
+ if (!output.includes(CHECKPOINT)) throw new Error('demo transcript checkpoint marker was not emitted');
401
+ if (!output.includes(TREE_CHECKPOINT)) throw new Error('demo transcript tree checkpoint marker was not emitted');
402
+ if (!/\.agents/.test(output)) throw new Error('demo transcript tree checkpoint was not emitted');
403
+ const exitEvent = exited as { exitCode: number; signal?: number | string } | null;
404
+ if (!exitEvent || exitEvent.exitCode !== 0) throw new Error(`demo shell exited with code ${exitEvent?.exitCode ?? 'unknown'}`);
405
+ const transcript = normalizeVisibleTranscript(output);
406
+ if (!transcript.trim()) throw new Error('demo transcript is empty');
407
+ return { status: 'ok', transcript, sha256: sha256Transcript(transcript) };
408
+ } catch (error) {
409
+ try { processHandle?.kill('SIGTERM'); } catch { /* best effort cleanup */ }
410
+ return { status: 'failed', reasonCode: 'DEMO_TRANSCRIPT_FAILED', message: error instanceof Error ? error.message : String(error) };
411
+ } finally {
412
+ fs.rmSync(project, { recursive: true, force: true });
413
+ fs.rmSync(shimDir, { recursive: true, force: true });
414
+ fs.rmSync(home, { recursive: true, force: true });
415
+ }
416
+ }
417
+
418
+ export { buildDemoPrepareCommand, DEMO_COLUMNS, DEMO_PROJECT_PATH, DEMO_ROWS, DEMO_VISIBLE_COMMANDS, collectDemoTranscript, normalizeVisibleTranscript, sha256Transcript };
419
+ export type { TranscriptCollector, TranscriptFailure, TranscriptResult, TranscriptSuccess };
@@ -0,0 +1,28 @@
1
+ import { dispatchHostControlCommand } from '../host-control/command.ts';
2
+ import { hostControlServiceStatus, installHostControlService, serveHostControlService, uninstallHostControlService } from '../host-control/service.ts';
3
+ import { internalHandlerRoute } from './cli-route-inventory.ts';
4
+
5
+ export async function hostControl(args: readonly string[]): Promise<void> {
6
+ const action = args[0] ?? '';
7
+ if (internalHandlerRoute('host-control', 'serve', action)) {
8
+ const controller = new AbortController();
9
+ process.once('SIGINT', () => controller.abort());
10
+ process.once('SIGTERM', () => controller.abort());
11
+ await serveHostControlService({ dispatch: dispatchHostControlCommand }, controller.signal);
12
+ return;
13
+ }
14
+ if (internalHandlerRoute('host-control', 'status', action)) {
15
+ process.stdout.write(`${JSON.stringify(hostControlServiceStatus())}\n`);
16
+ return;
17
+ }
18
+ if (internalHandlerRoute('host-control', 'install', action)) {
19
+ process.stdout.write(`${JSON.stringify({ status: 'completed', path: installHostControlService() })}\n`);
20
+ return;
21
+ }
22
+ if (internalHandlerRoute('host-control', 'uninstall', action)) {
23
+ process.stdout.write(`${JSON.stringify({ status: 'completed', path: uninstallHostControlService() })}\n`);
24
+ return;
25
+ }
26
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'HOST_CONTROL_COMMAND_INVALID', message: 'usage: host-control <serve|status|install|uninstall>' } })}\n`);
27
+ process.exitCode = 1;
28
+ }