@pikku/core 0.12.86 → 0.12.89
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 +115 -0
- package/dist/dev/hot-reload.d.ts +1 -1
- package/dist/dev/hot-reload.js +1 -1
- package/dist/types/core.types.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.js +1 -1
- package/dist/wirings/agent/agent-memory.d.ts +11 -0
- package/dist/wirings/agent/agent-memory.js +17 -2
- package/dist/wirings/agent/agent-stream.js +115 -91
- package/dist/wirings/agent/agent.types.d.ts +9 -8
- package/dist/wirings/rpc/rpc-runner.js +1 -1
- package/dist/wirings/variable/validate-variable-definitions.js +2 -2
- package/dist/wirings/variable/variable.types.d.ts +13 -7
- package/dist/wirings/workflow/pikku-scenario-service.d.ts +2 -2
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +20 -6
- package/dist/wirings/workflow/pikku-workflow-service.js +29 -27
- package/dist/wirings/workflow/workflow-constants.d.ts +17 -0
- package/dist/wirings/workflow/workflow-constants.js +17 -0
- package/dist/wirings/workflow/workflow-recovery.d.ts +18 -1
- package/dist/wirings/workflow/workflow-recovery.js +30 -2
- package/dist/wirings/workflow/workflow-step-claim.d.ts +16 -0
- package/dist/wirings/workflow/workflow-step-claim.js +27 -0
- package/knowledge/decisions/security/a-scaffold-flag-says-a-surface-exists-not-who-may-call-it.md +47 -0
- package/knowledge/decisions/security/index.md +2 -1
- package/knowledge/decisions/security/scaffold-features-are-authenticated-unless-opted-out.md +6 -0
- package/package.json +1 -1
- package/src/dev/hot-reload.ts +1 -1
- package/src/types/core.types.ts +1 -1
- package/src/types/index.ts +24 -1
- package/src/wirings/agent/agent-memory.test.ts +73 -0
- package/src/wirings/agent/agent-memory.ts +16 -2
- package/src/wirings/agent/agent-middleware.types.test.ts +41 -0
- package/src/wirings/agent/agent-stream-delegate.test.ts +381 -0
- package/src/wirings/agent/agent-stream.ts +158 -78
- package/src/wirings/agent/agent.types.ts +9 -8
- package/src/wirings/rpc/rpc-runner.test.ts +6 -1
- package/src/wirings/rpc/rpc-runner.ts +1 -1
- package/src/wirings/variable/validate-variable-definitions.test.ts +11 -10
- package/src/wirings/variable/validate-variable-definitions.ts +2 -2
- package/src/wirings/variable/variable.types.ts +13 -7
- package/src/wirings/workflow/pikku-scenario-service.ts +29 -6
- package/src/wirings/workflow/pikku-workflow-service.ts +34 -27
- package/src/wirings/workflow/workflow-constants.ts +19 -0
- package/src/wirings/workflow/workflow-recovery.ts +31 -1
- package/src/wirings/workflow/workflow-stalled-recovery.test.ts +46 -0
- package/src/wirings/workflow/workflow-step-claim.ts +46 -0
- package/src/wirings/workflow/workflow-terminal-run-guard.test.ts +105 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/tsconfig.type-tests.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,118 @@
|
|
|
1
|
+
## 0.12.89
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- 32616af: Carry the trace id across a remote RPC hop
|
|
6
|
+
|
|
7
|
+
`ContextAwareRPCService` sent the wire's trace id as `x-trace-id`, but the HTTP
|
|
8
|
+
runner on the receiving end reads `x-request-id` — the header every other sender
|
|
9
|
+
uses, including `buildRemoteHeaders`, which every deployment service goes
|
|
10
|
+
through. The receiving side therefore ignored the incoming id and generated a
|
|
11
|
+
fresh one, so a trace broke at each remote RPC boundary instead of spanning it.
|
|
12
|
+
Remote RPC now sends `x-request-id` too.
|
|
13
|
+
|
|
14
|
+
- 6848cd9: fix(workflow): back off the stalled-run sweep, and skip runs that cannot move
|
|
15
|
+
|
|
16
|
+
`sweepUndispatchedSteps` has always consulted a per-run backoff so a genuine
|
|
17
|
+
queue backlog is not amplified by a tick that keeps firing at the steps the
|
|
18
|
+
backlog is already delaying. `sweepStalledRuns` — its sibling, doing the same
|
|
19
|
+
re-drive through the same orchestrator queue — had none, and re-resumed every
|
|
20
|
+
stalled run on every tick. A resume does not clear whatever wedged a run, so
|
|
21
|
+
the same runs came back on the next tick and the next: in production seven
|
|
22
|
+
permanently stuck runs refilled a purged orchestrator queue at seven messages a
|
|
23
|
+
minute, and a backlog of six thousand could never drain because each pass added
|
|
24
|
+
work the previous pass had not finished. It now takes the same backoff, and
|
|
25
|
+
both sweeps share one instance — the record belongs to the re-drive, not to the
|
|
26
|
+
signal that asked for it, so a run the relay nudged a moment ago is not nudged
|
|
27
|
+
again by the sweep.
|
|
28
|
+
|
|
29
|
+
`runWorkflowJob` also now returns immediately for a run in a terminal state
|
|
30
|
+
instead of taking the run lock and replaying the workflow body. The orchestrator
|
|
31
|
+
queue is at-least-once and the relay re-dispatches on purpose, so a message for
|
|
32
|
+
a run that already settled is routine — and replaying one could park the body on
|
|
33
|
+
a wait that nothing would ever satisfy, holding the run lock, and the pooled
|
|
34
|
+
connection under it, until something external gave up. `suspended` is
|
|
35
|
+
deliberately not included: it ends a pass, not the run.
|
|
36
|
+
|
|
37
|
+
## 0.12.88
|
|
38
|
+
|
|
39
|
+
### Patch Changes
|
|
40
|
+
|
|
41
|
+
- 4712e73: fix: collect working memory from a delegate-mode parent agent
|
|
42
|
+
|
|
43
|
+
A delegating parent's `text-delta` events were dropped at the outermost output
|
|
44
|
+
channel, above the working-memory hook, so every `<working_memory>` block it
|
|
45
|
+
wrote from its first hand-off onward was discarded before anything could read
|
|
46
|
+
it. The parent's text is now routed through the working-memory hook and into a
|
|
47
|
+
sink instead of being dropped outright, so the blocks are collected while the
|
|
48
|
+
client, the thread history and user channel middleware still see nothing.
|
|
49
|
+
|
|
50
|
+
The resume path built no delegate filter at all and streamed a delegating
|
|
51
|
+
parent's text to the client after an approval; it now suppresses text the same
|
|
52
|
+
way the initial path does.
|
|
53
|
+
|
|
54
|
+
The AI SDK rejects a system message inside `messages` outright, so the working
|
|
55
|
+
memory prompt the framework injects as one failed every run that enabled
|
|
56
|
+
working memory at all. The runner now lifts system messages onto the `system`
|
|
57
|
+
option, after the agent's own instructions.
|
|
58
|
+
|
|
59
|
+
- 082403f: fix(agent): make working-memory array semantics explicit
|
|
60
|
+
|
|
61
|
+
`deepMergeWorkingMemory` replaced arrays wholesale as a side effect of its
|
|
62
|
+
object-recursion guard, so nothing in the code said whether that was the
|
|
63
|
+
contract or an accident. The merge now handles arrays in an explicit,
|
|
64
|
+
documented branch. Replace is kept over append: the full state is echoed back
|
|
65
|
+
every turn, so appending would duplicate every item whenever the model re-emitted
|
|
66
|
+
the array.
|
|
67
|
+
|
|
68
|
+
`buildWorkingMemoryPrompt` now states that contract to the model rather than
|
|
69
|
+
leaving "only include changed fields" to be read as permission to send a partial
|
|
70
|
+
array. This is defensive: measured against `gpt-4.1-mini` the wording changes
|
|
71
|
+
nothing, because that model already re-emits the whole list. It is there for
|
|
72
|
+
models that do not.
|
|
73
|
+
|
|
74
|
+
## 0.12.87
|
|
75
|
+
|
|
76
|
+
### Patch Changes
|
|
77
|
+
|
|
78
|
+
- 9687ad1: fix: hand agent middleware the singleton services its type promises
|
|
79
|
+
|
|
80
|
+
`PikkuAgentMiddlewareHooks` typed its `services` parameter as the project's full
|
|
81
|
+
wire `Services`, while every runtime call site has only ever passed the singleton
|
|
82
|
+
services. A middleware that destructured a wire service typechecked and silently
|
|
83
|
+
received `undefined`.
|
|
84
|
+
|
|
85
|
+
The hooks are now bounded by `CoreSingletonServices` in core, and the generated
|
|
86
|
+
`pikkuAgentMiddleware` defaults to `WiredSingletonServices` like the other
|
|
87
|
+
middleware definers. Nothing changes at runtime: agent middleware hooks a _run_,
|
|
88
|
+
and a run is not a request — it can start from a scheduler or a workflow with no
|
|
89
|
+
wire behind it. A tool the run calls is an ordinary function call and still gets
|
|
90
|
+
its own wire services through `runPikkuFunc`.
|
|
91
|
+
|
|
92
|
+
- 2d21628: fix(kysely): claim a workflow step atomically in every SQL dialect
|
|
93
|
+
|
|
94
|
+
The workflow engine's "atomic claim" was a read-then-write guarded by
|
|
95
|
+
`withStepLock`, and `@pikku/kysely` inherited a silent pass-through for that
|
|
96
|
+
lock — so on every dialect but Postgres and MySQL a redelivered queue job could
|
|
97
|
+
claim a step another dispatch was already running, executing a side-effecting
|
|
98
|
+
step twice.
|
|
99
|
+
|
|
100
|
+
`@pikku/kysely` now claims the step with a status-guarded `UPDATE` and reads the
|
|
101
|
+
affected-row count, which is atomic in every SQL dialect without an
|
|
102
|
+
advisory-lock primitive. Relay redispatch is enabled for all Kysely dialects as
|
|
103
|
+
a result, not just Postgres and MySQL.
|
|
104
|
+
|
|
105
|
+
- 985b87b: Follow through on the variable `required` → `optional` rename: regenerate the
|
|
106
|
+
core API report and update the inspector's `defineVariable` gating-flag test,
|
|
107
|
+
both of which #1369 left describing the deleted `required` flag.
|
|
108
|
+
- 3a83f85: Stop re-exporting package internals through entry points
|
|
109
|
+
|
|
110
|
+
66 names reached consumers only because an `export *` in an entry point swept
|
|
111
|
+
them up. Each one is referenced solely inside its own package, so the star is
|
|
112
|
+
now an explicit named re-export listing what is genuinely public. The
|
|
113
|
+
declarations themselves are untouched — this narrows the entry point, not the
|
|
114
|
+
module.
|
|
115
|
+
|
|
1
116
|
## 0.12.86
|
|
2
117
|
|
|
3
118
|
### Patch Changes
|
package/dist/dev/hot-reload.d.ts
CHANGED
package/dist/dev/hot-reload.js
CHANGED
|
@@ -7,7 +7,7 @@ import { clearPermissionsCache } from '../permissions.js';
|
|
|
7
7
|
import { clearChannelMiddlewareCache } from '../wirings/channel/channel-middleware-runner.js';
|
|
8
8
|
import { httpRouter } from '../wirings/http/routers/http-router.js';
|
|
9
9
|
import { createModuleRunner } from './module-runner.js';
|
|
10
|
-
export
|
|
10
|
+
export { reloadGeneratedMeta, reconcileAddonRegistry } from './reload-meta.js';
|
|
11
11
|
const isFunctionConfig = (value) => {
|
|
12
12
|
return (typeof value === 'object' &&
|
|
13
13
|
value !== null &&
|
|
@@ -182,7 +182,7 @@ export type PikkuWire<In = unknown, Out = unknown, HasInitialSession extends boo
|
|
|
182
182
|
* sets it; services that log fall back to the singleton logger.
|
|
183
183
|
*/
|
|
184
184
|
logger: Logger;
|
|
185
|
-
/** Trace ID for distributed tracing — propagated across remote RPC calls via x-
|
|
185
|
+
/** Trace ID for distributed tracing — propagated across remote RPC calls via the x-request-id header */
|
|
186
186
|
traceId: string;
|
|
187
187
|
functionId: string;
|
|
188
188
|
addonNamespace: string;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type { AuthInstance, CommonWireMeta, CoreConfig, CoreServices, CoreSingletonServices, CoreUserSession, CreateConfig, CreateSingletonServices, CreateWireServices, GetCredential, PikkuWire, PikkuWiringTypes, PostgresConfig, SecretlessServices, SecurityAuditIssue, SecurityAuditReport, SecurityAuditSummary, SecurityAuditUpdate, SecuritySeverity, SecurityUpdateLevel, ServerLifecycle, WireServices, } from './core.types.js';
|
|
2
2
|
export type * from './state.types.js';
|
package/dist/types/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export {};
|
|
@@ -8,6 +8,17 @@ export declare function resolveMemoryServices(agent: CoreAgent, singletonService
|
|
|
8
8
|
storage: AgentStorageService | undefined;
|
|
9
9
|
};
|
|
10
10
|
export declare function isWorkingMemoryEnabled(memoryConfig: AgentMemoryConfig | undefined, storage: AgentStorageService | undefined): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Merges a model-emitted working memory update into the stored state.
|
|
13
|
+
*
|
|
14
|
+
* Objects merge key by key, and `null` deletes a key. Arrays are replaced
|
|
15
|
+
* wholesale — deliberately, not as a side effect of the object guard below.
|
|
16
|
+
* The full state is echoed back to the model every turn by
|
|
17
|
+
* {@link buildWorkingMemoryPrompt}, so it can always re-emit an array in full,
|
|
18
|
+
* and appending instead would duplicate every item each time it did.
|
|
19
|
+
* `buildWorkingMemoryPrompt` states this contract to the model; the two must
|
|
20
|
+
* stay in agreement.
|
|
21
|
+
*/
|
|
11
22
|
export declare function deepMergeWorkingMemory(existing: Record<string, unknown>, updates: Record<string, unknown>): Record<string, unknown>;
|
|
12
23
|
export declare function buildWorkingMemoryPrompt(currentState: Record<string, unknown> | null, jsonSchema?: Record<string, unknown>): string;
|
|
13
24
|
export declare function loadContextMessages(memoryConfig: AgentMemoryConfig | undefined, storage: AgentStorageService | undefined, input: AgentInput, workingMemoryJsonSchema?: Record<string, unknown>): Promise<AgentMessage[]>;
|
|
@@ -11,6 +11,17 @@ export function resolveMemoryServices(agent, singletonServices) {
|
|
|
11
11
|
export function isWorkingMemoryEnabled(memoryConfig, storage) {
|
|
12
12
|
return !!memoryConfig?.workingMemory && !!storage;
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Merges a model-emitted working memory update into the stored state.
|
|
16
|
+
*
|
|
17
|
+
* Objects merge key by key, and `null` deletes a key. Arrays are replaced
|
|
18
|
+
* wholesale — deliberately, not as a side effect of the object guard below.
|
|
19
|
+
* The full state is echoed back to the model every turn by
|
|
20
|
+
* {@link buildWorkingMemoryPrompt}, so it can always re-emit an array in full,
|
|
21
|
+
* and appending instead would duplicate every item each time it did.
|
|
22
|
+
* `buildWorkingMemoryPrompt` states this contract to the model; the two must
|
|
23
|
+
* stay in agreement.
|
|
24
|
+
*/
|
|
14
25
|
export function deepMergeWorkingMemory(existing, updates) {
|
|
15
26
|
const result = { ...existing };
|
|
16
27
|
for (const key of Object.keys(updates)) {
|
|
@@ -21,8 +32,10 @@ export function deepMergeWorkingMemory(existing, updates) {
|
|
|
21
32
|
if (value === null) {
|
|
22
33
|
delete result[key];
|
|
23
34
|
}
|
|
35
|
+
else if (Array.isArray(value)) {
|
|
36
|
+
result[key] = value;
|
|
37
|
+
}
|
|
24
38
|
else if (typeof value === 'object' &&
|
|
25
|
-
!Array.isArray(value) &&
|
|
26
39
|
typeof result[key] === 'object' &&
|
|
27
40
|
result[key] !== null &&
|
|
28
41
|
!Array.isArray(result[key])) {
|
|
@@ -56,7 +69,9 @@ export function buildWorkingMemoryPrompt(currentState, jsonSchema) {
|
|
|
56
69
|
parts.push('When you learn new information, output a partial JSON update in <working_memory> tags. ' +
|
|
57
70
|
'Only include durable facts you have actually derived or the user has confirmed. ' +
|
|
58
71
|
'Do not output templates, placeholders, or narration. ' +
|
|
59
|
-
'Only include changed fields. Leave unknown fields untouched. Set a field to null to delete it.'
|
|
72
|
+
'Only include changed fields. Leave unknown fields untouched. Set a field to null to delete it. ' +
|
|
73
|
+
'Arrays are replaced, not merged: to change one, repeat every item you want to keep alongside the new ones, ' +
|
|
74
|
+
'because any item you leave out is deleted.');
|
|
60
75
|
return parts.join('\n\n');
|
|
61
76
|
}
|
|
62
77
|
export async function loadContextMessages(memoryConfig, storage, input, workingMemoryJsonSchema) {
|
|
@@ -203,6 +203,51 @@ async function postStreamCleanup(persistingChannel, agentRunState, runId, run) {
|
|
|
203
203
|
usage: persistingChannel.totalUsage,
|
|
204
204
|
});
|
|
205
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* Adapts `modifyOutputStream` hooks to channel middleware, one closure per
|
|
208
|
+
* hook so each keeps its own `state` and event log for the whole run.
|
|
209
|
+
*/
|
|
210
|
+
const toChannelStreamMiddleware = (hooks, sharedNotes, signal) => hooks
|
|
211
|
+
.filter((mw) => mw.modifyOutputStream)
|
|
212
|
+
.map((mw) => {
|
|
213
|
+
const state = {};
|
|
214
|
+
const allEvents = [];
|
|
215
|
+
return async (services, event, next) => {
|
|
216
|
+
allEvents.push(event);
|
|
217
|
+
const result = await mw.modifyOutputStream(services, {
|
|
218
|
+
event,
|
|
219
|
+
allEvents,
|
|
220
|
+
state,
|
|
221
|
+
shared: sharedNotes,
|
|
222
|
+
// Sends downstream directly, so a hook can hand back the fast event
|
|
223
|
+
// now and push the slow one when it is ready.
|
|
224
|
+
emit: next,
|
|
225
|
+
signal,
|
|
226
|
+
});
|
|
227
|
+
if (result == null)
|
|
228
|
+
return;
|
|
229
|
+
if (Array.isArray(result)) {
|
|
230
|
+
for (const r of result)
|
|
231
|
+
await next(r);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
await next(result);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
});
|
|
238
|
+
/**
|
|
239
|
+
* The branch a delegating parent's own spoken text is routed down once it has
|
|
240
|
+
* handed off: the same working-memory hook instances as the main chain, so the
|
|
241
|
+
* in-band `<working_memory>` blocks are still collected, ending in a sink so
|
|
242
|
+
* neither the client, the thread history, nor user channel middleware sees
|
|
243
|
+
* text the parent was supposed to keep to itself.
|
|
244
|
+
*
|
|
245
|
+
* Routing at ingress rather than dropping further down the chain keeps the
|
|
246
|
+
* decision synchronous with the send: the working-memory hook is async, so a
|
|
247
|
+
* hand-off landing mid-flight would otherwise retroactively suppress text the
|
|
248
|
+
* parent spoke before it.
|
|
249
|
+
*/
|
|
250
|
+
const createWorkingMemoryOnlyChannel = (channel, services, workingMemoryMiddleware) => wrapChannelWithMiddleware({ channel: { ...channel, send: () => { } } }, services, workingMemoryMiddleware).channel;
|
|
206
251
|
async function runStreamStepLoop(params) {
|
|
207
252
|
const { agent, runnerParams, maxSteps, agentRunner, streamChannel, channel, agentMiddlewares, } = params;
|
|
208
253
|
const singletonServices = getSingletonServices();
|
|
@@ -446,13 +491,14 @@ export async function streamAgent(agentName, input, channel, params, agentSessio
|
|
|
446
491
|
channel.send({ type: 'done' });
|
|
447
492
|
return '';
|
|
448
493
|
}
|
|
494
|
+
const workingMemoryMiddleware = getWorkingMemoryMiddleware(memoryConfig, storage, {
|
|
495
|
+
threadId,
|
|
496
|
+
workingMemorySchemaName,
|
|
497
|
+
logger: singletonServices.logger,
|
|
498
|
+
schemaService: singletonServices.schema,
|
|
499
|
+
});
|
|
449
500
|
const agentMiddlewares = [
|
|
450
|
-
...
|
|
451
|
-
threadId,
|
|
452
|
-
workingMemorySchemaName,
|
|
453
|
-
logger: singletonServices.logger,
|
|
454
|
-
schemaService: singletonServices.schema,
|
|
455
|
-
}),
|
|
501
|
+
...workingMemoryMiddleware,
|
|
456
502
|
...(agent.agentMiddleware ?? []),
|
|
457
503
|
];
|
|
458
504
|
// One bag per run, shared by every middleware — see PikkuAgentMiddlewareHooks.
|
|
@@ -491,43 +537,25 @@ export async function streamAgent(agentName, input, channel, params, agentSessio
|
|
|
491
537
|
await storage.saveMessages(threadId, [persistedUserMessage]);
|
|
492
538
|
}
|
|
493
539
|
warnUnstreamedOutputHooks(agentName, agentMiddlewares, singletonServices.logger);
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
.map((mw) => {
|
|
497
|
-
const state = {};
|
|
498
|
-
const allEvents = [];
|
|
499
|
-
return async (services, event, next) => {
|
|
500
|
-
allEvents.push(event);
|
|
501
|
-
const result = await mw.modifyOutputStream(services, {
|
|
502
|
-
event,
|
|
503
|
-
allEvents,
|
|
504
|
-
state,
|
|
505
|
-
shared: sharedNotes,
|
|
506
|
-
// Sends downstream directly, so a hook can hand back the fast event
|
|
507
|
-
// now and push the slow one when it is ready.
|
|
508
|
-
emit: next,
|
|
509
|
-
signal: interruptHandle.signal,
|
|
510
|
-
});
|
|
511
|
-
if (result == null)
|
|
512
|
-
return;
|
|
513
|
-
if (Array.isArray(result)) {
|
|
514
|
-
for (const r of result)
|
|
515
|
-
await next(r);
|
|
516
|
-
}
|
|
517
|
-
else {
|
|
518
|
-
await next(result);
|
|
519
|
-
}
|
|
520
|
-
};
|
|
521
|
-
});
|
|
540
|
+
const workingMemoryStreamMiddleware = toChannelStreamMiddleware(workingMemoryMiddleware, sharedNotes, interruptHandle.signal);
|
|
541
|
+
const streamMiddleware = toChannelStreamMiddleware(agent.agentMiddleware ?? [], sharedNotes, interruptHandle.signal);
|
|
522
542
|
const agentsMeta = pikkuState(packageName, 'agent', 'agentsMeta');
|
|
523
543
|
const meta = agentsMeta[resolvedName];
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
544
|
+
const isDelegateMode = agent.agentMode !== 'supervise' && meta?.agents?.length;
|
|
545
|
+
const delegateState = { delegated: false };
|
|
546
|
+
if (isDelegateMode) {
|
|
547
|
+
streamContext.delegateState = delegateState;
|
|
548
|
+
}
|
|
549
|
+
const allChannelMiddleware = [
|
|
550
|
+
...workingMemoryStreamMiddleware,
|
|
551
|
+
...combineChannelMiddleware('agent', `stream:${agentName}`, {
|
|
552
|
+
wireInheritedChannelMiddleware: meta?.channelMiddleware,
|
|
553
|
+
wireChannelMiddleware: [
|
|
554
|
+
...(agent.channelMiddleware ?? []),
|
|
555
|
+
...streamMiddleware,
|
|
556
|
+
],
|
|
557
|
+
}),
|
|
558
|
+
];
|
|
531
559
|
const persistingChannel = createPersistingChannel(channel, storage, threadId, singletonServices.logger);
|
|
532
560
|
const wrappedChannel = allChannelMiddleware.length > 0
|
|
533
561
|
? wrapChannelWithMiddleware({ channel: persistingChannel }, singletonServices, allChannelMiddleware).channel
|
|
@@ -545,21 +573,19 @@ export async function streamAgent(agentName, input, channel, params, agentSessio
|
|
|
545
573
|
return wrappedChannel.send(event);
|
|
546
574
|
},
|
|
547
575
|
};
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
streamContext.delegateState = delegateState;
|
|
552
|
-
}
|
|
576
|
+
const workingMemoryOnlyChannel = isDelegateMode && workingMemoryStreamMiddleware.length > 0
|
|
577
|
+
? createWorkingMemoryOnlyChannel(channel, singletonServices, workingMemoryStreamMiddleware)
|
|
578
|
+
: undefined;
|
|
553
579
|
const outputChannel = isDelegateMode
|
|
554
580
|
? {
|
|
555
581
|
...credentialFilteredChannel,
|
|
556
582
|
send: (event) => {
|
|
557
583
|
if (delegateState.delegated &&
|
|
558
|
-
(event.type === 'text-delta' || event.type === 'reasoning-delta'))
|
|
559
|
-
return;
|
|
584
|
+
(event.type === 'text-delta' || event.type === 'reasoning-delta')) {
|
|
585
|
+
return workingMemoryOnlyChannel?.send(event);
|
|
586
|
+
}
|
|
560
587
|
return credentialFilteredChannel.send(event);
|
|
561
588
|
},
|
|
562
|
-
delegateState,
|
|
563
589
|
}
|
|
564
590
|
: credentialFilteredChannel;
|
|
565
591
|
try {
|
|
@@ -880,13 +906,14 @@ async function continueAfterToolResult(run, agent, packageName, resolvedName, st
|
|
|
880
906
|
const allMessages = [...contextMessages, ...messages];
|
|
881
907
|
const trimmedMessages = trimMessages(allMessages);
|
|
882
908
|
const instructions = await buildInstructions(resolvedName, packageName);
|
|
909
|
+
const workingMemoryMiddleware = getWorkingMemoryMiddleware(memoryConfig, storage, {
|
|
910
|
+
threadId: run.threadId,
|
|
911
|
+
workingMemorySchemaName,
|
|
912
|
+
logger: singletonServices.logger,
|
|
913
|
+
schemaService: singletonServices.schema,
|
|
914
|
+
});
|
|
883
915
|
const agentMiddlewares = [
|
|
884
|
-
...
|
|
885
|
-
threadId: run.threadId,
|
|
886
|
-
workingMemorySchemaName,
|
|
887
|
-
logger: singletonServices.logger,
|
|
888
|
-
schemaService: singletonServices.schema,
|
|
889
|
-
}),
|
|
916
|
+
...workingMemoryMiddleware,
|
|
890
917
|
...(agent.agentMiddleware ?? []),
|
|
891
918
|
];
|
|
892
919
|
// One bag per run, shared by every middleware — see PikkuAgentMiddlewareHooks.
|
|
@@ -895,46 +922,43 @@ async function continueAfterToolResult(run, agent, packageName, resolvedName, st
|
|
|
895
922
|
// knowledge: decisions/internals/a-resumed-agent-turn-is-as-interruptible-as-the-first.md
|
|
896
923
|
const interruptHandle = registerInterruptibleRun(run.runId);
|
|
897
924
|
warnUnstreamedOutputHooks(run.agentName, agentMiddlewares, singletonServices.logger);
|
|
898
|
-
const
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
// Sends downstream directly, so a hook can hand back the fast event
|
|
911
|
-
// now and push the slow one when it is ready.
|
|
912
|
-
emit: next,
|
|
913
|
-
signal: interruptHandle.signal,
|
|
914
|
-
});
|
|
915
|
-
if (result == null)
|
|
916
|
-
return;
|
|
917
|
-
if (Array.isArray(result)) {
|
|
918
|
-
for (const r of result)
|
|
919
|
-
await next(r);
|
|
920
|
-
}
|
|
921
|
-
else {
|
|
922
|
-
await next(result);
|
|
923
|
-
}
|
|
924
|
-
};
|
|
925
|
-
});
|
|
926
|
-
const allChannelMiddleware = combineChannelMiddleware('agent', `stream:${run.agentName}`, {
|
|
927
|
-
wireInheritedChannelMiddleware: meta?.channelMiddleware,
|
|
928
|
-
wireChannelMiddleware: [
|
|
929
|
-
...(agent.channelMiddleware ?? []),
|
|
930
|
-
...streamMiddleware,
|
|
931
|
-
],
|
|
932
|
-
});
|
|
925
|
+
const workingMemoryStreamMiddleware = toChannelStreamMiddleware(workingMemoryMiddleware, sharedNotes, interruptHandle.signal);
|
|
926
|
+
const streamMiddleware = toChannelStreamMiddleware(agent.agentMiddleware ?? [], sharedNotes, interruptHandle.signal);
|
|
927
|
+
const allChannelMiddleware = [
|
|
928
|
+
...workingMemoryStreamMiddleware,
|
|
929
|
+
...combineChannelMiddleware('agent', `stream:${run.agentName}`, {
|
|
930
|
+
wireInheritedChannelMiddleware: meta?.channelMiddleware,
|
|
931
|
+
wireChannelMiddleware: [
|
|
932
|
+
...(agent.channelMiddleware ?? []),
|
|
933
|
+
...streamMiddleware,
|
|
934
|
+
],
|
|
935
|
+
}),
|
|
936
|
+
];
|
|
933
937
|
const persistingChannel = createPersistingChannel(channel, storage, run.threadId, singletonServices.logger);
|
|
934
938
|
const wrappedChannel = allChannelMiddleware.length > 0
|
|
935
939
|
? wrapChannelWithMiddleware({ channel: persistingChannel }, singletonServices, allChannelMiddleware).channel
|
|
936
940
|
: persistingChannel;
|
|
941
|
+
const isDelegateMode = agent.agentMode !== 'supervise' && meta?.agents?.length;
|
|
942
|
+
const delegateState = { delegated: false };
|
|
937
943
|
const streamContext = { channel, options };
|
|
944
|
+
if (isDelegateMode) {
|
|
945
|
+
streamContext.delegateState = delegateState;
|
|
946
|
+
}
|
|
947
|
+
const workingMemoryOnlyChannel = isDelegateMode && workingMemoryStreamMiddleware.length > 0
|
|
948
|
+
? createWorkingMemoryOnlyChannel(channel, singletonServices, workingMemoryStreamMiddleware)
|
|
949
|
+
: undefined;
|
|
950
|
+
const outputChannel = isDelegateMode
|
|
951
|
+
? {
|
|
952
|
+
...wrappedChannel,
|
|
953
|
+
send: (event) => {
|
|
954
|
+
if (delegateState.delegated &&
|
|
955
|
+
(event.type === 'text-delta' || event.type === 'reasoning-delta')) {
|
|
956
|
+
return workingMemoryOnlyChannel?.send(event);
|
|
957
|
+
}
|
|
958
|
+
return wrappedChannel.send(event);
|
|
959
|
+
},
|
|
960
|
+
}
|
|
961
|
+
: wrappedChannel;
|
|
938
962
|
const resumeTools = (await buildToolDefs(params, new Map(), run.resourceId, resolvedName, packageName, streamContext, agentMiddlewares)).tools;
|
|
939
963
|
const resolved = resolveModelConfig(resolvedName, agent);
|
|
940
964
|
const maxSteps = resolved.maxSteps ?? 10;
|
|
@@ -959,7 +983,7 @@ async function continueAfterToolResult(run, agent, packageName, resolvedName, st
|
|
|
959
983
|
runnerParams,
|
|
960
984
|
maxSteps,
|
|
961
985
|
agentRunner,
|
|
962
|
-
streamChannel:
|
|
986
|
+
streamChannel: outputChannel,
|
|
963
987
|
persistingChannel,
|
|
964
988
|
channel,
|
|
965
989
|
agentMiddlewares,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CorePermissionGroup, CorePikkuPermission } from '../../function/functions.types.js';
|
|
2
2
|
import type { CorePikkuMiddleware, MiddlewareMetadata } from '../../middleware/middleware.types.js';
|
|
3
|
+
import type { CoreSingletonServices } from '../../types/core.types.js';
|
|
3
4
|
import type { PermissionMetadata } from '../../function/function-meta.types.js';
|
|
4
5
|
import type { AIProviderOptions } from '../../services/agent-runner-service.js';
|
|
5
6
|
import type { PikkuChannel } from '../channel/channel.types.js';
|
|
@@ -169,8 +170,8 @@ export interface AgentToolDef extends Partial<ApprovalPolicy> {
|
|
|
169
170
|
*/
|
|
170
171
|
forwardsApproval?: boolean;
|
|
171
172
|
}
|
|
172
|
-
export interface PikkuAgentMiddlewareHooks<State extends Record<string, unknown> = Record<string, unknown>,
|
|
173
|
-
modifyInput?: (services:
|
|
173
|
+
export interface PikkuAgentMiddlewareHooks<State extends Record<string, unknown> = Record<string, unknown>, SingletonServices extends CoreSingletonServices = CoreSingletonServices> {
|
|
174
|
+
modifyInput?: (services: SingletonServices, ctx: {
|
|
174
175
|
messages: AgentMessage[];
|
|
175
176
|
instructions: string;
|
|
176
177
|
/**
|
|
@@ -197,7 +198,7 @@ export interface PikkuAgentMiddlewareHooks<State extends Record<string, unknown>
|
|
|
197
198
|
messages: AgentMessage[];
|
|
198
199
|
instructions: string;
|
|
199
200
|
};
|
|
200
|
-
modifyOutputStream?: (services:
|
|
201
|
+
modifyOutputStream?: (services: SingletonServices, ctx: {
|
|
201
202
|
event: AgentStreamEvent;
|
|
202
203
|
allEvents: readonly AgentStreamEvent[];
|
|
203
204
|
/** Private to this middleware, for this run. Cross-middleware facts go in `shared`. */
|
|
@@ -241,7 +242,7 @@ export interface PikkuAgentMiddlewareHooks<State extends Record<string, unknown>
|
|
|
241
242
|
* and handed to anything that grades the run, and scrubbing the reply alone
|
|
242
243
|
* leaves them untouched.
|
|
243
244
|
*/
|
|
244
|
-
modifyOutput?: (services:
|
|
245
|
+
modifyOutput?: (services: SingletonServices, ctx: {
|
|
245
246
|
text: string;
|
|
246
247
|
messages: AgentMessage[];
|
|
247
248
|
usage: {
|
|
@@ -258,7 +259,7 @@ export interface PikkuAgentMiddlewareHooks<State extends Record<string, unknown>
|
|
|
258
259
|
messages: AgentMessage[];
|
|
259
260
|
toolCalls?: NonNullable<AgentStep['toolCalls']>;
|
|
260
261
|
};
|
|
261
|
-
beforeToolCall?: (services:
|
|
262
|
+
beforeToolCall?: (services: SingletonServices, ctx: {
|
|
262
263
|
toolName: string;
|
|
263
264
|
toolCallId: string;
|
|
264
265
|
args: Record<string, unknown>;
|
|
@@ -267,7 +268,7 @@ export interface PikkuAgentMiddlewareHooks<State extends Record<string, unknown>
|
|
|
267
268
|
} | void> | {
|
|
268
269
|
args: Record<string, unknown>;
|
|
269
270
|
} | void;
|
|
270
|
-
afterToolCall?: (services:
|
|
271
|
+
afterToolCall?: (services: SingletonServices, ctx: {
|
|
271
272
|
toolName: string;
|
|
272
273
|
toolCallId: string;
|
|
273
274
|
args: Record<string, unknown>;
|
|
@@ -278,7 +279,7 @@ export interface PikkuAgentMiddlewareHooks<State extends Record<string, unknown>
|
|
|
278
279
|
} | void> | {
|
|
279
280
|
result: unknown;
|
|
280
281
|
} | void;
|
|
281
|
-
afterStep?: (services:
|
|
282
|
+
afterStep?: (services: SingletonServices, ctx: {
|
|
282
283
|
stepNumber: number;
|
|
283
284
|
text: string;
|
|
284
285
|
toolCalls: {
|
|
@@ -299,7 +300,7 @@ export interface PikkuAgentMiddlewareHooks<State extends Record<string, unknown>
|
|
|
299
300
|
};
|
|
300
301
|
finishReason: string;
|
|
301
302
|
}) => Promise<void> | void;
|
|
302
|
-
onError?: (services:
|
|
303
|
+
onError?: (services: SingletonServices, ctx: {
|
|
303
304
|
error: Error;
|
|
304
305
|
stepNumber: number;
|
|
305
306
|
messages: AgentMessage[];
|
|
@@ -245,7 +245,7 @@ export class ContextAwareRPCService {
|
|
|
245
245
|
headers.authorization = `Bearer ${token}`;
|
|
246
246
|
}
|
|
247
247
|
if (this.wire.traceId) {
|
|
248
|
-
headers['x-
|
|
248
|
+
headers['x-request-id'] = this.wire.traceId;
|
|
249
249
|
}
|
|
250
250
|
const base = serverUrl.replace(/\/+$/, '');
|
|
251
251
|
const res = await fetch(`${base}/remote/rpc/${encodeURIComponent(remoteFn)}`, {
|
|
@@ -24,7 +24,7 @@ export function validateAndBuildVariableDefinitionsMeta(definitions, schemaLooku
|
|
|
24
24
|
description: def.description,
|
|
25
25
|
variableId: def.variableId,
|
|
26
26
|
schema: def.schema,
|
|
27
|
-
|
|
27
|
+
optional: def.optional,
|
|
28
28
|
docsUrl: def.docsUrl,
|
|
29
29
|
sourceFile: def.sourceFile,
|
|
30
30
|
};
|
|
@@ -39,7 +39,7 @@ export function validateAndBuildVariableDefinitionsMeta(definitions, schemaLooku
|
|
|
39
39
|
description: def.description,
|
|
40
40
|
variableId: def.variableId,
|
|
41
41
|
schema: def.schema,
|
|
42
|
-
|
|
42
|
+
optional: def.optional,
|
|
43
43
|
docsUrl: def.docsUrl,
|
|
44
44
|
sourceFile: def.sourceFile,
|
|
45
45
|
};
|
|
@@ -5,13 +5,19 @@ export type CoreVariable<T = unknown> = {
|
|
|
5
5
|
variableId: string;
|
|
6
6
|
schema: T;
|
|
7
7
|
/**
|
|
8
|
-
* A variable is
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* A variable is REQUIRED by default, and marking it `optional` is how a
|
|
9
|
+
* declaration says its absence is a supported state. Same flag, same
|
|
10
|
+
* polarity and same meaning as `CoreSecret.optional` — one word to learn
|
|
11
|
+
* rather than two with opposite senses.
|
|
12
|
+
*
|
|
13
|
+
* Defaulting to required rather than following `variables.get`'s
|
|
14
|
+
* `T | undefined` return is deliberate. That signature describes what a
|
|
15
|
+
* caller must HANDLE, not whether a deployment is correct without the value:
|
|
16
|
+
* an undefined feature flag is fine, an undefined API base URL is an outage
|
|
17
|
+
* that the type system cannot tell apart. Declaring the difference is the
|
|
18
|
+
* point of the flag, and the safe default for an undeclared one is to ask.
|
|
13
19
|
*/
|
|
14
|
-
|
|
20
|
+
optional?: boolean;
|
|
15
21
|
docsUrl?: string;
|
|
16
22
|
};
|
|
17
23
|
export type VariableDefinitionMeta = {
|
|
@@ -20,7 +26,7 @@ export type VariableDefinitionMeta = {
|
|
|
20
26
|
description?: string;
|
|
21
27
|
variableId: string;
|
|
22
28
|
schema?: Record<string, unknown> | string;
|
|
23
|
-
|
|
29
|
+
optional?: boolean;
|
|
24
30
|
docsUrl?: string;
|
|
25
31
|
sourceFile?: string;
|
|
26
32
|
};
|
|
@@ -6,8 +6,8 @@ import type { ScenarioPersonas } from '../../services/personas-service.js';
|
|
|
6
6
|
import type { ScenarioBrowserProvider, ScenarioEnvironment, ScenarioSurface } from './scenario-step.types.js';
|
|
7
7
|
import type { PikkuWorkflowWire, WorkflowQueueOptions } from './workflow.types.js';
|
|
8
8
|
export { addFeature, resolveFeatureScenarios } from './feature.js';
|
|
9
|
-
export type
|
|
10
|
-
export type
|
|
9
|
+
export type { CoreFeature, CoreFeatureScenario, FeatureMeta, FeaturesMeta, PikkuBrowserWire, PikkuScenarioWire, ScenarioBrowserFailure, ScenarioBrowserProvider, ScenarioEnvironment, ScenarioStepKind, ScenarioStepMeta, ScenarioStepOptions, ScenarioStepPhase, ScenarioSurface, TestIdSelector, } from './scenario.types.js';
|
|
10
|
+
export type { ScenarioArtifact, ScenarioFailureDetail, ScenarioResult, ScenarioRunRecord, ScenarioRunReport, ScenarioRunStatus, ScenarioRunStore, ScenarioRunSummary, ScenarioStepRow, } from './scenario-run.types.js';
|
|
11
11
|
export { SCENARIO_SURFACES } from './scenario-step.types.js';
|
|
12
12
|
export { resolveScenarioSurfaces } from './scenario-surface.js';
|
|
13
13
|
export { pollUntil, type PollOptions } from './scenario-poll.js';
|