@getforgeai/cli 0.1.0-beta.2 → 0.1.0-beta.4

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 (37) hide show
  1. package/dist/cli.js +10 -1
  2. package/dist/commands/auth-setup-agent.d.ts +15 -0
  3. package/dist/commands/auth-setup-agent.js +98 -0
  4. package/dist/commands/config-set.js +14 -3
  5. package/dist/commands/connect.js +23 -7
  6. package/dist/config/config-manager.d.ts +2 -0
  7. package/dist/lib/connection-manager.d.ts +11 -0
  8. package/dist/lib/connection-manager.js +48 -25
  9. package/dist/lib/daemon-entry.js +23 -6
  10. package/dist/orchestrator/agent-profiles.d.ts +80 -0
  11. package/dist/orchestrator/agent-profiles.js +253 -0
  12. package/dist/orchestrator/agent-spawner.d.ts +2 -0
  13. package/dist/orchestrator/agent-spawner.js +25 -14
  14. package/dist/orchestrator/billing-detect.d.ts +20 -0
  15. package/dist/orchestrator/billing-detect.js +120 -0
  16. package/dist/orchestrator/codex-jsonl-extractor.d.ts +52 -0
  17. package/dist/orchestrator/codex-jsonl-extractor.js +210 -0
  18. package/dist/orchestrator/connectors/connector.d.ts +2 -0
  19. package/dist/orchestrator/connectors/docker-connector.js +197 -175
  20. package/dist/orchestrator/opencode-jsonl-extractor.d.ts +47 -0
  21. package/dist/orchestrator/opencode-jsonl-extractor.js +228 -0
  22. package/dist/orchestrator/resume-handler.js +2 -0
  23. package/dist/orchestrator/stream-json-extractor.d.ts +38 -10
  24. package/dist/orchestrator/stream-json-extractor.js +10 -105
  25. package/dist/vm/agent-auth-manager.d.ts +32 -0
  26. package/dist/vm/agent-auth-manager.js +68 -0
  27. package/dist/vm/docker-process-proxy.d.ts +2 -1
  28. package/dist/vm/docker-process-proxy.js +17 -10
  29. package/dist/vm/image-manager.js +1 -1
  30. package/dist/vm/secure-store.d.ts +24 -0
  31. package/dist/vm/secure-store.js +110 -0
  32. package/dist/vm/session-manager.d.ts +33 -5
  33. package/dist/vm/session-manager.js +129 -19
  34. package/dist/vm/session-snapshot.d.ts +13 -8
  35. package/dist/vm/session-snapshot.js +52 -36
  36. package/package.json +2 -2
  37. package/rootfs/Dockerfile +5 -2
@@ -0,0 +1,210 @@
1
+ import { vmLog } from "../vm/log.js";
2
+ import { extractResetTimestampFromTexts, matchesBillingPatterns, } from "./billing-detect.js";
3
+ import { TOOL_DETAIL_EXTRACTORS } from "./stream-json-extractor.js";
4
+ /**
5
+ * Extracts normalized events from Codex CLI's `codex exec --json` output.
6
+ *
7
+ * Codex emits newline-delimited JSON "thread events":
8
+ * {"type":"thread.started","thread_id":"..."}
9
+ * {"type":"turn.started"}
10
+ * {"type":"item.started"|"item.updated"|"item.completed","item":{...}}
11
+ * {"type":"turn.completed","usage":{"input_tokens":N,"cached_input_tokens":N,"output_tokens":N}}
12
+ * {"type":"turn.failed","error":{...}} / {"type":"error","message":"..."}
13
+ *
14
+ * Item types: agent_message (text), reasoning, command_execution,
15
+ * file_change, mcp_tool_call, web_search, todo_list.
16
+ *
17
+ * Exposes the same callback surface as StreamJsonExtractor so the spawn
18
+ * pipeline (StreamBatcher → Socket.io) is unchanged. Codex has no equivalent
19
+ * of Claude's AskUserQuestion tool — structured questions reach the Cloud via
20
+ * the forge_ask_human MCP tool instead, so onQuestion is never called here.
21
+ */
22
+ export class CodexJsonlExtractor {
23
+ onText;
24
+ lineBuf = "";
25
+ /** Emitted text length per agent_message item id (suffix dedup). */
26
+ emittedTextLengths = new Map();
27
+ /** Item ids already reported as tool activity. */
28
+ emittedActivityIds = new Set();
29
+ /** Whether any text was emitted (to separate consecutive messages). */
30
+ hasEmittedText = false;
31
+ turnIndex = 0;
32
+ lastError = null;
33
+ /** Codex thread id from thread.started — usable with `codex exec resume`. */
34
+ threadId = null;
35
+ inputTokens = 0;
36
+ outputTokens = 0;
37
+ onToolActivity;
38
+ onTokenUsage;
39
+ sessionId;
40
+ constructor(onText, _onQuestion, onToolActivity, onTokenUsage, sessionId) {
41
+ this.onText = onText;
42
+ this.onToolActivity = onToolActivity;
43
+ this.onTokenUsage = onTokenUsage;
44
+ this.sessionId = sessionId;
45
+ }
46
+ write(data) {
47
+ this.lineBuf += data;
48
+ let newlineIdx;
49
+ while ((newlineIdx = this.lineBuf.indexOf("\n")) !== -1) {
50
+ const line = this.lineBuf.slice(0, newlineIdx).trim();
51
+ this.lineBuf = this.lineBuf.slice(newlineIdx + 1);
52
+ if (line)
53
+ this.parseLine(line);
54
+ }
55
+ }
56
+ flush() {
57
+ const line = this.lineBuf.trim();
58
+ this.lineBuf = "";
59
+ if (line)
60
+ this.parseLine(line);
61
+ }
62
+ parseLine(line) {
63
+ let obj;
64
+ try {
65
+ obj = JSON.parse(line);
66
+ }
67
+ catch {
68
+ vmLog.dim("codex-jsonl", "Non-JSON line skipped", this.sessionId);
69
+ return;
70
+ }
71
+ switch (obj.type) {
72
+ case "thread.started":
73
+ if (typeof obj.thread_id === "string")
74
+ this.threadId = obj.thread_id;
75
+ break;
76
+ case "turn.started":
77
+ this.turnIndex++;
78
+ break;
79
+ case "turn.completed":
80
+ this.handleUsage(obj.usage);
81
+ break;
82
+ case "turn.failed":
83
+ this.captureError(obj.error ?? obj);
84
+ break;
85
+ case "error":
86
+ this.captureError(obj);
87
+ break;
88
+ case "item.started":
89
+ case "item.updated":
90
+ case "item.completed":
91
+ this.handleItem(obj.item);
92
+ break;
93
+ }
94
+ }
95
+ handleUsage(usage) {
96
+ if (!usage || !this.onTokenUsage)
97
+ return;
98
+ // Codex's input_tokens already includes cached_input_tokens — don't
99
+ // double-count the cache portion.
100
+ const input = typeof usage.input_tokens === "number" ? usage.input_tokens : 0;
101
+ const output = typeof usage.output_tokens === "number" ? usage.output_tokens : 0;
102
+ if (input === 0 && output === 0)
103
+ return;
104
+ this.inputTokens += input;
105
+ this.outputTokens += output;
106
+ this.onTokenUsage({
107
+ inputTokens: this.inputTokens,
108
+ outputTokens: this.outputTokens,
109
+ });
110
+ }
111
+ captureError(error) {
112
+ if (typeof error === "string") {
113
+ this.lastError = error;
114
+ return;
115
+ }
116
+ if (error && typeof error === "object") {
117
+ const rec = error;
118
+ if (typeof rec.message === "string") {
119
+ this.lastError = rec.message;
120
+ return;
121
+ }
122
+ this.lastError = JSON.stringify(rec).slice(0, 1000);
123
+ }
124
+ }
125
+ handleItem(item) {
126
+ if (!item || typeof item.id !== "string")
127
+ return;
128
+ switch (item.type) {
129
+ case "agent_message":
130
+ if (typeof item.text === "string") {
131
+ this.emitTextDelta(item.id, item.text);
132
+ }
133
+ break;
134
+ case "command_execution":
135
+ this.emitActivityOnce(item.id, "Bash", firstLine(item.command, 80));
136
+ break;
137
+ case "file_change":
138
+ this.emitActivityOnce(item.id, "Edit", describeFileChanges(item));
139
+ break;
140
+ case "mcp_tool_call":
141
+ this.emitMcpToolCall(item);
142
+ break;
143
+ case "web_search":
144
+ this.emitActivityOnce(item.id, "WebSearch", truncate(item.query, 60));
145
+ break;
146
+ // reasoning / todo_list: not surfaced
147
+ }
148
+ }
149
+ /** Emit only the not-yet-emitted suffix of an item's text. */
150
+ emitTextDelta(itemId, fullText) {
151
+ const emitted = this.emittedTextLengths.get(itemId) ?? 0;
152
+ if (fullText.length <= emitted)
153
+ return;
154
+ const delta = fullText.slice(emitted);
155
+ this.emittedTextLengths.set(itemId, fullText.length);
156
+ // Separate consecutive agent messages with a blank line
157
+ const prefix = emitted === 0 && this.hasEmittedText ? "\n\n" : "";
158
+ this.hasEmittedText = true;
159
+ this.onText(prefix + delta);
160
+ }
161
+ emitActivityOnce(itemId, toolName, detail) {
162
+ if (!this.onToolActivity)
163
+ return;
164
+ if (this.emittedActivityIds.has(itemId))
165
+ return;
166
+ this.emittedActivityIds.add(itemId);
167
+ this.onToolActivity({ toolName, toolDetail: detail ?? toolName });
168
+ }
169
+ emitMcpToolCall(item) {
170
+ const tool = typeof item.tool === "string" ? item.tool : "mcp";
171
+ let detail = null;
172
+ const extractor = TOOL_DETAIL_EXTRACTORS[tool];
173
+ const args = item.arguments;
174
+ if (extractor && args && typeof args === "object") {
175
+ detail = extractor(args);
176
+ }
177
+ this.emitActivityOnce(item.id, tool, detail);
178
+ }
179
+ isBillingError(extraText) {
180
+ return matchesBillingPatterns([this.lastError, extraText]);
181
+ }
182
+ extractResetTimestamp(extraText) {
183
+ return extractResetTimestampFromTexts([this.lastError, extraText]);
184
+ }
185
+ }
186
+ function truncate(value, maxLen) {
187
+ if (typeof value !== "string" || !value)
188
+ return null;
189
+ return value.length > maxLen ? value.slice(0, maxLen - 3) + "..." : value;
190
+ }
191
+ function firstLine(value, maxLen) {
192
+ if (typeof value !== "string" || !value)
193
+ return null;
194
+ const line = value.split("\n")[0] ?? value;
195
+ return line.length > maxLen ? line.slice(0, maxLen - 3) + "..." : line;
196
+ }
197
+ function describeFileChanges(item) {
198
+ const changes = item.changes;
199
+ if (!Array.isArray(changes) || changes.length === 0)
200
+ return null;
201
+ const first = changes[0];
202
+ const path = typeof first.path === "string" ? first.path : null;
203
+ if (!path)
204
+ return null;
205
+ const shortPath = path.length > 60 ? "..." + path.slice(-(60 - 3)) : path;
206
+ return changes.length > 1
207
+ ? `${shortPath} (+${changes.length - 1} more)`
208
+ : shortPath;
209
+ }
210
+ //# sourceMappingURL=codex-jsonl-extractor.js.map
@@ -25,6 +25,8 @@ export type SpawnParams = {
25
25
  skillName: string;
26
26
  /** Custom prompt override — used for workflow step agents */
27
27
  prompt?: string;
28
+ /** Agent engine to run (AGENT_TYPE) — defaults to CLAUDE_CODE. */
29
+ agentType?: string;
28
30
  };
29
31
  /**
30
32
  * Extended spawn params for VM connector — includes repo/branch/skill context