praxis-agent 0.45.3 → 0.46.0

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.
@@ -255,13 +255,58 @@ export class AgentRuntime {
255
255
  this.options = options;
256
256
  }
257
257
  async run(request) {
258
+ const steering = request.steering;
259
+ const rejectSteering = (reason) => {
260
+ for (const item of steering?.close() ?? []) {
261
+ this.emit({
262
+ type: 'user-input-rejected',
263
+ id: item.id,
264
+ content: item.content,
265
+ reason,
266
+ });
267
+ }
268
+ };
269
+ const cancelWithSteering = () => {
270
+ rejectSteering('cancelled');
271
+ return this.cancel();
272
+ };
273
+ const deliverSteering = async (item) => {
274
+ try {
275
+ if (!request.observer?.userInputDelivered) {
276
+ throw new Error('Active-turn steering requires a durable delivery observer');
277
+ }
278
+ await request.observer.userInputDelivered(item);
279
+ }
280
+ catch (error) {
281
+ this.emit({
282
+ type: 'user-input-rejected',
283
+ id: item.id,
284
+ content: item.content,
285
+ reason: 'failed',
286
+ });
287
+ throw error;
288
+ }
289
+ messages.push({ role: 'user', content: item.content });
290
+ this.emit({
291
+ type: 'user-input-delivered',
292
+ id: item.id,
293
+ content: item.content,
294
+ });
295
+ };
296
+ const deliverNextSteering = async () => {
297
+ const item = steering?.take();
298
+ if (!item)
299
+ return false;
300
+ await deliverSteering(item);
301
+ return true;
302
+ };
258
303
  const failurePresentationDeferred = (kind) => request.deferFailureKinds === true ||
259
304
  request.deferFailureKinds?.includes(kind) === true;
260
305
  if (this.options.emitInitialContextState !== false) {
261
306
  this.emit({ type: 'state', state: 'assembling-context' });
262
307
  }
263
308
  if (request.signal?.aborted)
264
- return this.cancel();
309
+ return cancelWithSteering();
265
310
  const messages = [...request.messages];
266
311
  let usage = emptyUsage();
267
312
  let modelUsage = emptyUsage();
@@ -295,7 +340,7 @@ export class AgentRuntime {
295
340
  activeAttemptHasPresentation = false;
296
341
  activeAttemptDiscarded = false;
297
342
  if (request.signal?.aborted)
298
- return this.cancel();
343
+ return cancelWithSteering();
299
344
  if (maxModelTurns !== undefined && modelTurns >= maxModelTurns) {
300
345
  throw new Error(`Maximum model turns of ${maxModelTurns} exceeded`);
301
346
  }
@@ -399,7 +444,7 @@ export class AgentRuntime {
399
444
  try {
400
445
  for await (const event of this.provider.complete(providerRequest)) {
401
446
  if (request.signal?.aborted)
402
- return this.cancel();
447
+ return cancelWithSteering();
403
448
  if (terminalReason !== undefined) {
404
449
  throw new ModelProviderError(`Provider emitted ${event.type} after terminal reason ${terminalReason}`, { retryable: false });
405
450
  }
@@ -499,7 +544,7 @@ export class AgentRuntime {
499
544
  }
500
545
  await toolScheduler.settle().catch(() => undefined);
501
546
  if (request.signal?.aborted)
502
- return this.cancel();
547
+ return cancelWithSteering();
503
548
  throw error;
504
549
  }
505
550
  finally {
@@ -582,6 +627,11 @@ export class AgentRuntime {
582
627
  messages.push(assistantMessage);
583
628
  toolScheduler.releaseExclusiveTools();
584
629
  if (toolCalls.length === 0) {
630
+ const preStopSteering = steering?.take();
631
+ if (preStopSteering !== undefined) {
632
+ await deliverSteering(preStopSteering);
633
+ continue;
634
+ }
585
635
  const stopResult = (await request.onStop?.(text)) ?? [];
586
636
  const stopBatch = Array.isArray(stopResult)
587
637
  ? null
@@ -617,11 +667,16 @@ export class AgentRuntime {
617
667
  if (request.reloadMessages) {
618
668
  const reloadedMessages = await request.reloadMessages();
619
669
  if (request.signal?.aborted)
620
- return this.cancel();
670
+ return cancelWithSteering();
621
671
  messages.splice(0, messages.length, ...reloadedMessages);
622
672
  }
623
673
  continue;
624
674
  }
675
+ const completionInput = steering?.takeOrSeal();
676
+ if (completionInput !== undefined) {
677
+ await deliverSteering(completionInput);
678
+ continue;
679
+ }
625
680
  this.emit({ type: 'state', state: 'completed' });
626
681
  const modelUsage = modelUsageByModel.size === 0
627
682
  ? undefined
@@ -709,14 +764,18 @@ export class AgentRuntime {
709
764
  if (request.reloadMessages) {
710
765
  const reloadedMessages = await request.reloadMessages();
711
766
  if (request.signal?.aborted)
712
- return this.cancel();
767
+ return cancelWithSteering();
713
768
  messages.splice(0, messages.length, ...reloadedMessages);
714
769
  }
770
+ await deliverNextSteering();
715
771
  }
716
772
  }
717
773
  catch (error) {
718
- if (request.signal?.aborted)
774
+ if (request.signal?.aborted) {
775
+ rejectSteering('cancelled');
719
776
  return this.cancel();
777
+ }
778
+ rejectSteering('failed');
720
779
  const message = error instanceof Error ? error.message : String(error);
721
780
  const retryable = error instanceof ModelProviderError ? error.retryable : false;
722
781
  const kind = error instanceof ModelProviderError
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "praxis-agent",
3
- "version": "0.45.3",
3
+ "version": "0.46.0",
4
4
  "description": "Local-first, single-user general agent for the command line.",
5
5
  "license": "MIT",
6
6
  "author": "wuqisen",