@pouchy_ai/admin-sdk 0.7.1 → 0.9.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 +31 -0
- package/README.md +51 -0
- package/dist/index.d.ts +131 -3
- package/dist/index.js +15 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@pouchy_ai/admin-sdk` are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.9.0 — 2026-07-28
|
|
6
|
+
|
|
7
|
+
- **Channel CRUD is typed (CR-77).** `createChannel` takes
|
|
8
|
+
`type: CreatableChannelType` instead of `string` — the full 31-transport
|
|
9
|
+
union, minus `internal-a2a`, which is in the platform's union but has no
|
|
10
|
+
adapter and is answered 400, so excluding it turns a runtime rejection into a
|
|
11
|
+
compile error. `secret` is now `ChannelSecretInput` (`token`,
|
|
12
|
+
`inboundSecret`, `extra`) rather than `Record<string, unknown>`.
|
|
13
|
+
- The union is a duplicate of the server's `TransportType` (this package is
|
|
14
|
+
standalone and cannot import it), so a drift gate binds the two and fails CI
|
|
15
|
+
in BOTH directions — a transport on one side and not the other is either an
|
|
16
|
+
integrator typed out of a working call, or a compile-time promise the API
|
|
17
|
+
rejects.
|
|
18
|
+
- Per-transport `secret.extra` field lists are still documented in
|
|
19
|
+
<https://pouchy.ai/docs/channel-setup> rather than modelled per type: the sets are
|
|
20
|
+
operator-supplied and move with each provider's own console.
|
|
21
|
+
|
|
22
|
+
## 0.8.0 — 2026-07-28
|
|
23
|
+
|
|
24
|
+
- **Durable runs.** `listRuns`, `createRun`, `getRun`, `cancelRun`, `resumeRun`,
|
|
25
|
+
`signalRun` — long-running agent work that is checkpointed per step and
|
|
26
|
+
advanced by the platform across ticks, so it survives restarts and can span
|
|
27
|
+
minutes to hours.
|
|
28
|
+
- The feature shipped server-side without this plane: the routes existed only
|
|
29
|
+
under `/v1/projects/{projectId}/…` (owner session auth), so an admin-key
|
|
30
|
+
integrator had no reachable endpoint at all. `/v1/admin/runs/*` now mirrors
|
|
31
|
+
them, and both planes share one implementation.
|
|
32
|
+
- Note the status codes: every run mutation answers **202**, not 200/201. The
|
|
33
|
+
work happens on a later tick, never inside your request — treating the
|
|
34
|
+
response as a result is the mistake the code is chosen to prevent.
|
|
35
|
+
|
|
5
36
|
## 0.7.1 — 2026-07-27
|
|
6
37
|
|
|
7
38
|
Fix: `timeoutMs: 0` now disables the deadline instead of aborting every request.
|
package/README.md
CHANGED
|
@@ -141,10 +141,61 @@ Reads (`GET`) are not covered by that bucket. `retryAfter` is available from
|
|
|
141
141
|
| Credentials | `listCredentials` · `putCredentials` · `deleteCredentials` |
|
|
142
142
|
| Channels | `listChannels` · `createChannel` · `getChannel` · `updateChannel` · `deleteChannel` |
|
|
143
143
|
| Schedules | `listSchedules` · `createSchedule` · `getSchedule` · `updateSchedule` · `deleteSchedule` |
|
|
144
|
+
| Durable runs | `listRuns` · `createRun` · `getRun` · `cancelRun` · `resumeRun` · `signalRun` |
|
|
145
|
+
|
|
146
|
+
Channel types are checked at compile time: `createChannel` takes a
|
|
147
|
+
`CreatableChannelType` (the platform's 31-transport union minus the adapterless
|
|
148
|
+
`internal-a2a`), and `secret` is a named `ChannelSecretInput`. Per-transport
|
|
149
|
+
`secret.extra` fields are listed in <https://pouchy.ai/docs/channel-setup>.
|
|
144
150
|
| Webhooks | `listWebhooks` · `createWebhook` · `updateWebhook` · `rotateWebhookSecret` · `deleteWebhook` · `testWebhook` · `redeliverWebhook` |
|
|
145
151
|
| Reporting | `getUsage` · `getBilling` · `getTracesSummary` · `getRecentTraces` · `getLogs` · `getProject` · `updateProject` |
|
|
146
152
|
| Escape hatch | `request(method, path, body?)` — any endpoint not yet typed |
|
|
147
153
|
|
|
154
|
+
### Durable runs
|
|
155
|
+
|
|
156
|
+
A schedule *fires* one turn and is done. A **run** is long-lived agent work:
|
|
157
|
+
checkpointed after every step and advanced by the platform across as many ticks
|
|
158
|
+
as it needs, so it survives restarts and can span minutes to hours.
|
|
159
|
+
|
|
160
|
+
Every mutation answers **202**, not 200/201 — the work happens on a later tick,
|
|
161
|
+
never inside your request:
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
const agentId = 'agt_123';
|
|
165
|
+
const { run } = await admin.createRun({
|
|
166
|
+
agentId,
|
|
167
|
+
externalUserId: 'user-42',
|
|
168
|
+
goal: 'Reconcile the overnight invoices and flag anything over $500.',
|
|
169
|
+
steps: [
|
|
170
|
+
{ id: 'gather', kind: 'agent_turn' },
|
|
171
|
+
{ id: 'ok', kind: 'human_approval', input: { prompt: 'Approve the flagged refunds?' } },
|
|
172
|
+
{ id: 'apply', kind: 'agent_turn' }
|
|
173
|
+
]
|
|
174
|
+
});
|
|
175
|
+
console.log(run.id, run.status); // → 'queued'
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
When a run reaches a `human_approval` step it **parks**: it holds no lease and
|
|
179
|
+
leaves the platform's due window, so nothing will move it until you answer.
|
|
180
|
+
You learn about it either way — the `agent.run_awaiting` webhook pushes the
|
|
181
|
+
token, and `getRun` always carries it:
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
const runId = 'run_abc';
|
|
185
|
+
const { run } = await admin.getRun(runId);
|
|
186
|
+
if (run.status === 'awaiting_human' && run.awaiting) {
|
|
187
|
+
await admin.resumeRun(runId, { approved: true, token: run.awaiting.token });
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
The `token` is a fencing token, not a secret — authorization is your admin key.
|
|
192
|
+
It exists so a stale or replayed decision cannot land on a question the run has
|
|
193
|
+
already moved past (you get `409 stale_token`).
|
|
194
|
+
|
|
195
|
+
An `await_event` step parks the same way until you call `signalRun` with the
|
|
196
|
+
matching key; a mismatch answers `409 event_mismatch`, which lets a webhook
|
|
197
|
+
retry tell "too late" from "rejected".
|
|
198
|
+
|
|
148
199
|
## OpenAPI
|
|
149
200
|
|
|
150
201
|
The machine-readable contract is served at **`GET https://pouchy.ai/v1/admin/openapi`**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const ADMIN_SDK_VERSION = "0.
|
|
1
|
+
export declare const ADMIN_SDK_VERSION = "0.9.0";
|
|
2
2
|
export declare const DEFAULT_BASE_URL = "https://pouchy.ai/v1/admin";
|
|
3
3
|
/** Deadline for the routes whose server handler declares `maxDuration: 300` —
|
|
4
4
|
* the server's own ceiling plus headroom, so a client abort can only ever mean
|
|
@@ -166,6 +166,73 @@ export interface DeliveryOutcome {
|
|
|
166
166
|
}
|
|
167
167
|
/** The Admin API client. Every method returns the parsed JSON body; failures
|
|
168
168
|
* throw AdminApiError. */
|
|
169
|
+
/** Every transport the platform knows. Mirrors the server's `TransportType`
|
|
170
|
+
* (`channels/types.ts`) — this package is standalone and cannot import it, so
|
|
171
|
+
* `admin-sdk-channel-types.drift.test.ts` binds the two and fails CI if either
|
|
172
|
+
* side gains a transport the other lacks. */
|
|
173
|
+
export type ChannelTransportType = 'echo' | 'matrix' | 'telegram' | 'web' | 'activitypub' | 'internal-a2a' | 'discord' | 'slack' | 'nostr' | 'atproto' | 'sms' | 'email' | 'whatsapp' | 'messenger' | 'instagram' | 'line' | 'teams' | 'wechat' | 'wecom' | 'feishu' | 'dingtalk' | 'twitter' | 'xmpp' | 'qq' | 'kakao' | 'viber' | 'signal' | 'weibo' | 'alexa' | 'google-assistant' | 'voice';
|
|
174
|
+
/** Transports `createChannel` accepts. `internal-a2a` is in the union but has
|
|
175
|
+
* no adapter, so the server answers 400 — excluding it here turns a runtime
|
|
176
|
+
* rejection into a compile error. */
|
|
177
|
+
export type CreatableChannelType = Exclude<ChannelTransportType, 'internal-a2a'>;
|
|
178
|
+
/** Credentials for a connector, sealed server-side as one AES-GCM blob.
|
|
179
|
+
*
|
|
180
|
+
* `extra` is where the transports needing more than a token live — Twitter
|
|
181
|
+
* OAuth 1.0a (`consumerKey`/`consumerSecret`/`accessToken`/`accessTokenSecret`),
|
|
182
|
+
* Teams AAD (`appId`/`appPassword`), QQ (`appId` + Ed25519 seed), the WeChat
|
|
183
|
+
* family's mint credentials. Per-transport field lists are documented in
|
|
184
|
+
* https://pouchy.ai/docs/channel-setup; they are not modelled per-type here because the set
|
|
185
|
+
* is operator-supplied and moves with each provider's own console. */
|
|
186
|
+
export interface ChannelSecretInput {
|
|
187
|
+
/** Bot access token / API key used for outbound delivery. */
|
|
188
|
+
token?: string;
|
|
189
|
+
/** Inbound signing secret (e.g. Telegram `secret_token`, Matrix `hs_token`). */
|
|
190
|
+
inboundSecret?: string;
|
|
191
|
+
/** Extra named credentials for transports whose auth needs more than the pair above. */
|
|
192
|
+
extra?: Record<string, string>;
|
|
193
|
+
}
|
|
194
|
+
/** A durable run as the API returns it. Typed rather than `unknown` because
|
|
195
|
+
* the parked-run flow — read `status`, read `awaiting.token`, answer — is the
|
|
196
|
+
* SDK's most important interaction, and forcing a cast there would put the
|
|
197
|
+
* burden on every integrator at exactly the wrong moment. */
|
|
198
|
+
export interface AdminRunStep {
|
|
199
|
+
id: string;
|
|
200
|
+
kind?: string;
|
|
201
|
+
status: 'pending' | 'running' | 'succeeded' | 'failed' | 'compensated' | 'skipped';
|
|
202
|
+
attempts: number;
|
|
203
|
+
error?: string;
|
|
204
|
+
nextAttemptAt?: number;
|
|
205
|
+
output?: unknown;
|
|
206
|
+
}
|
|
207
|
+
export interface AdminRun {
|
|
208
|
+
id: string;
|
|
209
|
+
projectId: string;
|
|
210
|
+
agentId: string;
|
|
211
|
+
externalUserId: string;
|
|
212
|
+
goal: string;
|
|
213
|
+
status: 'queued' | 'running' | 'awaiting_human' | 'awaiting_event' | 'succeeded' | 'failed' | 'compensating' | 'compensated' | 'compensation_failed' | 'cancelled';
|
|
214
|
+
journal: AdminRunStep[];
|
|
215
|
+
/** Epoch ms the platform next looks at this run. Absent when terminal, or
|
|
216
|
+
* parked on a human decision (only an answer moves it). */
|
|
217
|
+
wakeAt?: number;
|
|
218
|
+
/** Present while parked on a person. `token` fences your answer against a
|
|
219
|
+
* stale or replayed decision — it is not a secret. */
|
|
220
|
+
awaiting?: {
|
|
221
|
+
token: string;
|
|
222
|
+
prompt: string;
|
|
223
|
+
stepId: string;
|
|
224
|
+
since: number;
|
|
225
|
+
};
|
|
226
|
+
/** Present while parked on an external event, with its deadline if it has one. */
|
|
227
|
+
awaitingEvent?: {
|
|
228
|
+
key: string;
|
|
229
|
+
stepId: string;
|
|
230
|
+
since: number;
|
|
231
|
+
timeoutAt?: number;
|
|
232
|
+
};
|
|
233
|
+
createdAt: string;
|
|
234
|
+
updatedAt: string;
|
|
235
|
+
}
|
|
169
236
|
export interface AdminClient {
|
|
170
237
|
listAgents(): Promise<{
|
|
171
238
|
agents: Agent[];
|
|
@@ -469,13 +536,13 @@ export interface AdminClient {
|
|
|
469
536
|
connectors: unknown[];
|
|
470
537
|
}>;
|
|
471
538
|
createChannel(input: {
|
|
472
|
-
type:
|
|
539
|
+
type: CreatableChannelType;
|
|
473
540
|
agentId: string;
|
|
474
541
|
env?: Env;
|
|
475
542
|
config?: Record<string, unknown>;
|
|
476
543
|
/** Connector credentials (bot token, signing secret, …) — stored
|
|
477
544
|
* encrypted, never returned. */
|
|
478
|
-
secret?:
|
|
545
|
+
secret?: ChannelSecretInput;
|
|
479
546
|
}): Promise<{
|
|
480
547
|
connector: {
|
|
481
548
|
id: string;
|
|
@@ -521,6 +588,67 @@ export interface AdminClient {
|
|
|
521
588
|
deleteSchedule(scheduleId: string): Promise<{
|
|
522
589
|
ok: boolean;
|
|
523
590
|
}>;
|
|
591
|
+
listRuns(query?: {
|
|
592
|
+
status?: string;
|
|
593
|
+
limit?: number;
|
|
594
|
+
}): Promise<{
|
|
595
|
+
runs: AdminRun[];
|
|
596
|
+
}>;
|
|
597
|
+
/** Start a durable run. Omit `steps` for the common case — a single agent
|
|
598
|
+
* turn carrying `goal`. Step ids must be unique and match
|
|
599
|
+
* `[A-Za-z0-9_-]{1,64}`; `kind` is one of `agent_turn`, `subtask_fanout`,
|
|
600
|
+
* `human_approval`, `await_event`. Answers **202**, not 201: the run is
|
|
601
|
+
* accepted and has not run yet. */
|
|
602
|
+
createRun(input: {
|
|
603
|
+
agentId: string;
|
|
604
|
+
externalUserId: string;
|
|
605
|
+
goal: string;
|
|
606
|
+
env?: 'live' | 'test';
|
|
607
|
+
startAt?: number;
|
|
608
|
+
steps?: {
|
|
609
|
+
id: string;
|
|
610
|
+
kind?: string;
|
|
611
|
+
input?: Record<string, unknown>;
|
|
612
|
+
}[];
|
|
613
|
+
}): Promise<{
|
|
614
|
+
run: AdminRun;
|
|
615
|
+
}>;
|
|
616
|
+
/** Fetch a run with its full step journal — what ran, what retried, what was
|
|
617
|
+
* rolled back. For a failed run this is the only place the reason survives. */
|
|
618
|
+
getRun(runId: string): Promise<{
|
|
619
|
+
run: AdminRun;
|
|
620
|
+
}>;
|
|
621
|
+
/** Request cancellation. The run stops at the next STEP BOUNDARY and then
|
|
622
|
+
* rolls back completed work, so this returns `status: 'cancelling'`. */
|
|
623
|
+
cancelRun(runId: string): Promise<{
|
|
624
|
+
ok: boolean;
|
|
625
|
+
status: string;
|
|
626
|
+
}>;
|
|
627
|
+
/** Answer a run parked on a human decision. `token` comes from the run's
|
|
628
|
+
* `awaiting.token` (also carried by the `agent.run_awaiting` webhook) and
|
|
629
|
+
* fences the decision against a stale or replayed answer. Without this call
|
|
630
|
+
* a parked run never resumes: it holds no lease and has left the platform's
|
|
631
|
+
* due window by design. */
|
|
632
|
+
resumeRun(runId: string, input: {
|
|
633
|
+
approved: boolean;
|
|
634
|
+
token: string;
|
|
635
|
+
note?: string;
|
|
636
|
+
}): Promise<{
|
|
637
|
+
ok: boolean;
|
|
638
|
+
approved: boolean;
|
|
639
|
+
status: string;
|
|
640
|
+
}>;
|
|
641
|
+
/** Deliver an external event to a run parked on one. `event` must equal the
|
|
642
|
+
* key the run is waiting for — a mismatch answers 409 `event_mismatch`, so a
|
|
643
|
+
* webhook retry can tell "too late" from "rejected". */
|
|
644
|
+
signalRun(runId: string, input: {
|
|
645
|
+
event: string;
|
|
646
|
+
payload?: unknown;
|
|
647
|
+
}): Promise<{
|
|
648
|
+
ok: boolean;
|
|
649
|
+
event: string;
|
|
650
|
+
status: string;
|
|
651
|
+
}>;
|
|
524
652
|
listWebhooks(): Promise<{
|
|
525
653
|
webhooks: unknown[];
|
|
526
654
|
}>;
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// import { createAdminClient } from '@pouchy_ai/admin-sdk';
|
|
9
9
|
// const admin = createAdminClient({ adminKey: process.env.POUCHY_ADMIN_KEY! });
|
|
10
10
|
// const { agents } = await admin.listAgents();
|
|
11
|
-
export const ADMIN_SDK_VERSION = '0.
|
|
11
|
+
export const ADMIN_SDK_VERSION = '0.9.0';
|
|
12
12
|
export const DEFAULT_BASE_URL = 'https://pouchy.ai/v1/admin';
|
|
13
13
|
/** Default per-request timeout (ms). A hung upstream otherwise never rejects. */
|
|
14
14
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
@@ -223,6 +223,20 @@ export function createAdminClient(opts) {
|
|
|
223
223
|
getSchedule: (id) => request('GET', `/schedules/${encodeURIComponent(id)}`),
|
|
224
224
|
updateSchedule: (id, patch) => request('PATCH', `/schedules/${encodeURIComponent(id)}`, patch),
|
|
225
225
|
deleteSchedule: (id) => request('DELETE', `/schedules/${encodeURIComponent(id)}`),
|
|
226
|
+
listRuns: (q) => {
|
|
227
|
+
const qs = new URLSearchParams();
|
|
228
|
+
if (q?.status)
|
|
229
|
+
qs.set('status', q.status);
|
|
230
|
+
if (typeof q?.limit === 'number')
|
|
231
|
+
qs.set('limit', String(q.limit));
|
|
232
|
+
const tail = qs.toString();
|
|
233
|
+
return request('GET', `/runs${tail ? `?${tail}` : ''}`);
|
|
234
|
+
},
|
|
235
|
+
createRun: (input) => request('POST', '/runs', input),
|
|
236
|
+
getRun: (id) => request('GET', `/runs/${encodeURIComponent(id)}`),
|
|
237
|
+
cancelRun: (id) => request('DELETE', `/runs/${encodeURIComponent(id)}`),
|
|
238
|
+
resumeRun: (id, input) => request('POST', `/runs/${encodeURIComponent(id)}/resume`, input),
|
|
239
|
+
signalRun: (id, input) => request('POST', `/runs/${encodeURIComponent(id)}/signal`, input),
|
|
226
240
|
listWebhooks: () => request('GET', '/webhooks'),
|
|
227
241
|
createWebhook: (input) => request('POST', '/webhooks', input),
|
|
228
242
|
deleteWebhook: (id) => request('DELETE', `/webhooks/${encodeURIComponent(id)}`),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pouchy_ai/admin-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Typed TypeScript client for the Pouchy Admin API \u2014 manage agents, keys, end users, knowledge, skills, channels, schedules, webhooks and credentials headlessly, with a project Admin key.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|