@ziggs-ai/agent-sdk 0.10.8 → 0.11.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/CHANGELOG.md +11 -11
- package/dist/AgentHost.d.ts +15 -15
- package/dist/AgentHost.js +22 -22
- package/dist/adapters/AnthropicAdapter.js +1 -1
- package/dist/adapters/McpAdapter.d.ts +10 -10
- package/dist/adapters/McpAdapter.js +33 -21
- package/dist/adapters/OpenAIAdapter.js +1 -1
- package/dist/agent/Agent.d.ts +2 -2
- package/dist/agent/ClaudeSdkAgent.d.ts +6 -6
- package/dist/agent/ClaudeSdkAgent.js +25 -25
- package/dist/agent/claudeCognition.d.ts +3 -3
- package/dist/agent/claudeCognition.js +7 -7
- package/dist/agent/claudeSdkTools.d.ts +3 -3
- package/dist/agent/claudeSdkTools.js +9 -9
- package/dist/agent/hostedAgentConnection.d.ts +1 -1
- package/dist/agent/hostedAgentConnection.js +1 -1
- package/dist/agent/toolManagerToSdkMcp.d.ts +1 -1
- package/dist/agent/toolManagerToSdkMcp.js +2 -2
- package/dist/context/applyEffects.js +1 -1
- package/dist/defineAgent.js +2 -2
- package/dist/formatters/HistoryFormatter.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -5
- package/dist/ingress/normalizeIncoming.d.ts +1 -1
- package/dist/ingress/normalizeIncoming.js +7 -7
- package/dist/pricing/fleetDefaults.d.ts +3 -3
- package/dist/pricing/fleetDefaults.js +3 -3
- package/dist/pricing/fleetEvalFree.d.ts +1 -1
- package/dist/pricing/fleetEvalFree.js +1 -1
- package/dist/pricing/fleetFreeTierA.gen.d.ts +1 -1
- package/dist/pricing/fleetFreeTierA.gen.js +1 -1
- package/dist/pricing/fleetTierByAgentId.gen.d.ts +1 -1
- package/dist/runtime/AgentMachine.d.ts +1 -1
- package/dist/runtime/AgentMachine.js +8 -8
- package/dist/runtime/PromptBuilder.d.ts +2 -2
- package/dist/runtime/PromptBuilder.js +3 -3
- package/dist/runtime/bootstrapCtx.d.ts +1 -1
- package/dist/runtime/bootstrapCtx.js +2 -2
- package/dist/runtime/buildOutcome.d.ts +1 -1
- package/dist/runtime/buildOutcome.js +4 -4
- package/dist/runtime/defaults.d.ts +5 -5
- package/dist/runtime/defaults.js +4 -4
- package/dist/runtime/deliveryWriteKeys.d.ts +1 -1
- package/dist/runtime/deliveryWriteKeys.js +3 -3
- package/dist/runtime/kickoff.d.ts +3 -3
- package/dist/runtime/kickoff.js +3 -3
- package/dist/runtime/runMechanical.d.ts +1 -1
- package/dist/runtime/runMechanical.js +1 -1
- package/dist/runtime/runTurn.d.ts +1 -1
- package/dist/runtime/runTurn.js +10 -10
- package/dist/runtime/taskBookkeeping.d.ts +3 -3
- package/dist/runtime/taskBookkeeping.js +3 -3
- package/dist/runtime/taskIntake.js +2 -2
- package/dist/server/ConnectionPool.js +1 -1
- package/dist/server/EventQueue.d.ts +1 -1
- package/dist/server/EventQueue.js +1 -1
- package/dist/server/SeenMessages.d.ts +2 -2
- package/dist/server/SeenMessages.js +2 -2
- package/dist/server/ZiggsEffectHandler.d.ts +2 -2
- package/dist/server/ZiggsEffectHandler.js +8 -8
- package/dist/server/agreements/AgreementService.d.ts +3 -8
- package/dist/server/agreements/AgreementService.js +4 -7
- package/dist/server/inboxLoop.d.ts +5 -5
- package/dist/server/inboxLoop.js +21 -10
- package/dist/server/questMatch.d.ts +1 -1
- package/dist/server/questMatch.js +1 -1
- package/dist/server/runLauncher.js +3 -3
- package/dist/server/tools/index.d.ts +1 -0
- package/dist/server/tools/index.js +7 -6
- package/dist/server/tools/tier1/protocolRunner.d.ts +1 -1
- package/dist/server/tools/tier1/protocolRunner.js +19 -19
- package/dist/server/tools/tier1/protocolTools.js +4 -4
- package/dist/server/tools/tier2/artifactTools.js +2 -2
- package/dist/server/tools/tier2/chatTools.js +2 -2
- package/dist/server/tools/tier2/connectionTools.js +3 -3
- package/dist/server/tools/tier2/contextTools.js +1 -1
- package/dist/server/tools/tier2/discoveryTools.js +2 -2
- package/dist/server/tools/tier2/grantsTools.js +1 -1
- package/dist/server/tools/tier2/linkTools.js +3 -3
- package/dist/server/tools/tier2/marketplaceTools.js +1 -1
- package/dist/server/tools/tier2/mcpConnectionTools.d.ts +4 -0
- package/dist/server/tools/tier2/mcpConnectionTools.js +136 -0
- package/dist/server/tools/tier2/paymentTools.js +1 -1
- package/dist/shared/operatorKeyClaims.d.ts +2 -2
- package/dist/shared/operatorKeyClaims.js +1 -1
- package/dist/tasks/protocolRegistry.d.ts +1 -1
- package/dist/tasks/protocolRegistry.js +1 -1
- package/dist/tools/ToolManager.js +1 -1
- package/dist/tools/capabilityAdapter.d.ts +1 -1
- package/dist/tools/capabilityAdapter.js +3 -3
- package/dist/tools/defineTool.js +1 -1
- package/dist/tools/recordReport.d.ts +3 -3
- package/dist/tools/recordReport.js +3 -3
- package/dist/types.d.ts +17 -17
- package/package.json +3 -3
|
@@ -33,7 +33,7 @@ function wireAgreementId(metadata) {
|
|
|
33
33
|
* Self-hire / fully-consented create activates before the chat fan-out lands.
|
|
34
34
|
* The wire still says `proposal_created`, but the embedded agreement is already
|
|
35
35
|
* `active` / proposal `approved`. That is the hire work signal (same as
|
|
36
|
-
* `proposal_approved_execute`) — Claude must not park it as a notice
|
|
36
|
+
* `proposal_approved_execute`) — Claude must not park it as a notice.
|
|
37
37
|
*/
|
|
38
38
|
function isAlreadyActivatedProposalCreated(metadata) {
|
|
39
39
|
if (wireOperation(metadata) !== 'proposal_created')
|
|
@@ -46,13 +46,13 @@ function isAlreadyActivatedProposalCreated(metadata) {
|
|
|
46
46
|
return status === 'active' || proposalStatus === 'approved';
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
49
|
+
* a `proposal_created` notification addressed to a party whose
|
|
50
50
|
* approval is PENDING is a consent REQUEST, not a status notice. The backend
|
|
51
51
|
* fans `proposal_created` out specifically to the pending approvers
|
|
52
|
-
|
|
52
|
+
*, and parking it meant the hand-off provider — or any
|
|
53
53
|
* agent whose consent a proposal awaits — was alerted and then never looked:
|
|
54
54
|
* the approval sat pending until a human nudged the chat. Same technique as
|
|
55
|
-
* isAlreadyActivatedProposalCreated
|
|
55
|
+
* isAlreadyActivatedProposalCreated: reclassify by what the
|
|
56
56
|
* embedded agreement SAYS rather than adding a new wake word.
|
|
57
57
|
*/
|
|
58
58
|
function isPendingApprovalRequestNotification(metadata) {
|
|
@@ -90,7 +90,7 @@ function isTerminalTaskState(state) {
|
|
|
90
90
|
* compatibility but no longer gates anything.
|
|
91
91
|
*/
|
|
92
92
|
export function normalizeIncomingEvent({ text, metadata = {} }) {
|
|
93
|
-
//
|
|
93
|
+
// classifier data lives under `service` on the inbox-pull wire;
|
|
94
94
|
// fall back to top-level for hand-built (direct-injection) metadata.
|
|
95
95
|
const taskData = (metadata.service?.task ?? metadata.task) || null;
|
|
96
96
|
const chatId = normalizeChatId(metadata);
|
|
@@ -124,7 +124,7 @@ export function normalizeIncomingEvent({ text, metadata = {} }) {
|
|
|
124
124
|
};
|
|
125
125
|
}
|
|
126
126
|
if (isTaskRelated(metadata, taskData)) {
|
|
127
|
-
//
|
|
127
|
+
// active/spawn chat notices are not work — real assignment arrives
|
|
128
128
|
// on the task channel (`tasksAwaitingMe` → wakeTaskAssignment). Keep terminal
|
|
129
129
|
// task notices as `task_result` so orchestrators still see completions.
|
|
130
130
|
if (entryType === 'notification' && !isTerminalTaskState(taskData?.state)) {
|
|
@@ -144,7 +144,7 @@ export function normalizeIncomingEvent({ text, metadata = {} }) {
|
|
|
144
144
|
event: { ...base, type: 'task_result', result: taskData, text },
|
|
145
145
|
};
|
|
146
146
|
}
|
|
147
|
-
//
|
|
147
|
+
// residual lifecycle notices (proposal_created, agreement active, …)
|
|
148
148
|
// must not fall through as counterparty `message` speech.
|
|
149
149
|
if (entryType === 'notification') {
|
|
150
150
|
const operation = metadata.service?.operation ?? metadata.operation;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* default paid pricing for Ziggs-operated (first-party) agents.
|
|
3
3
|
* Prices are in wallet cents (1 ZIG = 100 cents).
|
|
4
4
|
*/
|
|
5
5
|
import { type FleetPricingTier } from './fleetTierByAgentId.gen.js';
|
|
@@ -22,7 +22,7 @@ export interface ProtocolPricingPayload {
|
|
|
22
22
|
[key: string]: unknown;
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
|
-
* Apply
|
|
25
|
+
* Apply defaults when `price` is omitted (null/undefined).
|
|
26
26
|
* Explicit `price: 0` is kept (free-on-purpose).
|
|
27
27
|
*/
|
|
28
28
|
export declare function applyFleetAgreementPricing(operation: 'agreement-propose' | 'agreement-subcontract', payload: ProtocolPricingPayload, actingAgentId: string): ProtocolPricingPayload;
|
|
@@ -36,7 +36,7 @@ export declare function applyFleetAgreementPricing(operation: 'agreement-propose
|
|
|
36
36
|
*/
|
|
37
37
|
export declare function applyFleetCounterPricing<T extends ProtocolPricingPayload>(payload: T, actingAgentId: string): T;
|
|
38
38
|
/**
|
|
39
|
-
* Infer fleet actor when tool context omits agentId (
|
|
39
|
+
* Infer fleet actor when tool context omits agentId (safety net).
|
|
40
40
|
*/
|
|
41
41
|
export declare function inferFleetPricingActorId(payload: ProtocolPricingPayload): string | null;
|
|
42
42
|
/** Apply fleet defaults with optional acting agent (falls back to payload inference). */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* default paid pricing for Ziggs-operated (first-party) agents.
|
|
3
3
|
* Prices are in wallet cents (1 ZIG = 100 cents).
|
|
4
4
|
*/
|
|
5
5
|
import { FLEET_TIER_BY_AGENT_ID } from './fleetTierByAgentId.gen.js';
|
|
@@ -54,7 +54,7 @@ export function resolveDefaultPriceCentsForExecutor(executorId, description) {
|
|
|
54
54
|
return tierPriceCents(tier);
|
|
55
55
|
}
|
|
56
56
|
/**
|
|
57
|
-
* Apply
|
|
57
|
+
* Apply defaults when `price` is omitted (null/undefined).
|
|
58
58
|
* Explicit `price: 0` is kept (free-on-purpose).
|
|
59
59
|
*/
|
|
60
60
|
export function applyFleetAgreementPricing(operation, payload, actingAgentId) {
|
|
@@ -122,7 +122,7 @@ export function applyFleetCounterPricing(payload, actingAgentId) {
|
|
|
122
122
|
return { ...payload, price: cents };
|
|
123
123
|
}
|
|
124
124
|
/**
|
|
125
|
-
* Infer fleet actor when tool context omits agentId (
|
|
125
|
+
* Infer fleet actor when tool context omits agentId (safety net).
|
|
126
126
|
*/
|
|
127
127
|
export function inferFleetPricingActorId(payload) {
|
|
128
128
|
const executor = payload.executorId != null ? String(payload.executorId) : '';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* eval/harness free pricing via env flag (no SANDBOX id-shape regex).
|
|
3
3
|
*/
|
|
4
4
|
/** Capability-probe + test harness agents (agents/test/). */
|
|
5
5
|
export declare const EVAL_HARNESS_AGENT_IDS: Set<string>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* eval/harness free pricing via env flag (no SANDBOX id-shape regex).
|
|
3
3
|
*/
|
|
4
4
|
/** Capability-probe + test harness agents (agents/test/). */
|
|
5
5
|
export const EVAL_HARNESS_AGENT_IDS = new Set([
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AUTO-GENERATED by scripts/build-fleet-tier-map.mjs — do not edit by hand.
|
|
3
|
-
*
|
|
3
|
+
* Tier A trivial public lookups (free when price omitted).
|
|
4
4
|
*/
|
|
5
5
|
export declare const FREE_TIER_A_AGENT_IDS: Set<string>;
|
|
6
6
|
export declare const FREE_TIER_A_COUNT = 4;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AUTO-GENERATED by scripts/build-fleet-tier-map.mjs — do not edit by hand.
|
|
3
|
-
*
|
|
3
|
+
* Tier A trivial public lookups (free when price omitted).
|
|
4
4
|
*/
|
|
5
5
|
export const FREE_TIER_A_AGENT_IDS = new Set([
|
|
6
6
|
"site-health-checker",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AUTO-GENERATED by scripts/build-fleet-tier-map.mjs — do not edit by hand.
|
|
3
|
-
*
|
|
3
|
+
* agent-sdk local default pricing (backend uses agent.pricingTier).
|
|
4
4
|
*/
|
|
5
5
|
export type FleetPricingTier = 'A' | 'B' | 'C' | 'D';
|
|
6
6
|
export declare const FLEET_TIER_BY_AGENT_ID: Record<string, FleetPricingTier>;
|
|
@@ -130,7 +130,7 @@ export declare class AgentMachine {
|
|
|
130
130
|
* an inbound event mid-turn re-entered _step with a stale state and a hot
|
|
131
131
|
* `_running=true`, which spawned overlapping turns and re-created the
|
|
132
132
|
* clarifyingDuringApproval feedback loop even after fixing the workflow
|
|
133
|
-
* fall-through
|
|
133
|
+
* fall-through.
|
|
134
134
|
*/
|
|
135
135
|
send(frame: IncomingFrame): Promise<void>;
|
|
136
136
|
_handleFrame(frame: IncomingFrame): Promise<void>;
|
|
@@ -4,7 +4,7 @@ import { runMechanical } from './runMechanical.js';
|
|
|
4
4
|
import { bootstrapCtx } from './bootstrapCtx.js';
|
|
5
5
|
/**
|
|
6
6
|
* Max thinking turns a session may run without any human (USER) input before it
|
|
7
|
-
* is treated as a runaway loop and parked
|
|
7
|
+
* is treated as a runaway loop and parked. The depth-guard only bounds
|
|
8
8
|
* re-entries within a single event chain; this bounds turns ACROSS events, which
|
|
9
9
|
* is how a supervising session ran 436 turns in a day. The counter resets on
|
|
10
10
|
* every USER frame, so human-driven conversations are never affected — only
|
|
@@ -86,7 +86,7 @@ export class AgentMachine {
|
|
|
86
86
|
* an inbound event mid-turn re-entered _step with a stale state and a hot
|
|
87
87
|
* `_running=true`, which spawned overlapping turns and re-created the
|
|
88
88
|
* clarifyingDuringApproval feedback loop even after fixing the workflow
|
|
89
|
-
* fall-through
|
|
89
|
+
* fall-through.
|
|
90
90
|
*/
|
|
91
91
|
async send(frame) {
|
|
92
92
|
if (this.status !== 'active')
|
|
@@ -114,7 +114,7 @@ export class AgentMachine {
|
|
|
114
114
|
this._lastIncomingEvent = ev ?? null;
|
|
115
115
|
if (ev?.senderId && String(ev?.senderType ?? '').toUpperCase() === 'USER') {
|
|
116
116
|
this._lastUserSenderId = String(ev.senderId);
|
|
117
|
-
// Human input is real progress — reset the runaway-loop budget
|
|
117
|
+
// Human input is real progress — reset the runaway-loop budget.
|
|
118
118
|
this._turnsSinceUserInput = 0;
|
|
119
119
|
}
|
|
120
120
|
runtimeLog.debug('FSM', `_handleFrame ev.type=${ev?.type} ev.senderId=${ev?.senderId} ev.receiverId=${ev?.receiverId} myId=${this.ctx.identity?.agentId}`);
|
|
@@ -132,7 +132,7 @@ export class AgentMachine {
|
|
|
132
132
|
// applyOutcomeToCtx clears it on resolution (buildOutcome.ts). Wire frames
|
|
133
133
|
// carry no trustworthy proposer field — agreement_lifecycle drops `parties`
|
|
134
134
|
// in normalizeIncomingEvent, and parties.creator is a principal id, not an
|
|
135
|
-
// agentId (backend
|
|
135
|
+
// agentId (backend) — so the SDK's own "I made this proposal"
|
|
136
136
|
// memory is the signal. Side effect we rely on: the first resolution clears
|
|
137
137
|
// the pending id, so duplicate approval frames compute proposedByMe=false
|
|
138
138
|
// and can never double-kickoff.
|
|
@@ -238,7 +238,7 @@ export class AgentMachine {
|
|
|
238
238
|
return;
|
|
239
239
|
}
|
|
240
240
|
// A thinking turn that yields an outcome no transition handles must not
|
|
241
|
-
// silently re-enter the same state and burn another LLM call
|
|
241
|
+
// silently re-enter the same state and burn another LLM call.
|
|
242
242
|
// Explicit self-loops still match (target is non-null, even if target === state).
|
|
243
243
|
if (sd.kind === 'thinking' &&
|
|
244
244
|
depth > 0 &&
|
|
@@ -257,7 +257,7 @@ export class AgentMachine {
|
|
|
257
257
|
// catching `tool-result` from an agent_network search) re-enters the
|
|
258
258
|
// same state forever — the LLM keeps re-rolling search/dummy actions
|
|
259
259
|
// until something accidentally matches, burning tokens and emitting
|
|
260
|
-
// duplicate side effects (
|
|
260
|
+
// duplicate side effects (ziggs ran `discoverSpecialist` six
|
|
261
261
|
// times before finally invoking `delegate`). 12 is well above any
|
|
262
262
|
// legitimate same-state turn chain we exercise in tests; if we trip
|
|
263
263
|
// it, treat the state as wedged and park.
|
|
@@ -268,7 +268,7 @@ export class AgentMachine {
|
|
|
268
268
|
this._notify();
|
|
269
269
|
return;
|
|
270
270
|
}
|
|
271
|
-
// Cross-event runaway guard
|
|
271
|
+
// Cross-event runaway guard. The depth-guard above only bounds
|
|
272
272
|
// one event chain; a supervising session that keeps waking on agent/system
|
|
273
273
|
// messages re-enters at depth 0 each time and never trips it. Bound the
|
|
274
274
|
// total thinking turns since the last human input instead — a runaway
|
|
@@ -356,7 +356,7 @@ export class AgentMachine {
|
|
|
356
356
|
if (!userId)
|
|
357
357
|
return;
|
|
358
358
|
try {
|
|
359
|
-
// Content-derived wake messageId
|
|
359
|
+
// Content-derived wake messageId: a crash-replay that errors
|
|
360
360
|
// the same way parks again and re-sends this apology — same key, backend
|
|
361
361
|
// drops the dupe.
|
|
362
362
|
const sessionId = this.ctx.identity.sessionId;
|
|
@@ -28,7 +28,7 @@ interface BuildArgs {
|
|
|
28
28
|
chatId: string | null;
|
|
29
29
|
stateId: string;
|
|
30
30
|
ctx: Ctx;
|
|
31
|
-
/** When true, respond-action schemas omit the "message" field — used for 2-call streaming
|
|
31
|
+
/** When true, respond-action schemas omit the "message" field — used for 2-call streaming. */
|
|
32
32
|
omitMessageFromRespond?: boolean;
|
|
33
33
|
}
|
|
34
34
|
interface HistoryFormatterI {
|
|
@@ -49,7 +49,7 @@ export interface PromptBuilderOptions {
|
|
|
49
49
|
/**
|
|
50
50
|
* Token budget for the <history> block. History is included newest-first up
|
|
51
51
|
* to this many (estimated) tokens, so a few large turns can't blow the prompt
|
|
52
|
-
* up to tens of thousands of input tokens
|
|
52
|
+
* up to tens of thousands of input tokens. maxHistoryEntries still
|
|
53
53
|
* applies as a secondary hard ceiling.
|
|
54
54
|
*/
|
|
55
55
|
maxHistoryTokens?: number;
|
|
@@ -29,7 +29,7 @@ export class PromptBuilder {
|
|
|
29
29
|
this.agreementFormatter = (options.agreementFormatter || new AgreementFormatter());
|
|
30
30
|
this.logger = options.logger || null;
|
|
31
31
|
}
|
|
32
|
-
// Section order is deliberate and cache-stable
|
|
32
|
+
// Section order is deliberate and cache-stable: <self> (identity,
|
|
33
33
|
// state, tools) is stable per agent+state and comes first, so OpenAI automatic
|
|
34
34
|
// prompt caching can reuse it as a prefix across a conversation's turns. All
|
|
35
35
|
// volatile content (the incoming event, history, and currentTime) lives later
|
|
@@ -298,7 +298,7 @@ ${this._renderDecisionSchemaFromActions(actions, senderMeta, ctxObj, omitMessage
|
|
|
298
298
|
const pid = parties[field];
|
|
299
299
|
if (!pid || pid === myAgentId)
|
|
300
300
|
continue;
|
|
301
|
-
// Opaque presentation refs are not principal slots
|
|
301
|
+
// Opaque presentation refs are not principal slots.
|
|
302
302
|
if (isOpaquePresentationRef(pid))
|
|
303
303
|
continue;
|
|
304
304
|
if (!agreementParties.has(pid))
|
|
@@ -309,7 +309,7 @@ ${this._renderDecisionSchemaFromActions(actions, senderMeta, ctxObj, omitMessage
|
|
|
309
309
|
// Chat keeps the full directory — including opaque `rpb_*` / `psn_*` refs
|
|
310
310
|
// the persona layer may surface. Echo those as chat_send receiverId; the
|
|
311
311
|
// backend resolves them in-room. Transfer (and any account-id action) must
|
|
312
|
-
// never list opaque refs — lookup / wake / pay against them 400s
|
|
312
|
+
// never list opaque refs — lookup / wake / pay against them 400s.
|
|
313
313
|
const chatIds = new Set([...userIds, ...agentIds]);
|
|
314
314
|
const transferIds = new Set([...userIds, ...agentIds].filter((id) => !isOpaquePresentationRef(id)));
|
|
315
315
|
const lines = ['<recipients>'];
|
|
@@ -12,7 +12,7 @@ import type { Ctx, Identity } from '../types.js';
|
|
|
12
12
|
* - `existing` present → restore it, re-stamping `identity` and defaulting the
|
|
13
13
|
* fields that sessions persisted before they existed may lack. Sessions live
|
|
14
14
|
* up to SESSION_TTL_SECONDS (24h) in DynamoDB, so an old row can come back
|
|
15
|
-
*
|
|
15
|
+
* without `tasks` (pre- lane-level pendingSubtasks/collectedResults);
|
|
16
16
|
* migrate/default them here, nowhere else.
|
|
17
17
|
*/
|
|
18
18
|
export declare function bootstrapCtx(identity: Identity, existing?: Ctx | null): Ctx;
|
|
@@ -12,13 +12,13 @@ import { migrateLegacyTaskFields } from './taskBookkeeping.js';
|
|
|
12
12
|
* - `existing` present → restore it, re-stamping `identity` and defaulting the
|
|
13
13
|
* fields that sessions persisted before they existed may lack. Sessions live
|
|
14
14
|
* up to SESSION_TTL_SECONDS (24h) in DynamoDB, so an old row can come back
|
|
15
|
-
*
|
|
15
|
+
* without `tasks` (pre- lane-level pendingSubtasks/collectedResults);
|
|
16
16
|
* migrate/default them here, nowhere else.
|
|
17
17
|
*/
|
|
18
18
|
export function bootstrapCtx(identity, existing) {
|
|
19
19
|
if (existing) {
|
|
20
20
|
const legacy = existing;
|
|
21
|
-
//
|
|
21
|
+
// the lane-level mirrors are destructured OFF, not carried through.
|
|
22
22
|
// `migrateLegacyTaskFields` has already folded whatever they held into
|
|
23
23
|
// `tasks`; keeping the empty husks on the restored Ctx is what let a live FSM
|
|
24
24
|
// guard read one and see "nothing delegated" forever.
|
|
@@ -69,7 +69,7 @@ export declare function outcomeFromEvent(rawEvent: RawEvent | null | undefined,
|
|
|
69
69
|
export declare function outcomeFromActionResult(actionName: string | null, action: Action | null, args: unknown, emittedEvents: EmittedEvent[], toolResultsOut: ToolResultEntry[]): Outcome;
|
|
70
70
|
/**
|
|
71
71
|
* Mutates ctx to absorb persistent state from a new outcome:
|
|
72
|
-
* - task bookkeeping keyed by taskId
|
|
72
|
+
* - task bookkeeping keyed by taskId — close clears only that entry
|
|
73
73
|
* - focus activeAgreementId/activeTaskId on assignment
|
|
74
74
|
* - pending proposal id on proposal-made, cleared on resolution
|
|
75
75
|
* - delegated children accumulated under the parent engagement's book
|
|
@@ -134,7 +134,7 @@ function singleEventToOutcome(ev, ownAgentId) {
|
|
|
134
134
|
action: reason === 'proposal_approved' ? 'approve' : 'reject',
|
|
135
135
|
proposal,
|
|
136
136
|
proposedByMe: false, // real value stamped in AgentMachine._handleFrame from ctx
|
|
137
|
-
//
|
|
137
|
+
// resource_changed + proposal_approved is the store-claim
|
|
138
138
|
// fan-out (chat-less). Direct-proposal counterparty approves arrive as
|
|
139
139
|
// agreement_lifecycle and must not set this.
|
|
140
140
|
...(reason === 'proposal_approved' ? { fromClaimWake: true } : {}),
|
|
@@ -391,7 +391,7 @@ function buildOutcomeOfKind(kind, primary, action) {
|
|
|
391
391
|
// ── applyOutcomeToCtx: persistent ctx updates from a new outcome ─────────────
|
|
392
392
|
/**
|
|
393
393
|
* Mutates ctx to absorb persistent state from a new outcome:
|
|
394
|
-
* - task bookkeeping keyed by taskId
|
|
394
|
+
* - task bookkeeping keyed by taskId — close clears only that entry
|
|
395
395
|
* - focus activeAgreementId/activeTaskId on assignment
|
|
396
396
|
* - pending proposal id on proposal-made, cleared on resolution
|
|
397
397
|
* - delegated children accumulated under the parent engagement's book
|
|
@@ -489,8 +489,8 @@ export function applyOutcomeToCtx(ctx, outcome) {
|
|
|
489
489
|
break;
|
|
490
490
|
}
|
|
491
491
|
case 'task-closed': {
|
|
492
|
-
// Clear only this task's entry — sibling open tasks keep their books
|
|
493
|
-
//
|
|
492
|
+
// Clear only this task's entry — sibling open tasks keep their books.
|
|
493
|
+
// buildOutcomeOfKind already collapses result.task.taskId /
|
|
494
494
|
// result.taskId into outcome.taskId — do not re-walk the raw result here.
|
|
495
495
|
const closedId = outcome.taskId || ctx.activeTaskId;
|
|
496
496
|
if (closedId)
|
|
@@ -8,8 +8,8 @@ import type { Action, Ctx, MechanicalState, Outcome, Produces, Transition, Think
|
|
|
8
8
|
* listening: {
|
|
9
9
|
* kind: 'thinking',
|
|
10
10
|
* ...dflt,
|
|
11
|
-
* prompt: {
|
|
12
|
-
* actions: { ...dflt.actions, greetUser: {
|
|
11
|
+
* prompt: { .. },
|
|
12
|
+
* actions: { ...dflt.actions, greetUser: { .. } },
|
|
13
13
|
* transitions: [
|
|
14
14
|
* { to: 'executing', when: o => o.kind === 'task-assigned' },
|
|
15
15
|
* ...dflt.transitions,
|
|
@@ -33,8 +33,8 @@ export declare function thinkingDefaults(opts: {
|
|
|
33
33
|
* Equivalent to spreading manually but the spread points are in one place.
|
|
34
34
|
*
|
|
35
35
|
* defineThinkingState({ initial: 'idle' }, {
|
|
36
|
-
* prompt: {
|
|
37
|
-
* actions: { greetUser: {
|
|
36
|
+
* prompt: { .. },
|
|
37
|
+
* actions: { greetUser: { .. } },
|
|
38
38
|
* transitions: [ { to: 'executing', when: o => o.kind === 'task-assigned' } ],
|
|
39
39
|
* })
|
|
40
40
|
*/
|
|
@@ -44,7 +44,7 @@ export declare function defineThinkingState(opts: {
|
|
|
44
44
|
actions: Record<string, Action>;
|
|
45
45
|
transitions: Transition[];
|
|
46
46
|
}): ThinkingState;
|
|
47
|
-
/**
|
|
47
|
+
/** mechanical state — one tool call, zero LLM turns. */
|
|
48
48
|
export declare function defineMechanicalState(state: {
|
|
49
49
|
tool: string;
|
|
50
50
|
args?: Record<string, unknown>;
|
package/dist/runtime/defaults.js
CHANGED
|
@@ -16,7 +16,7 @@ export function thinkingDefaults(opts) {
|
|
|
16
16
|
{ to: initial, when: o => o.kind === 'wait' },
|
|
17
17
|
{ to: initial, when: o => o.kind === 'message-sent' },
|
|
18
18
|
// Errors (empty/failed turns) fall back to the initial state instead of
|
|
19
|
-
// silently re-entering and recursing until the depth guard
|
|
19
|
+
// silently re-entering and recursing until the depth guard.
|
|
20
20
|
{ to: initial, when: o => o.kind === 'error' },
|
|
21
21
|
],
|
|
22
22
|
};
|
|
@@ -26,8 +26,8 @@ export function thinkingDefaults(opts) {
|
|
|
26
26
|
* Equivalent to spreading manually but the spread points are in one place.
|
|
27
27
|
*
|
|
28
28
|
* defineThinkingState({ initial: 'idle' }, {
|
|
29
|
-
* prompt: {
|
|
30
|
-
* actions: { greetUser: {
|
|
29
|
+
* prompt: { .. },
|
|
30
|
+
* actions: { greetUser: { .. } },
|
|
31
31
|
* transitions: [ { to: 'executing', when: o => o.kind === 'task-assigned' } ],
|
|
32
32
|
* })
|
|
33
33
|
*/
|
|
@@ -41,7 +41,7 @@ export function defineThinkingState(opts, state) {
|
|
|
41
41
|
transitions: [...state.transitions, ...dflt.transitions],
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
-
/**
|
|
44
|
+
/** mechanical state — one tool call, zero LLM turns. */
|
|
45
45
|
export function defineMechanicalState(state) {
|
|
46
46
|
return { kind: 'mechanical', ...state };
|
|
47
47
|
}
|
|
@@ -11,7 +11,7 @@ import type { DeliveryWriteKeys } from '../types.js';
|
|
|
11
11
|
* replayed wake happens to re-issue its tool calls.
|
|
12
12
|
* - chat messages: `sdk_idem_wk_<wake-hash>_m_<hash(chat, receiver, text)>`
|
|
13
13
|
* — used directly as the messageId (the backend dedups on (chatId, messageId),
|
|
14
|
-
*
|
|
14
|
+
*). Content-derived, not ordinal: a wake may emit several
|
|
15
15
|
* distinct messages without colliding, and a crash-replay that re-sends the
|
|
16
16
|
* same text reuses the key; a re-think with different text gets a new key
|
|
17
17
|
* instead of 409-dropping on the old ordinal slot. The prefix matches the
|
|
@@ -80,7 +80,7 @@ function sha(input) {
|
|
|
80
80
|
* replayed wake happens to re-issue its tool calls.
|
|
81
81
|
* - chat messages: `sdk_idem_wk_<wake-hash>_m_<hash(chat, receiver, text)>`
|
|
82
82
|
* — used directly as the messageId (the backend dedups on (chatId, messageId),
|
|
83
|
-
*
|
|
83
|
+
*). Content-derived, not ordinal: a wake may emit several
|
|
84
84
|
* distinct messages without colliding, and a crash-replay that re-sends the
|
|
85
85
|
* same text reuses the key; a re-think with different text gets a new key
|
|
86
86
|
* instead of 409-dropping on the old ordinal slot. The prefix matches the
|
|
@@ -97,7 +97,7 @@ export function createDeliveryWriteKeys(laneKey, event) {
|
|
|
97
97
|
const identity = eventIdentity(event);
|
|
98
98
|
const wakeHash = identity === null ? null : sha(`${laneKey}\n${identity}`).slice(0, 40);
|
|
99
99
|
let ordinal = 0;
|
|
100
|
-
//
|
|
100
|
+
// one content-hash builder; tags differ, the shape does not.
|
|
101
101
|
const keyed = (tag, material, opts) => {
|
|
102
102
|
if (wakeHash === null)
|
|
103
103
|
return undefined;
|
|
@@ -109,7 +109,7 @@ export function createDeliveryWriteKeys(laneKey, event) {
|
|
|
109
109
|
artifactKey(content) {
|
|
110
110
|
if (wakeHash === null)
|
|
111
111
|
return undefined;
|
|
112
|
-
// Content-derived for the same reason messageKey is
|
|
112
|
+
// Content-derived for the same reason messageKey is: the
|
|
113
113
|
// ordinal restarts at a0 on every wake, so a redelivered event whose
|
|
114
114
|
// re-think writes different artifacts collided with the first wake's
|
|
115
115
|
// and the backend discarded the new write. Harmless for a breadcrumb,
|
|
@@ -2,12 +2,12 @@ import type { ThinkingState, Workflow } from '../types.js';
|
|
|
2
2
|
/** Reserved state name for the injected agreement-kickoff default (proposer). */
|
|
3
3
|
export declare const KICKOFF_STATE = "kickingOff";
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* reserved state for the injected claim/provider kickoff default.
|
|
6
6
|
* Store listing claimed → open buyer chat + greet (not task_create).
|
|
7
7
|
*/
|
|
8
8
|
export declare const CLAIM_KICKOFF_STATE = "claimKickingOff";
|
|
9
9
|
export declare function buildKickoffState(initial: string): ThinkingState;
|
|
10
|
-
/**
|
|
10
|
+
/** provider side after a store/listing claim. */
|
|
11
11
|
export declare function buildClaimKickoffState(initial: string): ThinkingState;
|
|
12
12
|
/**
|
|
13
13
|
* Agreement-kickoff defaults. For every state whose transitions do not already
|
|
@@ -16,7 +16,7 @@ export declare function buildClaimKickoffState(initial: string): ThinkingState;
|
|
|
16
16
|
* (first match wins in AgentMachine._evaluateTransitions). Returns a NEW
|
|
17
17
|
* workflow — input states are not mutated.
|
|
18
18
|
*
|
|
19
|
-
* Two kickoffs
|
|
19
|
+
* Two kickoffs:
|
|
20
20
|
* - `kickingOff` — proposer-side approval → task_create
|
|
21
21
|
* - `claimKickingOff` — store-claim provider wake → chat_open + greet
|
|
22
22
|
* Direct-proposal counterparties (lifecycle approve, !proposedByMe, no
|
package/dist/runtime/kickoff.js
CHANGED
|
@@ -4,7 +4,7 @@ import { hasDelegatedAgreement, hasOpenTaskForAgreement } from './taskBookkeepin
|
|
|
4
4
|
/** Reserved state name for the injected agreement-kickoff default (proposer). */
|
|
5
5
|
export const KICKOFF_STATE = 'kickingOff';
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* reserved state for the injected claim/provider kickoff default.
|
|
8
8
|
* Store listing claimed → open buyer chat + greet (not task_create).
|
|
9
9
|
*/
|
|
10
10
|
export const CLAIM_KICKOFF_STATE = 'claimKickingOff';
|
|
@@ -115,7 +115,7 @@ export function buildKickoffState(initial) {
|
|
|
115
115
|
],
|
|
116
116
|
};
|
|
117
117
|
}
|
|
118
|
-
/**
|
|
118
|
+
/** provider side after a store/listing claim. */
|
|
119
119
|
export function buildClaimKickoffState(initial) {
|
|
120
120
|
const dflt = thinkingDefaults({ initial });
|
|
121
121
|
return {
|
|
@@ -166,7 +166,7 @@ export function buildClaimKickoffState(initial) {
|
|
|
166
166
|
* (first match wins in AgentMachine._evaluateTransitions). Returns a NEW
|
|
167
167
|
* workflow — input states are not mutated.
|
|
168
168
|
*
|
|
169
|
-
* Two kickoffs
|
|
169
|
+
* Two kickoffs:
|
|
170
170
|
* - `kickingOff` — proposer-side approval → task_create
|
|
171
171
|
* - `claimKickingOff` — store-claim provider wake → chat_open + greet
|
|
172
172
|
* Direct-proposal counterparties (lifecycle approve, !proposedByMe, no
|
|
@@ -12,7 +12,7 @@ export interface RunMechanicalResult {
|
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
15
|
+
* run a mechanical state — invoke one bound tool with no LLM turn.
|
|
16
16
|
* Used by dumb coordinators whose only job is a fixed tool call on task-assigned.
|
|
17
17
|
*/
|
|
18
18
|
export declare function runMechanical(input: {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { outcomeFromActionResult } from './buildOutcome.js';
|
|
2
2
|
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* run a mechanical state — invoke one bound tool with no LLM turn.
|
|
5
5
|
* Used by dumb coordinators whose only job is a fixed tool call on task-assigned.
|
|
6
6
|
*/
|
|
7
7
|
export async function runMechanical(input) {
|
|
@@ -25,7 +25,7 @@ interface RunTurnInput {
|
|
|
25
25
|
};
|
|
26
26
|
/**
|
|
27
27
|
* Respond actions use 2-call streaming: Call 1 picks action+receiverId,
|
|
28
|
-
* Call 2 streams the text
|
|
28
|
+
* Call 2 streams the text. On by default; pass false
|
|
29
29
|
* (or set ENABLE_LLM_STREAMING=false) to fold the reply text back into the
|
|
30
30
|
* single decision call. The env flag is a deployment kill-switch — the
|
|
31
31
|
* streaming path once broke long replies in production, so ops keeps a way
|
package/dist/runtime/runTurn.js
CHANGED
|
@@ -46,7 +46,7 @@ function isGenericFallbackTool(name, toolManager) {
|
|
|
46
46
|
*/
|
|
47
47
|
export async function runTurn(input) {
|
|
48
48
|
const { stateId, statePrompt, actions, incomingEvent, ctx, eff, toolManager, promptBuilder, memory, definition, writeKeys } = input;
|
|
49
|
-
// Default on
|
|
49
|
+
// Default on; ENABLE_LLM_STREAMING=false is the deployment
|
|
50
50
|
// kill-switch. Any other value (unset, 'true', …) streams.
|
|
51
51
|
const streamRespondActions = input.streamRespondActions ?? process.env.ENABLE_LLM_STREAMING !== 'false';
|
|
52
52
|
const sessionId = ctx.identity.sessionId;
|
|
@@ -55,7 +55,7 @@ export async function runTurn(input) {
|
|
|
55
55
|
// and the wake's replay-stable write keys onto tool-call effects.
|
|
56
56
|
// Send-message effects without an explicit messageId get a wake-derived one
|
|
57
57
|
// here — a crash-replay of the wake re-derives the same id and the backend's
|
|
58
|
-
// (chatId, messageId) dedup drops the duplicate
|
|
58
|
+
// (chatId, messageId) dedup drops the duplicate. Undefined on
|
|
59
59
|
// keyless wakes, where the transport generates a fresh id as before.
|
|
60
60
|
const effWithTrace = (effect) => {
|
|
61
61
|
if (effect.kind === 'llm-call') {
|
|
@@ -113,7 +113,7 @@ export async function runTurn(input) {
|
|
|
113
113
|
// to this agent) has no world to decide over — deciding anyway means
|
|
114
114
|
// reasoning about a fabricated empty history plus whatever stale scraps
|
|
115
115
|
// other fetches returned. Skip the LLM and park. Gated on message wakes
|
|
116
|
-
// only.
|
|
116
|
+
// only. a bare agreement id as sessionId is a mis-keyed lane —
|
|
117
117
|
// loud-log the empty snapshot rather than staying silent.
|
|
118
118
|
if (ctxObj.chatMissing) {
|
|
119
119
|
const looksLikeBareAgreement = typeof sessionId === 'string' &&
|
|
@@ -155,7 +155,7 @@ export async function runTurn(input) {
|
|
|
155
155
|
let llmCallsTotal = 0;
|
|
156
156
|
let tokensTotal = 0, tokensInput = 0, tokensOutput = 0;
|
|
157
157
|
// Fold a tool's nested LLM usage (llmTool) into the turn's authoritative
|
|
158
|
-
// token count so tool-internal spend reaches result.metrics
|
|
158
|
+
// token count so tool-internal spend reaches result.metrics.
|
|
159
159
|
const addToolUsage = (u) => {
|
|
160
160
|
if (!u)
|
|
161
161
|
return;
|
|
@@ -325,9 +325,9 @@ export async function runTurn(input) {
|
|
|
325
325
|
runtimeLog.info('runTurn', `streaming check: enabled=${streamRespondActions} receiverId=${decision.receiverId ?? 'none'} tool=${actionDef?.tool ?? 'none'} action=${chosenAction} inboundFromAgent=${inboundFromAgent}`);
|
|
326
326
|
if (streamRespondActions && !inboundFromAgent && decision.receiverId && !actionDef?.tool && chosenAction !== 'wait') {
|
|
327
327
|
// Live chunks use a provisional id (text not known yet). The persisted
|
|
328
|
-
// send below switches to a content-derived wake key
|
|
328
|
+
// send below switches to a content-derived wake key once the
|
|
329
329
|
// reply text exists — a crash-replay of the same text reuses that key
|
|
330
|
-
|
|
330
|
+
//, and a re-think with different text does not collide.
|
|
331
331
|
const chunkMessageId = crypto.randomUUID();
|
|
332
332
|
runtimeLog.info('runTurn', `stream-text start action=${chosenAction} receiverId=${decision.receiverId} msgId=${chunkMessageId}`);
|
|
333
333
|
const streamMessages = [
|
|
@@ -348,8 +348,8 @@ export async function runTurn(input) {
|
|
|
348
348
|
// Count the streamed reply only when it actually produced the text we
|
|
349
349
|
// send (an empty resolve falls through to the llm-call fallback below,
|
|
350
350
|
// which counts itself). The streamed reply is a real billed completion
|
|
351
|
-
// (call 2
|
|
352
|
-
// output of the turn leaks from every usage/cost sink
|
|
351
|
+
// (call 2 2-call streaming) — count it or the largest single
|
|
352
|
+
// output of the turn leaks from every usage/cost sink.
|
|
353
353
|
if (replyText) {
|
|
354
354
|
llmCallsTotal++;
|
|
355
355
|
const su = streamResult.usage;
|
|
@@ -679,7 +679,7 @@ function truncateMsg(msg) {
|
|
|
679
679
|
* gate on a wake-derived id: this wake's reply already exists. Two legal ways
|
|
680
680
|
* that happens — a crash-replay re-ran the wake, or a second host raced the
|
|
681
681
|
* same at-least-once delivery — and in both the re-run LLM produced different
|
|
682
|
-
* text, so the backend rejects instead of no-op'ing (
|
|
682
|
+
* text, so the backend rejects instead of no-op'ing (treats
|
|
683
683
|
* same-key-same-content as replay, same-key-different-content as conflict).
|
|
684
684
|
* The user already has the winner's reply. Re-sending under a fresh id would
|
|
685
685
|
* double-message; failing the turn marks an answered wake as an error. Treat
|
|
@@ -780,7 +780,7 @@ async function executeTool(decision, eff, sessionId, memory, incomingEvent = nul
|
|
|
780
780
|
memory,
|
|
781
781
|
senderId: incomingEvent?.senderId != null ? String(incomingEvent.senderId) : undefined,
|
|
782
782
|
senderType: incomingEvent?.senderType != null ? String(incomingEvent.senderType) : undefined,
|
|
783
|
-
//
|
|
783
|
+
// the subcontract agreement this wake is under (task events carry
|
|
784
784
|
// it under `result.agreementId`) so a delegated per-user MCP call can
|
|
785
785
|
// resolve the originating end-user, not the delegating agent (senderId).
|
|
786
786
|
onBehalfOfAgreementId: (() => {
|
|
@@ -2,7 +2,7 @@ import type { Ctx, TaskBookkeeping } from '../types.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Reserved key for coordinator-side pending/collected state when the lane has
|
|
4
4
|
* an active agreement but no assigned parent task yet (kickoff → spawn).
|
|
5
|
-
* Never deleted by task-closed of a real taskId
|
|
5
|
+
* Never deleted by task-closed of a real taskId.
|
|
6
6
|
*/
|
|
7
7
|
export declare const LANE_BOOK_KEY = "__lane__";
|
|
8
8
|
export declare function emptyTaskBook(agreementId?: string | null): TaskBookkeeping;
|
|
@@ -25,9 +25,9 @@ export declare function hasDelegatedAgreement(ctx: Ctx, agreementId: string): bo
|
|
|
25
25
|
*/
|
|
26
26
|
export declare function removeTaskBook(ctx: Ctx, taskId: string): void;
|
|
27
27
|
/**
|
|
28
|
-
* Migrate a pre-
|
|
28
|
+
* Migrate a pre- persisted ctx (lane-level pendingSubtasks /
|
|
29
29
|
* collectedResults / delegatedAgreementIds) into the tasks map.
|
|
30
|
-
*
|
|
30
|
+
* the deprecated lane aggregates are no longer auto-synced —
|
|
31
31
|
* readers use allPendingSubtasks / allCollectedResults / allDelegatedAgreementIds.
|
|
32
32
|
* This migrator stays so a session persisted before the tasks map still boots.
|
|
33
33
|
*/
|