hankweave 0.2.1 → 0.2.3

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.
@@ -145,7 +145,7 @@ async function runSelfTest() {
145
145
  import * as fs4 from "fs";
146
146
  import * as path4 from "path";
147
147
 
148
- // ../../node_modules/.bun/@openai+codex-sdk@0.81.0/node_modules/@openai/codex-sdk/dist/index.js
148
+ // ../../node_modules/.bun/@openai+codex-sdk@0.98.0/node_modules/@openai/codex-sdk/dist/index.js
149
149
  import { promises as fs2 } from "fs";
150
150
  import os2 from "os";
151
151
  import path2 from "path";
@@ -218,6 +218,7 @@ var Thread = class {
218
218
  modelReasoningEffort: options?.modelReasoningEffort,
219
219
  signal: turnOptions.signal,
220
220
  networkAccessEnabled: options?.networkAccessEnabled,
221
+ webSearchMode: options?.webSearchMode,
221
222
  webSearchEnabled: options?.webSearchEnabled,
222
223
  approvalPolicy: options?.approvalPolicy,
223
224
  additionalDirectories: options?.additionalDirectories
@@ -285,12 +286,19 @@ var TYPESCRIPT_SDK_ORIGINATOR = "codex_sdk_ts";
285
286
  var CodexExec = class {
286
287
  executablePath;
287
288
  envOverride;
288
- constructor(executablePath = null, env) {
289
+ configOverrides;
290
+ constructor(executablePath = null, env, configOverrides) {
289
291
  this.executablePath = executablePath || findCodexPath();
290
292
  this.envOverride = env;
293
+ this.configOverrides = configOverrides;
291
294
  }
292
295
  async *run(args) {
293
296
  const commandArgs = ["exec", "--experimental-json"];
297
+ if (this.configOverrides) {
298
+ for (const override of serializeConfigOverrides(this.configOverrides)) {
299
+ commandArgs.push("--config", override);
300
+ }
301
+ }
294
302
  if (args.model) {
295
303
  commandArgs.push("--model", args.model);
296
304
  }
@@ -320,20 +328,24 @@ var CodexExec = class {
320
328
  `sandbox_workspace_write.network_access=${args.networkAccessEnabled}`
321
329
  );
322
330
  }
323
- if (args.webSearchEnabled !== void 0) {
324
- commandArgs.push("--config", `features.web_search_request=${args.webSearchEnabled}`);
331
+ if (args.webSearchMode) {
332
+ commandArgs.push("--config", `web_search="${args.webSearchMode}"`);
333
+ } else if (args.webSearchEnabled === true) {
334
+ commandArgs.push("--config", `web_search="live"`);
335
+ } else if (args.webSearchEnabled === false) {
336
+ commandArgs.push("--config", `web_search="disabled"`);
325
337
  }
326
338
  if (args.approvalPolicy) {
327
339
  commandArgs.push("--config", `approval_policy="${args.approvalPolicy}"`);
328
340
  }
341
+ if (args.threadId) {
342
+ commandArgs.push("resume", args.threadId);
343
+ }
329
344
  if (args.images?.length) {
330
345
  for (const image of args.images) {
331
346
  commandArgs.push("--image", image);
332
347
  }
333
348
  }
334
- if (args.threadId) {
335
- commandArgs.push("resume", args.threadId);
336
- }
337
349
  const env = {};
338
350
  if (this.envOverride) {
339
351
  Object.assign(env, this.envOverride);
@@ -407,6 +419,82 @@ var CodexExec = class {
407
419
  }
408
420
  }
409
421
  };
422
+ function serializeConfigOverrides(configOverrides) {
423
+ const overrides = [];
424
+ flattenConfigOverrides(configOverrides, "", overrides);
425
+ return overrides;
426
+ }
427
+ function flattenConfigOverrides(value, prefix, overrides) {
428
+ if (!isPlainObject(value)) {
429
+ if (prefix) {
430
+ overrides.push(`${prefix}=${toTomlValue(value, prefix)}`);
431
+ return;
432
+ } else {
433
+ throw new Error("Codex config overrides must be a plain object");
434
+ }
435
+ }
436
+ const entries = Object.entries(value);
437
+ if (!prefix && entries.length === 0) {
438
+ return;
439
+ }
440
+ if (prefix && entries.length === 0) {
441
+ overrides.push(`${prefix}={}`);
442
+ return;
443
+ }
444
+ for (const [key, child] of entries) {
445
+ if (!key) {
446
+ throw new Error("Codex config override keys must be non-empty strings");
447
+ }
448
+ if (child === void 0) {
449
+ continue;
450
+ }
451
+ const path32 = prefix ? `${prefix}.${key}` : key;
452
+ if (isPlainObject(child)) {
453
+ flattenConfigOverrides(child, path32, overrides);
454
+ } else {
455
+ overrides.push(`${path32}=${toTomlValue(child, path32)}`);
456
+ }
457
+ }
458
+ }
459
+ function toTomlValue(value, path32) {
460
+ if (typeof value === "string") {
461
+ return JSON.stringify(value);
462
+ } else if (typeof value === "number") {
463
+ if (!Number.isFinite(value)) {
464
+ throw new Error(`Codex config override at ${path32} must be a finite number`);
465
+ }
466
+ return `${value}`;
467
+ } else if (typeof value === "boolean") {
468
+ return value ? "true" : "false";
469
+ } else if (Array.isArray(value)) {
470
+ const rendered = value.map((item, index) => toTomlValue(item, `${path32}[${index}]`));
471
+ return `[${rendered.join(", ")}]`;
472
+ } else if (isPlainObject(value)) {
473
+ const parts = [];
474
+ for (const [key, child] of Object.entries(value)) {
475
+ if (!key) {
476
+ throw new Error("Codex config override keys must be non-empty strings");
477
+ }
478
+ if (child === void 0) {
479
+ continue;
480
+ }
481
+ parts.push(`${formatTomlKey(key)} = ${toTomlValue(child, `${path32}.${key}`)}`);
482
+ }
483
+ return `{${parts.join(", ")}}`;
484
+ } else if (value === null) {
485
+ throw new Error(`Codex config override at ${path32} cannot be null`);
486
+ } else {
487
+ const typeName = typeof value;
488
+ throw new Error(`Unsupported Codex config override value at ${path32}: ${typeName}`);
489
+ }
490
+ }
491
+ var TOML_BARE_KEY = /^[A-Za-z0-9_-]+$/;
492
+ function formatTomlKey(key) {
493
+ return TOML_BARE_KEY.test(key) ? key : JSON.stringify(key);
494
+ }
495
+ function isPlainObject(value) {
496
+ return typeof value === "object" && value !== null && !Array.isArray(value);
497
+ }
410
498
  var scriptFileName = fileURLToPath(import.meta.url);
411
499
  var scriptDirName = path22.dirname(scriptFileName);
412
500
  function findCodexPath() {
@@ -466,7 +554,8 @@ var Codex = class {
466
554
  exec;
467
555
  options;
468
556
  constructor(options = {}) {
469
- this.exec = new CodexExec(options.codexPathOverride, options.env);
557
+ const { codexPathOverride, env, config } = options;
558
+ this.exec = new CodexExec(codexPathOverride, env, config);
470
559
  this.options = options;
471
560
  }
472
561
  /**
@@ -632,6 +721,27 @@ function getCodexModelId(spec) {
632
721
  return spec.modelID;
633
722
  }
634
723
 
724
+ // Model resolution assertions - verify reasoning effort parsing at load time
725
+ {
726
+ const testCases = [
727
+ { input: "gpt-5.3-codex-high", expectedModelID: "gpt-5.3-codex", expectedEffort: "high" },
728
+ { input: "gpt-5.3-codex-xhigh", expectedModelID: "gpt-5.3-codex", expectedEffort: "xhigh" },
729
+ { input: "gpt-5.2-codex-high", expectedModelID: "gpt-5.2-codex", expectedEffort: "high" },
730
+ { input: "gpt-5.2-xhigh", expectedModelID: "gpt-5.2", expectedEffort: "xhigh" },
731
+ { input: "gpt-5.3-codex", expectedModelID: "gpt-5.3-codex", expectedEffort: undefined },
732
+ ];
733
+ for (const tc of testCases) {
734
+ const result = resolveModel(tc.input);
735
+ if (result.modelID !== tc.expectedModelID || result.reasoningEffort !== tc.expectedEffort) {
736
+ throw new Error(
737
+ `Model resolution assertion failed for "${tc.input}": ` +
738
+ `expected modelID="${tc.expectedModelID}" effort="${tc.expectedEffort}", ` +
739
+ `got modelID="${result.modelID}" effort="${result.reasoningEffort}"`
740
+ );
741
+ }
742
+ }
743
+ }
744
+
635
745
  // src/utils/output.ts
636
746
  function emit(message) {
637
747
  console.log(JSON.stringify(message));
@@ -793,6 +903,8 @@ var CodexShim = class {
793
903
  },
794
904
  workStarted: false,
795
905
  hasReceivedDeltas: false,
906
+ lastEmittedAssistantText: "",
907
+ lastEmittedReasoningByItemId: /* @__PURE__ */ new Map(),
796
908
  emittedToolIds: /* @__PURE__ */ new Set(),
797
909
  toolIdMapping: /* @__PURE__ */ new Map()
798
910
  };
@@ -864,7 +976,7 @@ ${this.args.appendSystemPrompt}`;
864
976
  this.apiStartTime = state.apiStartTime;
865
977
  const { events } = await state.thread.runStreamed(finalPrompt);
866
978
  const currentMessageContent = [];
867
- const currentMessageId = generateMessageId();
979
+ let currentMessageId = generateMessageId();
868
980
  const pendingToolResults = /* @__PURE__ */ new Map();
869
981
  let systemInitEmitted = false;
870
982
  for await (const event of events) {
@@ -908,10 +1020,11 @@ ${this.args.appendSystemPrompt}`;
908
1020
  currentMessageContent,
909
1021
  pendingToolResults,
910
1022
  model,
1023
+ currentMessageId,
911
1024
  event.type === "item.completed"
912
1025
  );
913
1026
  break;
914
- case "turn.completed":
1027
+ case "turn.completed": {
915
1028
  if (event.usage) {
916
1029
  state.totalUsage.input_tokens += event.usage.input_tokens;
917
1030
  state.totalUsage.output_tokens += event.usage.output_tokens;
@@ -920,11 +1033,12 @@ ${this.args.appendSystemPrompt}`;
920
1033
  }
921
1034
  }
922
1035
  verboseLog(this.args.verbose, "Turn completed", event.usage);
923
- if (currentMessageContent.length > 0) {
1036
+ const completionContent = state.hasReceivedDeltas ? [] : currentMessageContent;
1037
+ if (completionContent.length > 0 || event.usage) {
924
1038
  this.emitAssistantMessage(
925
1039
  currentMessageId,
926
1040
  model,
927
- currentMessageContent,
1041
+ completionContent,
928
1042
  event.usage,
929
1043
  "end_turn"
930
1044
  );
@@ -933,7 +1047,13 @@ ${this.args.appendSystemPrompt}`;
933
1047
  this.emitToolResult(result);
934
1048
  }
935
1049
  pendingToolResults.clear();
1050
+ currentMessageContent.length = 0;
1051
+ state.hasReceivedDeltas = false;
1052
+ state.lastEmittedAssistantText = "";
1053
+ state.lastEmittedReasoningByItemId.clear();
1054
+ currentMessageId = generateMessageId();
936
1055
  break;
1056
+ }
937
1057
  case "turn.failed":
938
1058
  verboseLog(this.args.verbose, "Turn failed:", event.error);
939
1059
  if (this.args.debugDir) {
@@ -958,10 +1078,23 @@ ${this.args.appendSystemPrompt}`;
958
1078
  this.createRawLogFile(state.sessionId);
959
1079
  }
960
1080
  }
961
- handleItemEvent(item, state, currentMessageContent, pendingToolResults, _model, _isCompleted = false) {
1081
+ handleItemEvent(item, state, currentMessageContent, pendingToolResults, model, currentMessageId, _isCompleted = false) {
962
1082
  switch (item.type) {
963
1083
  case "agent_message":
964
- if (item.text && !state.hasReceivedDeltas) {
1084
+ if (item.text) {
1085
+ const previousText = state.lastEmittedAssistantText || "";
1086
+ const deltaText = item.text.startsWith(previousText) ? item.text.slice(previousText.length) : item.text;
1087
+ if (deltaText) {
1088
+ this.emitAssistantMessage(
1089
+ currentMessageId,
1090
+ model,
1091
+ [{ type: "text", text: deltaText }],
1092
+ void 0,
1093
+ null
1094
+ );
1095
+ state.hasReceivedDeltas = true;
1096
+ }
1097
+ state.lastEmittedAssistantText = item.text;
965
1098
  const existing = currentMessageContent.find((c) => c.type === "text");
966
1099
  if (existing && existing.type === "text") {
967
1100
  existing.text = item.text;
@@ -972,31 +1105,69 @@ ${this.args.appendSystemPrompt}`;
972
1105
  break;
973
1106
  case "reasoning":
974
1107
  if (item.text) {
1108
+ const reasoningItemId = item.id || "reasoning";
1109
+ const previousReasoning = state.lastEmittedReasoningByItemId.get(reasoningItemId) || "";
1110
+ const deltaThinking = item.text.startsWith(previousReasoning) ? item.text.slice(previousReasoning.length) : item.text;
1111
+ if (deltaThinking) {
1112
+ this.emitAssistantMessage(
1113
+ currentMessageId,
1114
+ model,
1115
+ [{ type: "thinking", thinking: deltaThinking }],
1116
+ void 0,
1117
+ null
1118
+ );
1119
+ state.hasReceivedDeltas = true;
1120
+ }
1121
+ state.lastEmittedReasoningByItemId.set(reasoningItemId, item.text);
975
1122
  currentMessageContent.push({ type: "thinking", thinking: item.text });
976
1123
  }
977
1124
  break;
978
1125
  case "command_execution":
979
- this.handleCommandExecution(item, state, currentMessageContent, pendingToolResults);
1126
+ this.handleCommandExecution(
1127
+ item,
1128
+ state,
1129
+ currentMessageContent,
1130
+ pendingToolResults,
1131
+ model,
1132
+ currentMessageId
1133
+ );
980
1134
  break;
981
1135
  case "mcp_tool_call":
982
- this.handleMcpToolCall(item, state, currentMessageContent, pendingToolResults);
1136
+ this.handleMcpToolCall(
1137
+ item,
1138
+ state,
1139
+ currentMessageContent,
1140
+ pendingToolResults,
1141
+ model,
1142
+ currentMessageId
1143
+ );
983
1144
  break;
984
1145
  case "file_change":
985
- this.handleFileChange(item, state, currentMessageContent, pendingToolResults);
1146
+ this.handleFileChange(
1147
+ item,
1148
+ state,
1149
+ currentMessageContent,
1150
+ pendingToolResults,
1151
+ model,
1152
+ currentMessageId
1153
+ );
986
1154
  break;
987
1155
  }
988
1156
  }
989
- handleCommandExecution(item, state, currentMessageContent, pendingToolResults) {
1157
+ handleCommandExecution(item, state, currentMessageContent, pendingToolResults, model, currentMessageId) {
990
1158
  if (!state.emittedToolIds.has(item.id)) {
991
1159
  const toolUseId2 = generateToolUseId();
992
1160
  state.emittedToolIds.add(item.id);
993
1161
  state.toolIdMapping.set(item.id, toolUseId2);
994
- currentMessageContent.push({
1162
+ const toolUse = {
995
1163
  type: "tool_use",
996
1164
  id: toolUseId2,
997
1165
  name: "Bash",
998
1166
  input: { command: item.command }
999
- });
1167
+ };
1168
+ currentMessageContent.push(toolUse);
1169
+ this.emitAssistantMessage(currentMessageId, model, [toolUse], void 0, null);
1170
+ state.hasReceivedDeltas = true;
1000
1171
  }
1001
1172
  const toolUseId = state.toolIdMapping.get(item.id);
1002
1173
  if (!toolUseId) return;
@@ -1012,18 +1183,21 @@ ${this.args.appendSystemPrompt}`;
1012
1183
  content: item.status === "failed" ? { is_error: true, error: content } : content
1013
1184
  });
1014
1185
  }
1015
- handleMcpToolCall(item, state, currentMessageContent, pendingToolResults) {
1186
+ handleMcpToolCall(item, state, currentMessageContent, pendingToolResults, model, currentMessageId) {
1016
1187
  if (!state.emittedToolIds.has(item.id)) {
1017
1188
  const toolUseId2 = generateToolUseId();
1018
1189
  state.emittedToolIds.add(item.id);
1019
1190
  state.toolIdMapping.set(item.id, toolUseId2);
1020
1191
  const toolName = normalizeToolName(item.tool);
1021
- currentMessageContent.push({
1192
+ const toolUse = {
1022
1193
  type: "tool_use",
1023
1194
  id: toolUseId2,
1024
1195
  name: toolName,
1025
1196
  input: normalizeToolInput(item.arguments || {})
1026
- });
1197
+ };
1198
+ currentMessageContent.push(toolUse);
1199
+ this.emitAssistantMessage(currentMessageId, model, [toolUse], void 0, null);
1200
+ state.hasReceivedDeltas = true;
1027
1201
  }
1028
1202
  const toolUseId = state.toolIdMapping.get(item.id);
1029
1203
  if (!toolUseId) return;
@@ -1040,19 +1214,22 @@ ${this.args.appendSystemPrompt}`;
1040
1214
  content: item.status === "failed" ? { is_error: true, error: item.error?.message || "Failed" } : content
1041
1215
  });
1042
1216
  }
1043
- handleFileChange(item, state, currentMessageContent, pendingToolResults) {
1217
+ handleFileChange(item, state, currentMessageContent, pendingToolResults, model, currentMessageId) {
1044
1218
  if (!state.emittedToolIds.has(item.id)) {
1045
1219
  const toolUseId2 = generateToolUseId();
1046
1220
  state.emittedToolIds.add(item.id);
1047
1221
  state.toolIdMapping.set(item.id, toolUseId2);
1048
1222
  const change2 = item.changes[0];
1049
1223
  const toolName = change2.kind === "add" ? "Write" : "Edit";
1050
- currentMessageContent.push({
1224
+ const toolUse = {
1051
1225
  type: "tool_use",
1052
1226
  id: toolUseId2,
1053
1227
  name: toolName,
1054
1228
  input: { file_path: change2.path }
1055
- });
1229
+ };
1230
+ currentMessageContent.push(toolUse);
1231
+ this.emitAssistantMessage(currentMessageId, model, [toolUse], void 0, null);
1232
+ state.hasReceivedDeltas = true;
1056
1233
  }
1057
1234
  const toolUseId = state.toolIdMapping.get(item.id);
1058
1235
  if (!toolUseId) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hankweave",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Orchestration runtime for antibrittle agentic workflows",
5
5
  "author": {
6
6
  "name": "Southbridge AI",
@@ -81,7 +81,7 @@
81
81
  "@ai-sdk/groq": "^2.0.11",
82
82
  "@ai-sdk/openai": "^2.0.18",
83
83
  "@anthropic-ai/claude-agent-sdk": "^0.1.70",
84
- "@openai/codex-sdk": "^0.87.0",
84
+ "@openai/codex-sdk": "^0.98.0",
85
85
  "ai": "^5.0.15",
86
86
  "crossws": "^0.4.1",
87
87
  "eta": "^3.5.0",
@@ -89,6 +89,7 @@
89
89
  "ignore": "^7.0.5",
90
90
  "lodash.merge": "^4.6.2",
91
91
  "minimatch": "^10.0.1",
92
+ "posthog-node": "^5.24.10",
92
93
  "simple-git": "^3.28.0",
93
94
  "srvx": "^0.10.0",
94
95
  "zod": "^3.25.74"
@@ -79,6 +79,26 @@
79
79
  "additionalProperties": false,
80
80
  "description": "Sentinel system configuration"
81
81
  },
82
+ "telemetry": {
83
+ "type": "object",
84
+ "properties": {
85
+ "enabled": {
86
+ "type": "boolean",
87
+ "description": "Enable or disable telemetry"
88
+ },
89
+ "endpoint": {
90
+ "type": "string",
91
+ "format": "uri",
92
+ "description": "Custom telemetry endpoint URL"
93
+ },
94
+ "debug": {
95
+ "type": "boolean",
96
+ "description": "Print payloads to console instead of sending"
97
+ }
98
+ },
99
+ "additionalProperties": false,
100
+ "description": "Telemetry configuration"
101
+ },
82
102
  "$schema": {
83
103
  "type": "string",
84
104
  "description": "JSON Schema URL for editor support"