@peakflo/agent-harness 0.2.14 → 0.2.15
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.
|
@@ -67,7 +67,9 @@ relist) {
|
|
|
67
67
|
Date.now() - checkedAt < SUCCESS_CACHE_TTL_MS)
|
|
68
68
|
return null;
|
|
69
69
|
const priorFailure = failed?.get(key);
|
|
70
|
-
if (priorFailure &&
|
|
70
|
+
if (priorFailure &&
|
|
71
|
+
!relist?.has(name) &&
|
|
72
|
+
Date.now() < priorFailure.nextRetryAt) {
|
|
71
73
|
return {
|
|
72
74
|
name,
|
|
73
75
|
reason: priorFailure.reason,
|
|
@@ -11,11 +11,11 @@ import { type McpServerToolListing, type McpToolsetMember } from './mcp-toolset'
|
|
|
11
11
|
* request before the prompt.
|
|
12
12
|
*
|
|
13
13
|
* WHEN A LISTING IS REUSED. Only while all of these still hold:
|
|
14
|
-
* - the lease carries `mcpToolScope` (the speaker and
|
|
15
|
-
* permissions the gateway applies). An older control plane does not send
|
|
14
|
+
* - the lease carries `mcpToolScope` (the speaker, Space and effective grants
|
|
15
|
+
* whose live permissions the gateway applies). An older control plane does not send
|
|
16
16
|
* it, and then nothing is reused, so a list never crosses speakers;
|
|
17
17
|
* - the scope is the one the listing was taken under. A different speaker in
|
|
18
|
-
* a shared chat drops every stored listing
|
|
18
|
+
* a shared chat or a changed grant set drops every stored listing;
|
|
19
19
|
* - the server's own fingerprint (name, upstream URL, allowlist) is the same;
|
|
20
20
|
* - the server is still configured at the URL the listing was taken from;
|
|
21
21
|
* - the listing is younger than `MCP_TOOL_STORE_MAX_AGE_MS` and than the
|
|
@@ -36,10 +36,11 @@ import { type McpServerToolListing, type McpToolsetMember } from './mcp-toolset'
|
|
|
36
36
|
* success cache, so a turn a few minutes after the last one skips the
|
|
37
37
|
* preflight too (the baseline turn `91c442db` came 6 minutes after the one
|
|
38
38
|
* before it and spent 9.7 s in it). Short, because a catalogue change that is
|
|
39
|
-
* not in the lease (
|
|
40
|
-
* shows only after this
|
|
41
|
-
*
|
|
42
|
-
* but the gateway refuses
|
|
39
|
+
* not represented in the lease (for example an upstream tool schema change)
|
|
40
|
+
* shows only after this. Integration grant changes invalidate the lease scope
|
|
41
|
+
* key on the next turn. Within a running turn a revoked tool can stay listed,
|
|
42
|
+
* but the gateway refuses its next call. Pi also replaces its cached metadata
|
|
43
|
+
* whenever it connects to the server.
|
|
43
44
|
*/
|
|
44
45
|
export declare const MCP_TOOL_STORE_MAX_AGE_MS: number;
|
|
45
46
|
interface StoredServer {
|
|
@@ -48,6 +49,7 @@ interface StoredServer {
|
|
|
48
49
|
}
|
|
49
50
|
interface StoredToolset {
|
|
50
51
|
scopeKey: string;
|
|
52
|
+
members: Map<string, string>;
|
|
51
53
|
servers: Map<string, StoredServer>;
|
|
52
54
|
}
|
|
53
55
|
/** Shared by the harness across the per-turn runners, keyed by session id. */
|
|
@@ -16,11 +16,11 @@ const mcp_toolset_1 = require("./mcp-toolset");
|
|
|
16
16
|
* request before the prompt.
|
|
17
17
|
*
|
|
18
18
|
* WHEN A LISTING IS REUSED. Only while all of these still hold:
|
|
19
|
-
* - the lease carries `mcpToolScope` (the speaker and
|
|
20
|
-
* permissions the gateway applies). An older control plane does not send
|
|
19
|
+
* - the lease carries `mcpToolScope` (the speaker, Space and effective grants
|
|
20
|
+
* whose live permissions the gateway applies). An older control plane does not send
|
|
21
21
|
* it, and then nothing is reused, so a list never crosses speakers;
|
|
22
22
|
* - the scope is the one the listing was taken under. A different speaker in
|
|
23
|
-
* a shared chat drops every stored listing
|
|
23
|
+
* a shared chat or a changed grant set drops every stored listing;
|
|
24
24
|
* - the server's own fingerprint (name, upstream URL, allowlist) is the same;
|
|
25
25
|
* - the server is still configured at the URL the listing was taken from;
|
|
26
26
|
* - the listing is younger than `MCP_TOOL_STORE_MAX_AGE_MS` and than the
|
|
@@ -41,10 +41,11 @@ const mcp_toolset_1 = require("./mcp-toolset");
|
|
|
41
41
|
* success cache, so a turn a few minutes after the last one skips the
|
|
42
42
|
* preflight too (the baseline turn `91c442db` came 6 minutes after the one
|
|
43
43
|
* before it and spent 9.7 s in it). Short, because a catalogue change that is
|
|
44
|
-
* not in the lease (
|
|
45
|
-
* shows only after this
|
|
46
|
-
*
|
|
47
|
-
* but the gateway refuses
|
|
44
|
+
* not represented in the lease (for example an upstream tool schema change)
|
|
45
|
+
* shows only after this. Integration grant changes invalidate the lease scope
|
|
46
|
+
* key on the next turn. Within a running turn a revoked tool can stay listed,
|
|
47
|
+
* but the gateway refuses its next call. Pi also replaces its cached metadata
|
|
48
|
+
* whenever it connects to the server.
|
|
48
49
|
*/
|
|
49
50
|
exports.MCP_TOOL_STORE_MAX_AGE_MS = 15 * 60_000;
|
|
50
51
|
const MAX_STORED_SESSIONS = 256;
|
|
@@ -67,13 +68,21 @@ function reusableUntil(listing) {
|
|
|
67
68
|
function planMcpToolReuse(store, sessionId, members, scope, configured, now = Date.now()) {
|
|
68
69
|
const plan = { reused: {}, relist: new Set(), dropped: {} };
|
|
69
70
|
const stored = store.get(sessionId);
|
|
70
|
-
if (!stored)
|
|
71
|
+
if (!stored) {
|
|
72
|
+
// A server can reappear inside the preflight success/failure TTL. With no
|
|
73
|
+
// listing for this scope, probe it now rather than inherit that old hint.
|
|
74
|
+
if (scope)
|
|
75
|
+
for (const member of members)
|
|
76
|
+
plan.relist.add(member.name);
|
|
71
77
|
return plan;
|
|
78
|
+
}
|
|
72
79
|
const dropAll = (reason) => {
|
|
73
80
|
for (const name of stored.servers.keys()) {
|
|
74
81
|
plan.relist.add(name);
|
|
75
82
|
plan.dropped[name] = reason;
|
|
76
83
|
}
|
|
84
|
+
for (const member of members)
|
|
85
|
+
plan.relist.add(member.name);
|
|
77
86
|
store.delete(sessionId);
|
|
78
87
|
};
|
|
79
88
|
if (!scope) {
|
|
@@ -81,7 +90,7 @@ function planMcpToolReuse(store, sessionId, members, scope, configured, now = Da
|
|
|
81
90
|
return plan;
|
|
82
91
|
}
|
|
83
92
|
if (stored.scopeKey !== scopeKeyOf(scope)) {
|
|
84
|
-
dropAll('
|
|
93
|
+
dropAll('tool scope changed');
|
|
85
94
|
return plan;
|
|
86
95
|
}
|
|
87
96
|
const current = new Map(members.map((member) => [member.name, member]));
|
|
@@ -111,10 +120,13 @@ function planMcpToolReuse(store, sessionId, members, scope, configured, now = Da
|
|
|
111
120
|
}
|
|
112
121
|
plan.reused[name] = entry.listing;
|
|
113
122
|
}
|
|
123
|
+
for (const member of members) {
|
|
124
|
+
if (stored.members.get(member.name) !== memberKey(member))
|
|
125
|
+
plan.relist.add(member.name);
|
|
126
|
+
}
|
|
114
127
|
// A reuse counts as use: move the session to the newest end.
|
|
115
128
|
store.delete(sessionId);
|
|
116
|
-
|
|
117
|
-
store.set(sessionId, stored);
|
|
129
|
+
store.set(sessionId, stored);
|
|
118
130
|
return plan;
|
|
119
131
|
}
|
|
120
132
|
/**
|
|
@@ -129,8 +141,9 @@ function rememberMcpToolListings(store, sessionId, members, scope, listings) {
|
|
|
129
141
|
const scopeKey = scopeKeyOf(scope);
|
|
130
142
|
let stored = store.get(sessionId);
|
|
131
143
|
if (!stored || stored.scopeKey !== scopeKey) {
|
|
132
|
-
stored = { scopeKey, servers: new Map() };
|
|
144
|
+
stored = { scopeKey, members: new Map(), servers: new Map() };
|
|
133
145
|
}
|
|
146
|
+
stored.members = new Map(members.map((member) => [member.name, memberKey(member)]));
|
|
134
147
|
const current = new Map(members.map((member) => [member.name, member]));
|
|
135
148
|
const kept = [];
|
|
136
149
|
for (const [name, listing] of listings) {
|
|
@@ -142,8 +155,7 @@ function rememberMcpToolListings(store, sessionId, members, scope, listings) {
|
|
|
142
155
|
}
|
|
143
156
|
// Most recently used last, so the oldest session is evicted first.
|
|
144
157
|
store.delete(sessionId);
|
|
145
|
-
|
|
146
|
-
store.set(sessionId, stored);
|
|
158
|
+
store.set(sessionId, stored);
|
|
147
159
|
while (store.size > MAX_STORED_SESSIONS) {
|
|
148
160
|
store.delete(store.keys().next().value);
|
|
149
161
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -546,8 +546,8 @@ export interface LeasedSession {
|
|
|
546
546
|
turnId: string;
|
|
547
547
|
runtime: RunnerRuntime;
|
|
548
548
|
/**
|
|
549
|
-
* Opaque. Changes
|
|
550
|
-
*
|
|
549
|
+
* Opaque. Changes with the speaker, Space or effective integration grants
|
|
550
|
+
* (including tool limits), so the tool catalogue may differ. The harness
|
|
551
551
|
* reuses a session's stored MCP tool listings on a later turn only while
|
|
552
552
|
* this is the same. Optional and additive: absent (an older control plane)
|
|
553
553
|
* means nothing is reused across turns.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peakflo/agent-harness",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.15",
|
|
4
4
|
"description": "Headless Peakflo coding-agent harness. One codebase, three hosting modes: a Daytona cloud sandbox, a customer VM, and the 20x desktop.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|