@oxecli/oxe 1.0.94 → 1.0.96

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.
package/dist/api.js CHANGED
@@ -4,8 +4,7 @@ export async function validateOxeApiKey(apiKey) {
4
4
  if (!key.startsWith(OXE_KEY_PREFIX)) {
5
5
  return {
6
6
  valid: false,
7
- error: "Invalid API key format. Only authentic Oxe API keys (starting with 'oxe_live_') are permitted.\n" +
8
- "Generate your key at: http://localhost:5173/dashboard/api-keys",
7
+ error: "Invalid API key format. Generate your key at: http://localhost:5173/dashboard/api-keys",
9
8
  };
10
9
  }
11
10
  try {
package/dist/config.js CHANGED
@@ -98,7 +98,8 @@ export function detectedShell() {
98
98
  export function osPrefix() {
99
99
  return (`Running environment: ${runtimeOsSummary()}. ` +
100
100
  `Shell commands run via ${detectedShell()} ` +
101
- "with `shell=True`; use commands, quoting, and path separators " +
101
+ `with \`shell=True\`, from the working directory \`${process.cwd()}\`; ` +
102
+ "use commands, quoting, and path separators " +
102
103
  "appropriate to this OS.\n\n");
103
104
  }
104
105
  export const IGNORED_DIRS_BY_CATEGORY = [
package/dist/engine.js CHANGED
@@ -49,6 +49,10 @@ export class InferenceEngine {
49
49
  queryHasOutput = false;
50
50
  queryCalledTool = false;
51
51
  temperature;
52
+ /** True once a "Worked for …" line has been committed during the current
53
+ * query, so subsequent tool iterations don't pile up duplicate working
54
+ * blocks. The total worked time is summarized in the final footer. */
55
+ workedCommitted = false;
52
56
  storedResponseIds = [];
53
57
  constructor(config) {
54
58
  this.modelName = config["model_name"];
@@ -151,10 +155,19 @@ export class InferenceEngine {
151
155
  let sawReasoning = false;
152
156
  let status = null;
153
157
  let thinkingStart = null;
154
- dockTransientStart();
158
+ // A "Worked for …" block is committed once per working phase. Each phase is
159
+ // interrupted (and the flag reset) by either real answer text or a tool
160
+ // call, so unlimited working blocks can appear — but never two in a row
161
+ // without an answer or tool call in between. The total worked time also
162
+ // lands in the final footer.
163
+ const showWorkSpinner = !this.workedCommitted;
164
+ if (showWorkSpinner)
165
+ dockTransientStart();
155
166
  const workStatus = new Spinner();
156
167
  const workingStarted = Date.now();
157
- workStatus.startWithRender(() => `Working ${tickDuration((Date.now() - workingStarted) / 1000)}`);
168
+ if (showWorkSpinner) {
169
+ workStatus.startWithRender(() => `Working ${tickDuration((Date.now() - workingStarted) / 1000)}`);
170
+ }
158
171
  let workingReported = false;
159
172
  let response = null;
160
173
  let etype = null;
@@ -169,6 +182,12 @@ export class InferenceEngine {
169
182
  workingReported = true;
170
183
  const elapsed = (Date.now() - workingStarted) / 1000;
171
184
  const t = tickDuration(elapsed);
185
+ if (!showWorkSpinner)
186
+ return;
187
+ // Reset immediately so a later working phase (after an answer or a tool
188
+ // call) may commit a fresh block; the per-streamOnce guard above keeps
189
+ // the current phase from duplicating.
190
+ this.workedCommitted = false;
172
191
  story.push({ type: "worked", text: `Worked for ${t}` });
173
192
  dockReplaceTransient(mutedMarkdown(`Worked for ${t}`) + "\n");
174
193
  };
@@ -399,6 +418,7 @@ export class InferenceEngine {
399
418
  this.interrupted = false;
400
419
  this.queryHasOutput = false;
401
420
  this.queryCalledTool = false;
421
+ this.workedCommitted = false;
402
422
  this.activeAbort = new AbortController();
403
423
  hideCursor();
404
424
  inputItems.push({
@@ -521,7 +541,13 @@ export class InferenceEngine {
521
541
  conversation.push(rp);
522
542
  inputItems.push(rp);
523
543
  retryPrompts.push(rp);
524
- pending = prevId === null ? conversation : [retryPrompt];
544
+ // Always retry with the full transcript in visible context (which
545
+ // includes the original user prompt), instead of relying solely on
546
+ // previous_response_id continuation. Server-side continuation of a
547
+ // cut-off response can lose the task context, leaving the model with
548
+ // "no context about what was being worked on."
549
+ prevId = null;
550
+ pending = conversation;
525
551
  continue;
526
552
  }
527
553
  dropRetryPrompts(conversation, inputItems, retryPrompts);
@@ -546,6 +572,9 @@ export class InferenceEngine {
546
572
  const toolSpinner = new Spinner();
547
573
  toolSpinner.startDots("\x1b[90m╰─\x1b[0m Working");
548
574
  const rawResult = await this.runTool(c.name, c.arguments);
575
+ // A tool call was made, which interrupts any current working phase,
576
+ // so the next agent iteration may commit a fresh "Worked for …" block.
577
+ this.workedCommitted = false;
549
578
  if (this.interrupted) {
550
579
  toolSpinner.stop();
551
580
  throw new Error("interrupt");
package/dist/ui.js CHANGED
@@ -1299,6 +1299,9 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = [], hin
1299
1299
  if (done)
1300
1300
  return;
1301
1301
  done = true;
1302
+ const sentValue = isErr
1303
+ ? null
1304
+ : value;
1302
1305
  process.stdout.write("\x1b[?2004l");
1303
1306
  process.stdin.setRawMode(false);
1304
1307
  if (pasteBurstTimer)
@@ -1308,10 +1311,30 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = [], hin
1308
1311
  dockSetInactive();
1309
1312
  }
1310
1313
  else {
1314
+ // Clear the sent prompt from the box immediately, so the field shows an
1315
+ // empty prompt as soon as the user sends it while the query streams
1316
+ // above the dock. The resolved value still carries the original sent
1317
+ // buffer/paste spans so the query and draft-restore keep working.
1318
+ const { frame, totalRows } = renderBufferWithCursor("", [], prefix, label, 0, hints);
1319
+ const newLines = frame.split("\n");
1311
1320
  if (lastCursorRow > 0)
1312
1321
  process.stdout.write(`\x1b[${lastCursorRow}A`);
1313
1322
  process.stdout.write("\r");
1314
- dockSetFrame((prevFrameLines ?? []).join("\n"), lastTotalRows);
1323
+ const oldLines = prevFrameLines ?? [];
1324
+ const maxLen = Math.max(oldLines.length, newLines.length);
1325
+ for (let i = 0; i < maxLen; i++) {
1326
+ process.stdout.write("\r\x1b[K" + (newLines[i] ?? ""));
1327
+ if (i < maxLen - 1)
1328
+ process.stdout.write("\n");
1329
+ }
1330
+ // Reposition at the box's top row so the dock can manage it. The erase
1331
+ // loop above ends with the cursor at row (maxLen - 1), so move up by
1332
+ // that many rows — NOT newLines.length - 1, which is too few whenever
1333
+ // the sent (old) box was taller than the emptied box (e.g. a wrapped or
1334
+ // multi-line prompt). Using the wrong count leaves the cursor below the
1335
+ // box top, desyncing the dock and corrupting later redraws.
1336
+ process.stdout.write(`\x1b[${maxLen - 1}A\r`);
1337
+ dockSetFrame(frame, totalRows);
1315
1338
  dockEnterDocked();
1316
1339
  }
1317
1340
  process.stdin.removeListener("keypress", onKeypress);
@@ -1320,7 +1343,7 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = [], hin
1320
1343
  if (isErr)
1321
1344
  reject(value);
1322
1345
  else
1323
- resolve(value);
1346
+ resolve(sentValue);
1324
1347
  };
1325
1348
  const repaint = (isFirst = false) => {
1326
1349
  const { frame, cursorRow, cursorCol, totalRows } = renderBufferWithCursor(buffer, pasteSpans, prefix, label, cursor, hints);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxecli/oxe",
3
- "version": "1.0.94",
3
+ "version": "1.0.96",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },