@agentex/agent 0.0.23 → 0.0.26

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 (177) hide show
  1. package/CHANGELOG.md +338 -0
  2. package/LICENSE +21 -0
  3. package/README.md +110 -0
  4. package/dist/derived.d.ts +5 -3
  5. package/dist/derived.d.ts.map +1 -1
  6. package/dist/derived.js +11 -7
  7. package/dist/derived.js.map +1 -1
  8. package/dist/index.d.ts +7 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +4 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/providers/acp/index.d.ts +1 -1
  13. package/dist/providers/acp/index.d.ts.map +1 -1
  14. package/dist/providers/acp/index.js +5 -97
  15. package/dist/providers/acp/index.js.map +1 -1
  16. package/dist/providers/acp/session.d.ts +8 -1
  17. package/dist/providers/acp/session.d.ts.map +1 -1
  18. package/dist/providers/acp/session.js +94 -0
  19. package/dist/providers/acp/session.js.map +1 -1
  20. package/dist/providers/claude/attach.d.ts +8 -0
  21. package/dist/providers/claude/attach.d.ts.map +1 -0
  22. package/dist/providers/claude/attach.js +113 -0
  23. package/dist/providers/claude/attach.js.map +1 -0
  24. package/dist/providers/claude/execute.d.ts.map +1 -1
  25. package/dist/providers/claude/execute.js +17 -2
  26. package/dist/providers/claude/execute.js.map +1 -1
  27. package/dist/providers/claude/goal-capability.d.ts +15 -0
  28. package/dist/providers/claude/goal-capability.d.ts.map +1 -0
  29. package/dist/providers/claude/goal-capability.js +20 -0
  30. package/dist/providers/claude/goal-capability.js.map +1 -0
  31. package/dist/providers/claude/index.d.ts.map +1 -1
  32. package/dist/providers/claude/index.js +8 -4
  33. package/dist/providers/claude/index.js.map +1 -1
  34. package/dist/providers/claude/session.d.ts +11 -9
  35. package/dist/providers/claude/session.d.ts.map +1 -1
  36. package/dist/providers/claude/session.js +36 -14
  37. package/dist/providers/claude/session.js.map +1 -1
  38. package/dist/providers/codex/attach.d.ts +9 -0
  39. package/dist/providers/codex/attach.d.ts.map +1 -0
  40. package/dist/providers/codex/attach.js +93 -0
  41. package/dist/providers/codex/attach.js.map +1 -0
  42. package/dist/providers/codex/execute.d.ts.map +1 -1
  43. package/dist/providers/codex/execute.js +17 -3
  44. package/dist/providers/codex/execute.js.map +1 -1
  45. package/dist/providers/codex/goal-capability.d.ts +13 -0
  46. package/dist/providers/codex/goal-capability.d.ts.map +1 -0
  47. package/dist/providers/codex/goal-capability.js +18 -0
  48. package/dist/providers/codex/goal-capability.js.map +1 -0
  49. package/dist/providers/codex/index.d.ts +1 -0
  50. package/dist/providers/codex/index.d.ts.map +1 -1
  51. package/dist/providers/codex/index.js +9 -6
  52. package/dist/providers/codex/index.js.map +1 -1
  53. package/dist/providers/codex/session.d.ts +11 -7
  54. package/dist/providers/codex/session.d.ts.map +1 -1
  55. package/dist/providers/codex/session.js +37 -12
  56. package/dist/providers/codex/session.js.map +1 -1
  57. package/dist/providers/codex/transcript-normalize.d.ts +28 -0
  58. package/dist/providers/codex/transcript-normalize.d.ts.map +1 -0
  59. package/dist/providers/codex/transcript-normalize.js +191 -0
  60. package/dist/providers/codex/transcript-normalize.js.map +1 -0
  61. package/dist/providers/cursor/index.d.ts.map +1 -1
  62. package/dist/providers/cursor/index.js +2 -2
  63. package/dist/providers/cursor/index.js.map +1 -1
  64. package/dist/providers/openclaw/index.d.ts.map +1 -1
  65. package/dist/providers/openclaw/index.js +2 -2
  66. package/dist/providers/openclaw/index.js.map +1 -1
  67. package/dist/providers/opencode/index.d.ts.map +1 -1
  68. package/dist/providers/opencode/index.js +3 -5
  69. package/dist/providers/opencode/index.js.map +1 -1
  70. package/dist/providers/pi/index.d.ts.map +1 -1
  71. package/dist/providers/pi/index.js +3 -5
  72. package/dist/providers/pi/index.js.map +1 -1
  73. package/dist/providers/process/index.d.ts.map +1 -1
  74. package/dist/providers/process/index.js +2 -2
  75. package/dist/providers/process/index.js.map +1 -1
  76. package/dist/registry.d.ts +0 -1
  77. package/dist/registry.d.ts.map +1 -1
  78. package/dist/registry.js +0 -4
  79. package/dist/registry.js.map +1 -1
  80. package/dist/sessions/index.d.ts +3 -0
  81. package/dist/sessions/index.d.ts.map +1 -0
  82. package/dist/sessions/index.js +2 -0
  83. package/dist/sessions/index.js.map +1 -0
  84. package/dist/sessions/record.d.ts +43 -0
  85. package/dist/sessions/record.d.ts.map +1 -0
  86. package/dist/sessions/record.js +85 -0
  87. package/dist/sessions/record.js.map +1 -0
  88. package/dist/types.d.ts +176 -0
  89. package/dist/types.d.ts.map +1 -1
  90. package/dist/types.js.map +1 -1
  91. package/dist/utils/endpoint.d.ts +38 -0
  92. package/dist/utils/endpoint.d.ts.map +1 -0
  93. package/dist/utils/endpoint.js +151 -0
  94. package/dist/utils/endpoint.js.map +1 -0
  95. package/dist/utils/env.d.ts.map +1 -1
  96. package/dist/utils/env.js +5 -1
  97. package/dist/utils/env.js.map +1 -1
  98. package/dist/utils/uuid.d.ts +7 -1
  99. package/dist/utils/uuid.d.ts.map +1 -1
  100. package/dist/utils/uuid.js +21 -1
  101. package/dist/utils/uuid.js.map +1 -1
  102. package/package.json +64 -7
  103. package/src/derived.ts +311 -0
  104. package/src/goals/controller.ts +442 -0
  105. package/src/goals/index.ts +21 -0
  106. package/src/goals/normalize.ts +173 -0
  107. package/src/goals/sentinel.ts +90 -0
  108. package/src/index.ts +270 -0
  109. package/src/providers/_shared/http-agent.ts +304 -0
  110. package/src/providers/acp/index.ts +103 -0
  111. package/src/providers/acp/parse.ts +131 -0
  112. package/src/providers/acp/session.ts +744 -0
  113. package/src/providers/claude/attach.ts +147 -0
  114. package/src/providers/claude/codec.ts +43 -0
  115. package/src/providers/claude/execute.ts +300 -0
  116. package/src/providers/claude/goal-capability.ts +21 -0
  117. package/src/providers/claude/index.ts +72 -0
  118. package/src/providers/claude/mcp.ts +82 -0
  119. package/src/providers/claude/parse.ts +824 -0
  120. package/src/providers/claude/session.ts +1192 -0
  121. package/src/providers/claude/transcript.ts +555 -0
  122. package/src/providers/codex/attach.ts +123 -0
  123. package/src/providers/codex/codec.ts +50 -0
  124. package/src/providers/codex/execute.ts +337 -0
  125. package/src/providers/codex/goal-capability.ts +19 -0
  126. package/src/providers/codex/index.ts +57 -0
  127. package/src/providers/codex/modes.ts +159 -0
  128. package/src/providers/codex/parse.ts +691 -0
  129. package/src/providers/codex/plan-mode.ts +49 -0
  130. package/src/providers/codex/session.ts +1287 -0
  131. package/src/providers/codex/transcript-normalize.ts +197 -0
  132. package/src/providers/codex/transcript.ts +487 -0
  133. package/src/providers/codex/usage-scanner.ts +178 -0
  134. package/src/providers/copilot/index.ts +19 -0
  135. package/src/providers/cursor/codec.ts +44 -0
  136. package/src/providers/cursor/execute.ts +271 -0
  137. package/src/providers/cursor/index.ts +25 -0
  138. package/src/providers/cursor/parse.ts +288 -0
  139. package/src/providers/gemini/index.ts +21 -0
  140. package/src/providers/openclaw/codec.ts +40 -0
  141. package/src/providers/openclaw/execute.ts +19 -0
  142. package/src/providers/openclaw/index.ts +29 -0
  143. package/src/providers/opencode/codec.ts +50 -0
  144. package/src/providers/opencode/event-parse.ts +141 -0
  145. package/src/providers/opencode/execute.ts +251 -0
  146. package/src/providers/opencode/http-session.ts +427 -0
  147. package/src/providers/opencode/index.ts +30 -0
  148. package/src/providers/opencode/parse.ts +203 -0
  149. package/src/providers/opencode/server.ts +0 -0
  150. package/src/providers/pi/codec.ts +44 -0
  151. package/src/providers/pi/execute.ts +297 -0
  152. package/src/providers/pi/index.ts +30 -0
  153. package/src/providers/pi/parse.ts +231 -0
  154. package/src/providers/pi/session.ts +381 -0
  155. package/src/providers/process/execute.ts +148 -0
  156. package/src/providers/process/index.ts +52 -0
  157. package/src/registry.ts +40 -0
  158. package/src/sessions/index.ts +8 -0
  159. package/src/sessions/record.ts +108 -0
  160. package/src/types.ts +1638 -0
  161. package/src/utils/ask-user-question.ts +57 -0
  162. package/src/utils/auth.ts +661 -0
  163. package/src/utils/binary.ts +179 -0
  164. package/src/utils/endpoint.ts +172 -0
  165. package/src/utils/env.ts +63 -0
  166. package/src/utils/execute-all.ts +68 -0
  167. package/src/utils/exit-plan-mode.ts +40 -0
  168. package/src/utils/instructions.ts +427 -0
  169. package/src/utils/process.ts +223 -0
  170. package/src/utils/runtime-config.ts +100 -0
  171. package/src/utils/runtime-homes.ts +49 -0
  172. package/src/utils/skill-commands.ts +493 -0
  173. package/src/utils/skills.ts +500 -0
  174. package/src/utils/template.ts +16 -0
  175. package/src/utils/tool-names.ts +51 -0
  176. package/src/utils/uuid.ts +21 -0
  177. package/src/utils/workspace.ts +156 -0
@@ -0,0 +1,90 @@
1
+ import type {
2
+ GoalSentinel,
3
+ GoalSentinelContext,
4
+ GoalSentinelVerdict,
5
+ TurnResult,
6
+ } from "../types.js";
7
+
8
+ /** Normalize a sentinel's `boolean | {met,nudge}` return into a verdict. */
9
+ export async function runSentinel(
10
+ sentinel: GoalSentinel,
11
+ ctx: GoalSentinelContext,
12
+ ): Promise<GoalSentinelVerdict> {
13
+ const out = await sentinel(ctx);
14
+ if (typeof out === "boolean") return { met: out };
15
+ return { met: out.met === true, ...(out.nudge ? { nudge: out.nudge } : {}) };
16
+ }
17
+
18
+ /**
19
+ * The objective doubles as the first turn's directive (matching native `/goal`,
20
+ * which starts a turn immediately with the condition). Kept verbatim so a goal
21
+ * like "All tests pass under `pnpm test`" reads naturally as a task.
22
+ */
23
+ export function buildKickoffMessage(objective: string): string {
24
+ return objective;
25
+ }
26
+
27
+ /** Continuation message sent when the sentinel judges the goal not-yet-met. */
28
+ export function defaultNudge(objective: string, iterations: number): string {
29
+ return [
30
+ `The goal is not yet satisfied (attempt ${iterations + 1}). Keep working until it is fully met, then stop.`,
31
+ "",
32
+ "Goal:",
33
+ objective,
34
+ ].join("\n");
35
+ }
36
+
37
+ /**
38
+ * Prompt for the default sentinel's self-assessment turn. The model must answer
39
+ * with a single leading token so the parse is unambiguous.
40
+ */
41
+ export function buildAssessmentPrompt(objective: string): string {
42
+ return [
43
+ "Assess ONLY whether the goal below is now fully and verifiably met by the work done so far in this conversation.",
44
+ "Answer with exactly one word on the first line: YES if it is fully met, otherwise NO.",
45
+ "Do not perform any further work; do not edit files. Just judge.",
46
+ "",
47
+ "Goal:",
48
+ objective,
49
+ ].join("\n");
50
+ }
51
+
52
+ /**
53
+ * Parse a yes/no self-assessment answer. Conservative: only an explicit
54
+ * affirmative ("yes"/"met"/"complete"/"done"/"satisfied" as the first token, or
55
+ * a clear standalone "YES") counts as met. Anything ambiguous → not met, so the
56
+ * loop keeps working rather than declaring premature success.
57
+ */
58
+ export function parseAssessment(text: string): boolean {
59
+ const trimmed = text.trim().toLowerCase();
60
+ if (!trimmed) return false;
61
+ const firstToken = (trimmed.split(/[\s,.:!]+/)[0] ?? "").replace(/[^a-z]/g, "");
62
+ if (["no", "not", "incomplete", "unmet", "false"].includes(firstToken)) return false;
63
+ if (["yes", "met", "complete", "completed", "done", "satisfied", "true"].includes(firstToken)) {
64
+ return true;
65
+ }
66
+ // Fall back to a strict scan for an unambiguous affirmative phrase.
67
+ return /\b(goal\s+is\s+)?(fully\s+)?(met|complete|completed|satisfied)\b/.test(trimmed)
68
+ && !/\bnot\b/.test(trimmed);
69
+ }
70
+
71
+ /**
72
+ * Build the library's default sentinel: ask the running provider to self-assess
73
+ * whether the goal is met. Borrows Claude's design (judge from what the working
74
+ * model surfaced) but uses the same session — so it adds a meta-turn to the
75
+ * transcript. NOTE: the meta-turn runs with the session's normal tool/permission
76
+ * grant; the prompt ASKS the model not to act, but that is not enforced, so this
77
+ * is not a true read-only judge. Hosts with a deterministic check (e.g. run
78
+ * tests) should pass their own `sentinel`; this is the of-last-resort default.
79
+ *
80
+ * `metaSend` runs a turn and resolves with its TurnResult. The controller wires
81
+ * it so the meta-turn's own settle is ignored by the goal loop (no recursion).
82
+ */
83
+ export function createDefaultSentinel(deps: {
84
+ metaSend: (message: string) => Promise<TurnResult>;
85
+ }): GoalSentinel {
86
+ return async (ctx: GoalSentinelContext): Promise<GoalSentinelVerdict> => {
87
+ const turn = await deps.metaSend(buildAssessmentPrompt(ctx.objective));
88
+ return { met: parseAssessment(turn.summary ?? "") };
89
+ };
90
+ }
package/src/index.ts ADDED
@@ -0,0 +1,270 @@
1
+ // Functions
2
+ export { getProvider, listProviders, registerProvider } from "./registry.js";
3
+ export {
4
+ defineDerivedProvider,
5
+ loadProvidersFromConfig,
6
+ registerAcpFactory,
7
+ MalformedProviderConfigError,
8
+ } from "./derived.js";
9
+ export type { DerivedProviderConfig, AcpFactory } from "./derived.js";
10
+ export { acpProvider } from "./providers/acp/index.js";
11
+ export type { AcpProviderConfig } from "./providers/acp/index.js";
12
+ export type { AcpTransformers } from "./providers/acp/session.js";
13
+ export {
14
+ runHttpAgent,
15
+ httpAgentProvider,
16
+ httpAgentSessionCodec,
17
+ } from "./providers/_shared/http-agent.js";
18
+ export type { HttpAgentOptions } from "./providers/_shared/http-agent.js";
19
+ export { renderTemplate } from "./utils/template.js";
20
+ export { redactEnvForLogs } from "./utils/env.js";
21
+ export {
22
+ translateEndpoint,
23
+ CODEX_CUSTOM_PROVIDER_ID,
24
+ CODEX_CUSTOM_KEY_ENV,
25
+ } from "./utils/endpoint.js";
26
+ export type { EndpointTranslation } from "./utils/endpoint.js";
27
+ export { parseAskUserQuestion } from "./utils/ask-user-question.js";
28
+ export { parseExitPlanMode } from "./utils/exit-plan-mode.js";
29
+ export { aggregateUsage } from "./types.js";
30
+ export {
31
+ resolveInstructions,
32
+ installInstructions,
33
+ removeInstructions,
34
+ resolveInstructionTargets,
35
+ upsertManagedBlock,
36
+ stripManagedBlock,
37
+ } from "./utils/instructions.js";
38
+ export type {
39
+ InstallInstructionsOptions,
40
+ InstructionStatus,
41
+ InstructionInstallEntry,
42
+ InstructionInstallResult,
43
+ InstructionTarget,
44
+ RemoveInstructionsOptions,
45
+ InstructionRemoveStatus,
46
+ InstructionRemoveEntry,
47
+ InstructionRemoveResult,
48
+ ManagedBlockOptions,
49
+ } from "./utils/instructions.js";
50
+ export { getRuntimeHomeEnvVar, getDefaultRuntimeHome } from "./utils/runtime-homes.js";
51
+ export { findBinary, ensureCommandResolvable, clearBinaryCache } from "./utils/binary.js";
52
+ export type { ResolvedBinary } from "./utils/binary.js";
53
+ export {
54
+ detectAuth,
55
+ resolveAuthForProvider,
56
+ clearAuthCache,
57
+ hasSubscription,
58
+ hasApiKey,
59
+ hasBedrock,
60
+ isLoggedIn,
61
+ loginCommandFor,
62
+ } from "./utils/auth.js";
63
+ export type { ResolvedAuth } from "./utils/auth.js";
64
+ export { prepareWorkspace } from "./utils/workspace.js";
65
+ export type { WorkspaceOptions, PreparedWorkspace, DiffOptions } from "./utils/workspace.js";
66
+ export { withTempConfig } from "./utils/runtime-config.js";
67
+ export type { TempConfigResult, TempConfigOptions } from "./utils/runtime-config.js";
68
+ export { executeAll } from "./utils/execute-all.js";
69
+ export type { ExecuteAllOptions, ExecuteAllTask } from "./utils/execute-all.js";
70
+ export {
71
+ getClaudeUnknownDetails,
72
+ getClaudeTaskDetails,
73
+ classifyClaudeAuthFromResult,
74
+ CLAUDE_LOGIN_COMMAND,
75
+ } from "./providers/claude/parse.js";
76
+ export type {
77
+ ClaudeTaskDetails,
78
+ ClaudeTaskUsage,
79
+ ClaudeTaskStatus,
80
+ } from "./providers/claude/parse.js";
81
+ export {
82
+ getClaudeTranscriptPath,
83
+ findClaudeTranscriptBySessionId,
84
+ readClaudeTranscript,
85
+ peekClaudeTranscript,
86
+ claudeTranscriptOps,
87
+ sanitizeProjectPath,
88
+ resolveClaudeHome,
89
+ canonicalizeCwd,
90
+ MAX_SANITIZED_LENGTH,
91
+ } from "./providers/claude/transcript.js";
92
+ export type {
93
+ GetClaudeTranscriptPathOptions,
94
+ ClaudeTranscriptLocation,
95
+ FindClaudeTranscriptOptions,
96
+ FoundClaudeTranscript,
97
+ ReadClaudeTranscriptOptions,
98
+ ClaudeTranscriptYield,
99
+ ClaudePeekResult,
100
+ } from "./providers/claude/transcript.js";
101
+ export {
102
+ getCodexTranscriptPath,
103
+ readCodexTranscript,
104
+ peekCodexTranscript,
105
+ readCodexCwd,
106
+ codexTranscriptOps,
107
+ parseCodexLine,
108
+ resolveCodexHome,
109
+ } from "./providers/codex/transcript.js";
110
+ export { codexLineToStreamEvents } from "./providers/codex/transcript-normalize.js";
111
+ export type {
112
+ GetCodexTranscriptPathOptions,
113
+ CodexTranscriptLocation,
114
+ ReadCodexTranscriptOptions,
115
+ CodexTranscriptYield,
116
+ CodexTranscriptLine,
117
+ CodexPeekResult,
118
+ } from "./providers/codex/transcript.js";
119
+
120
+ // Skills
121
+ export {
122
+ installSkills,
123
+ removeSkills,
124
+ listInstalledSkills,
125
+ resolveSkillsHome,
126
+ resolveSkillsWorkspace,
127
+ resolveNativeSkillsHome,
128
+ resolveNativeSkillsWorkspace,
129
+ ensureSkillSymlink,
130
+ } from "./utils/skills.js";
131
+ export {
132
+ commandInventoryFromEvent,
133
+ discoverSkillCommands,
134
+ reconcileSkillCommands,
135
+ formatSlashInvocation,
136
+ invokeSkill,
137
+ buildExpandedSkillPrompt,
138
+ } from "./utils/skill-commands.js";
139
+
140
+ // Types
141
+ export type {
142
+ ProviderModule,
143
+ ProviderCapabilities,
144
+ AgentMode,
145
+ ListModesOptions,
146
+ ExecutionContext,
147
+ ExecutionResult,
148
+ ExecutionStatus,
149
+ ProviderConfig,
150
+ ProviderEndpointConfig,
151
+ McpServerConfig,
152
+ StreamEvent,
153
+ SessionCodec,
154
+ SessionState,
155
+ TokenUsage,
156
+ ModelUsage,
157
+ RateLimitInfo,
158
+ BaseStreamEventFields,
159
+ LifecycleEvent,
160
+ QuotaStatus,
161
+ QuotaContext,
162
+ AuthMethod,
163
+ AuthSource,
164
+ AuthOption,
165
+ AuthReport,
166
+ AuthResolveContext,
167
+ AuthIdentity,
168
+ AuthRequiredReason,
169
+ BinaryStatus,
170
+ ProviderModel,
171
+ SessionContext,
172
+ AgentSession,
173
+ SendHandle,
174
+ SendOptions,
175
+ CancelResult,
176
+ StopTaskResult,
177
+ TurnResult,
178
+ UserInputRequest,
179
+ UserInputResponse,
180
+ ElicitationRequest,
181
+ ElicitationResponse,
182
+ HookCallbackRequest,
183
+ HookCallbackResponse,
184
+ TranscriptOps,
185
+ TranscriptYield,
186
+ TranscriptPeek,
187
+ FoundTranscript,
188
+ GoalStatus,
189
+ GoalBlockedReason,
190
+ GoalSource,
191
+ GoalCapability,
192
+ GoalState,
193
+ GoalOptions,
194
+ SetGoalResult,
195
+ ClearGoalResult,
196
+ GoalSentinel,
197
+ GoalSentinelVerdict,
198
+ GoalSentinelContext,
199
+ } from "./types.js";
200
+
201
+ // Goals
202
+ export {
203
+ GoalController,
204
+ EMULATED_GOAL_CAPABILITY,
205
+ GOAL_OBJECTIVE_MAX,
206
+ CODEX_GOAL_TOOLS,
207
+ isTerminalGoalStatus,
208
+ goalStateFromEvent,
209
+ latestGoalFromEvents,
210
+ normalizeClaudeGoalAttachment,
211
+ normalizeCodexGoalStatus,
212
+ normalizeCodexGoalRecord,
213
+ createDefaultSentinel,
214
+ parseAssessment,
215
+ } from "./goals/index.js";
216
+ export type {
217
+ GoalControllerDeps,
218
+ GoalStatusEvent,
219
+ NormalizedGoalFields,
220
+ } from "./goals/index.js";
221
+
222
+ // Durable sessions
223
+ export {
224
+ SESSION_RECORD_VERSION,
225
+ MalformedSessionRecordError,
226
+ createSessionRecord,
227
+ isSessionRecord,
228
+ assertSessionRecord,
229
+ } from "./sessions/index.js";
230
+ export type { CreateSessionRecordInput } from "./sessions/index.js";
231
+ export type {
232
+ SessionRecord,
233
+ LastTurnStatus,
234
+ CatchUpYield,
235
+ CatchUpOptions,
236
+ AttachOptions,
237
+ SessionAttachment,
238
+ } from "./types.js";
239
+
240
+ export type {
241
+ AskUserQuestion,
242
+ QuestionOption,
243
+ } from "./utils/ask-user-question.js";
244
+
245
+ export type { ExitPlanModeRequest } from "./utils/exit-plan-mode.js";
246
+
247
+ export type {
248
+ SkillRuntime,
249
+ SkillLocation,
250
+ SkillChannel,
251
+ InstallSkillsOptions,
252
+ SkillInstallEntry,
253
+ SkillInstallResult,
254
+ RemoveSkillsOptions,
255
+ SkillRemoveEntry,
256
+ SkillRemoveResult,
257
+ InstalledSkill,
258
+ } from "./utils/skills.js";
259
+
260
+ export type {
261
+ SkillCommandSource,
262
+ SkillCommandExecution,
263
+ SkillCommandDescriptor,
264
+ RuntimeCommandInventory,
265
+ SkillCommandDiagnostic,
266
+ DiscoverSkillCommandsOptions,
267
+ DiscoverSkillCommandsResult,
268
+ ReconcileSkillCommandsOptions,
269
+ InvokeSkillOptions,
270
+ } from "./utils/skill-commands.js";
@@ -0,0 +1,304 @@
1
+ import type {
2
+ AuthReport,
3
+ ExecutionContext,
4
+ ExecutionResult,
5
+ ProviderModule,
6
+ SessionCodec,
7
+ } from "../../types.js";
8
+ import { uuidv7 } from "../../utils/uuid.js";
9
+
10
+ /**
11
+ * Reusable "remote HTTP agent" adapter — the pattern OpenClaw encodes and the
12
+ * shape any gateway-backed agent reuses: resolve a gateway URL (per-call command
13
+ * override → saved sessionParams → default), round-trip a session key through
14
+ * `sessionParams`, POST the prompt, map 401/403 to an `auth_required` event, and
15
+ * bound the request with an AbortController timeout.
16
+ */
17
+ export interface HttpAgentOptions {
18
+ /** Provider id (used on events + the result). */
19
+ providerType: string;
20
+ /** Gateway base URL used when neither `config.command` nor a saved URL is set. */
21
+ defaultBaseUrl: string;
22
+ /** Path appended to the base URL for the run endpoint (e.g. "/api/agent/run"). */
23
+ runPath: string;
24
+ /** Recovery command surfaced on `auth_required` (e.g. "openclaw login"). */
25
+ loginCommand?: string;
26
+ /** Build the JSON request body. Default: `{ prompt, sessionKey?, model? }`. */
27
+ buildBody?: (input: { prompt: string; sessionKey: string | null; model: string | null }) => Record<string, unknown>;
28
+ /** Pull the summary text from the gateway response. Default: summary ?? result ?? output. */
29
+ extractSummary?: (response: Record<string, unknown>) => string | null;
30
+ /** Pull the (possibly new) session key from the response. Default: `response.sessionKey ?? prev`. */
31
+ extractSessionKey?: (response: Record<string, unknown>, prev: string | null) => string | null;
32
+ /** Pull the model id from the response. Default: `response.model ?? fallback`. */
33
+ extractModel?: (response: Record<string, unknown>, fallback: string | null) => string | null;
34
+ /** Pull the cost from the response. Default: `response.costUsd` when numeric. */
35
+ extractCost?: (response: Record<string, unknown>) => number | null;
36
+ }
37
+
38
+ function str(v: unknown): string | null {
39
+ return typeof v === "string" && v.length > 0 ? v : null;
40
+ }
41
+
42
+ function defaultSummary(r: Record<string, unknown>): string | null {
43
+ return str(r["summary"]) ?? str(r["result"]) ?? str(r["output"]);
44
+ }
45
+
46
+ /** Resolve the session key from session params (sessionKey / session_key). */
47
+ function readSessionKey(sessionParams: Record<string, unknown> | null | undefined): string | null {
48
+ if (!sessionParams) return null;
49
+ return str(sessionParams["sessionKey"]) ?? str(sessionParams["session_key"]);
50
+ }
51
+
52
+ /** Execute a single turn against a remote HTTP agent gateway. */
53
+ export async function runHttpAgent(
54
+ opts: HttpAgentOptions,
55
+ ctx: ExecutionContext,
56
+ ): Promise<ExecutionResult> {
57
+ const runId = ctx.runId ?? uuidv7();
58
+ const model = ctx.model ?? ctx.config?.model ?? null;
59
+ const config = ctx.config ?? {};
60
+ const startedAt = new Date().toISOString();
61
+ const startMs = Date.now();
62
+
63
+ const savedGatewayUrl = str(ctx.sessionParams?.["gatewayUrl"]);
64
+ const gatewayUrl = config.command?.trim() || savedGatewayUrl || opts.defaultBaseUrl;
65
+ const endpoint = gatewayUrl.replace(/\/$/, "") + opts.runPath;
66
+
67
+ const sessionKey = readSessionKey(ctx.sessionParams);
68
+ const body = opts.buildBody
69
+ ? opts.buildBody({ prompt: ctx.prompt, sessionKey, model })
70
+ : {
71
+ prompt: ctx.prompt,
72
+ ...(sessionKey ? { sessionKey } : {}),
73
+ ...(model ? { model } : {}),
74
+ };
75
+
76
+ const abortController = new AbortController();
77
+ let timeoutHandle: ReturnType<typeof setTimeout> | null = null;
78
+ let timedOut = false;
79
+ if (config.timeoutSec && config.timeoutSec > 0) {
80
+ timeoutHandle = setTimeout(() => {
81
+ timedOut = true;
82
+ abortController.abort();
83
+ }, config.timeoutSec * 1000);
84
+ }
85
+ // Chain a caller-supplied signal (a caller abort is distinct from a timeout).
86
+ if (ctx.signal) {
87
+ if (ctx.signal.aborted) abortController.abort();
88
+ else ctx.signal.addEventListener("abort", () => abortController.abort(), { once: true });
89
+ }
90
+
91
+ try {
92
+ const response = await fetch(endpoint, {
93
+ method: "POST",
94
+ headers: { "Content-Type": "application/json" },
95
+ body: JSON.stringify(body),
96
+ signal: abortController.signal,
97
+ });
98
+ if (timeoutHandle) clearTimeout(timeoutHandle);
99
+
100
+ if (!response.ok) {
101
+ const errorText = await response.text().catch(() => "");
102
+ const isAuth = response.status === 401 || response.status === 403;
103
+ if (isAuth && ctx.onEvent) {
104
+ try {
105
+ await ctx.onEvent({
106
+ type: "auth_required",
107
+ httpStatus: response.status,
108
+ reason: "unknown",
109
+ loginCommand: opts.loginCommand ?? `${opts.providerType} login`,
110
+ message: errorText || null,
111
+ timestamp: new Date().toISOString(),
112
+ providerType: opts.providerType,
113
+ sessionId: sessionKey ?? null,
114
+ messageId: null,
115
+ eventId: null,
116
+ turnId: null,
117
+ parentToolCallId: null,
118
+ raw: { status: response.status, body: errorText },
119
+ });
120
+ } catch {
121
+ /* swallow */
122
+ }
123
+ }
124
+ return {
125
+ runId,
126
+ exitCode: 1,
127
+ signal: null,
128
+ status: "failed",
129
+ startedAt,
130
+ completedAt: new Date().toISOString(),
131
+ durationMs: Date.now() - startMs,
132
+ errorMessage: `${opts.providerType} gateway returned ${response.status}: ${errorText}`,
133
+ errorCode: isAuth ? "auth_required" : null,
134
+ costUsd: null,
135
+ model,
136
+ summary: null,
137
+ sessionParams: sessionKey ? { sessionKey, gatewayUrl } : null,
138
+ sessionDisplayId: sessionKey,
139
+ clearSession: false,
140
+ billingType: null,
141
+ };
142
+ }
143
+
144
+ const result = (await response.json()) as Record<string, unknown>;
145
+ const newSessionKey = opts.extractSessionKey
146
+ ? opts.extractSessionKey(result, sessionKey)
147
+ : (str(result["sessionKey"]) ?? sessionKey);
148
+ const summary = (opts.extractSummary ?? defaultSummary)(result);
149
+ const resolvedModel = opts.extractModel
150
+ ? opts.extractModel(result, model)
151
+ : (str(result["model"]) ?? model);
152
+ const costUsd = opts.extractCost
153
+ ? opts.extractCost(result)
154
+ : typeof result["costUsd"] === "number"
155
+ ? (result["costUsd"] as number)
156
+ : null;
157
+
158
+ if (ctx.onEvent && summary) {
159
+ try {
160
+ await ctx.onEvent({
161
+ type: "result",
162
+ text: summary,
163
+ costUsd,
164
+ isError: false,
165
+ stopReason: null,
166
+ terminalReason: null,
167
+ numTurns: null,
168
+ durationMs: null,
169
+ timestamp: new Date().toISOString(),
170
+ providerType: opts.providerType,
171
+ sessionId: newSessionKey ?? null,
172
+ messageId: null,
173
+ eventId: null,
174
+ turnId: null,
175
+ parentToolCallId: null,
176
+ raw: result,
177
+ });
178
+ } catch {
179
+ /* swallow */
180
+ }
181
+ }
182
+
183
+ return {
184
+ runId,
185
+ exitCode: 0,
186
+ signal: null,
187
+ status: "completed",
188
+ startedAt,
189
+ completedAt: new Date().toISOString(),
190
+ durationMs: Date.now() - startMs,
191
+ errorMessage: null,
192
+ errorCode: null,
193
+ costUsd,
194
+ model: resolvedModel,
195
+ summary,
196
+ sessionParams: newSessionKey ? { sessionKey: newSessionKey, gatewayUrl } : null,
197
+ sessionDisplayId: newSessionKey,
198
+ clearSession: false,
199
+ billingType: null,
200
+ raw: result,
201
+ };
202
+ } catch (err) {
203
+ if (timeoutHandle) clearTimeout(timeoutHandle);
204
+ if (err instanceof Error && err.name === "AbortError") {
205
+ // A timeout and a caller-supplied signal abort both surface as AbortError;
206
+ // distinguish them so consumers can tell a deadline from a cancellation.
207
+ const isTimeout = timedOut;
208
+ return {
209
+ runId,
210
+ exitCode: null,
211
+ signal: null,
212
+ status: isTimeout ? "timeout" : "aborted",
213
+ startedAt,
214
+ completedAt: new Date().toISOString(),
215
+ durationMs: Date.now() - startMs,
216
+ errorMessage: isTimeout ? `Timed out after ${config.timeoutSec ?? 0}s` : "Aborted by caller",
217
+ errorCode: isTimeout ? "timeout" : "aborted",
218
+ costUsd: null,
219
+ model,
220
+ summary: null,
221
+ sessionParams: sessionKey ? { sessionKey, gatewayUrl } : null,
222
+ sessionDisplayId: sessionKey,
223
+ clearSession: false,
224
+ billingType: null,
225
+ };
226
+ }
227
+ // A transient network error (DNS, reset) is recoverable — preserve the
228
+ // session key / gateway so the caller can resume rather than losing it.
229
+ return {
230
+ runId,
231
+ exitCode: null,
232
+ signal: null,
233
+ status: "failed",
234
+ startedAt,
235
+ completedAt: new Date().toISOString(),
236
+ durationMs: Date.now() - startMs,
237
+ errorMessage: err instanceof Error ? err.message : `${opts.providerType} execution failed`,
238
+ errorCode: null,
239
+ costUsd: null,
240
+ model,
241
+ summary: null,
242
+ sessionParams: sessionKey ? { sessionKey, gatewayUrl } : null,
243
+ sessionDisplayId: sessionKey,
244
+ clearSession: false,
245
+ billingType: null,
246
+ };
247
+ }
248
+ }
249
+
250
+ /** Session codec for HTTP-agent providers — round-trips `{ sessionKey, gatewayUrl }`. */
251
+ export const httpAgentSessionCodec: SessionCodec = {
252
+ deserialize(raw: unknown): Record<string, unknown> | null {
253
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
254
+ const obj = raw as Record<string, unknown>;
255
+ const sessionKey = str(obj["sessionKey"]) ?? str(obj["session_key"]);
256
+ if (!sessionKey) return null;
257
+ const gatewayUrl = str(obj["gatewayUrl"]);
258
+ return { sessionKey, ...(gatewayUrl ? { gatewayUrl } : {}) };
259
+ },
260
+ serialize(params: Record<string, unknown> | null): Record<string, unknown> | null {
261
+ if (!params) return null;
262
+ const sessionKey = str(params["sessionKey"]) ?? str(params["session_key"]);
263
+ if (!sessionKey) return null;
264
+ const gatewayUrl = str(params["gatewayUrl"]);
265
+ return { sessionKey, ...(gatewayUrl ? { gatewayUrl } : {}) };
266
+ },
267
+ getDisplayId(params: Record<string, unknown> | null): string | null {
268
+ if (!params) return null;
269
+ return str(params["sessionKey"]) ?? str(params["session_key"]);
270
+ },
271
+ };
272
+
273
+ /** Build a `ProviderModule` for a remote HTTP agent gateway. */
274
+ export function httpAgentProvider(opts: HttpAgentOptions): ProviderModule {
275
+ async function resolveAuth(): Promise<AuthReport> {
276
+ // The gateway is a URL, not a local binary, so we can't probe presence here.
277
+ return {
278
+ providerType: opts.providerType,
279
+ binary: { installed: true },
280
+ options: [],
281
+ source: "filesystem",
282
+ };
283
+ }
284
+ return {
285
+ type: opts.providerType,
286
+ capabilities: {
287
+ sessions: false,
288
+ modelDiscovery: false,
289
+ quotaProbing: false,
290
+ mcp: false,
291
+ skills: false,
292
+ instructions: false,
293
+ workspace: false,
294
+ planMode: false,
295
+ concurrentSend: false,
296
+ cancelQueuedMessage: false,
297
+ stopTask: false,
298
+ modes: false,
299
+ },
300
+ execute: (ctx) => runHttpAgent(opts, ctx),
301
+ resolveAuth,
302
+ sessionCodec: httpAgentSessionCodec,
303
+ };
304
+ }