@pasko70/pibo 3.2.6 → 3.3.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/dist/agent-runtime/history.js +5 -16
- package/dist/agent-runtimes/codex-native/adapter.js +44 -14
- package/dist/agent-runtimes/codex-native/process.js +1 -1
- package/dist/agent-runtimes/codex-native/protocol-version.js +6 -6
- package/dist/agent-runtimes/omp/process.js +23 -3
- package/dist/agent-runtimes/omp/protocol-version.js +2 -0
- package/dist/agent-runtimes/pi/adapter.js +1 -1
- package/dist/agent-runtimes/pi/model-catalog.js +2 -2
- package/dist/agent-runtimes/pi/runtime.js +6 -3
- package/dist/apps/chat/chat-api-routes.js +1 -1
- package/dist/apps/chat/chat-request-normalizers.js +7 -2
- package/dist/apps/chat/data/read-state-service.js +13 -11
- package/dist/apps/chat/session-metadata.js +30 -0
- package/dist/apps/chat/trace.js +15 -2
- package/dist/apps/chat/web-app.js +125 -17
- package/dist/apps/chat-ui/assets/{dist-CLxh9uSM.js → dist-BbCjOLiw.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-B9gnHZqL.js → dist-Bd9gWCbk.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CR75V9VE.js → dist-C9a0_446.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BUupvbV9.js → dist-CcAkJ8iv.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-DXdSiTyu.js → dist-Kc3zbvQi.js} +1 -1
- package/dist/apps/chat-ui/assets/index-DWJnUye8.css +1 -0
- package/dist/apps/chat-ui/assets/index-exohEOUE.js +228 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/core/session-router.js +12 -6
- package/dist/debug/agents.js +9 -3
- package/dist/plugins/builtin.js +2 -0
- package/dist/plugins/registry.js +1 -0
- package/dist/providers/openai-gpt56.js +41 -3
- package/dist/subagents/context.js +2 -2
- package/dist/subagents/observation-query.js +49 -6
- package/dist/subagents/observation-text-regex.js +124 -0
- package/dist/subagents/tool.js +3 -2
- package/dist/tools/hashline.js +64 -0
- package/npm-shrinkwrap.json +781 -280
- package/package.json +6 -5
- package/dist/apps/chat-ui/assets/index-BMkUGVRS.js +0 -228
- package/dist/apps/chat-ui/assets/index-BwT5grLv.css +0 -1
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
<link rel="manifest" href="/apps/chat/manifest.webmanifest" />
|
|
12
12
|
<link rel="apple-touch-icon" href="/apps/chat/assets/pwa-images/ios/180.png" />
|
|
13
13
|
<title>Pibo Web Chat</title>
|
|
14
|
-
<script type="module" crossorigin src="/apps/chat/assets/index-
|
|
14
|
+
<script type="module" crossorigin src="/apps/chat/assets/index-exohEOUE.js"></script>
|
|
15
15
|
<link rel="modulepreload" crossorigin href="/apps/chat/assets/dist-CqM4On9D.js">
|
|
16
16
|
<link rel="modulepreload" crossorigin href="/apps/chat/assets/dist-DLJCGrGQ.js">
|
|
17
17
|
<link rel="modulepreload" crossorigin href="/apps/chat/assets/dist-S3zJGntM.js">
|
|
18
|
-
<link rel="stylesheet" crossorigin href="/apps/chat/assets/index-
|
|
18
|
+
<link rel="stylesheet" crossorigin href="/apps/chat/assets/index-DWJnUye8.css">
|
|
19
19
|
</head>
|
|
20
20
|
<body>
|
|
21
21
|
<div id="root"></div>
|
|
@@ -2090,12 +2090,18 @@ export class PiboSessionRouter {
|
|
|
2090
2090
|
for (const agentId of input.agentIds ?? [])
|
|
2091
2091
|
this.requireManagedAgent(parentPiboSessionId, agentId);
|
|
2092
2092
|
const query = preparePiboAgentObservationQuery(input);
|
|
2093
|
-
const
|
|
2094
|
-
|
|
2095
|
-
:
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2093
|
+
const observations = this.agentObservations;
|
|
2094
|
+
function* ordered() {
|
|
2095
|
+
const start = query.scanOrder === "asc" ? 0 : observations.length - 1;
|
|
2096
|
+
const end = query.scanOrder === "asc" ? observations.length : -1;
|
|
2097
|
+
const step = query.scanOrder === "asc" ? 1 : -1;
|
|
2098
|
+
for (let index = start; index !== end; index += step) {
|
|
2099
|
+
const { managingParentId, ...observation } = observations[index];
|
|
2100
|
+
if (managingParentId === parentPiboSessionId)
|
|
2101
|
+
yield observation;
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
return selectPiboAgentObservationPage(ordered(), query, { evictedThrough: this.agentObservationEvictedThroughByParent.get(parentPiboSessionId) ?? 0 });
|
|
2099
2105
|
}
|
|
2100
2106
|
async killManagedAgent(parentPiboSessionId, agentId) {
|
|
2101
2107
|
const child = this.requireManagedAgent(parentPiboSessionId, agentId);
|
package/dist/debug/agents.js
CHANGED
|
@@ -47,6 +47,7 @@ export async function runDebugAgentsCli(args) {
|
|
|
47
47
|
since: parsed.since,
|
|
48
48
|
until: parsed.until,
|
|
49
49
|
textContains: parsed.textContains,
|
|
50
|
+
textRegex: parsed.textRegex,
|
|
50
51
|
afterSequence: parsed.afterSequence,
|
|
51
52
|
order: parsed.order,
|
|
52
53
|
limit: parsed.limit,
|
|
@@ -282,7 +283,7 @@ Filters use exact values. The command inspects only direct pibo.subagents childr
|
|
|
282
283
|
Usage:
|
|
283
284
|
pibo debug agents ${parentPiboSessionId} observe [--tool-call-id id] [--agent-id ps_...] [--name name]
|
|
284
285
|
[--thread-key key] [--event-type type] [--kind message|thinking|tool|error|lifecycle|event]
|
|
285
|
-
[--role role] [--since iso] [--until iso] [--contains text] [--after-sequence n]
|
|
286
|
+
[--role role] [--since iso] [--until iso] [--contains text] [--regex pattern] [--after-sequence n]
|
|
286
287
|
[--order asc|desc] [--limit 1..200] [--include-tools]
|
|
287
288
|
[--tool-detail summary|full] [--details] [--json]
|
|
288
289
|
|
|
@@ -290,7 +291,9 @@ Default: the newest 20 completed assistant messages, with streaming deltas and t
|
|
|
290
291
|
Use --include-tools for compact tool calls and terminal results. Explicit --event-type or --kind
|
|
291
292
|
filters retain access to progress events. Repeat plural filters for OR within that field.
|
|
292
293
|
Different fields combine with AND. With --after-sequence, pages always consume the oldest unseen rows;
|
|
293
|
-
--order desc reverses only the returned page, so nextAfterSequence remains safe for polling
|
|
294
|
+
--order desc reverses only the returned page, so nextAfterSequence remains safe for polling.
|
|
295
|
+
--regex uses case-sensitive bundled rg/Rust-regex syntax; inline flags change case or multiline behavior.
|
|
296
|
+
Regex rejects NUL text and literal or escaped NUL patterns and requires the optional rg platform binary.`);
|
|
294
297
|
}
|
|
295
298
|
function parseAgentDebugOptions(args) {
|
|
296
299
|
const parsed = {
|
|
@@ -350,6 +353,8 @@ function parseAgentDebugOptions(args) {
|
|
|
350
353
|
parsed.until = value;
|
|
351
354
|
else if (arg === "--contains")
|
|
352
355
|
parsed.textContains = value;
|
|
356
|
+
else if (arg === "--regex")
|
|
357
|
+
parsed.textRegex = value;
|
|
353
358
|
else if (arg === "--after-sequence")
|
|
354
359
|
parsed.afterSequence = parseNonNegativeInteger(value, arg);
|
|
355
360
|
else if (arg === "--order") {
|
|
@@ -377,7 +382,8 @@ function validateAgentDebugOptions(command, parsed) {
|
|
|
377
382
|
if (parsed.details || parsed.includeTools || parsed.toolCallIds.length > 0 || parsed.agentIds.length > 0
|
|
378
383
|
|| parsed.threadKeys.length > 0 || parsed.eventTypes.length > 0 || parsed.kinds.length > 0
|
|
379
384
|
|| parsed.roles.length > 0 || parsed.since !== undefined || parsed.until !== undefined
|
|
380
|
-
|| parsed.textContains !== undefined || parsed.
|
|
385
|
+
|| parsed.textContains !== undefined || parsed.textRegex !== undefined
|
|
386
|
+
|| parsed.afterSequence !== undefined || parsed.order !== undefined
|
|
381
387
|
|| parsed.limit !== undefined || parsed.toolDetail !== undefined)
|
|
382
388
|
throw new Error("Unsupported option for pibo debug agents list. Run the list command with --help.");
|
|
383
389
|
return;
|
package/dist/plugins/builtin.js
CHANGED
|
@@ -7,6 +7,7 @@ import { parsePiboThinkingLevel } from "../core/thinking.js";
|
|
|
7
7
|
import { createWebSearchToolProfile } from "../tools/web-search.js";
|
|
8
8
|
import { CODEX_BROWSER_TOOL_NAMES, createCodexBrowserToolProfiles } from "../tools/codex-browser.js";
|
|
9
9
|
import { createRuntimeToolProfile } from "../tools/runtime/tool.js";
|
|
10
|
+
import { createHashlineToolProfile } from "../tools/hashline.js";
|
|
10
11
|
import { loadModelCatalog } from "../apps/chat/model-catalog.js";
|
|
11
12
|
import { piboCodexCompatPlugin } from "./codex-compat.js";
|
|
12
13
|
import { piboCodexNativePlugin } from "./codex-native.js";
|
|
@@ -213,6 +214,7 @@ export const piboCorePlugin = definePiboPlugin({
|
|
|
213
214
|
});
|
|
214
215
|
api.registerTool(createWebSearchToolProfile());
|
|
215
216
|
api.registerTool(createRuntimeToolProfile());
|
|
217
|
+
api.registerTool(createHashlineToolProfile());
|
|
216
218
|
api.registerTools(createCodexBrowserToolProfiles());
|
|
217
219
|
api.registerCapabilityPackage({
|
|
218
220
|
name: "codex-browser-interface",
|
package/dist/plugins/registry.js
CHANGED
|
@@ -553,6 +553,7 @@ export class PiboPluginRegistry {
|
|
|
553
553
|
yieldable: tool.yieldable !== false,
|
|
554
554
|
hasDefinition: tool.definition !== undefined || tool.createDefinition !== undefined,
|
|
555
555
|
portable: toolIsPortable(tool),
|
|
556
|
+
...(tool.replacesBuiltinTools?.length ? { replacesBuiltinTools: [...tool.replacesBuiltinTools] } : {}),
|
|
556
557
|
pluginId: tool.pluginId,
|
|
557
558
|
pluginName: tool.pluginId ? this.pluginNames.get(tool.pluginId) : undefined,
|
|
558
559
|
...(tool.providerTool ? { providerTool: tool.providerTool } : {}),
|
|
@@ -9,6 +9,8 @@ export const OPENAI_CODEX_BASE_URL = "https://chatgpt.com/backend-api";
|
|
|
9
9
|
const OPENAI_GPT_56_CONTEXT_WINDOW = 1_050_000;
|
|
10
10
|
const OPENAI_CODEX_GPT_56_CONTEXT_WINDOW = 272_000;
|
|
11
11
|
const GPT_56_MAX_TOKENS = 128_000;
|
|
12
|
+
const OPENAI_CODEX_GPT_6_ASTRA_CONTEXT_WINDOW = 272_000;
|
|
13
|
+
const GPT_6_ASTRA_MAX_TOKENS = 128_000;
|
|
12
14
|
export const OPENAI_GPT_56_MODELS = [
|
|
13
15
|
{
|
|
14
16
|
id: "gpt-5.6",
|
|
@@ -31,6 +33,11 @@ export const OPENAI_GPT_56_MODELS = [
|
|
|
31
33
|
cost: { input: 1, output: 6, cacheRead: 0.1, cacheWrite: 1.25 },
|
|
32
34
|
},
|
|
33
35
|
];
|
|
36
|
+
export const OPENAI_CODEX_GPT_6_ASTRA_MODEL = {
|
|
37
|
+
id: "gpt-6-astra",
|
|
38
|
+
name: "GPT-6-Astra",
|
|
39
|
+
cost: { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 },
|
|
40
|
+
};
|
|
34
41
|
const OPENAI_GPT_56_MODEL_IDS = new Set(OPENAI_GPT_56_MODELS.map((model) => model.id));
|
|
35
42
|
export function getBuiltInOpenAiModels() {
|
|
36
43
|
return getBuiltInProviderModels(OPENAI_PROVIDER_ID);
|
|
@@ -60,7 +67,13 @@ export function buildOpenAiCodexGpt56Models(baseModels = getBuiltInOpenAiCodexMo
|
|
|
60
67
|
modelCost: (model) => ({ ...model.cost, cacheWrite: 0 }),
|
|
61
68
|
});
|
|
62
69
|
}
|
|
63
|
-
export function
|
|
70
|
+
export function buildOpenAiCodexSupplementalModels(baseModels = getBuiltInOpenAiCodexModels()) {
|
|
71
|
+
const models = buildOpenAiCodexGpt56Models(baseModels);
|
|
72
|
+
if (models.some((model) => model.id === OPENAI_CODEX_GPT_6_ASTRA_MODEL.id))
|
|
73
|
+
return models;
|
|
74
|
+
return [...models, openAiCodexAstraModelToRegistryModel()];
|
|
75
|
+
}
|
|
76
|
+
export function registerOpenAiSupplementalModels(modelRegistry, options = {}) {
|
|
64
77
|
const baseOpenAiModels = options.baseOpenAiModels ?? getBuiltInOpenAiModels();
|
|
65
78
|
const openAiModels = buildOpenAiGpt56Models(baseOpenAiModels);
|
|
66
79
|
const openAiAdded = countMissingGpt56Models(baseOpenAiModels, OPENAI_PROVIDER_ID);
|
|
@@ -71,8 +84,10 @@ export function registerOpenAiGpt56Models(modelRegistry, options = {}) {
|
|
|
71
84
|
models: openAiModels,
|
|
72
85
|
});
|
|
73
86
|
const baseOpenAiCodexModels = options.baseOpenAiCodexModels ?? getBuiltInOpenAiCodexModels();
|
|
74
|
-
const openAiCodexModels =
|
|
75
|
-
const
|
|
87
|
+
const openAiCodexModels = buildOpenAiCodexSupplementalModels(baseOpenAiCodexModels);
|
|
88
|
+
const hasBuiltInAstra = baseOpenAiCodexModels.some((model) => model.provider === OPENAI_CODEX_PROVIDER_ID && model.id === OPENAI_CODEX_GPT_6_ASTRA_MODEL.id);
|
|
89
|
+
const openAiCodexAdded = countMissingGpt56Models(baseOpenAiCodexModels, OPENAI_CODEX_PROVIDER_ID)
|
|
90
|
+
+ (hasBuiltInAstra ? 0 : 1);
|
|
76
91
|
modelRegistry.registerProvider(OPENAI_CODEX_PROVIDER_ID, {
|
|
77
92
|
baseUrl: OPENAI_CODEX_BASE_URL,
|
|
78
93
|
api: OPENAI_CODEX_RESPONSES_API,
|
|
@@ -85,6 +100,7 @@ export function registerOpenAiGpt56Models(modelRegistry, options = {}) {
|
|
|
85
100
|
added: openAiAdded + openAiCodexAdded,
|
|
86
101
|
};
|
|
87
102
|
}
|
|
103
|
+
export const registerOpenAiGpt56Models = registerOpenAiSupplementalModels;
|
|
88
104
|
export function findOpenAiGpt56Model(modelRegistry, model) {
|
|
89
105
|
if (!model?.provider || !model.id)
|
|
90
106
|
return undefined;
|
|
@@ -140,3 +156,25 @@ function openAiGpt56ModelToRegistryModel(model, options) {
|
|
|
140
156
|
maxTokens: GPT_56_MAX_TOKENS,
|
|
141
157
|
};
|
|
142
158
|
}
|
|
159
|
+
function openAiCodexAstraModelToRegistryModel() {
|
|
160
|
+
return {
|
|
161
|
+
id: OPENAI_CODEX_GPT_6_ASTRA_MODEL.id,
|
|
162
|
+
name: OPENAI_CODEX_GPT_6_ASTRA_MODEL.name,
|
|
163
|
+
api: OPENAI_CODEX_RESPONSES_API,
|
|
164
|
+
provider: OPENAI_CODEX_PROVIDER_ID,
|
|
165
|
+
baseUrl: OPENAI_CODEX_BASE_URL,
|
|
166
|
+
reasoning: true,
|
|
167
|
+
// Codex also advertises "ultra", but Pibo's portable thinking-level contract ends at "max".
|
|
168
|
+
thinkingLevelMap: { off: null, minimal: null, xhigh: "xhigh", max: "max" },
|
|
169
|
+
input: ["text", "image"],
|
|
170
|
+
compat: {
|
|
171
|
+
supportsOpenAIGrammarTools: true,
|
|
172
|
+
supportsAdditionalTools: true,
|
|
173
|
+
supportsToolSearch: true,
|
|
174
|
+
},
|
|
175
|
+
cost: { ...OPENAI_CODEX_GPT_6_ASTRA_MODEL.cost, cacheWrite: 0 },
|
|
176
|
+
// Codex uses 272k by default and advertises 872k only as an optional maximum override.
|
|
177
|
+
contextWindow: OPENAI_CODEX_GPT_6_ASTRA_CONTEXT_WINDOW,
|
|
178
|
+
maxTokens: GPT_6_ASTRA_MAX_TOKENS,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
@@ -33,14 +33,14 @@ export function getDelegatedAgentContextFile(subagents) {
|
|
|
33
33
|
"",
|
|
34
34
|
"pibo_run_wait({ runId, timeoutMs? }) # bounded wait only; expiry does not stop the child",
|
|
35
35
|
"pibo_run_status({ runId }) # compact lifecycle state",
|
|
36
|
-
"pibo_agents_observe({ requestIds?: [runId], afterSequence?, limit?, includeTools?, toolDetail?, ... })",
|
|
36
|
+
"pibo_agents_observe({ requestIds?: [runId], textContains?, textRegex?, afterSequence?, limit?, includeTools?, toolDetail?, ... })",
|
|
37
37
|
"pibo_run_read({ runId }) # terminal result, including the complete final agent message",
|
|
38
38
|
"pibo_run_cancel({ runId }) # explicit request cancellation",
|
|
39
39
|
"pibo_agents_list_agents({}) # available definitions and persistent child instances",
|
|
40
40
|
"pibo_agents_kill({ agentId }) # terminate one persistent child session subtree",
|
|
41
41
|
"```",
|
|
42
42
|
"",
|
|
43
|
-
"Set `sessionName` on every send to a nonblank human-readable child title of at most 40 Unicode code points. Pibo trims surrounding whitespace and rejects missing, blank, non-string, or oversized names before creating a yielded run or child session. Reuse a stable `threadKey` to continue the same child Pibo Session; a new `sessionName` updates its title without changing identity. A wait timeout is only an orchestrator wake-up. Observe defaults to the newest 20 completed assistant messages with streaming deltas, duplicate tool progress events, and tools hidden. Set `includeTools: true` for compact tool call/result summaries, `toolDetail: \"full\"` only for bounded diagnostics, and `limit: 50` when a larger page is necessary. Use `afterSequence` from the prior result for polling.",
|
|
43
|
+
"Set `sessionName` on every send to a nonblank human-readable child title of at most 40 Unicode code points. Pibo trims surrounding whitespace and rejects missing, blank, non-string, or oversized names before creating a yielded run or child session. Reuse a stable `threadKey` to continue the same child Pibo Session; a new `sessionName` updates its title without changing identity. A wait timeout is only an orchestrator wake-up. Observe defaults to the newest 20 completed assistant messages with streaming deltas, duplicate tool progress events, and tools hidden. Use `textContains` for case-insensitive substring matching or `textRegex` for case-sensitive rg/Rust-regex matching; both must match when supplied together. Set `includeTools: true` for compact tool call/result summaries, `toolDetail: \"full\"` only for bounded diagnostics, and `limit: 50` when a larger page is necessary. Use `afterSequence` from the prior result for polling.",
|
|
44
44
|
"",
|
|
45
45
|
"Observe progress and decide whether to continue waiting, steer through a new message after the current turn, cancel the request, or kill the child session.",
|
|
46
46
|
"",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PIBO_AGENT_OBSERVATION_DEFAULT_EVENT_TYPES, PIBO_AGENT_OBSERVATION_DEFAULT_TOOL_EVENT_TYPES, normalizePiboAgentObservationCursor, normalizePiboAgentObservationLimit, normalizePiboAgentObservationOrder, normalizePiboAgentObservationToolDetail, parsePiboAgentObservationTimestamp, piboAgentObservationKind, piboAgentObservationToolSummary, } from "./observations.js";
|
|
2
|
+
import { PIBO_AGENT_TEXT_REGEX_BATCH_MAX_ITEMS, PIBO_AGENT_TEXT_REGEX_BATCH_TARGET_BYTES, matchPiboAgentObservationTextRegex, preparePiboAgentObservationTextRegex, } from "./observation-text-regex.js";
|
|
2
3
|
export function preparePiboAgentObservationQuery(input = {}) {
|
|
3
4
|
const order = normalizePiboAgentObservationOrder(input.order);
|
|
4
5
|
const limit = normalizePiboAgentObservationLimit(input.limit);
|
|
@@ -21,6 +22,7 @@ export function preparePiboAgentObservationQuery(input = {}) {
|
|
|
21
22
|
const kinds = input.kinds ? new Set(input.kinds) : undefined;
|
|
22
23
|
const roles = input.roles ? new Set(input.roles) : undefined;
|
|
23
24
|
const textContains = input.textContains?.toLowerCase();
|
|
25
|
+
const textRegex = preparePiboAgentObservationTextRegex(input.textRegex);
|
|
24
26
|
const defaultMessageView = eventTypes === undefined && kinds === undefined;
|
|
25
27
|
const explicitlySelectsTools = input.eventTypes?.some((eventType) => piboAgentObservationKind(eventType) === "tool") === true
|
|
26
28
|
|| input.kinds?.includes("tool") === true
|
|
@@ -53,6 +55,7 @@ export function preparePiboAgentObservationQuery(input = {}) {
|
|
|
53
55
|
includeTools,
|
|
54
56
|
toolDetail,
|
|
55
57
|
...(scanEventTypes ? { scanEventTypes } : {}),
|
|
58
|
+
...(textRegex ? { textRegex } : {}),
|
|
56
59
|
matches(observation) {
|
|
57
60
|
if (requestIds && (!observation.requestId || !requestIds.has(observation.requestId)))
|
|
58
61
|
return false;
|
|
@@ -89,12 +92,52 @@ export function preparePiboAgentObservationQuery(input = {}) {
|
|
|
89
92
|
}
|
|
90
93
|
export function selectPiboAgentObservationPage(observations, query, options = {}) {
|
|
91
94
|
const matches = [];
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
if (query.textRegex) {
|
|
96
|
+
let candidates = [];
|
|
97
|
+
let candidateBytes = 0;
|
|
98
|
+
const matchCandidates = () => {
|
|
99
|
+
const regexMatches = matchPiboAgentObservationTextRegex(query.textRegex, candidates.map((observation) => observation.text ?? ""));
|
|
100
|
+
for (let index = 0; index < candidates.length; index += 1) {
|
|
101
|
+
if (!regexMatches[index])
|
|
102
|
+
continue;
|
|
103
|
+
matches.push(candidates[index]);
|
|
104
|
+
if (matches.length > query.limit) {
|
|
105
|
+
candidates = [];
|
|
106
|
+
candidateBytes = 0;
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
candidates = [];
|
|
111
|
+
candidateBytes = 0;
|
|
112
|
+
return false;
|
|
113
|
+
};
|
|
114
|
+
for (const observation of observations) {
|
|
115
|
+
if (!query.matches(observation))
|
|
116
|
+
continue;
|
|
117
|
+
const textBytes = Buffer.byteLength(observation.text ?? "", "utf8");
|
|
118
|
+
if (candidates.length > 0
|
|
119
|
+
&& candidateBytes + textBytes > PIBO_AGENT_TEXT_REGEX_BATCH_TARGET_BYTES
|
|
120
|
+
&& matchCandidates())
|
|
121
|
+
break;
|
|
122
|
+
candidates.push(observation);
|
|
123
|
+
candidateBytes += textBytes;
|
|
124
|
+
if (candidates.length >= PIBO_AGENT_TEXT_REGEX_BATCH_MAX_ITEMS
|
|
125
|
+
|| candidateBytes >= PIBO_AGENT_TEXT_REGEX_BATCH_TARGET_BYTES) {
|
|
126
|
+
if (matchCandidates())
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (matches.length <= query.limit && candidates.length > 0)
|
|
131
|
+
matchCandidates();
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
for (const observation of observations) {
|
|
135
|
+
if (!query.matches(observation))
|
|
136
|
+
continue;
|
|
137
|
+
matches.push(observation);
|
|
138
|
+
if (matches.length > query.limit)
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
98
141
|
}
|
|
99
142
|
const pageLimited = matches.length > query.limit;
|
|
100
143
|
const selected = matches.slice(0, query.limit);
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
export const PIBO_AGENT_TEXT_REGEX_BATCH_MAX_ITEMS = 128;
|
|
7
|
+
export const PIBO_AGENT_TEXT_REGEX_BATCH_TARGET_BYTES = 64 * 1024;
|
|
8
|
+
const PIBO_AGENT_TEXT_REGEX_MAX_OUTPUT_BYTES = 64 * 1024;
|
|
9
|
+
const requireFromHere = createRequire(import.meta.url);
|
|
10
|
+
function escapedNulIndex(pattern) {
|
|
11
|
+
for (let index = 0; index < pattern.length; index += 1) {
|
|
12
|
+
if (pattern[index] !== "\\")
|
|
13
|
+
continue;
|
|
14
|
+
let slashEnd = index;
|
|
15
|
+
while (pattern[slashEnd + 1] === "\\")
|
|
16
|
+
slashEnd += 1;
|
|
17
|
+
const slashCount = slashEnd - index + 1;
|
|
18
|
+
if (slashCount % 2 === 1) {
|
|
19
|
+
const remainder = pattern.slice(slashEnd + 1);
|
|
20
|
+
if (/^(?:x00|x\{0+\}|u\{0+\})/i.test(remainder))
|
|
21
|
+
return index;
|
|
22
|
+
}
|
|
23
|
+
index = slashEnd;
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
function assertSupportedNulPattern(pattern) {
|
|
28
|
+
if (pattern.includes("\0") || escapedNulIndex(pattern) !== undefined) {
|
|
29
|
+
throw new Error("Agent observation textRegex is invalid: matching NUL bytes is not supported.");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function resolvePiboAgentObservationRipgrepPath() {
|
|
33
|
+
const arch = process.env.npm_config_arch || process.arch;
|
|
34
|
+
const binaryName = process.platform === "win32" ? "rg.exe" : "rg";
|
|
35
|
+
const platformPackage = `@vscode/ripgrep-${process.platform}-${arch}`;
|
|
36
|
+
try {
|
|
37
|
+
const wrapperPath = requireFromHere.resolve("@vscode/ripgrep");
|
|
38
|
+
return createRequire(wrapperPath).resolve(`${platformPackage}/bin/${binaryName}`);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
throw new Error(`Agent observation textRegex is unavailable: ${platformPackage} is not installed for this platform.`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function ripgrepErrorReason(stderr) {
|
|
45
|
+
if (stderr.includes("pattern contains \"\\0\""))
|
|
46
|
+
return "matching NUL bytes is not supported";
|
|
47
|
+
const reasons = [...stderr.matchAll(/^error:\s*(.+)$/gm)];
|
|
48
|
+
return reasons.at(-1)?.[1]?.trim().replace(/\.$/, "");
|
|
49
|
+
}
|
|
50
|
+
function runRipgrepTextRegex(prepared, args, options = {}) {
|
|
51
|
+
return spawnSync(prepared.rgPath, [
|
|
52
|
+
"--no-config",
|
|
53
|
+
"--color=never",
|
|
54
|
+
"--null-data",
|
|
55
|
+
...args,
|
|
56
|
+
], {
|
|
57
|
+
...options,
|
|
58
|
+
encoding: "utf8",
|
|
59
|
+
maxBuffer: PIBO_AGENT_TEXT_REGEX_MAX_OUTPUT_BYTES,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function throwRipgrepExecutionError(action, error) {
|
|
63
|
+
const errorCode = error && "code" in error ? error.code : undefined;
|
|
64
|
+
if (errorCode === "ENOENT" || errorCode === "EACCES") {
|
|
65
|
+
throw new Error("Agent observation textRegex is unavailable: bundled rg could not be executed.");
|
|
66
|
+
}
|
|
67
|
+
const detail = errorCode === "ENOBUFS"
|
|
68
|
+
? "bundled rg output exceeded the bounded observation batch limit"
|
|
69
|
+
: "bundled rg could not run";
|
|
70
|
+
throw new Error(`Agent observation textRegex ${action} failed: ${detail}.`);
|
|
71
|
+
}
|
|
72
|
+
export function preparePiboAgentObservationTextRegex(pattern) {
|
|
73
|
+
if (pattern === undefined)
|
|
74
|
+
return undefined;
|
|
75
|
+
assertSupportedNulPattern(pattern);
|
|
76
|
+
const prepared = { pattern, rgPath: resolvePiboAgentObservationRipgrepPath() };
|
|
77
|
+
const result = runRipgrepTextRegex(prepared, ["--quiet", "--", pattern, "-"], { input: "" });
|
|
78
|
+
if (result.error)
|
|
79
|
+
throwRipgrepExecutionError("validation", result.error);
|
|
80
|
+
if (result.status === 0 || result.status === 1)
|
|
81
|
+
return prepared;
|
|
82
|
+
const reason = ripgrepErrorReason(result.stderr);
|
|
83
|
+
if (reason)
|
|
84
|
+
throw new Error(`Agent observation textRegex is invalid: ${reason}.`);
|
|
85
|
+
throwRipgrepExecutionError("validation");
|
|
86
|
+
}
|
|
87
|
+
export function matchPiboAgentObservationTextRegex(prepared, texts) {
|
|
88
|
+
for (const text of texts) {
|
|
89
|
+
if (text.includes("\0")) {
|
|
90
|
+
throw new Error("Agent observation textRegex cannot match observation text containing NUL bytes.");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (texts.length === 0)
|
|
94
|
+
return [];
|
|
95
|
+
// One private file per observation preserves record boundaries. --files-with-matches
|
|
96
|
+
// emits each fixed filename at most once, so output cannot grow with submatch count.
|
|
97
|
+
const directory = mkdtempSync(join(tmpdir(), "pibo-agent-observe-regex-"));
|
|
98
|
+
const filenames = texts.map((_, index) => index.toString().padStart(6, "0"));
|
|
99
|
+
try {
|
|
100
|
+
for (let index = 0; index < texts.length; index += 1) {
|
|
101
|
+
writeFileSync(join(directory, filenames[index]), `${texts[index]}\0`, {
|
|
102
|
+
encoding: "utf8",
|
|
103
|
+
flag: "wx",
|
|
104
|
+
mode: 0o600,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
const result = runRipgrepTextRegex(prepared, ["--files-with-matches", "--null", "--", prepared.pattern, ...filenames], { cwd: directory });
|
|
108
|
+
if (result.error)
|
|
109
|
+
throwRipgrepExecutionError("matching", result.error);
|
|
110
|
+
if (result.status === 1)
|
|
111
|
+
return texts.map(() => false);
|
|
112
|
+
if (result.status !== 0) {
|
|
113
|
+
const reason = ripgrepErrorReason(result.stderr);
|
|
114
|
+
if (reason)
|
|
115
|
+
throw new Error(`Agent observation textRegex is invalid: ${reason}.`);
|
|
116
|
+
throwRipgrepExecutionError("matching");
|
|
117
|
+
}
|
|
118
|
+
const matched = new Set(result.stdout.split("\0").filter(Boolean));
|
|
119
|
+
return filenames.map((filename) => matched.has(filename));
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
rmSync(directory, { recursive: true, force: true });
|
|
123
|
+
}
|
|
124
|
+
}
|
package/dist/subagents/tool.js
CHANGED
|
@@ -187,11 +187,11 @@ export function createAgentToolDefinitions(subagents, controller) {
|
|
|
187
187
|
name: "pibo_agents_observe",
|
|
188
188
|
title: "Pibo Agents Observe",
|
|
189
189
|
description: [
|
|
190
|
-
"Read completed delegated-agent messages with bounded cursor, identity, event, time,
|
|
190
|
+
"Read completed delegated-agent messages with bounded cursor, identity, event, time, substring, regex, order, and limit filters.",
|
|
191
191
|
"Default: the newest 20 completed assistant messages, with streaming deltas and tools hidden.",
|
|
192
192
|
"Set includeTools=true to add compact tool calls and terminal results, or pass toolCallIds to retrieve only those exact tool observations. Set toolDetail=full only for bounded diagnostic inspection.",
|
|
193
193
|
].join("\n"),
|
|
194
|
-
promptSnippet: "Observe child-agent progress through completed assistant messages. Defaults: newest 20 messages, no streaming deltas, no duplicate tool progress events, no tools. Set includeTools=true for compact tool call/result summaries, pass up to 50 exact toolCallIds to retrieve only those tool observations, use toolDetail=full for bounded raw tool text, or eventTypes/kinds for explicit progress diagnostics. Compact tool entries expose their toolCallId for follow-up queries. Pass afterSequence from the previous result for cursor polling; cursor pages consume the oldest unseen matches even when order is desc.",
|
|
194
|
+
promptSnippet: "Observe child-agent progress through completed assistant messages. Defaults: newest 20 messages, no streaming deltas, no duplicate tool progress events, no tools. Use textContains for case-insensitive substring matching or textRegex for case-sensitive rg/Rust-regex matching; when both are present, both must match. Set includeTools=true for compact tool call/result summaries, pass up to 50 exact toolCallIds to retrieve only those tool observations, use toolDetail=full for bounded raw tool text, or eventTypes/kinds for explicit progress diagnostics. Compact tool entries expose their toolCallId for follow-up queries. Pass afterSequence from the previous result for cursor polling; cursor pages consume the oldest unseen matches even when order is desc.",
|
|
195
195
|
executionMode: "parallel",
|
|
196
196
|
annotations: { readOnly: true },
|
|
197
197
|
inputSchema: Type.Object({
|
|
@@ -206,6 +206,7 @@ export function createAgentToolDefinitions(subagents, controller) {
|
|
|
206
206
|
since: Type.Optional(Type.String({ description: "Inclusive ISO-8601 lower timestamp bound" })),
|
|
207
207
|
until: Type.Optional(Type.String({ description: "Inclusive ISO-8601 upper timestamp bound" })),
|
|
208
208
|
textContains: Type.Optional(Type.String({ description: "Case-insensitive substring match against normalized observation text" })),
|
|
209
|
+
textRegex: Type.Optional(Type.String({ description: "Case-sensitive rg/Rust-regex match against normalized observation text. Use inline flags such as (?i) to change case behavior. Combines with textContains using AND semantics. NUL text and literal or escaped NUL patterns are rejected; regex use requires the optional rg platform binary." })),
|
|
209
210
|
afterSequence: Type.Optional(Type.Integer({ description: "Exclusive live observation cursor. Cursor pages consume the oldest unseen matches; desc reverses only the returned page.", minimum: 0 })),
|
|
210
211
|
order: Type.Optional(piboStringEnum(["asc", "desc"], { default: "desc", description: "Newest first by default when no cursor is supplied" })),
|
|
211
212
|
limit: Type.Optional(Type.Integer({ description: "Maximum completed messages or activity records to return. Use 50 explicitly when needed.", minimum: 1, maximum: 200, default: 20 })),
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { createReadToolDefinition, } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
export const HASHLINE_TOOL_NAME = "hashline";
|
|
4
|
+
export const HASHLINE_REPLACED_BUILTIN_TOOLS = ["read"];
|
|
5
|
+
const HASH_ALPHABET = "ZPMQVRWSNKTXJBYH";
|
|
6
|
+
const CONTINUATION_NOTICE_RE = /\n\n(\[(?:Showing lines \d+-\d+ of \d+(?: \([^)]+ limit\))?\. Use offset=\d+ to continue\.|\d+ more lines in file\. Use offset=\d+ to continue\.)\])$/;
|
|
7
|
+
export function hashLineContent(line) {
|
|
8
|
+
const normalized = line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
9
|
+
const byte = createHash("sha256").update(normalized, "utf8").digest()[0];
|
|
10
|
+
return `${HASH_ALPHABET[(byte >>> 4) & 0x0f]}${HASH_ALPHABET[byte & 0x0f]}`;
|
|
11
|
+
}
|
|
12
|
+
export function formatHashlineReadText(text, offset = 1, details) {
|
|
13
|
+
if (details?.truncation?.firstLineExceedsLimit || text.startsWith("Read image file ["))
|
|
14
|
+
return text;
|
|
15
|
+
if (text.length === 0)
|
|
16
|
+
return "File is empty.";
|
|
17
|
+
const continuation = text.match(CONTINUATION_NOTICE_RE);
|
|
18
|
+
const body = continuation ? text.slice(0, -continuation[0].length) : text;
|
|
19
|
+
const notice = continuation?.[1];
|
|
20
|
+
const lines = body.split("\n").map((line) => line.endsWith("\r") ? line.slice(0, -1) : line);
|
|
21
|
+
const firstLine = Number.isFinite(offset) && offset > 0 ? Math.floor(offset) : 1;
|
|
22
|
+
const width = String(firstLine + lines.length - 1).length;
|
|
23
|
+
const formatted = lines.map((line, index) => {
|
|
24
|
+
const lineNumber = String(firstLine + index).padStart(width, " ");
|
|
25
|
+
return `${lineNumber}#${hashLineContent(line)}:${line}`;
|
|
26
|
+
}).join("\n");
|
|
27
|
+
return notice ? `${formatted}\n\n${notice}` : formatted;
|
|
28
|
+
}
|
|
29
|
+
export function createHashlineToolDefinition(cwd) {
|
|
30
|
+
const read = createReadToolDefinition(cwd);
|
|
31
|
+
return {
|
|
32
|
+
...read,
|
|
33
|
+
name: HASHLINE_TOOL_NAME,
|
|
34
|
+
label: HASHLINE_TOOL_NAME,
|
|
35
|
+
description: "Read workspace files with short content hashes on every text line. Text output uses LINE#HASH:CONTENT anchors; images remain attachments. Use offset and limit for large files.",
|
|
36
|
+
promptSnippet: "Read file contents with LINE#HASH anchors",
|
|
37
|
+
promptGuidelines: [
|
|
38
|
+
"Use hashline instead of read, cat, or sed when examining files.",
|
|
39
|
+
"Treat the LINE#HASH prefix as metadata; the text after the first colon is the file content.",
|
|
40
|
+
],
|
|
41
|
+
async execute(toolCallId, input, signal, onUpdate, context) {
|
|
42
|
+
const result = await read.execute(toolCallId, input, signal, onUpdate, context);
|
|
43
|
+
if (result.content.some((item) => item.type === "image"))
|
|
44
|
+
return result;
|
|
45
|
+
return {
|
|
46
|
+
...result,
|
|
47
|
+
content: result.content.map((item) => item.type === "text"
|
|
48
|
+
? { ...item, text: formatHashlineReadText(item.text, input.offset, result.details) }
|
|
49
|
+
: item),
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export function createHashlineToolProfile() {
|
|
55
|
+
return {
|
|
56
|
+
name: HASHLINE_TOOL_NAME,
|
|
57
|
+
description: "Pi-only read replacement that prefixes every text line with a short content hash.",
|
|
58
|
+
yieldable: false,
|
|
59
|
+
replacesBuiltinTools: HASHLINE_REPLACED_BUILTIN_TOOLS,
|
|
60
|
+
createDefinition(context) {
|
|
61
|
+
return createHashlineToolDefinition(context.cwd ?? process.cwd());
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|