@ziggs-ai/agent-sdk 0.41.0 → 0.41.2
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 +6 -0
- package/dist/Agent.d.ts +14 -0
- package/dist/Agent.js +64 -1
- package/dist/platform/StateTimers.d.ts +8 -6
- package/dist/platform/StateTimers.js +8 -6
- package/dist/platform/WakeLedger.d.ts +43 -0
- package/dist/platform/WakeLedger.js +79 -0
- package/dist/platform/agreements/AgreementService.d.ts +4 -2
- package/dist/platform/agreements/AgreementService.js +4 -2
- package/dist/platform/tools/index.d.ts +2 -1
- package/dist/platform/tools/index.js +2 -1
- package/dist/platform/tools/tier1/protocolTools.js +1 -1
- package/dist/platform/tools/tier1/wakeTools.d.ts +3 -1
- package/dist/platform/tools/tier1/wakeTools.js +3 -1
- package/dist/platform/tools/tier2/marketplaceTools.js +1 -1
- package/dist/runtime/agreementContext.js +2 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -32,6 +32,12 @@
|
|
|
32
32
|
|
|
33
33
|
## Unreleased
|
|
34
34
|
|
|
35
|
+
## 0.41.2
|
|
36
|
+
|
|
37
|
+
## 0.41.1
|
|
38
|
+
|
|
39
|
+
- Publishing names a third audience, `company`: every team in your active organization's company. A listing posted to it counts as a listing when the agent reads its wakes. Built on `@ziggs-ai/api-client` 0.37.26.
|
|
40
|
+
|
|
35
41
|
## 0.41.0
|
|
36
42
|
|
|
37
43
|
- Claude-brain agents run work only under a task they hold. A custom tool, and the platform tools that write work onto a task, need a task assigned to the agent under an active work agreement, verified with the backend on every call; the task's agreement is the permission. The agent names the task in each wake: custom tools gain a `taskId` parameter the model fills and the handler never sees, later calls in the wake keep the task last named, and a task the agent opens for itself in that wake is the one it works under next. A task from an earlier wake is never a default. The host never opens a task and reads nothing off the waking message. Agreeing, managing, reading and talking stay open. Tool results carry the verified binding and operation records are scoped to it. `executionControl` lets other brains opt in or a host opt out; `execution: 'conversation'` keeps a custom tool open. Third-party API and MCP clients are unchanged.
|
package/dist/Agent.d.ts
CHANGED
|
@@ -258,6 +258,8 @@ export declare class Agent {
|
|
|
258
258
|
private telemetryClient?;
|
|
259
259
|
private seenMessages;
|
|
260
260
|
private stateTimers;
|
|
261
|
+
/** The agent's own check-backs, kept so a restart re-arms them. */
|
|
262
|
+
private wakeLedger;
|
|
261
263
|
/** The per-agent inbox long-poll loop — this agent's sole wake source. */
|
|
262
264
|
private inboxLoop;
|
|
263
265
|
private inboxLoopEnabled;
|
|
@@ -435,6 +437,18 @@ export declare class Agent {
|
|
|
435
437
|
private _armIfTimedParked;
|
|
436
438
|
private _resolveOwnAgentId;
|
|
437
439
|
connectAsync(_timeout?: number): Promise<this>;
|
|
440
|
+
/**
|
|
441
|
+
* Arm the lane's timer for the agent's own `wake_at`, and save it so a
|
|
442
|
+
* restart arms it again.
|
|
443
|
+
*/
|
|
444
|
+
private armOwnWake;
|
|
445
|
+
/**
|
|
446
|
+
* Arm again the check-backs this agent asked for before the process went
|
|
447
|
+
* down. A wake whose time passed in the meantime fires on the next turn,
|
|
448
|
+
* once. The lane gets a session first, since a timer frame for a lane with
|
|
449
|
+
* no session is dropped.
|
|
450
|
+
*/
|
|
451
|
+
private restoreWakes;
|
|
438
452
|
/**
|
|
439
453
|
* plain string compare against {@link AgentOptions.requestMatchTags}
|
|
440
454
|
* and `specialization`. No match → return (caller acks). Match → normal wake.
|
package/dist/Agent.js
CHANGED
|
@@ -20,6 +20,7 @@ import { contextReadTool, openTool, accessExplainTool } from './platform/tools/t
|
|
|
20
20
|
import { EventQueue } from './platform/EventQueue.js';
|
|
21
21
|
import { StateTimers } from './platform/StateTimers.js';
|
|
22
22
|
import { laneKeyFor } from './platform/laneKey.js';
|
|
23
|
+
import { WakeLedger } from './platform/WakeLedger.js';
|
|
23
24
|
import { WAKE_TOOLS } from './platform/tools/tier1/wakeTools.js';
|
|
24
25
|
import { createZiggsEffectHandler } from './platform/ZiggsEffectHandler.js';
|
|
25
26
|
import { normalizeIncomingEvent } from './ingress/normalizeIncoming.js';
|
|
@@ -222,6 +223,8 @@ export class Agent {
|
|
|
222
223
|
telemetryClient;
|
|
223
224
|
seenMessages = new SeenMessages();
|
|
224
225
|
stateTimers;
|
|
226
|
+
/** The agent's own check-backs, kept so a restart re-arms them. */
|
|
227
|
+
wakeLedger;
|
|
225
228
|
/** The per-agent inbox long-poll loop — this agent's sole wake source. */
|
|
226
229
|
inboxLoop = null;
|
|
227
230
|
inboxLoopEnabled;
|
|
@@ -304,6 +307,7 @@ export class Agent {
|
|
|
304
307
|
operatorKey: options.operatorKey,
|
|
305
308
|
agentId: options.agentId,
|
|
306
309
|
});
|
|
310
|
+
this.wakeLedger = new WakeLedger(this.memory);
|
|
307
311
|
this.sessionStore = options.sessionStore ?? null;
|
|
308
312
|
this._validate();
|
|
309
313
|
this.pollRunner = createDeclaredPollRunner(this.options, {
|
|
@@ -424,7 +428,7 @@ export class Agent {
|
|
|
424
428
|
// is built once and has no laneKey, so it derives one the same way the
|
|
425
429
|
// event queue does (see laneKeyFor).
|
|
426
430
|
wakeScheduler: {
|
|
427
|
-
armWakeAt: (sessionId, at, reason) => this.
|
|
431
|
+
armWakeAt: (sessionId, at, reason) => this.armOwnWake(sessionId, at, reason),
|
|
428
432
|
},
|
|
429
433
|
});
|
|
430
434
|
this.brain = this.options.brain?.kind === 'handler'
|
|
@@ -466,6 +470,10 @@ export class Agent {
|
|
|
466
470
|
// nothing to redeliver, so its failure is logged and dropped here rather
|
|
467
471
|
// than left as an unhandled rejection (made enqueue reject).
|
|
468
472
|
this.stateTimers = new StateTimers((ev, laneKey) => {
|
|
473
|
+
// A check-back that fired is no longer waiting: a restart from here on
|
|
474
|
+
// must not fire it again.
|
|
475
|
+
if (ev.type === 'wake-at')
|
|
476
|
+
this.wakeLedger.done(laneKey);
|
|
469
477
|
void this.eventQueue.enqueue(ev, laneKey).catch((err) => {
|
|
470
478
|
runtimeLog.warn('Agent', `state-timer frame failed laneKey=${laneKey}: ${err?.message || err}`);
|
|
471
479
|
});
|
|
@@ -1604,8 +1612,63 @@ export class Agent {
|
|
|
1604
1612
|
}
|
|
1605
1613
|
this.pollRunner?.start();
|
|
1606
1614
|
this.started = true;
|
|
1615
|
+
// Not awaited: starting stays synchronous, so a second connect in the
|
|
1616
|
+
// same turn still sees `started`. A wake re-armed a moment later is fine.
|
|
1617
|
+
void this.restoreWakes();
|
|
1607
1618
|
return this;
|
|
1608
1619
|
}
|
|
1620
|
+
/**
|
|
1621
|
+
* Arm the lane's timer for the agent's own `wake_at`, and save it so a
|
|
1622
|
+
* restart arms it again.
|
|
1623
|
+
*/
|
|
1624
|
+
armOwnWake(sessionId, at, reason) {
|
|
1625
|
+
const laneKey = laneKeyFor(this.options.agentId, sessionId);
|
|
1626
|
+
const scope = this.wakeEffects.getStore()?.scope;
|
|
1627
|
+
const armed = this.stateTimers.armWakeAt(laneKey, at, reason, scope);
|
|
1628
|
+
this.wakeLedger.arm({
|
|
1629
|
+
laneKey,
|
|
1630
|
+
sessionId,
|
|
1631
|
+
firesAt: armed.firesAt,
|
|
1632
|
+
reason,
|
|
1633
|
+
...(scope ? { scope } : {}),
|
|
1634
|
+
});
|
|
1635
|
+
return { ...armed, durable: this.wakeLedger.durable };
|
|
1636
|
+
}
|
|
1637
|
+
/**
|
|
1638
|
+
* Arm again the check-backs this agent asked for before the process went
|
|
1639
|
+
* down. A wake whose time passed in the meantime fires on the next turn,
|
|
1640
|
+
* once. The lane gets a session first, since a timer frame for a lane with
|
|
1641
|
+
* no session is dropped.
|
|
1642
|
+
*/
|
|
1643
|
+
async restoreWakes() {
|
|
1644
|
+
const agentId = this.options.agentId;
|
|
1645
|
+
if (!agentId)
|
|
1646
|
+
return;
|
|
1647
|
+
let pending;
|
|
1648
|
+
try {
|
|
1649
|
+
pending = await this.wakeLedger.pending();
|
|
1650
|
+
}
|
|
1651
|
+
catch (err) {
|
|
1652
|
+
runtimeLog.warn(this.options.name || 'agent', `could not read saved wakes; they will not be re-armed: ${err?.message || err}`);
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1655
|
+
for (const wake of pending) {
|
|
1656
|
+
if (wake.laneKey !== laneKeyFor(agentId, wake.sessionId))
|
|
1657
|
+
continue;
|
|
1658
|
+
if (this.stateTimers.peek(wake.laneKey))
|
|
1659
|
+
continue;
|
|
1660
|
+
if (!this.sessions.has(wake.laneKey)) {
|
|
1661
|
+
this.sessions.set(wake.laneKey, { sessionId: wake.sessionId, ownAgentId: agentId });
|
|
1662
|
+
}
|
|
1663
|
+
try {
|
|
1664
|
+
this.stateTimers.armWakeAt(wake.laneKey, wake.firesAt, wake.reason, wake.scope);
|
|
1665
|
+
runtimeLog.info(this.options.name || 'agent', `re-armed a saved wake laneKey=${wake.laneKey} at=${new Date(wake.firesAt).toISOString()}`);
|
|
1666
|
+
}
|
|
1667
|
+
catch (err) {
|
|
1668
|
+
runtimeLog.warn(this.options.name || 'agent', `could not re-arm a saved wake laneKey=${wake.laneKey}: ${err?.message || err}`);
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1609
1672
|
/**
|
|
1610
1673
|
* plain string compare against {@link AgentOptions.requestMatchTags}
|
|
1611
1674
|
* and `specialization`. No match → return (caller acks). Match → normal wake.
|
|
@@ -38,8 +38,10 @@ export declare const MAX_WAKE_DELAY_MS: number;
|
|
|
38
38
|
* queued timeout frame whose epoch is not the lane's currently-armed epoch was
|
|
39
39
|
* cancelled or replaced and must be dropped.
|
|
40
40
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
41
|
+
* The timers themselves live in memory. An agent's own `wake_at` is also
|
|
42
|
+
* written to its memory notes (`WakeLedger`), and the host arms those again
|
|
43
|
+
* when it starts; a host liveness timer is not, and re-arms on the lane's next
|
|
44
|
+
* real event.
|
|
43
45
|
*/
|
|
44
46
|
export declare class StateTimers {
|
|
45
47
|
private _timers;
|
|
@@ -67,10 +69,10 @@ export declare class StateTimers {
|
|
|
67
69
|
* the agent changing its mind, which is allowed and is why this replaces
|
|
68
70
|
* rather than adds.
|
|
69
71
|
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
72
|
+
* The timer is in memory; the Agent saves what it needs to arm it again
|
|
73
|
+
* after a restart. Returns what actually got armed so the caller can tell
|
|
74
|
+
* the agent, since "I will wake at X" is a promise the agent will otherwise
|
|
75
|
+
* make on its own.
|
|
74
76
|
*/
|
|
75
77
|
armWakeAt(laneKey: string, at: Date | number, reason?: string | null, scope?: WorkScope): {
|
|
76
78
|
firesAt: number;
|
|
@@ -14,8 +14,10 @@ export const MAX_WAKE_DELAY_MS = 2 ** 31 - 1;
|
|
|
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
|
-
*
|
|
18
|
-
*
|
|
17
|
+
* The timers themselves live in memory. An agent's own `wake_at` is also
|
|
18
|
+
* written to its memory notes (`WakeLedger`), and the host arms those again
|
|
19
|
+
* when it starts; a host liveness timer is not, and re-arms on the lane's next
|
|
20
|
+
* real event.
|
|
19
21
|
*/
|
|
20
22
|
export class StateTimers {
|
|
21
23
|
_timers = new Map();
|
|
@@ -68,10 +70,10 @@ export class StateTimers {
|
|
|
68
70
|
* the agent changing its mind, which is allowed and is why this replaces
|
|
69
71
|
* rather than adds.
|
|
70
72
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
73
|
+
* The timer is in memory; the Agent saves what it needs to arm it again
|
|
74
|
+
* after a restart. Returns what actually got armed so the caller can tell
|
|
75
|
+
* the agent, since "I will wake at X" is a promise the agent will otherwise
|
|
76
|
+
* make on its own.
|
|
75
77
|
*/
|
|
76
78
|
armWakeAt(laneKey, at, reason = null, scope) {
|
|
77
79
|
const firesAtRaw = at instanceof Date ? at.getTime() : Number(at);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type MemoryStore } from '../memory/MemoryStore.js';
|
|
2
|
+
import type { WorkScope } from '../runtime/wakeEffects.js';
|
|
3
|
+
/** One check-back an agent asked for, as it is kept across a restart. */
|
|
4
|
+
export interface SavedWake {
|
|
5
|
+
laneKey: string;
|
|
6
|
+
sessionId: string;
|
|
7
|
+
firesAt: number;
|
|
8
|
+
reason: string | null;
|
|
9
|
+
scope?: WorkScope;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Where an agent's own check-backs are kept, so a restart does not lose them.
|
|
13
|
+
*
|
|
14
|
+
* The timer itself stays in memory (`StateTimers`). This keeps the facts
|
|
15
|
+
* needed to arm it again: which lane, when, why, and under which work. A wake
|
|
16
|
+
* is written when it is armed and marked done when it fires, so on start the
|
|
17
|
+
* host re-arms exactly the ones still waiting, and one that passed while the
|
|
18
|
+
* host was down fires once.
|
|
19
|
+
*
|
|
20
|
+
* The notes live in the agent's own memory store under a prefix no memory
|
|
21
|
+
* section uses (sections are `hire:<id>`), so the agent's memory tools never
|
|
22
|
+
* see them. Writes are chained so a mark can never land before the arm it
|
|
23
|
+
* follows.
|
|
24
|
+
*
|
|
25
|
+
* Two hosts of the same agent running at once, as during a rolling deploy,
|
|
26
|
+
* can both re-arm a wake and both fire it. That costs one extra wake, and the
|
|
27
|
+
* agent checks for an answer before it chases anyone.
|
|
28
|
+
*/
|
|
29
|
+
export declare class WakeLedger {
|
|
30
|
+
private readonly store;
|
|
31
|
+
private writes;
|
|
32
|
+
constructor(store: MemoryStore);
|
|
33
|
+
/** False when the store is in memory: then a restart loses wakes anyway. */
|
|
34
|
+
get durable(): boolean;
|
|
35
|
+
arm(wake: SavedWake): void;
|
|
36
|
+
done(laneKey: string): void;
|
|
37
|
+
/** The wakes still waiting to fire, for the host to arm again on start. */
|
|
38
|
+
pending(): Promise<SavedWake[]>;
|
|
39
|
+
/** Resolves when every write so far has landed. For tests and shutdown. */
|
|
40
|
+
flushed(): Promise<void>;
|
|
41
|
+
private write;
|
|
42
|
+
private warn;
|
|
43
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { runtimeLog } from '../shared/runtimeLog.js';
|
|
2
|
+
import { InMemoryStore } from '../memory/MemoryStore.js';
|
|
3
|
+
const PREFIX = 'host:wake-at:';
|
|
4
|
+
/**
|
|
5
|
+
* Where an agent's own check-backs are kept, so a restart does not lose them.
|
|
6
|
+
*
|
|
7
|
+
* The timer itself stays in memory (`StateTimers`). This keeps the facts
|
|
8
|
+
* needed to arm it again: which lane, when, why, and under which work. A wake
|
|
9
|
+
* is written when it is armed and marked done when it fires, so on start the
|
|
10
|
+
* host re-arms exactly the ones still waiting, and one that passed while the
|
|
11
|
+
* host was down fires once.
|
|
12
|
+
*
|
|
13
|
+
* The notes live in the agent's own memory store under a prefix no memory
|
|
14
|
+
* section uses (sections are `hire:<id>`), so the agent's memory tools never
|
|
15
|
+
* see them. Writes are chained so a mark can never land before the arm it
|
|
16
|
+
* follows.
|
|
17
|
+
*
|
|
18
|
+
* Two hosts of the same agent running at once, as during a rolling deploy,
|
|
19
|
+
* can both re-arm a wake and both fire it. That costs one extra wake, and the
|
|
20
|
+
* agent checks for an answer before it chases anyone.
|
|
21
|
+
*/
|
|
22
|
+
export class WakeLedger {
|
|
23
|
+
store;
|
|
24
|
+
writes = Promise.resolve();
|
|
25
|
+
constructor(store) {
|
|
26
|
+
this.store = store;
|
|
27
|
+
}
|
|
28
|
+
/** False when the store is in memory: then a restart loses wakes anyway. */
|
|
29
|
+
get durable() {
|
|
30
|
+
return !(this.store instanceof InMemoryStore);
|
|
31
|
+
}
|
|
32
|
+
arm(wake) {
|
|
33
|
+
const { laneKey, ...rest } = wake;
|
|
34
|
+
this.write(laneKey, { ...rest, state: 'armed' });
|
|
35
|
+
}
|
|
36
|
+
done(laneKey) {
|
|
37
|
+
this.writes = this.writes
|
|
38
|
+
.then(async () => {
|
|
39
|
+
const saved = (await this.store.get(PREFIX + laneKey));
|
|
40
|
+
if (saved?.state === 'armed') {
|
|
41
|
+
await this.store.put(PREFIX + laneKey, { ...saved, state: 'done' });
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
.catch((err) => this.warn('mark done', laneKey, err));
|
|
45
|
+
}
|
|
46
|
+
/** The wakes still waiting to fire, for the host to arm again on start. */
|
|
47
|
+
async pending() {
|
|
48
|
+
await this.writes;
|
|
49
|
+
const rows = await this.store.list(PREFIX);
|
|
50
|
+
const out = [];
|
|
51
|
+
for (const { key, value } of rows) {
|
|
52
|
+
const saved = value;
|
|
53
|
+
if (saved?.state !== 'armed' || typeof saved.sessionId !== 'string')
|
|
54
|
+
continue;
|
|
55
|
+
if (!Number.isFinite(saved.firesAt))
|
|
56
|
+
continue;
|
|
57
|
+
out.push({
|
|
58
|
+
laneKey: key.slice(PREFIX.length),
|
|
59
|
+
sessionId: saved.sessionId,
|
|
60
|
+
firesAt: saved.firesAt,
|
|
61
|
+
reason: saved.reason ?? null,
|
|
62
|
+
...(saved.scope ? { scope: saved.scope } : {}),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
/** Resolves when every write so far has landed. For tests and shutdown. */
|
|
68
|
+
flushed() {
|
|
69
|
+
return this.writes;
|
|
70
|
+
}
|
|
71
|
+
write(laneKey, value) {
|
|
72
|
+
this.writes = this.writes
|
|
73
|
+
.then(() => this.store.put(PREFIX + laneKey, value))
|
|
74
|
+
.catch((err) => this.warn('save', laneKey, err));
|
|
75
|
+
}
|
|
76
|
+
warn(what, laneKey, err) {
|
|
77
|
+
runtimeLog.warn('WakeLedger', `could not ${what} the wake for laneKey=${laneKey}: ${err?.message || err}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -6,7 +6,8 @@ export declare class AgreementService {
|
|
|
6
6
|
proposeDirect(payload: ProposeDirectInput): Promise<Agreement>;
|
|
7
7
|
/**
|
|
8
8
|
* Open buyer-broadcast. `payload.audience` selects who may see/claim it:
|
|
9
|
-
* 'everyone' (default, fully public)
|
|
9
|
+
* 'everyone' (default, fully public), 'org' (members of your active org) or
|
|
10
|
+
* 'company' (members of every team in your active org's company).
|
|
10
11
|
* Server defaults engagementKind to `service`.
|
|
11
12
|
*/
|
|
12
13
|
proposeBroadcast(payload: ProposeBroadcastInput): Promise<Agreement>;
|
|
@@ -23,7 +24,8 @@ export declare class AgreementService {
|
|
|
23
24
|
}>;
|
|
24
25
|
/**
|
|
25
26
|
* Seller-broadcast marketplace offer. `payload.audience` sets the `payer`
|
|
26
|
-
* sentinel: 'everyone' (default, public)
|
|
27
|
+
* sentinel: 'everyone' (default, public), 'org' (your active org only) or
|
|
28
|
+
* 'company' (every team in your company).
|
|
27
29
|
*/
|
|
28
30
|
publishOffer(payload: PublishOfferPayload): Promise<Agreement>;
|
|
29
31
|
delegate(payload: DelegateAgreementData): Promise<Agreement>;
|
|
@@ -14,7 +14,8 @@ export class AgreementService {
|
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
16
|
* Open buyer-broadcast. `payload.audience` selects who may see/claim it:
|
|
17
|
-
* 'everyone' (default, fully public)
|
|
17
|
+
* 'everyone' (default, fully public), 'org' (members of your active org) or
|
|
18
|
+
* 'company' (members of every team in your active org's company).
|
|
18
19
|
* Server defaults engagementKind to `service`.
|
|
19
20
|
*/
|
|
20
21
|
proposeBroadcast(payload) {
|
|
@@ -34,7 +35,8 @@ export class AgreementService {
|
|
|
34
35
|
}
|
|
35
36
|
/**
|
|
36
37
|
* Seller-broadcast marketplace offer. `payload.audience` sets the `payer`
|
|
37
|
-
* sentinel: 'everyone' (default, public)
|
|
38
|
+
* sentinel: 'everyone' (default, public), 'org' (your active org only) or
|
|
39
|
+
* 'company' (every team in your company).
|
|
38
40
|
*/
|
|
39
41
|
publishOffer(payload) {
|
|
40
42
|
return publishOfferApi(payload, this.creds);
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* agreementService) injected into tool context — NOT passed in the user `tools:`
|
|
9
9
|
* array. Publishing open work is part of this grammar:
|
|
10
10
|
* agreement_propose({ proposedTo: "everyone" }) for a public broadcast, or
|
|
11
|
-
* proposedTo: "org" to scope it to your org
|
|
11
|
+
* proposedTo: "org" to scope it to your org, or "company" to every team in your
|
|
12
|
+
* company — there is no separate publish tool.
|
|
12
13
|
*
|
|
13
14
|
* Tier 2 — capability bundles (tier2/). Opt-in, HTTP-backed tool bundles an agent
|
|
14
15
|
* chooses to have. Off by default; an agent gains a bundle by spreading it into
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* agreementService) injected into tool context — NOT passed in the user `tools:`
|
|
9
9
|
* array. Publishing open work is part of this grammar:
|
|
10
10
|
* agreement_propose({ proposedTo: "everyone" }) for a public broadcast, or
|
|
11
|
-
* proposedTo: "org" to scope it to your org
|
|
11
|
+
* proposedTo: "org" to scope it to your org, or "company" to every team in your
|
|
12
|
+
* company — there is no separate publish tool.
|
|
12
13
|
*
|
|
13
14
|
* Tier 2 — capability bundles (tier2/). Opt-in, HTTP-backed tool bundles an agent
|
|
14
15
|
* chooses to have. Off by default; an agent gains a bundle by spreading it into
|
|
@@ -64,7 +64,7 @@ export const agreementProposeTool = defineTool('agreement_propose', {
|
|
|
64
64
|
parentAgreementId: 'string',
|
|
65
65
|
providerId: { type: 'string', description: AGREEMENT_PROPOSE_PROVIDER_ID_DESCRIPTION },
|
|
66
66
|
agreementDescription: 'string',
|
|
67
|
-
}, handlerFor('agreement_propose'), { description: 'Propose an agreement — direct, broadcast, or link; there are no separate publish tools. Engagement precedence (posted-first): 1) REUSE an active agreement on matching terms, 2) CLAIM the counterparty\'s listing (marketplace_view → agreement_claim; never counter a listing), 3) POST a request broadcast here when nothing listed fits, 4) direct-propose ONLY for bespoke terms, renegotiation, or commissioning a named counterparty with no listing — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Direction is stated, never guessed: providerId = your own id ("I work, you pay") or providerId = proposedTo ("you work, I pay") on direct proposals. Broadcast with proposedTo="everyone" (fully public)
|
|
67
|
+
}, handlerFor('agreement_propose'), { description: 'Propose an agreement — direct, broadcast, or link; there are no separate publish tools. Engagement precedence (posted-first): 1) REUSE an active agreement on matching terms, 2) CLAIM the counterparty\'s listing (marketplace_view → agreement_claim; never counter a listing), 3) POST a request broadcast here when nothing listed fits, 4) direct-propose ONLY for bespoke terms, renegotiation, or commissioning a named counterparty with no listing — most published agents are claim-only and refuse direct proposals with a pointer at their listing. Direction is stated, never guessed: providerId = your own id ("I work, you pay") or providerId = proposedTo ("you work, I pay") on direct proposals. Broadcast with proposedTo="everyone" (fully public), "org" (your org only) or "company" (every team in your company) and no providerId posts a REQUEST any agent may claim. A person or the deploy publishes a standing listing; this agent does not. For a bilateral trust link, use link_propose: it takes an email or an agent id, or neither for a share link. engagementKind defaults to service. Agreements are STANDING by default (no expiry, unlimited tasks under one agreement); set expiresAt or maxExecutions only when the engagement should end on its own.', isAgreementCreation: true });
|
|
68
68
|
export const agreementSubcontractTool = defineTool('agreement_subcontract', agreementSubcontractCapability.params, handlerFor('agreement_subcontract'), { description: agreementSubcontractCapability.descriptions.sdk, isAgreementCreation: true });
|
|
69
69
|
export const agreementRespondTool = defineTool('agreement_respond', { agreementId: { type: 'string', required: true }, action: { type: 'string', required: true, enum: ['approve', 'reject'] } }, handlerFor('agreement_respond'), { description: 'Approve or reject a pending DIRECT agreement proposal addressed to you. Open broadcasts (requests, standing offers, link invites) have no personal approval slot — claim those with agreement_claim instead, or ignore them to pass. ONE exception: a hand-off that pins YOU as provider carries your pending approval even as an open broadcast — approving consents to serving whoever claims it (the row stays open for claims); rejecting cancels the hand-off. Ignoring it leaves the hand-off unclaimable, so always respond.' });
|
|
70
70
|
export const agreementClaimTool = defineTool('agreement_claim', agreementClaimCapability.params, handlerFor('agreement_claim'), { description: agreementClaimCapability.descriptions.sdk });
|
|
@@ -10,13 +10,15 @@ import { type ToolDefinition } from '../../../tools/defineTool.js';
|
|
|
10
10
|
*
|
|
11
11
|
* What the platform learns from this: nothing. The wake is the agent's own
|
|
12
12
|
* business — it arrives on the agent's lane, behind any real event, and no
|
|
13
|
-
* server-side schedule is created.
|
|
13
|
+
* server-side schedule is created. The host keeps it in the agent's own
|
|
14
|
+
* memory notes, so a restart arms it again.
|
|
14
15
|
*/
|
|
15
16
|
export interface WakeSchedulerPort {
|
|
16
17
|
/** Arm the lane's timer. Returns when it will actually fire. */
|
|
17
18
|
armWakeAt(sessionId: string, at: Date | number, reason: string | null): {
|
|
18
19
|
firesAt: number;
|
|
19
20
|
delayMs: number;
|
|
21
|
+
durable?: boolean;
|
|
20
22
|
};
|
|
21
23
|
}
|
|
22
24
|
export interface WakeToolContext {
|
|
@@ -38,7 +38,9 @@ export const wakeAtTool = defineTool('wake_at', {
|
|
|
38
38
|
wakeAt: new Date(armed.firesAt).toISOString(),
|
|
39
39
|
inMs: armed.delayMs,
|
|
40
40
|
reason,
|
|
41
|
-
note:
|
|
41
|
+
note: armed.durable
|
|
42
|
+
? 'One wake is armed per lane, and arming again replaces it. It is saved, so a restart of your host arms it again; if its time passed while the host was down, it wakes you once when the host is back.'
|
|
43
|
+
: 'One wake is armed per lane, and arming again replaces it. This wake is held in memory: if the process restarts before it fires it is lost, and you will pick up on your next real event instead.',
|
|
42
44
|
};
|
|
43
45
|
}, {
|
|
44
46
|
description: 'Ask to be woken at a time, to check back on something you are waiting for. The wake arrives on this conversation, behind anything real that happens first. Arming again replaces the previous wake — a check-back schedule is a series of single wakes, so re-arm the next one each time you wake. Counts against your wake budget like any other wake.',
|
|
@@ -3,7 +3,7 @@ import { toolFromCapability, toolsFromCapabilities } from '../../../tools/capabi
|
|
|
3
3
|
// the marketplace read (requests AND standing offers),
|
|
4
4
|
// defined once in api-client's shared capability layer. Publishing is NOT
|
|
5
5
|
// here: it is the tier-1 verb agreement_propose with proposedTo
|
|
6
|
-
// "everyone" | "org" (providerId = own id for an offer, omitted for a request);
|
|
6
|
+
// "everyone" | "org" | "company" (providerId = own id for an offer, omitted for a request);
|
|
7
7
|
// claiming is agreement_claim.
|
|
8
8
|
export const marketplaceViewTool = toolFromCapability(marketplaceViewCapability);
|
|
9
9
|
export const MARKETPLACE_TOOLS = toolsFromCapabilities(MARKETPLACE_CAPABILITIES);
|
|
@@ -10,7 +10,8 @@ export function isListing(agreement) {
|
|
|
10
10
|
const a = row(agreement);
|
|
11
11
|
const payer = row(a.parties).payer;
|
|
12
12
|
const principal = typeof payer === 'string' ? payer : row(payer).principal;
|
|
13
|
-
return (principal === 'everyone' || principal === 'org'
|
|
13
|
+
return ((principal === 'everyone' || principal === 'org' || principal === 'company') &&
|
|
14
|
+
a.engagementKind !== 'link');
|
|
14
15
|
}
|
|
15
16
|
/** Enrich each wake from the agreement it actually names, never from the room's
|
|
16
17
|
* latest deal. Reads use the agent's credentials; no grants are inferred here. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ziggs-ai/agent-sdk",
|
|
3
|
-
"version": "0.41.
|
|
3
|
+
"version": "0.41.2",
|
|
4
4
|
"description": "Agent framework SDK for building autonomous agents on the Ziggs platform",
|
|
5
5
|
"homepage": "https://ziggsai.com/docs",
|
|
6
6
|
"bugs": {
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"@anthropic-ai/claude-agent-sdk": "^0.3.284",
|
|
31
31
|
"@anthropic-ai/sdk": "^0.110.0",
|
|
32
32
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
33
|
-
"@ziggs-ai/api-client": "0.37.
|
|
34
|
-
"@ziggs-ai/contracts": "^0.
|
|
33
|
+
"@ziggs-ai/api-client": "0.37.27",
|
|
34
|
+
"@ziggs-ai/contracts": "^0.31.0",
|
|
35
35
|
"ajv": "^8.17.1",
|
|
36
36
|
"dotenv": "^17.2.3",
|
|
37
37
|
"openai": "^4.0.0",
|