@ziggs-ai/agent-sdk 0.20.0 → 0.22.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 +5 -0
- package/dist/Agent.d.ts +28 -7
- package/dist/Agent.js +189 -74
- package/dist/brain/ClaudeBrain.d.ts +11 -1
- package/dist/brain/ClaudeBrain.js +71 -18
- package/dist/brain/ZiggsBrain.d.ts +2 -0
- package/dist/brain/ZiggsBrain.js +1 -0
- package/dist/brain/claudeCognition.d.ts +1 -1
- package/dist/brain/claudeCognition.js +54 -7
- package/dist/brain/claudeSdkProtocol.d.ts +1 -1
- package/dist/brain/claudeSdkProtocol.js +5 -2
- package/dist/brain/claudeSdkTools.d.ts +5 -2
- package/dist/brain/claudeSdkTools.js +39 -10
- package/dist/brain/hireActivationNotice.d.ts +19 -0
- package/dist/brain/hireActivationNotice.js +79 -0
- package/dist/brain/standingOfferNotice.d.ts +15 -0
- package/dist/brain/standingOfferNotice.js +89 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/ingress/normalizeIncoming.d.ts +3 -0
- package/dist/ingress/normalizeIncoming.js +8 -0
- package/dist/mcp/createMcpAgent.d.ts +1 -1
- package/dist/mcp/createMcpAgent.js +1 -1
- package/dist/memory/MemoryStore.d.ts +18 -4
- package/dist/memory/MemoryStore.js +52 -12
- package/dist/platform/ConnectionPool.d.ts +9 -4
- package/dist/platform/ConnectionPool.js +16 -6
- package/dist/platform/StateTimers.d.ts +12 -6
- package/dist/platform/StateTimers.js +26 -6
- package/dist/platform/ZiggsEffectHandler.js +17 -3
- package/dist/platform/inboxLoop.d.ts +5 -7
- package/dist/platform/inboxLoop.js +36 -80
- package/dist/platform/polls/declaredPollRunner.d.ts +7 -1
- package/dist/platform/polls/declaredPollRunner.js +7 -9
- package/dist/platform/tasks/TaskService.d.ts +23 -1
- package/dist/platform/tasks/TaskService.js +8 -2
- package/dist/platform/tools/index.d.ts +3 -2
- package/dist/platform/tools/index.js +3 -2
- package/dist/platform/tools/tier1/protocolRunner.js +8 -1
- package/dist/platform/tools/tier1/protocolTools.js +3 -3
- package/dist/platform/tools/tier2/contextTools.d.ts +3 -0
- package/dist/platform/tools/tier2/contextTools.js +4 -1
- package/dist/platform/tools/tier2/planTools.d.ts +20 -0
- package/dist/platform/tools/tier2/planTools.js +64 -0
- package/dist/platform/tools/tier2/taskTools.d.ts +1 -0
- package/dist/platform/tools/tier2/taskTools.js +2 -1
- package/dist/pricing/fleetDefaults.js +4 -9
- package/dist/pricing/fleetTierByAgentId.gen.d.ts +1 -1
- package/dist/pricing/fleetTierByAgentId.gen.js +2 -752
- package/dist/runtime/AgentMachine.d.ts +3 -0
- package/dist/runtime/AgentMachine.js +6 -1
- package/dist/runtime/runTurn.d.ts +1 -0
- package/dist/runtime/runTurn.js +19 -15
- package/dist/runtime/wakeEffects.d.ts +35 -0
- package/dist/runtime/wakeEffects.js +121 -0
- package/dist/runtime/wakeOutcome.d.ts +33 -0
- package/dist/runtime/wakeOutcome.js +61 -6
- package/dist/types.d.ts +13 -0
- package/package.json +2 -2
- package/dist/pricing/fleetFreeTierA.gen.d.ts +0 -6
- package/dist/pricing/fleetFreeTierA.gen.js +0 -11
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { isAgreementId, isBroadcastTarget } from '@ziggs-ai/api-client';
|
|
2
|
+
import { getBatchEvents } from '../context/batch.js';
|
|
3
|
+
function record(value) {
|
|
4
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
5
|
+
? value : null;
|
|
6
|
+
}
|
|
7
|
+
function agreementEventIds(event) {
|
|
8
|
+
const resource = record(event?.resource);
|
|
9
|
+
const result = record(event?.result);
|
|
10
|
+
const candidates = [
|
|
11
|
+
resource?.agreementId,
|
|
12
|
+
resource?.kind === 'agreement' ? resource.resourceId : undefined,
|
|
13
|
+
event?.agreementId,
|
|
14
|
+
result?.agreementId,
|
|
15
|
+
];
|
|
16
|
+
return [...new Set(candidates.filter((value) => typeof value === 'string' && value.length > 0))];
|
|
17
|
+
}
|
|
18
|
+
export function agreementEventId(event) {
|
|
19
|
+
const ids = agreementEventIds(event);
|
|
20
|
+
return ids.length === 1 ? ids[0] : undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Provider acceptance leaves a standing listing OPEN with its buyer slot still
|
|
24
|
+
* a broadcast. It is not a customer hire. Check the full authoritative row;
|
|
25
|
+
* /status, event actor ids and the word "approved" cannot prove this shape.
|
|
26
|
+
*/
|
|
27
|
+
export function isOwnUnclaimedStandingOffer(value, agreementId, agentId) {
|
|
28
|
+
const row = record(value);
|
|
29
|
+
const parties = record(row?.parties);
|
|
30
|
+
const payer = record(parties?.payer);
|
|
31
|
+
const provider = record(parties?.provider);
|
|
32
|
+
return row?.agreementId === agreementId &&
|
|
33
|
+
row.status === 'open' &&
|
|
34
|
+
row.providerPinned === true &&
|
|
35
|
+
provider?.actor === agentId &&
|
|
36
|
+
typeof payer?.principal === 'string' &&
|
|
37
|
+
isBroadcastTarget(payer.principal);
|
|
38
|
+
}
|
|
39
|
+
/** Remove only proven listing-acceptance notices; mixed batches keep all real work. */
|
|
40
|
+
export async function filterStandingOfferNotices(event, agentId, readAgreement) {
|
|
41
|
+
const events = getBatchEvents(event);
|
|
42
|
+
const retained = [];
|
|
43
|
+
const ignoredAgreementIds = [];
|
|
44
|
+
const reads = new Map();
|
|
45
|
+
for (const item of events) {
|
|
46
|
+
const resource = record(item?.resource);
|
|
47
|
+
const reason = resource?.reason ?? item?.reason;
|
|
48
|
+
const id = agreementEventId(item);
|
|
49
|
+
if (item?.type === 'resource_changed' && resource?.kind === 'agreement' &&
|
|
50
|
+
(reason === 'approval:approved' || reason === 'proposal_approved') && id) {
|
|
51
|
+
// Inconclusive reads preserve the wake. An unavailable read must not eat
|
|
52
|
+
// a legitimate customer hire or the answer to an agent's own request.
|
|
53
|
+
if (!reads.has(id))
|
|
54
|
+
reads.set(id, Promise.resolve().then(() => readAgreement(id)).catch(() => null));
|
|
55
|
+
if (isOwnUnclaimedStandingOffer(await reads.get(id), id, agentId)) {
|
|
56
|
+
ignoredAgreementIds.push(id);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
retained.push(item);
|
|
61
|
+
}
|
|
62
|
+
if (ignoredAgreementIds.length === 0)
|
|
63
|
+
return { event, ignoredAgreementIds };
|
|
64
|
+
return {
|
|
65
|
+
event: retained.length === 0 ? undefined
|
|
66
|
+
: retained.length === 1 ? retained[0] : { ...event, events: retained },
|
|
67
|
+
ignoredAgreementIds,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** A synthetic mailbox has no chat row; access to its agreement must still be proven. */
|
|
71
|
+
export async function isReadableAgreementNoticeLane(sessionId, event, readAgreement) {
|
|
72
|
+
const id = sessionId.startsWith('agrn-') ? sessionId.slice('agrn-'.length) : sessionId;
|
|
73
|
+
if (!isAgreementId(id))
|
|
74
|
+
return false;
|
|
75
|
+
const events = getBatchEvents(event);
|
|
76
|
+
if (events.length === 0 || events.some((item) => {
|
|
77
|
+
const eventIds = agreementEventIds(item);
|
|
78
|
+
// Old in-memory timers do not carry scope. They may resume only a readable
|
|
79
|
+
// agreement mailbox. Never derive authority from the model-written reason.
|
|
80
|
+
return eventIds.length ? eventIds.some((eventId) => eventId !== id) : item?.type !== 'wake-at';
|
|
81
|
+
}))
|
|
82
|
+
return false;
|
|
83
|
+
try {
|
|
84
|
+
return record(await readAgreement(id))?.agreementId === id;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -67,7 +67,7 @@ export { HistoryFormatter, historyFormatter, AgreementFormatter, agreementFormat
|
|
|
67
67
|
export { ProactiveTrigger } from './platform/proactive/ProactiveTrigger.js';
|
|
68
68
|
export { PollRegistry, declaredPolls, type PollBinding, type PollContext, type PollKind, type PollResult, } from './platform/polls/PollKind.js';
|
|
69
69
|
export { PollRunner, type PollRunnerOptions, type WakeTarget } from './platform/polls/PollRunner.js';
|
|
70
|
-
export { PROTOCOL_TOOLS, agreementProposeTool, agreementSubcontractTool, agreementRespondTool, agreementClaimTool, agreementCounterTool, agreementGetTool, agreementRevokeTool, agreementFulfillTool, taskCreateTool, taskSetResultTool, taskUpdatePlanStepTool, dispatchProtocolOp, PAYMENT_TOOLS, paymentBalanceTool, CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, MCP_CONNECTION_TOOLS, mcpToolCallTool, mcpToolsListTool, DISCOVERY_TOOLS, agentSearchTool, agentGetTool, MARKETPLACE_TOOLS, marketplaceViewTool, CONTEXT_TOOLS, contextReadTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, GRANTS_TOOLS, listGrantsTool, LINK_TOOLS, linkProposeTool, linkListTool, INTRODUCTION_TOOLS, introductionMintTool, introductionRedeemTool, introductionListTool, introductionRevokeTool, ARTIFACT_TOOLS, recordArtifactTool, CHAT_TOOLS, openConversationTool, } from './platform/tools/index.js';
|
|
70
|
+
export { PROTOCOL_TOOLS, agreementProposeTool, agreementSubcontractTool, agreementRespondTool, agreementClaimTool, agreementCounterTool, agreementGetTool, agreementRevokeTool, agreementFulfillTool, taskCreateTool, taskSetResultTool, taskUpdatePlanStepTool, dispatchProtocolOp, PAYMENT_TOOLS, paymentBalanceTool, CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, MCP_CONNECTION_TOOLS, mcpToolCallTool, mcpToolsListTool, DISCOVERY_TOOLS, agentSearchTool, agentGetTool, MARKETPLACE_TOOLS, marketplaceViewTool, CONTEXT_TOOLS, contextReadTool, contextRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, GRANTS_TOOLS, listGrantsTool, PLAN_TOOLS, taskReplacePlanTool, LINK_TOOLS, linkProposeTool, linkListTool, INTRODUCTION_TOOLS, introductionMintTool, introductionRedeemTool, introductionListTool, introductionRevokeTool, ARTIFACT_TOOLS, recordArtifactTool, CHAT_TOOLS, openConversationTool, } from './platform/tools/index.js';
|
|
71
71
|
export { CLAUDE_SDK_TOOLS } from './brain/claudeSdkTools.js';
|
|
72
72
|
export { PROTOCOL_TOOL_NAMES, PROTOCOL_TOOL_TO_OPERATION, mapProtocolToolToOperation, isProtocolToolName } from './tasks/protocolRegistry.js';
|
|
73
73
|
export { InMemoryStore, FileMemoryStore } from './memory/MemoryStore.js';
|
package/dist/index.js
CHANGED
|
@@ -79,7 +79,7 @@ export {
|
|
|
79
79
|
// Tier 1 — protocol grammar (framework-managed via taskTools).
|
|
80
80
|
PROTOCOL_TOOLS, agreementProposeTool, agreementSubcontractTool, agreementRespondTool, agreementClaimTool, agreementCounterTool, agreementGetTool, agreementRevokeTool, agreementFulfillTool, taskCreateTool, taskSetResultTool, taskUpdatePlanStepTool, dispatchProtocolOp,
|
|
81
81
|
// Tier 2 — opt-in capability bundles (spread into `tools:`).
|
|
82
|
-
PAYMENT_TOOLS, paymentBalanceTool, CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, MCP_CONNECTION_TOOLS, mcpToolCallTool, mcpToolsListTool, DISCOVERY_TOOLS, agentSearchTool, agentGetTool, MARKETPLACE_TOOLS, marketplaceViewTool, CONTEXT_TOOLS, contextReadTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, GRANTS_TOOLS, listGrantsTool, LINK_TOOLS, linkProposeTool, linkListTool, INTRODUCTION_TOOLS, introductionMintTool, introductionRedeemTool, introductionListTool, introductionRevokeTool, ARTIFACT_TOOLS, recordArtifactTool, CHAT_TOOLS, openConversationTool, } from './platform/tools/index.js';
|
|
82
|
+
PAYMENT_TOOLS, paymentBalanceTool, CONNECTION_TOOLS, connectionProxyTool, connectionListGrantsTool, requestConnectionTool, MCP_CONNECTION_TOOLS, mcpToolCallTool, mcpToolsListTool, DISCOVERY_TOOLS, agentSearchTool, agentGetTool, MARKETPLACE_TOOLS, marketplaceViewTool, CONTEXT_TOOLS, contextReadTool, contextRequestTool, contextDelegateTool, contextExpandReachTool, contextDiscoverGrantableTool, GRANTS_TOOLS, listGrantsTool, PLAN_TOOLS, taskReplacePlanTool, LINK_TOOLS, linkProposeTool, linkListTool, INTRODUCTION_TOOLS, introductionMintTool, introductionRedeemTool, introductionListTool, introductionRevokeTool, ARTIFACT_TOOLS, recordArtifactTool, CHAT_TOOLS, openConversationTool, } from './platform/tools/index.js';
|
|
83
83
|
// The always-on toolset for the hosted Claude cognition (parity guard
|
|
84
84
|
// reads this as part of the real SDK registry).
|
|
85
85
|
export { CLAUDE_SDK_TOOLS } from './brain/claudeSdkTools.js';
|
|
@@ -22,6 +22,8 @@ export interface NormalizedEvent {
|
|
|
22
22
|
entryType?: string;
|
|
23
23
|
operation?: string;
|
|
24
24
|
agreementId?: string;
|
|
25
|
+
/** Agreement row stamp — host skip for agent-formed activation reads this. */
|
|
26
|
+
formedBy?: 'principal' | 'agent';
|
|
25
27
|
result?: unknown;
|
|
26
28
|
text?: string;
|
|
27
29
|
};
|
|
@@ -63,6 +65,7 @@ interface WireMetadata {
|
|
|
63
65
|
agreement?: {
|
|
64
66
|
agreementId?: string;
|
|
65
67
|
status?: string;
|
|
68
|
+
formedBy?: string;
|
|
66
69
|
proposal?: {
|
|
67
70
|
status?: string;
|
|
68
71
|
};
|
|
@@ -32,6 +32,10 @@ function wireAgreementId(metadata) {
|
|
|
32
32
|
metadata.agreementId;
|
|
33
33
|
return typeof raw === 'string' ? raw : '';
|
|
34
34
|
}
|
|
35
|
+
function wireFormedBy(metadata) {
|
|
36
|
+
const raw = metadata.service?.agreement?.formedBy;
|
|
37
|
+
return raw === 'principal' || raw === 'agent' ? raw : undefined;
|
|
38
|
+
}
|
|
35
39
|
/**
|
|
36
40
|
* Self-hire / fully-consented create activates before the chat fan-out lands.
|
|
37
41
|
* The wire still says `proposal_created`, but the embedded agreement is already
|
|
@@ -102,6 +106,8 @@ export function normalizeIncomingEvent({ text, metadata = {} }) {
|
|
|
102
106
|
const entryType = typeof metadata.entryType === 'string' && metadata.entryType
|
|
103
107
|
? metadata.entryType
|
|
104
108
|
: undefined;
|
|
109
|
+
const wakeAgreementId = wireAgreementId(metadata);
|
|
110
|
+
const formedBy = wireFormedBy(metadata);
|
|
105
111
|
const base = {
|
|
106
112
|
senderId: metadata.sender?.id,
|
|
107
113
|
senderType,
|
|
@@ -109,6 +115,8 @@ export function normalizeIncomingEvent({ text, metadata = {} }) {
|
|
|
109
115
|
messageId: typeof metadata.messageId === 'string' && metadata.messageId ? metadata.messageId : undefined,
|
|
110
116
|
timestamp: Date.now(),
|
|
111
117
|
...(entryType ? { entryType } : {}),
|
|
118
|
+
...(wakeAgreementId ? { agreementId: wakeAgreementId } : {}),
|
|
119
|
+
...(formedBy ? { formedBy } : {}),
|
|
112
120
|
};
|
|
113
121
|
if (isAgreementLifecycleNotification(metadata)) {
|
|
114
122
|
const operation = isAlreadyActivatedProposalCreated(metadata)
|
|
@@ -192,7 +192,7 @@ export declare function trimDescription(desc: string | undefined): string | unde
|
|
|
192
192
|
* await agent.connectAsync();
|
|
193
193
|
*
|
|
194
194
|
* process.on('SIGTERM', async () => {
|
|
195
|
-
* agent.disconnect();
|
|
195
|
+
* await agent.disconnect();
|
|
196
196
|
* await agent.drain();
|
|
197
197
|
* await closeMcp();
|
|
198
198
|
* });
|
|
@@ -333,7 +333,7 @@ function capTools(tools, agentId, maxTools, extraToolCount, allowedTools) {
|
|
|
333
333
|
* await agent.connectAsync();
|
|
334
334
|
*
|
|
335
335
|
* process.on('SIGTERM', async () => {
|
|
336
|
-
* agent.disconnect();
|
|
336
|
+
* await agent.disconnect();
|
|
337
337
|
* await agent.drain();
|
|
338
338
|
* await closeMcp();
|
|
339
339
|
* });
|
|
@@ -56,15 +56,29 @@ export declare class FileMemoryStore implements MemoryStore {
|
|
|
56
56
|
delete(key: string): Promise<void>;
|
|
57
57
|
}
|
|
58
58
|
/**
|
|
59
|
-
* Section a wake may read and write.
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
59
|
+
* Section a wake may read and write.
|
|
60
|
+
*
|
|
61
|
+
* Pick from the hire this wake is acting under — the same fact the
|
|
62
|
+
* server uses for acting-for: a pinned agreement, the delivery / event
|
|
63
|
+
* hire, or an `agrn-` lane. A chat-room wake with exactly one hire
|
|
64
|
+
* behind it lands in that hire's section. Zero hires, or more than one
|
|
65
|
+
* customer present, share the agent's own section — the "operator only"
|
|
66
|
+
* answer in memory form. Never pick from the chat id alone.
|
|
63
67
|
*/
|
|
64
68
|
export declare function memorySectionForWake(input: {
|
|
65
69
|
sessionId?: string | null;
|
|
66
70
|
activeAgreementId?: string | null;
|
|
71
|
+
/** Hire ids already on this wake (delivery / event / acting-for). */
|
|
72
|
+
wakeAgreementIds?: readonly (string | null | undefined)[];
|
|
67
73
|
}): string;
|
|
74
|
+
/** Distinct non-empty hire ids, first-seen order. */
|
|
75
|
+
export declare function uniqueHireIds(raw: readonly (string | null | undefined)[]): string[];
|
|
76
|
+
/**
|
|
77
|
+
* Hire ids the waking event already named. A chat-room message that
|
|
78
|
+
* carries the delivery's `agreementId` is how two customer rooms
|
|
79
|
+
* partition without a pinned ctx.
|
|
80
|
+
*/
|
|
81
|
+
export declare function hireIdsFromEvent(event: unknown): string[];
|
|
68
82
|
/**
|
|
69
83
|
* Present a store as if it only contained one section. Agent keys stay
|
|
70
84
|
* unchanged; get/put/list/delete all go through `section:`.
|
|
@@ -78,22 +78,62 @@ export class FileMemoryStore {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
/**
|
|
81
|
-
* Section a wake may read and write.
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
81
|
+
* Section a wake may read and write.
|
|
82
|
+
*
|
|
83
|
+
* Pick from the hire this wake is acting under — the same fact the
|
|
84
|
+
* server uses for acting-for: a pinned agreement, the delivery / event
|
|
85
|
+
* hire, or an `agrn-` lane. A chat-room wake with exactly one hire
|
|
86
|
+
* behind it lands in that hire's section. Zero hires, or more than one
|
|
87
|
+
* customer present, share the agent's own section — the "operator only"
|
|
88
|
+
* answer in memory form. Never pick from the chat id alone.
|
|
85
89
|
*/
|
|
86
90
|
export function memorySectionForWake(input) {
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return `hire:${fromLane}`;
|
|
91
|
+
const ids = uniqueHireIds([
|
|
92
|
+
input.activeAgreementId,
|
|
93
|
+
...(input.wakeAgreementIds ?? []),
|
|
94
|
+
agreementIdFromAgrnSession(input.sessionId),
|
|
95
|
+
]);
|
|
96
|
+
if (ids.length === 1)
|
|
97
|
+
return `hire:${ids[0]}`;
|
|
95
98
|
return 'agent';
|
|
96
99
|
}
|
|
100
|
+
/** Distinct non-empty hire ids, first-seen order. */
|
|
101
|
+
export function uniqueHireIds(raw) {
|
|
102
|
+
const out = [];
|
|
103
|
+
const seen = new Set();
|
|
104
|
+
for (const value of raw) {
|
|
105
|
+
const id = typeof value === 'string' ? value.trim() : '';
|
|
106
|
+
if (!id || seen.has(id))
|
|
107
|
+
continue;
|
|
108
|
+
seen.add(id);
|
|
109
|
+
out.push(id);
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Hire ids the waking event already named. A chat-room message that
|
|
115
|
+
* carries the delivery's `agreementId` is how two customer rooms
|
|
116
|
+
* partition without a pinned ctx.
|
|
117
|
+
*/
|
|
118
|
+
export function hireIdsFromEvent(event) {
|
|
119
|
+
if (!event || typeof event !== 'object')
|
|
120
|
+
return [];
|
|
121
|
+
const rec = event;
|
|
122
|
+
const resource = rec.resource && typeof rec.resource === 'object'
|
|
123
|
+
? rec.resource
|
|
124
|
+
: {};
|
|
125
|
+
const result = rec.result && typeof rec.result === 'object'
|
|
126
|
+
? rec.result
|
|
127
|
+
: {};
|
|
128
|
+
return uniqueHireIds([
|
|
129
|
+
typeof rec.agreementId === 'string' ? rec.agreementId : null,
|
|
130
|
+
typeof resource.agreementId === 'string' ? resource.agreementId : null,
|
|
131
|
+
typeof result.agreementId === 'string' ? result.agreementId : null,
|
|
132
|
+
rec.kind === 'agreement' && typeof rec.resourceId === 'string'
|
|
133
|
+
? rec.resourceId
|
|
134
|
+
: null,
|
|
135
|
+
]);
|
|
136
|
+
}
|
|
97
137
|
function agreementIdFromAgrnSession(sessionId) {
|
|
98
138
|
return agreementIdFromLane(sessionId)?.trim() || null;
|
|
99
139
|
}
|
|
@@ -19,12 +19,17 @@ export declare class ConnectionPool {
|
|
|
19
19
|
private readonly hosts;
|
|
20
20
|
private readonly responseListeners;
|
|
21
21
|
/**
|
|
22
|
-
* Register N agent configs. Nothing connects until `connectAll()
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* Register N agent configs. Nothing connects until `connectAll()`. A config
|
|
23
|
+
* that cannot start is reported and left down; the others still host. Only
|
|
24
|
+
* a launch where nobody came up is refused.
|
|
25
25
|
*/
|
|
26
26
|
register(configs: AgentOptions[], metaArr?: ConnectionManagerMeta[]): void;
|
|
27
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Connect every registered agent. One host that cannot come up is reported
|
|
29
|
+
* and does not take the rest down — a fleet where 28 of 29 are live is not
|
|
30
|
+
* a launch worth aborting. Zero started is: the process has nothing to
|
|
31
|
+
* host and must restart.
|
|
32
|
+
*/
|
|
28
33
|
connectAll(): Promise<void>;
|
|
29
34
|
/** The host for one agent, connecting it if it somehow is not up yet. */
|
|
30
35
|
startAgent(agentId: string): Promise<Agent>;
|
|
@@ -26,9 +26,9 @@ export class ConnectionPool {
|
|
|
26
26
|
// Listeners enqueued by requestFrom — drained on outbound send-message effects.
|
|
27
27
|
responseListeners = new Map();
|
|
28
28
|
/**
|
|
29
|
-
* Register N agent configs. Nothing connects until `connectAll()
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
* Register N agent configs. Nothing connects until `connectAll()`. A config
|
|
30
|
+
* that cannot start is reported and left down; the others still host. Only
|
|
31
|
+
* a launch where nobody came up is refused.
|
|
32
32
|
*/
|
|
33
33
|
register(configs, metaArr = []) {
|
|
34
34
|
const metaByAgentId = new Map();
|
|
@@ -64,18 +64,28 @@ export class ConnectionPool {
|
|
|
64
64
|
runtimeLog.debug('ConnectionPool', `connected "${agentId}"`);
|
|
65
65
|
return host;
|
|
66
66
|
}, async (host) => {
|
|
67
|
-
host.disconnect();
|
|
67
|
+
await host.disconnect();
|
|
68
68
|
this.hosts.delete(agentId);
|
|
69
69
|
runtimeLog.debug('ConnectionPool', `disconnected "${agentId}"`);
|
|
70
70
|
}, metaByAgentId.get(agentId));
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* Connect every registered agent. One host that cannot come up is reported
|
|
75
|
+
* and does not take the rest down — a fleet where 28 of 29 are live is not
|
|
76
|
+
* a launch worth aborting. Zero started is: the process has nothing to
|
|
77
|
+
* host and must restart.
|
|
78
|
+
*/
|
|
74
79
|
async connectAll() {
|
|
75
80
|
await this.manager.connectAll();
|
|
76
81
|
const connected = this.listActive().length;
|
|
82
|
+
if (this.size === 0)
|
|
83
|
+
return;
|
|
84
|
+
if (connected === 0) {
|
|
85
|
+
throw new Error(`0/${this.size} agents started; process must restart`);
|
|
86
|
+
}
|
|
77
87
|
if (connected !== this.size) {
|
|
78
|
-
|
|
88
|
+
runtimeLog.warn('ConnectionPool', `Only ${connected}/${this.size} agents started — hosting the ones that came up. One host must not take the rest down.`);
|
|
79
89
|
}
|
|
80
90
|
}
|
|
81
91
|
/** The host for one agent, connecting it if it somehow is not up yet. */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { WorkScope } from '../runtime/wakeEffects.js';
|
|
1
2
|
export interface StateTimeoutEvent {
|
|
2
3
|
type: 'state-timeout';
|
|
3
4
|
state: string;
|
|
@@ -13,7 +14,7 @@ export interface StateTimeoutEvent {
|
|
|
13
14
|
* check-back is none of that: it belongs to the agent's own plan, not to a
|
|
14
15
|
* state's declared timeout, and the Claude brain has no states at all.
|
|
15
16
|
*/
|
|
16
|
-
export interface WakeAtEvent {
|
|
17
|
+
export interface WakeAtEvent extends WorkScope {
|
|
17
18
|
type: 'wake-at';
|
|
18
19
|
/** What the agent said it was waiting for. Free text, for the brain to read. */
|
|
19
20
|
reason: string | null;
|
|
@@ -35,7 +36,7 @@ export declare const MAX_WAKE_DELAY_MS: number;
|
|
|
35
36
|
* (caller-provided enqueue → EventQueue), so it serializes behind any real
|
|
36
37
|
* event and can never race one. Epochs make stale frames identifiable: a
|
|
37
38
|
* queued timeout frame whose epoch is not the lane's currently-armed epoch was
|
|
38
|
-
*
|
|
39
|
+
* cancelled or replaced and must be dropped.
|
|
39
40
|
*
|
|
40
41
|
* In-memory only: process restart loses all timers; each lane re-arms on its
|
|
41
42
|
* next real event (sessionStore persists { ctx, state } but not timers).
|
|
@@ -66,13 +67,12 @@ export declare class StateTimers {
|
|
|
66
67
|
* the agent changing its mind, which is allowed and is why this replaces
|
|
67
68
|
* rather than adds.
|
|
68
69
|
*
|
|
69
|
-
* In memory only. A restart loses it
|
|
70
|
-
*
|
|
71
|
-
* listed Later on the ticket. Returns what actually got armed so the caller
|
|
70
|
+
* In memory only. A restart loses it; recovery requires the agent to re-arm
|
|
71
|
+
* on its next real event. Returns what actually got armed so the caller
|
|
72
72
|
* can tell the agent, since "I will wake at X" is a promise the agent will
|
|
73
73
|
* otherwise make on its own.
|
|
74
74
|
*/
|
|
75
|
-
armWakeAt(laneKey: string, at: Date | number, reason?: string | null): {
|
|
75
|
+
armWakeAt(laneKey: string, at: Date | number, reason?: string | null, scope?: WorkScope): {
|
|
76
76
|
firesAt: number;
|
|
77
77
|
delayMs: number;
|
|
78
78
|
};
|
|
@@ -82,6 +82,12 @@ export declare class StateTimers {
|
|
|
82
82
|
firesAt: number;
|
|
83
83
|
reason: string | null;
|
|
84
84
|
} | null;
|
|
85
|
+
/**
|
|
86
|
+
* Consume the timer for a frame that survived stripStale. Liveness tracks
|
|
87
|
+
* inactivity, so any event resets it. An explicit wake is an appointment:
|
|
88
|
+
* unrelated traffic must leave it armed, even after it fired and queued.
|
|
89
|
+
*/
|
|
90
|
+
consumeForEvent(event: unknown, laneKey: string): void;
|
|
85
91
|
clear(laneKey: string): void;
|
|
86
92
|
clearAll(): void;
|
|
87
93
|
/**
|
|
@@ -12,7 +12,7 @@ export const MAX_WAKE_DELAY_MS = 2 ** 31 - 1;
|
|
|
12
12
|
* (caller-provided enqueue → EventQueue), so it serializes behind any real
|
|
13
13
|
* event and can never race one. Epochs make stale frames identifiable: a
|
|
14
14
|
* queued timeout frame whose epoch is not the lane's currently-armed epoch was
|
|
15
|
-
*
|
|
15
|
+
* cancelled or replaced and must be dropped.
|
|
16
16
|
*
|
|
17
17
|
* In-memory only: process restart loses all timers; each lane re-arms on its
|
|
18
18
|
* next real event (sessionStore persists { ctx, state } but not timers).
|
|
@@ -68,17 +68,17 @@ export class StateTimers {
|
|
|
68
68
|
* the agent changing its mind, which is allowed and is why this replaces
|
|
69
69
|
* rather than adds.
|
|
70
70
|
*
|
|
71
|
-
* In memory only. A restart loses it
|
|
72
|
-
*
|
|
73
|
-
* listed Later on the ticket. Returns what actually got armed so the caller
|
|
71
|
+
* In memory only. A restart loses it; recovery requires the agent to re-arm
|
|
72
|
+
* on its next real event. Returns what actually got armed so the caller
|
|
74
73
|
* can tell the agent, since "I will wake at X" is a promise the agent will
|
|
75
74
|
* otherwise make on its own.
|
|
76
75
|
*/
|
|
77
|
-
armWakeAt(laneKey, at, reason = null) {
|
|
76
|
+
armWakeAt(laneKey, at, reason = null, scope) {
|
|
78
77
|
const firesAtRaw = at instanceof Date ? at.getTime() : Number(at);
|
|
79
78
|
if (!Number.isFinite(firesAtRaw)) {
|
|
80
79
|
throw new Error('StateTimers.armWakeAt: `at` must be a Date or epoch ms');
|
|
81
80
|
}
|
|
81
|
+
const wakeScope = { ...scope };
|
|
82
82
|
const now = Date.now();
|
|
83
83
|
// A wake already due fires on the next turn of the event loop rather than
|
|
84
84
|
// being refused: "wake me at 09:00" arriving at 09:00:01 means now, and an
|
|
@@ -92,7 +92,7 @@ export class StateTimers {
|
|
|
92
92
|
const epoch = ++this._epoch;
|
|
93
93
|
const handle = setTimeout(() => {
|
|
94
94
|
runtimeLog.debug('StateTimers', `wake-at fired laneKey=${laneKey} reason=${reason ?? 'none'} epoch=${epoch}`);
|
|
95
|
-
this._enqueue({ type: 'wake-at', reason, scheduledFor: firesAtRaw, epoch }, laneKey);
|
|
95
|
+
this._enqueue({ type: 'wake-at', reason, scheduledFor: firesAtRaw, epoch, ...wakeScope }, laneKey);
|
|
96
96
|
}, delayMs);
|
|
97
97
|
handle.unref?.();
|
|
98
98
|
this._timers.set(laneKey, {
|
|
@@ -112,6 +112,26 @@ export class StateTimers {
|
|
|
112
112
|
const t = this._timers.get(laneKey);
|
|
113
113
|
return t ? { kind: t.kind, firesAt: t.firesAt, reason: t.reason } : null;
|
|
114
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Consume the timer for a frame that survived stripStale. Liveness tracks
|
|
117
|
+
* inactivity, so any event resets it. An explicit wake is an appointment:
|
|
118
|
+
* unrelated traffic must leave it armed, even after it fired and queued.
|
|
119
|
+
*/
|
|
120
|
+
consumeForEvent(event, laneKey) {
|
|
121
|
+
const pending = this._timers.get(laneKey);
|
|
122
|
+
if (!pending)
|
|
123
|
+
return;
|
|
124
|
+
const frame = event;
|
|
125
|
+
const events = frame?.type === 'batch' && Array.isArray(frame.events)
|
|
126
|
+
? frame.events
|
|
127
|
+
: [event];
|
|
128
|
+
const consumesWake = events.some((entry) => {
|
|
129
|
+
const e = entry;
|
|
130
|
+
return e?.type === 'wake-at' && e.epoch === pending.epoch;
|
|
131
|
+
});
|
|
132
|
+
if (pending.kind === 'liveness' || consumesWake)
|
|
133
|
+
this.clear(laneKey);
|
|
134
|
+
}
|
|
115
135
|
clear(laneKey) {
|
|
116
136
|
const t = this._timers.get(laneKey);
|
|
117
137
|
if (!t)
|
|
@@ -35,7 +35,11 @@ async function buildSnapshot(deps, chatId, agentId, opts = {}) {
|
|
|
35
35
|
catch (err) {
|
|
36
36
|
// Defensive: server normally signals a dead chat as 200 + chatMissing, but
|
|
37
37
|
// a raw 404 degrades the same way so message wakes still skip it.
|
|
38
|
-
|
|
38
|
+
const status = err?.status;
|
|
39
|
+
// 403 = exists but this agent has no grant. Same as deleted:
|
|
40
|
+
// no world to act in. Leaving it as an empty snapshot used to run a
|
|
41
|
+
// full turn against refusals.
|
|
42
|
+
if (status === 404 || status === 403) {
|
|
39
43
|
return emptySnapshot(agentId, true);
|
|
40
44
|
}
|
|
41
45
|
runtimeLog.warn('ZiggsEffectHandler', `snapshot fetch failed: ${err?.message ?? err}`);
|
|
@@ -301,9 +305,19 @@ export function createZiggsEffectHandler(deps) {
|
|
|
301
305
|
});
|
|
302
306
|
}
|
|
303
307
|
case 'send-message': {
|
|
304
|
-
await deps.messageSender(effect.text, effect.receiverId, effect.sessionId, effect.messageId);
|
|
308
|
+
const sent = await deps.messageSender(effect.text, effect.receiverId, effect.sessionId, effect.messageId);
|
|
305
309
|
outbox.track(effect.sessionId, { text: effect.text, receiverId: effect.receiverId });
|
|
306
|
-
|
|
310
|
+
// Carry the send's own report back to whoever asked for the send. A
|
|
311
|
+
// sender that cannot see where its message went cannot tell a delivered
|
|
312
|
+
// answer from a dropped one, which is the whole failure here.
|
|
313
|
+
const report = (sent ?? {});
|
|
314
|
+
return {
|
|
315
|
+
ok: true,
|
|
316
|
+
...(report.chatId ? { chatId: report.chatId } : {}),
|
|
317
|
+
...(report.messageId ? { messageId: report.messageId } : {}),
|
|
318
|
+
...(report.delivered ? { delivered: report.delivered } : {}),
|
|
319
|
+
...(report.to ? { to: report.to } : {}),
|
|
320
|
+
};
|
|
307
321
|
}
|
|
308
322
|
case 'record-event': {
|
|
309
323
|
// Routes through ArtifactsClient as an agent-private artifact —
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type InboxChatNews, type InboxRequestRef, type InboxTaskRef, type Creds } from '@ziggs-ai/api-client';
|
|
1
|
+
import { type InboxChatNews, type InboxDeliveryRef, type InboxRequestRef, type InboxTaskRef, type Creds } from '@ziggs-ai/api-client';
|
|
2
2
|
import { SeenMessages } from './SeenMessages.js';
|
|
3
3
|
/**
|
|
4
4
|
* Per-agent inbox long-poll loop (delivery law, phase 4). This is the ONLY way
|
|
@@ -60,12 +60,11 @@ export interface InboxPassResult {
|
|
|
60
60
|
*/
|
|
61
61
|
export declare function chatsInWakeOrder(chats: InboxChatNews[]): InboxChatNews[];
|
|
62
62
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* It never acks — the main pass still owns the watermark.
|
|
63
|
+
* The one hire a chat's assigned deliveries name, or null when the room
|
|
64
|
+
* has none or more than one. Memory (and acting-for) treat "several
|
|
65
|
+
* customers in one room" as the shared section.
|
|
67
66
|
*/
|
|
68
|
-
export declare function
|
|
67
|
+
export declare function soleHireAgreementIdForChat(deliveries: readonly Pick<InboxDeliveryRef, 'chatId' | 'agreementId' | 'assigneeId'>[], chatId: string, ownAgentId: string): string | null;
|
|
69
68
|
/**
|
|
70
69
|
* One long-poll pass: hold up to `waitSeconds` for new inbox content, then read
|
|
71
70
|
* the deltas, deliver them into the host, and ack. Returns what it delivered.
|
|
@@ -114,5 +113,4 @@ export declare function heldPassAfter(prev: number, pass: Pick<InboxPassResult,
|
|
|
114
113
|
*/
|
|
115
114
|
export declare function runInboxLoop(deps: InboxLoopDeps, opts?: {
|
|
116
115
|
waitSeconds?: number;
|
|
117
|
-
probePollMs?: number;
|
|
118
116
|
}): InboxLoopHandle;
|