agentfootprint 9.0.0 → 9.2.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.
- package/README.md +15 -0
- package/dist/core/Agent.js +344 -18
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/LLMCall.js +17 -0
- package/dist/core/LLMCall.js.map +1 -1
- package/dist/core/RunnerBase.js +22 -6
- package/dist/core/RunnerBase.js.map +1 -1
- package/dist/core/agent/AgentBuilder.js +20 -0
- package/dist/core/agent/AgentBuilder.js.map +1 -1
- package/dist/core/conversation.js +139 -0
- package/dist/core/conversation.js.map +1 -0
- package/dist/core/runCheckpoint.js +60 -2
- package/dist/core/runCheckpoint.js.map +1 -1
- package/dist/embedders/index.js +112 -0
- package/dist/embedders/index.js.map +1 -1
- package/dist/esm/core/Agent.d.ts +218 -3
- package/dist/esm/core/Agent.js +345 -19
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/core/LLMCall.d.ts +9 -0
- package/dist/esm/core/LLMCall.js +17 -0
- package/dist/esm/core/LLMCall.js.map +1 -1
- package/dist/esm/core/RunnerBase.d.ts +22 -6
- package/dist/esm/core/RunnerBase.js +22 -6
- package/dist/esm/core/RunnerBase.js.map +1 -1
- package/dist/esm/core/agent/AgentBuilder.d.ts +5 -0
- package/dist/esm/core/agent/AgentBuilder.js +20 -0
- package/dist/esm/core/agent/AgentBuilder.js.map +1 -1
- package/dist/esm/core/agent/types.d.ts +45 -3
- package/dist/esm/core/conversation.d.ts +96 -0
- package/dist/esm/core/conversation.js +133 -0
- package/dist/esm/core/conversation.js.map +1 -0
- package/dist/esm/core/runCheckpoint.d.ts +85 -1
- package/dist/esm/core/runCheckpoint.js +57 -1
- package/dist/esm/core/runCheckpoint.js.map +1 -1
- package/dist/esm/embedders/index.d.ts +31 -0
- package/dist/esm/embedders/index.js +112 -0
- package/dist/esm/embedders/index.js.map +1 -1
- package/dist/esm/hosting/standingAgent.d.ts +6 -2
- package/dist/esm/hosting/standingAgent.js +36 -27
- package/dist/esm/hosting/standingAgent.js.map +1 -1
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +5 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/rag/indexDocuments.d.ts +18 -0
- package/dist/esm/lib/rag/indexDocuments.js +29 -0
- package/dist/esm/lib/rag/indexDocuments.js.map +1 -1
- package/dist/esm/memory/embedding/inputCeiling.d.ts +98 -0
- package/dist/esm/memory/embedding/inputCeiling.js +53 -0
- package/dist/esm/memory/embedding/inputCeiling.js.map +1 -0
- package/dist/esm/memory/embedding/mockEmbedder.js +13 -0
- package/dist/esm/memory/embedding/mockEmbedder.js.map +1 -1
- package/dist/esm/memory/embedding/types.d.ts +32 -0
- package/dist/esm/rag/indexCorpus.d.ts +16 -2
- package/dist/esm/rag/indexCorpus.js +27 -3
- package/dist/esm/rag/indexCorpus.js.map +1 -1
- package/dist/esm/rag/splitters/index.d.ts +22 -0
- package/dist/esm/rag/splitters/index.js +22 -0
- package/dist/esm/rag/splitters/index.js.map +1 -1
- package/dist/esm/rag/types.d.ts +9 -0
- package/dist/hosting/standingAgent.js +36 -27
- package/dist/hosting/standingAgent.js.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/rag/indexDocuments.js +29 -0
- package/dist/lib/rag/indexDocuments.js.map +1 -1
- package/dist/memory/embedding/inputCeiling.js +58 -0
- package/dist/memory/embedding/inputCeiling.js.map +1 -0
- package/dist/memory/embedding/mockEmbedder.js +13 -0
- package/dist/memory/embedding/mockEmbedder.js.map +1 -1
- package/dist/rag/indexCorpus.js +27 -3
- package/dist/rag/indexCorpus.js.map +1 -1
- package/dist/rag/splitters/index.js +22 -0
- package/dist/rag/splitters/index.js.map +1 -1
- package/dist/types/core/Agent.d.ts +218 -3
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/core/LLMCall.d.ts +9 -0
- package/dist/types/core/LLMCall.d.ts.map +1 -1
- package/dist/types/core/RunnerBase.d.ts +22 -6
- package/dist/types/core/RunnerBase.d.ts.map +1 -1
- package/dist/types/core/agent/AgentBuilder.d.ts +5 -0
- package/dist/types/core/agent/AgentBuilder.d.ts.map +1 -1
- package/dist/types/core/agent/types.d.ts +45 -3
- package/dist/types/core/agent/types.d.ts.map +1 -1
- package/dist/types/core/conversation.d.ts +97 -0
- package/dist/types/core/conversation.d.ts.map +1 -0
- package/dist/types/core/runCheckpoint.d.ts +85 -1
- package/dist/types/core/runCheckpoint.d.ts.map +1 -1
- package/dist/types/embedders/index.d.ts +31 -0
- package/dist/types/embedders/index.d.ts.map +1 -1
- package/dist/types/hosting/standingAgent.d.ts +6 -2
- package/dist/types/hosting/standingAgent.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lib/rag/indexDocuments.d.ts +18 -0
- package/dist/types/lib/rag/indexDocuments.d.ts.map +1 -1
- package/dist/types/memory/embedding/inputCeiling.d.ts +99 -0
- package/dist/types/memory/embedding/inputCeiling.d.ts.map +1 -0
- package/dist/types/memory/embedding/mockEmbedder.d.ts.map +1 -1
- package/dist/types/memory/embedding/types.d.ts +32 -0
- package/dist/types/memory/embedding/types.d.ts.map +1 -1
- package/dist/types/rag/indexCorpus.d.ts +16 -2
- package/dist/types/rag/indexCorpus.d.ts.map +1 -1
- package/dist/types/rag/splitters/index.d.ts +22 -0
- package/dist/types/rag/splitters/index.d.ts.map +1 -1
- package/dist/types/rag/types.d.ts +9 -0
- package/dist/types/rag/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/esm/core/Agent.js
CHANGED
|
@@ -56,7 +56,8 @@ import { buildReadSkillTool } from '../lib/injection-engine/skillTools.js';
|
|
|
56
56
|
import { buildInjectionEngineSubflow } from '../lib/injection-engine/buildInjectionEngineSubflow.js';
|
|
57
57
|
import { makePickEntryStage } from './agent/stages/pickEntry.js';
|
|
58
58
|
import { applyOutputFallback } from './outputFallback.js';
|
|
59
|
-
import { buildCheckpoint, classifyFailurePhase, RunCheckpointError, validateCheckpoint, } from './runCheckpoint.js';
|
|
59
|
+
import { assertContinuable, buildCheckpoint, classifyFailurePhase, RunCheckpointError, validateCheckpoint, } from './runCheckpoint.js';
|
|
60
|
+
import { NoConversationError, PendingQuestionError, RunInFlightError } from './conversation.js';
|
|
60
61
|
import { applyOutputSchema, OutputSchemaError } from './outputSchema.js';
|
|
61
62
|
import { normalizeRunInput } from './runInput.js';
|
|
62
63
|
import { buildOutputRetryStage } from './agent/stages/outputRetry.js';
|
|
@@ -280,6 +281,28 @@ export class Agent extends RunnerBase {
|
|
|
280
281
|
* kept here rather than read back from the recording. Undefined after a run
|
|
281
282
|
* that failed or paused. */
|
|
282
283
|
lastRunAnswer;
|
|
284
|
+
/** The id the CONSUMER chose, or undefined when they took the default.
|
|
285
|
+
* `this.id` cannot answer that question — it is `'agent'` either way — and
|
|
286
|
+
* the stored-conversation fingerprint refuses only on ids somebody picked
|
|
287
|
+
* (see `AgentRunCheckpoint.agent`). */
|
|
288
|
+
explicitId;
|
|
289
|
+
/** The identity the caller gave the last run, or undefined when they gave
|
|
290
|
+
* none. Only an EXPLICIT identity is carried onto `checkpoint()`: the
|
|
291
|
+
* default is derived from a runId, and storing that would pin a whole
|
|
292
|
+
* conversation to the id of the one run that started it. */
|
|
293
|
+
lastRunIdentity;
|
|
294
|
+
/** The run in flight, by id — the whole of the one-turn-at-a-time guard.
|
|
295
|
+
* Set before the executor is built and cleared in `finally`, so a run that
|
|
296
|
+
* throws does not leave the agent permanently refusing. */
|
|
297
|
+
inFlightRunId;
|
|
298
|
+
/** The question a person still owes this agent an answer to. Set when a run
|
|
299
|
+
* ends paused, cleared by `resume()`, `abandonPause()`, or a run that
|
|
300
|
+
* completes. Read by the `run()` guard — see `PendingQuestionError`. */
|
|
301
|
+
pendingQuestion;
|
|
302
|
+
/** The `.selfExplain()` binding, when the builder mounted one. Held so
|
|
303
|
+
* `canExplain()` can answer the same question the trace tools answer, from
|
|
304
|
+
* the same fact. Undefined on every agent that never called `.selfExplain()`. */
|
|
305
|
+
selfExplainBinding;
|
|
283
306
|
/**
|
|
284
307
|
* Optional `ToolProvider` set via the builder's `.toolProvider()`.
|
|
285
308
|
* When present, the Tools slot subflow consults it per iteration
|
|
@@ -330,6 +353,8 @@ export class Agent extends RunnerBase {
|
|
|
330
353
|
this.provider = opts.provider;
|
|
331
354
|
this.name = opts.name ?? 'Agent';
|
|
332
355
|
this.id = opts.id ?? 'agent';
|
|
356
|
+
if (opts.id !== undefined)
|
|
357
|
+
this.explicitId = opts.id;
|
|
333
358
|
this.model = opts.model;
|
|
334
359
|
this.temperature = opts.temperature;
|
|
335
360
|
this.maxTokens = opts.maxTokens;
|
|
@@ -505,9 +530,17 @@ export class Agent extends RunnerBase {
|
|
|
505
530
|
* prop) so consumers can scrub the execution timeline post-run without
|
|
506
531
|
* threading a recorder through the call site.
|
|
507
532
|
*
|
|
508
|
-
*
|
|
509
|
-
* snapshot
|
|
510
|
-
*
|
|
533
|
+
* `undefined` until a run has STARTED. After that it is the most recent
|
|
534
|
+
* run's snapshot — including across multiple turns of the same instance.
|
|
535
|
+
*
|
|
536
|
+
* **It is LIVE during a run, not a completed-runs-only view.** The executor
|
|
537
|
+
* is assigned at run start, so calling this from an event listener, a tool,
|
|
538
|
+
* or any other mid-run vantage point returns the IN-FLIGHT run, partially
|
|
539
|
+
* filled. That is deliberate (Lens scrubs a running agent through it), and
|
|
540
|
+
* it is why `.selfExplain()` captures at the terminal flush instead of
|
|
541
|
+
* resolving through this: evidence that is supposed to describe a FINISHED
|
|
542
|
+
* turn cannot be read from a getter that also answers about an unfinished
|
|
543
|
+
* one.
|
|
511
544
|
*/
|
|
512
545
|
getLastSnapshot() {
|
|
513
546
|
return this.lastExecutor?.getSnapshot();
|
|
@@ -653,14 +686,73 @@ export class Agent extends RunnerBase {
|
|
|
653
686
|
}
|
|
654
687
|
return this.parseOutputAsync(out);
|
|
655
688
|
}
|
|
689
|
+
/**
|
|
690
|
+
* Answer one turn.
|
|
691
|
+
*
|
|
692
|
+
* **`run()` is ONE turn, and it starts a new conversation every time.** The
|
|
693
|
+
* chart seeds its history from this call's `message` alone, so a second
|
|
694
|
+
* `run()` on the same agent does not continue the first: the model is shown
|
|
695
|
+
* one user message and will honestly tell your user it has not spoken to
|
|
696
|
+
* them before. That is deliberate — a primitive that quietly accumulated
|
|
697
|
+
* state across calls could never be used for one-shot work, and a hidden
|
|
698
|
+
* transcript is the most expensive thing an agent can carry.
|
|
699
|
+
*
|
|
700
|
+
* To continue a conversation, name it:
|
|
701
|
+
*
|
|
702
|
+
* - `agent.followUp(message)` — continue THIS agent's own last completed
|
|
703
|
+
* run. The one-liner, and what most callers want.
|
|
704
|
+
* - `run({ message, continueFrom })` — continue a conversation you are
|
|
705
|
+
* holding: `agent.checkpoint()` from an earlier turn, persisted anywhere
|
|
706
|
+
* and handed back. Works across a restart, a deploy, or a different
|
|
707
|
+
* machine, and is what `standingAgent` uses per session.
|
|
708
|
+
*
|
|
709
|
+
* Passing the same `identity.conversationId` to two `run()` calls does NOT
|
|
710
|
+
* continue anything — see {@link AgentInput.identity}. What a registered
|
|
711
|
+
* memory adds is *recall* of prior turns into the system-prompt slot, which
|
|
712
|
+
* is a different thing from the conversation itself.
|
|
713
|
+
*
|
|
714
|
+
* Two refusals guard the per-instance state this agent keeps; both replace
|
|
715
|
+
* behavior that used to succeed while quietly being wrong (9.2.0):
|
|
716
|
+
* {@link RunInFlightError} when a run is already in flight, and
|
|
717
|
+
* {@link PendingQuestionError} when the last run paused to ask a person
|
|
718
|
+
* something that nobody has answered.
|
|
719
|
+
*
|
|
720
|
+
* @example One turn, then a follow-up
|
|
721
|
+
* ```ts
|
|
722
|
+
* await agent.run({ message: 'Book me a table for two.' });
|
|
723
|
+
* await agent.followUp('Make it three.'); // remembers the table
|
|
724
|
+
* ```
|
|
725
|
+
*/
|
|
656
726
|
async run(input, options) {
|
|
657
727
|
// Normalize or refuse BEFORE anything is created. A bare string is the
|
|
658
728
|
// message; anything that is not a message is named and refused here
|
|
659
729
|
// rather than becoming `content: undefined` inside the messages slot.
|
|
660
730
|
const runInput = normalizeRunInput(input, 'Agent.run');
|
|
731
|
+
// Timing next, and before the executor exists: both of these refuse a call
|
|
732
|
+
// that would have SUCCEEDED into corrupted per-instance state or an
|
|
733
|
+
// orphaned human question. See ./conversation.ts for why they are throws.
|
|
734
|
+
this.assertNotRunning('Agent.run');
|
|
735
|
+
this.assertNoPendingQuestion('Agent.run');
|
|
736
|
+
// A conversation handed in continues through the same side channel
|
|
737
|
+
// `resumeOnError` uses — one restoration path, so the two doors cannot
|
|
738
|
+
// drift about what "continue" means. This turn's message IS appended:
|
|
739
|
+
// continuing a conversation adds a turn to it.
|
|
740
|
+
let continued;
|
|
741
|
+
if (runInput.continueFrom !== undefined) {
|
|
742
|
+
continued = validateCheckpoint(runInput.continueFrom);
|
|
743
|
+
this.applyContinuation(continued, 'Agent.run({ continueFrom })', runInput.message);
|
|
744
|
+
}
|
|
745
|
+
// Only an EXPLICIT identity is remembered for `checkpoint()`; see the
|
|
746
|
+
// field's note. `input.identity` wins over `options.identity` because the
|
|
747
|
+
// input bag is where a caller looks first, and both win over the stored
|
|
748
|
+
// conversation's — but the conversation's is used when neither was given,
|
|
749
|
+
// so a continued turn stays in the namespace it started in.
|
|
750
|
+
this.lastRunIdentity =
|
|
751
|
+
runInput.identity ?? options?.identity ?? (continued ? continued.identity : undefined);
|
|
661
752
|
// (helper used in the catch block below — module-private function
|
|
662
753
|
// declared at file end via hoisting)
|
|
663
754
|
const executor = this.createExecutor(options);
|
|
755
|
+
this.inFlightRunId = this.currentRunContext.runId;
|
|
664
756
|
// Auto-checkpoint at iteration boundaries — captures the latest
|
|
665
757
|
// conversation history into a per-run tracker. On error, we
|
|
666
758
|
// wrap the underlying error in `RunCheckpointError` carrying
|
|
@@ -684,7 +776,7 @@ export class Agent extends RunnerBase {
|
|
|
684
776
|
const result = await executor.run({
|
|
685
777
|
input: {
|
|
686
778
|
message: runInput.message,
|
|
687
|
-
...(
|
|
779
|
+
...(this.lastRunIdentity !== undefined && { identity: this.lastRunIdentity }),
|
|
688
780
|
},
|
|
689
781
|
// Co-engineered boundary (#16): the engine's loop-iteration limit
|
|
690
782
|
// (footprintjs 9 default 1000) must never fire BELOW the agent's own
|
|
@@ -696,6 +788,7 @@ export class Agent extends RunnerBase {
|
|
|
696
788
|
const finalized = this.finalizeResult(executor, result);
|
|
697
789
|
if (typeof finalized === 'string')
|
|
698
790
|
this.lastRunAnswer = finalized;
|
|
791
|
+
this.recordPendingQuestion(finalized);
|
|
699
792
|
return finalized;
|
|
700
793
|
}
|
|
701
794
|
catch (cause) {
|
|
@@ -739,14 +832,109 @@ export class Agent extends RunnerBase {
|
|
|
739
832
|
// are committed state. A crash checkpoint that carried the summary
|
|
740
833
|
// in its history but not the span behind it would resume into a
|
|
741
834
|
// conversation whose evidence the crash had quietly eaten.
|
|
742
|
-
this.foldedSpansOf(this.getLastSnapshot()?.sharedState)
|
|
835
|
+
this.foldedSpansOf(this.getLastSnapshot()?.sharedState),
|
|
836
|
+
// A crash checkpoint is the same conversation carrier as
|
|
837
|
+
// `checkpoint()`, so it carries the same two owner facts — otherwise
|
|
838
|
+
// resuming after a crash would be the one path that still lost the
|
|
839
|
+
// identity, and the memory written after the recovery would land
|
|
840
|
+
// where nothing could read it.
|
|
841
|
+
this.conversationOwner());
|
|
743
842
|
throw new RunCheckpointError(cause, checkpoint);
|
|
744
843
|
}
|
|
745
844
|
throw cause;
|
|
746
845
|
}
|
|
747
846
|
finally {
|
|
748
847
|
stopTracking();
|
|
848
|
+
this.inFlightRunId = undefined;
|
|
849
|
+
// `seed` consumes the restored conversation on its way past. A run that
|
|
850
|
+
// died BEFORE seed never did, and a history left armed here would be
|
|
851
|
+
// picked up by the next run — which would then continue a conversation
|
|
852
|
+
// nobody asked it to. One run, one continuation.
|
|
853
|
+
this.pendingResumeHistory = undefined;
|
|
854
|
+
this.pendingResumeFolded = undefined;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Continue this agent's own last completed conversation.
|
|
859
|
+
*
|
|
860
|
+
* The one-liner for turn two and after. `run()` is one turn and starts a new
|
|
861
|
+
* conversation each time (see {@link Agent.run}); this reads the
|
|
862
|
+
* conversation off the last completed run, appends `message` as the next
|
|
863
|
+
* user turn, and runs from there — so the model sees what was actually said.
|
|
864
|
+
*
|
|
865
|
+
* Sugar over `run({ message, continueFrom: this.checkpoint() })` and nothing
|
|
866
|
+
* more: one restoration path, so the convenience cannot drift from the
|
|
867
|
+
* mechanism. Reach for `run({ continueFrom })` directly when the
|
|
868
|
+
* conversation comes from somewhere other than this instance's last run — a
|
|
869
|
+
* store, another process, a different machine.
|
|
870
|
+
*
|
|
871
|
+
* Refuses rather than guessing: {@link NoConversationError} when this agent
|
|
872
|
+
* has no completed run to continue (a "follow-up" that quietly became a
|
|
873
|
+
* first turn would be exactly the confusion this door exists to remove),
|
|
874
|
+
* and — through `run()` — {@link PendingQuestionError} when the last run
|
|
875
|
+
* paused to ask a person something, because a pause has its own door:
|
|
876
|
+
* `resume(checkpoint, decision)`.
|
|
877
|
+
*
|
|
878
|
+
* The conversation grows every turn and nothing here trims it; bounding what
|
|
879
|
+
* the model is shown is `.window()` / `.compaction()` / `.memory()`, not a
|
|
880
|
+
* silent cap on the way through.
|
|
881
|
+
*
|
|
882
|
+
* @example
|
|
883
|
+
* ```ts
|
|
884
|
+
* await agent.run({ message: 'Book me a table for two.' });
|
|
885
|
+
* await agent.followUp('Make it three.');
|
|
886
|
+
* await agent.followUp('And move it to 8pm.');
|
|
887
|
+
* ```
|
|
888
|
+
*/
|
|
889
|
+
async followUp(message, options) {
|
|
890
|
+
// Refuse BEFORE the timing guards, so "there is nothing to follow up on"
|
|
891
|
+
// is never reported as "a run is in flight" for an agent that has simply
|
|
892
|
+
// not run yet.
|
|
893
|
+
if (this.getLastSnapshot() === undefined) {
|
|
894
|
+
throw new NoConversationError('Agent.followUp', 'never-run');
|
|
895
|
+
}
|
|
896
|
+
const conversation = this.checkpoint();
|
|
897
|
+
if (conversation === undefined || conversation.history.length === 0) {
|
|
898
|
+
throw new NoConversationError('Agent.followUp', 'last-run-unfinished');
|
|
749
899
|
}
|
|
900
|
+
return this.run({ message, continueFrom: conversation }, options);
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Drop the question this agent's last run paused to ask, on the record.
|
|
904
|
+
*
|
|
905
|
+
* A paused run is waiting on a person. Sending a different message while one
|
|
906
|
+
* is outstanding is refused ({@link PendingQuestionError}) because silently
|
|
907
|
+
* discarding a pending question makes a consent gate something any later
|
|
908
|
+
* message can walk around. When the question really is being dropped —
|
|
909
|
+
* the user changed the subject, the session timed out, the approval is no
|
|
910
|
+
* longer wanted — say so with this, and the next `run()` proceeds.
|
|
911
|
+
*
|
|
912
|
+
* Returns what was dropped (`undefined` when nothing was pending), so a
|
|
913
|
+
* caller can log or audit the abandonment rather than perform it blind. It
|
|
914
|
+
* does not touch the paused run's checkpoint: if you still hold that, it
|
|
915
|
+
* remains resumable.
|
|
916
|
+
*/
|
|
917
|
+
abandonPause() {
|
|
918
|
+
const dropped = this.pendingQuestion;
|
|
919
|
+
this.pendingQuestion = undefined;
|
|
920
|
+
return dropped;
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Whether {@link Agent.selfExplain}'s why-questions have a run to answer
|
|
924
|
+
* from right now.
|
|
925
|
+
*
|
|
926
|
+
* `false` for two different reasons, both honest: this agent was not built
|
|
927
|
+
* with `.selfExplain()`, or it was and no turn has completed yet (evidence
|
|
928
|
+
* binds at the END of a run, never to the one in flight). Either way there
|
|
929
|
+
* is nothing to explain, which is what a caller routing a why-question needs
|
|
930
|
+
* to know before it routes.
|
|
931
|
+
*
|
|
932
|
+
* The model is told the same thing by the same fact — the trace tools answer
|
|
933
|
+
* "No completed run is available yet" and the skill body says to say so
|
|
934
|
+
* plainly. This is that answer, for the program.
|
|
935
|
+
*/
|
|
936
|
+
canExplain() {
|
|
937
|
+
return this.selfExplainBinding?.artifacts !== undefined;
|
|
750
938
|
}
|
|
751
939
|
/**
|
|
752
940
|
* Resume an agent run from a checkpoint produced by a prior
|
|
@@ -794,14 +982,36 @@ export class Agent extends RunnerBase {
|
|
|
794
982
|
*/
|
|
795
983
|
async resumeOnError(checkpoint, options) {
|
|
796
984
|
const cp = validateCheckpoint(checkpoint);
|
|
797
|
-
//
|
|
798
|
-
//
|
|
799
|
-
|
|
800
|
-
//
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
985
|
+
// The timing guards run HERE, not only inside `run()`, because the line
|
|
986
|
+
// below writes the side channel: a refusal after that write would leave a
|
|
987
|
+
// restored history armed and the NEXT run would silently continue somebody
|
|
988
|
+
// else's conversation.
|
|
989
|
+
this.assertNotRunning('Agent.resumeOnError');
|
|
990
|
+
this.assertNoPendingQuestion('Agent.resumeOnError');
|
|
991
|
+
// Stash the checkpointed history on the side channel; the seed function
|
|
992
|
+
// reads + clears it before scope.history initializes. No message is
|
|
993
|
+
// appended — the failing run's message is already the last user turn in
|
|
994
|
+
// that history, and adding it again would ask twice.
|
|
995
|
+
this.applyContinuation(cp, 'Agent.resumeOnError');
|
|
996
|
+
return this.run({
|
|
997
|
+
message: cp.originalInput.message,
|
|
998
|
+
// The conversation's own identity, unless this call named one. Until
|
|
999
|
+
// 9.2.0 there was no way to pass either, so a recovered run silently
|
|
1000
|
+
// re-namespaced its memory under a fresh runId and wrote turn two
|
|
1001
|
+
// where turn three could not read it.
|
|
1002
|
+
...(this.identityFor(options, cp) !== undefined && {
|
|
1003
|
+
identity: this.identityFor(options, cp),
|
|
1004
|
+
}),
|
|
1005
|
+
}, options);
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* Which identity a continued turn runs under: the caller's if they named
|
|
1009
|
+
* one, otherwise the conversation's own.
|
|
1010
|
+
*
|
|
1011
|
+
* @internal
|
|
1012
|
+
*/
|
|
1013
|
+
identityFor(options, cp) {
|
|
1014
|
+
return options?.identity ?? cp.identity;
|
|
805
1015
|
}
|
|
806
1016
|
/**
|
|
807
1017
|
* Install a per-run checkpoint tracker. Listens for the agent's
|
|
@@ -868,20 +1078,37 @@ export class Agent extends RunnerBase {
|
|
|
868
1078
|
const gate = pauseDemandsDecision(checkpoint.pauseData);
|
|
869
1079
|
if (gate && !isCheckInDecision(input))
|
|
870
1080
|
throw new DecisionRequiredError(gate, input);
|
|
1081
|
+
// The same one-turn-at-a-time guard `run()` carries: a resume writes the
|
|
1082
|
+
// same per-instance state a run does. Answering the question is what this
|
|
1083
|
+
// door is FOR, so it never checks `pendingQuestion` — it clears it.
|
|
1084
|
+
this.assertNotRunning('Agent.resume');
|
|
1085
|
+
// Settled the moment the answer is handed over, not when the resumed run
|
|
1086
|
+
// finishes: a resume that then FAILS must not leave the agent refusing
|
|
1087
|
+
// every later message on behalf of a question that has been answered.
|
|
1088
|
+
this.pendingQuestion = undefined;
|
|
871
1089
|
this.emitPauseResume(checkpoint, input);
|
|
872
1090
|
// Fresh executor — footprintjs 4.17.0+ seeds the runtime from
|
|
873
1091
|
// `checkpoint.sharedState` (and nested subflow states) automatically
|
|
874
1092
|
// on a fresh executor's `resume()`. No need to retain a paused
|
|
875
1093
|
// executor between run/resume.
|
|
876
1094
|
const executor = this.createExecutor(options);
|
|
1095
|
+
this.inFlightRunId = this.currentRunContext.runId;
|
|
877
1096
|
this.lastRunAnswer = undefined;
|
|
878
1097
|
// One run can never raise on another run's consent block.
|
|
879
1098
|
this.consentOutstanding.clear();
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1099
|
+
try {
|
|
1100
|
+
const result = await executor.resume(checkpoint, input, options);
|
|
1101
|
+
const finalized = this.finalizeResult(executor, result);
|
|
1102
|
+
if (typeof finalized === 'string')
|
|
1103
|
+
this.lastRunAnswer = finalized;
|
|
1104
|
+
// The question this resume answered is settled; a resume that paused
|
|
1105
|
+
// AGAIN has asked a new one, and that one is outstanding from here.
|
|
1106
|
+
this.recordPendingQuestion(finalized);
|
|
1107
|
+
return finalized;
|
|
1108
|
+
}
|
|
1109
|
+
finally {
|
|
1110
|
+
this.inFlightRunId = undefined;
|
|
1111
|
+
}
|
|
885
1112
|
}
|
|
886
1113
|
/**
|
|
887
1114
|
* The conversation this agent's LAST completed run leaves behind, packed as
|
|
@@ -936,6 +1163,7 @@ export class Agent extends RunnerBase {
|
|
|
936
1163
|
history.push({ role: 'assistant', content: this.lastRunAnswer });
|
|
937
1164
|
}
|
|
938
1165
|
const folded = this.foldedSpansOf(state);
|
|
1166
|
+
const owner = this.conversationOwner();
|
|
939
1167
|
return {
|
|
940
1168
|
version: 1,
|
|
941
1169
|
runId: this.currentRunContext.runId,
|
|
@@ -947,6 +1175,9 @@ export class Agent extends RunnerBase {
|
|
|
947
1175
|
// usually empty reads like "no folds were retained", which is a
|
|
948
1176
|
// different claim from "there were no folds".
|
|
949
1177
|
...(folded !== undefined && { folded }),
|
|
1178
|
+
// Who it was for and who ran it (9.2.0) — both absent unless chosen.
|
|
1179
|
+
...(owner.identity !== undefined && { identity: owner.identity }),
|
|
1180
|
+
...(owner.agentId !== undefined && { agent: { id: owner.agentId } }),
|
|
950
1181
|
};
|
|
951
1182
|
}
|
|
952
1183
|
/**
|
|
@@ -966,6 +1197,101 @@ export class Agent extends RunnerBase {
|
|
|
966
1197
|
return undefined;
|
|
967
1198
|
return structuredClone(spans);
|
|
968
1199
|
}
|
|
1200
|
+
/**
|
|
1201
|
+
* The two owner facts every conversation carrier stamps — who the run was
|
|
1202
|
+
* for, and which agent ran it (9.2.0).
|
|
1203
|
+
*
|
|
1204
|
+
* One reader for `checkpoint()` and the crash checkpoint, the same rule
|
|
1205
|
+
* `foldedSpansOf` follows: a fact kept on one carrier and lost on the other
|
|
1206
|
+
* is worse than a fact kept on neither. Both are absent unless the caller
|
|
1207
|
+
* chose them, which is what keeps the fingerprint refusal narrow and the
|
|
1208
|
+
* default `conversationId` out of storage.
|
|
1209
|
+
*
|
|
1210
|
+
* @internal
|
|
1211
|
+
*/
|
|
1212
|
+
conversationOwner() {
|
|
1213
|
+
return {
|
|
1214
|
+
...(this.lastRunIdentity !== undefined && { identity: this.lastRunIdentity }),
|
|
1215
|
+
...(this.explicitId !== undefined && { agentId: this.explicitId }),
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Restore a stored conversation onto the side channel `seed` reads.
|
|
1220
|
+
*
|
|
1221
|
+
* THE one restoration path — `run({ continueFrom })` and `resumeOnError()`
|
|
1222
|
+
* both come through here, so the conversation door and the error door cannot
|
|
1223
|
+
* disagree about what continuing means. It checks the agent fingerprint,
|
|
1224
|
+
* restores history + folded spans, and adopts the conversation's identity so
|
|
1225
|
+
* the continued turn writes its memory where the earlier turns are.
|
|
1226
|
+
*
|
|
1227
|
+
* `appendMessage` is the difference between the two callers, and it is the
|
|
1228
|
+
* whole difference. Continuing a conversation ADDS this turn's user message
|
|
1229
|
+
* to the stored history; resuming after an error does NOT, because there the
|
|
1230
|
+
* message is already the last user turn in that history and appending it
|
|
1231
|
+
* would ask the same question twice.
|
|
1232
|
+
*
|
|
1233
|
+
* @internal
|
|
1234
|
+
*/
|
|
1235
|
+
applyContinuation(cp, door, appendMessage) {
|
|
1236
|
+
assertContinuable(cp, this.explicitId, door);
|
|
1237
|
+
const history = cp.history;
|
|
1238
|
+
this.pendingResumeHistory =
|
|
1239
|
+
appendMessage === undefined
|
|
1240
|
+
? history
|
|
1241
|
+
: [...history, { role: 'user', content: appendMessage }];
|
|
1242
|
+
// The folded spans beside it. A conversation stored before 8.2 has none,
|
|
1243
|
+
// and `undefined` is the right answer there — it means "this conversation
|
|
1244
|
+
// recorded no folds", which is exactly true.
|
|
1245
|
+
this.pendingResumeFolded = cp.folded;
|
|
1246
|
+
}
|
|
1247
|
+
/** One turn at a time — see `RunInFlightError`. @internal */
|
|
1248
|
+
assertNotRunning(door) {
|
|
1249
|
+
if (this.inFlightRunId !== undefined) {
|
|
1250
|
+
throw new RunInFlightError(door, this.id, this.inFlightRunId);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
/** A person's unanswered question outranks a new message — see
|
|
1254
|
+
* `PendingQuestionError`. @internal */
|
|
1255
|
+
assertNoPendingQuestion(door) {
|
|
1256
|
+
if (this.pendingQuestion !== undefined) {
|
|
1257
|
+
throw new PendingQuestionError(door, this.pendingQuestion);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Remember (or forget) the question this run ended on.
|
|
1262
|
+
*
|
|
1263
|
+
* A paused outcome sets it; anything else clears it, because a run that
|
|
1264
|
+
* reached an answer has no outstanding question by definition. Reads the
|
|
1265
|
+
* same `pauseData` fields `standingAgent.describePause` reads — the tool
|
|
1266
|
+
* name and question the dispatch loop stamped — and invents nothing.
|
|
1267
|
+
*
|
|
1268
|
+
* @internal
|
|
1269
|
+
*/
|
|
1270
|
+
recordPendingQuestion(outcome) {
|
|
1271
|
+
if (typeof outcome === 'string') {
|
|
1272
|
+
this.pendingQuestion = undefined;
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
const data = outcome.pauseData;
|
|
1276
|
+
this.pendingQuestion = {
|
|
1277
|
+
...(typeof data?.toolName === 'string' && { toolName: data.toolName }),
|
|
1278
|
+
...(typeof data?.toolCallId === 'string' && { toolCallId: data.toolCallId }),
|
|
1279
|
+
...(typeof data?.question === 'string' && { question: data.question }),
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
/**
|
|
1283
|
+
* Hand the `.selfExplain()` binding to the agent that owns it.
|
|
1284
|
+
*
|
|
1285
|
+
* Called once by `AgentBuilder.build()`, immediately after `bindTo`. The
|
|
1286
|
+
* binding stays the tool provider's to read; the Agent holds it only so
|
|
1287
|
+
* `canExplain()` answers from the same fact the trace tools answer from,
|
|
1288
|
+
* rather than from a second guess about whether a run has completed.
|
|
1289
|
+
*
|
|
1290
|
+
* @internal
|
|
1291
|
+
*/
|
|
1292
|
+
bindSelfExplain(binding) {
|
|
1293
|
+
this.selfExplainBinding = binding;
|
|
1294
|
+
}
|
|
969
1295
|
/**
|
|
970
1296
|
* Refuse, at run start, any declared messages-slot role this provider
|
|
971
1297
|
* cannot carry inside its message list (7.21, D2).
|