pi-advisor-flow 0.2.0 → 0.2.1
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/extensions/index.ts +15 -6
- package/package.json +12 -4
- package/src/commands.ts +123 -97
- package/src/config.ts +233 -178
- package/src/conversation.ts +43 -24
- package/src/herdr.ts +3 -6
- package/src/session-state.ts +14 -6
- package/src/tools.ts +282 -185
- package/src/ui.ts +107 -73
package/src/config.ts
CHANGED
|
@@ -154,127 +154,149 @@ export interface AdvisorConfig {
|
|
|
154
154
|
gateFailureMode?: GateFailureMode;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
const CONFIG_KEYS = new Set(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
157
|
+
const CONFIG_KEYS = new Set<keyof AdvisorConfig>([
|
|
158
|
+
"advisor",
|
|
159
|
+
"advisorAutoLoopGate",
|
|
160
|
+
"advisorBlockOnBlocked",
|
|
161
|
+
"advisorCollapseResponses",
|
|
162
|
+
"advisorCompletionGate",
|
|
163
|
+
"advisorCustomInvocation",
|
|
164
|
+
"advisorEffort",
|
|
165
|
+
"advisorFailureGate",
|
|
166
|
+
"advisorHerdrIntegration",
|
|
167
|
+
"advisorLoopThreshold",
|
|
168
|
+
"advisorMaxCallsPerSession",
|
|
169
|
+
"advisorPlanGate",
|
|
170
|
+
"advisorSessionSummary",
|
|
171
|
+
"advisorToolResultMaxBytes",
|
|
172
|
+
"advisorToolResultMaxLines",
|
|
173
|
+
"contextMaxChars",
|
|
174
|
+
"executor",
|
|
175
|
+
"executorEffort",
|
|
176
|
+
"gateFailureMode",
|
|
177
|
+
]);
|
|
178
|
+
const BOOLEAN_CONFIG_KEYS = [
|
|
179
|
+
"advisorPlanGate",
|
|
180
|
+
"advisorFailureGate",
|
|
181
|
+
"advisorCompletionGate",
|
|
182
|
+
"advisorCollapseResponses",
|
|
183
|
+
"advisorBlockOnBlocked",
|
|
184
|
+
"advisorAutoLoopGate",
|
|
185
|
+
"advisorSessionSummary",
|
|
186
|
+
"advisorHerdrIntegration",
|
|
187
|
+
] as const;
|
|
188
|
+
const STRING_CONFIG_KEYS = [
|
|
189
|
+
"executor",
|
|
190
|
+
"advisor",
|
|
191
|
+
"executorEffort",
|
|
192
|
+
"advisorEffort",
|
|
193
|
+
"advisorCustomInvocation",
|
|
194
|
+
] as const;
|
|
195
|
+
const ARGUMENT_WHITESPACE = /\s+/;
|
|
180
196
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
|
|
197
|
+
type ConfigRecord = Record<string, unknown>;
|
|
198
|
+
|
|
199
|
+
const invalidConfigValue = (
|
|
200
|
+
path: string,
|
|
201
|
+
key: string,
|
|
202
|
+
accepted: string
|
|
203
|
+
): never => {
|
|
204
|
+
throw new TypeError(
|
|
205
|
+
`Invalid advisor configuration at ${path}, key ${JSON.stringify(key)}: expected ${accepted}.`
|
|
206
|
+
);
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const validateKnownKeys = (config: ConfigRecord, path: string) => {
|
|
210
|
+
const unknownKeys = Object.keys(config).filter(
|
|
211
|
+
(key) => !CONFIG_KEYS.has(key as keyof AdvisorConfig)
|
|
212
|
+
);
|
|
213
|
+
if (unknownKeys.length > 0) {
|
|
198
214
|
throw new TypeError(
|
|
199
|
-
`Invalid advisor configuration at ${path}
|
|
215
|
+
`Invalid advisor configuration at ${path}: unknown key(s) ${unknownKeys.map((key) => JSON.stringify(key)).join(", ")}. Remove them or upgrade pi-advisor.`
|
|
200
216
|
);
|
|
201
|
-
};
|
|
202
|
-
if (config.executor !== undefined && typeof config.executor !== "string") {
|
|
203
|
-
invalid("executor", "a provider/model string");
|
|
204
|
-
}
|
|
205
|
-
if (config.advisor !== undefined && typeof config.advisor !== "string") {
|
|
206
|
-
invalid("advisor", "a provider/model string");
|
|
207
|
-
}
|
|
208
|
-
if (
|
|
209
|
-
config.executorEffort !== undefined &&
|
|
210
|
-
typeof config.executorEffort !== "string"
|
|
211
|
-
) {
|
|
212
|
-
invalid("executorEffort", "a string");
|
|
213
217
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const validateStringValues = (config: ConfigRecord, path: string) => {
|
|
221
|
+
for (const key of STRING_CONFIG_KEYS) {
|
|
222
|
+
if (config[key] !== undefined && typeof config[key] !== "string") {
|
|
223
|
+
invalidConfigValue(
|
|
224
|
+
path,
|
|
225
|
+
key,
|
|
226
|
+
key === "executor" || key === "advisor"
|
|
227
|
+
? "a provider/model string"
|
|
228
|
+
: "a string"
|
|
229
|
+
);
|
|
230
|
+
}
|
|
228
231
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
"advisorCollapseResponses",
|
|
234
|
-
"advisorBlockOnBlocked",
|
|
235
|
-
"advisorAutoLoopGate",
|
|
236
|
-
"advisorSessionSummary",
|
|
237
|
-
"advisorHerdrIntegration",
|
|
238
|
-
]) {
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const validateBooleanValues = (config: ConfigRecord, path: string) => {
|
|
235
|
+
for (const key of BOOLEAN_CONFIG_KEYS) {
|
|
239
236
|
if (config[key] !== undefined && typeof config[key] !== "boolean") {
|
|
240
|
-
|
|
237
|
+
invalidConfigValue(path, key, "true or false");
|
|
241
238
|
}
|
|
242
239
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const validateNumericValues = (config: ConfigRecord, path: string) => {
|
|
243
|
+
const numericRules: [
|
|
244
|
+
keyof AdvisorConfig,
|
|
245
|
+
(value: unknown) => boolean,
|
|
246
|
+
string,
|
|
247
|
+
][] = [
|
|
248
|
+
[
|
|
249
|
+
"contextMaxChars",
|
|
250
|
+
isValidContextMaxChars,
|
|
251
|
+
`a safe integer from 0 through ${MAX_CONTEXT_MAX_CHARS}`,
|
|
252
|
+
],
|
|
253
|
+
[
|
|
254
|
+
"advisorLoopThreshold",
|
|
255
|
+
isValidLoopThreshold,
|
|
256
|
+
"a safe integer of at least 2",
|
|
257
|
+
],
|
|
258
|
+
[
|
|
259
|
+
"advisorMaxCallsPerSession",
|
|
260
|
+
isValidMaxCallsPerSession,
|
|
261
|
+
"a non-negative safe integer",
|
|
262
|
+
],
|
|
263
|
+
[
|
|
264
|
+
"advisorToolResultMaxLines",
|
|
265
|
+
isValidToolResultMaxLines,
|
|
266
|
+
"a non-negative safe integer",
|
|
267
|
+
],
|
|
268
|
+
[
|
|
269
|
+
"advisorToolResultMaxBytes",
|
|
270
|
+
isValidToolResultMaxBytes,
|
|
271
|
+
"a non-negative safe integer",
|
|
272
|
+
],
|
|
273
|
+
];
|
|
274
|
+
for (const [key, isValid, description] of numericRules) {
|
|
275
|
+
if (config[key] !== undefined && !isValid(config[key])) {
|
|
276
|
+
invalidConfigValue(path, key, description);
|
|
277
|
+
}
|
|
254
278
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
export const validateConfig = (
|
|
282
|
+
value: unknown,
|
|
283
|
+
path = "advisor.json"
|
|
284
|
+
): value is AdvisorConfig => {
|
|
285
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
286
|
+
throw new TypeError(
|
|
287
|
+
`Invalid advisor configuration at ${path}: expected a JSON object.`
|
|
288
|
+
);
|
|
260
289
|
}
|
|
290
|
+
const config = value as ConfigRecord;
|
|
291
|
+
validateKnownKeys(config, path);
|
|
292
|
+
validateStringValues(config, path);
|
|
293
|
+
validateBooleanValues(config, path);
|
|
294
|
+
validateNumericValues(config, path);
|
|
261
295
|
if (
|
|
262
296
|
config.gateFailureMode !== undefined &&
|
|
263
297
|
!isValidGateFailureMode(config.gateFailureMode)
|
|
264
298
|
) {
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
if (
|
|
268
|
-
config.advisorToolResultMaxLines !== undefined &&
|
|
269
|
-
!isValidToolResultMaxLines(config.advisorToolResultMaxLines)
|
|
270
|
-
) {
|
|
271
|
-
invalid("advisorToolResultMaxLines", "a non-negative safe integer");
|
|
272
|
-
}
|
|
273
|
-
if (
|
|
274
|
-
config.advisorToolResultMaxBytes !== undefined &&
|
|
275
|
-
!isValidToolResultMaxBytes(config.advisorToolResultMaxBytes)
|
|
276
|
-
) {
|
|
277
|
-
invalid("advisorToolResultMaxBytes", "a non-negative safe integer");
|
|
299
|
+
invalidConfigValue(path, "gateFailureMode", GATE_FAILURE_MODES.join(", "));
|
|
278
300
|
}
|
|
279
301
|
return true;
|
|
280
302
|
};
|
|
@@ -301,79 +323,109 @@ const resetDefaults = () => {
|
|
|
301
323
|
advisorToolResultMaxBytesRef = DEFAULT_ADVISOR_TOOL_RESULT_MAX_BYTES;
|
|
302
324
|
};
|
|
303
325
|
|
|
326
|
+
const applyOptionalConfig = <Key extends keyof AdvisorConfig>(
|
|
327
|
+
config: AdvisorConfig,
|
|
328
|
+
key: Key,
|
|
329
|
+
apply: (value: NonNullable<AdvisorConfig[Key]>) => void
|
|
330
|
+
) => {
|
|
331
|
+
const value = config[key];
|
|
332
|
+
if (value !== undefined) {
|
|
333
|
+
apply(value as NonNullable<AdvisorConfig[Key]>);
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const applyNonEmptyStringConfig = (
|
|
338
|
+
value: string | undefined,
|
|
339
|
+
apply: (value: string) => void
|
|
340
|
+
) => {
|
|
341
|
+
if (value) {
|
|
342
|
+
apply(value);
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const applyConfig = (config: AdvisorConfig) => {
|
|
347
|
+
applyNonEmptyStringConfig(config.executor, setExecutorRef);
|
|
348
|
+
applyNonEmptyStringConfig(config.advisor, setAdvisorRef);
|
|
349
|
+
applyNonEmptyStringConfig(config.executorEffort, setExecutorEffortRef);
|
|
350
|
+
applyNonEmptyStringConfig(config.advisorEffort, setAdvisorEffortRef);
|
|
351
|
+
applyOptionalConfig(config, "contextMaxChars", setContextMaxCharsRef);
|
|
352
|
+
applyOptionalConfig(config, "advisorPlanGate", setAdvisorPlanGateRef);
|
|
353
|
+
applyOptionalConfig(config, "advisorFailureGate", setAdvisorFailureGateRef);
|
|
354
|
+
applyOptionalConfig(
|
|
355
|
+
config,
|
|
356
|
+
"advisorCompletionGate",
|
|
357
|
+
setAdvisorCompletionGateRef
|
|
358
|
+
);
|
|
359
|
+
applyOptionalConfig(
|
|
360
|
+
config,
|
|
361
|
+
"advisorCustomInvocation",
|
|
362
|
+
setAdvisorCustomInvocationRef
|
|
363
|
+
);
|
|
364
|
+
applyOptionalConfig(
|
|
365
|
+
config,
|
|
366
|
+
"advisorCollapseResponses",
|
|
367
|
+
setAdvisorCollapseResponsesRef
|
|
368
|
+
);
|
|
369
|
+
applyOptionalConfig(
|
|
370
|
+
config,
|
|
371
|
+
"advisorBlockOnBlocked",
|
|
372
|
+
setAdvisorBlockOnBlockedRef
|
|
373
|
+
);
|
|
374
|
+
applyOptionalConfig(config, "advisorAutoLoopGate", setAdvisorAutoLoopGateRef);
|
|
375
|
+
applyOptionalConfig(
|
|
376
|
+
config,
|
|
377
|
+
"advisorLoopThreshold",
|
|
378
|
+
setAdvisorLoopThresholdRef
|
|
379
|
+
);
|
|
380
|
+
applyOptionalConfig(
|
|
381
|
+
config,
|
|
382
|
+
"advisorMaxCallsPerSession",
|
|
383
|
+
setAdvisorMaxCallsPerSessionRef
|
|
384
|
+
);
|
|
385
|
+
applyOptionalConfig(
|
|
386
|
+
config,
|
|
387
|
+
"advisorSessionSummary",
|
|
388
|
+
setAdvisorSessionSummaryRef
|
|
389
|
+
);
|
|
390
|
+
applyOptionalConfig(config, "gateFailureMode", setAdvisorFailureModeRef);
|
|
391
|
+
applyOptionalConfig(
|
|
392
|
+
config,
|
|
393
|
+
"advisorHerdrIntegration",
|
|
394
|
+
setAdvisorHerdrIntegrationRef
|
|
395
|
+
);
|
|
396
|
+
applyOptionalConfig(
|
|
397
|
+
config,
|
|
398
|
+
"advisorToolResultMaxLines",
|
|
399
|
+
setAdvisorToolResultMaxLinesRef
|
|
400
|
+
);
|
|
401
|
+
applyOptionalConfig(
|
|
402
|
+
config,
|
|
403
|
+
"advisorToolResultMaxBytes",
|
|
404
|
+
setAdvisorToolResultMaxBytesRef
|
|
405
|
+
);
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
const readConfig = (path: string): AdvisorConfig => {
|
|
409
|
+
try {
|
|
410
|
+
const config = JSON.parse(readFileSync(path, "utf8"));
|
|
411
|
+
validateConfig(config, path);
|
|
412
|
+
return config;
|
|
413
|
+
} catch (error) {
|
|
414
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
|
|
304
418
|
export const loadConfig = (ctx: ExtensionContext) => {
|
|
305
419
|
resetDefaults();
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
config = JSON.parse(readFileSync(path, "utf8")) as AdvisorConfig;
|
|
313
|
-
validateConfig(config, path);
|
|
314
|
-
} catch (error) {
|
|
315
|
-
throw error instanceof Error ? error : new Error(String(error));
|
|
316
|
-
}
|
|
317
|
-
if (config.executor) {
|
|
318
|
-
executorRef = config.executor;
|
|
319
|
-
}
|
|
320
|
-
if (config.advisor) {
|
|
321
|
-
advisorRef = config.advisor;
|
|
322
|
-
}
|
|
323
|
-
if (config.executorEffort) {
|
|
324
|
-
executorEffortRef = config.executorEffort;
|
|
325
|
-
}
|
|
326
|
-
if (config.advisorEffort) {
|
|
327
|
-
advisorEffortRef = config.advisorEffort;
|
|
328
|
-
}
|
|
329
|
-
if (config.contextMaxChars !== undefined) {
|
|
330
|
-
contextMaxCharsRef = config.contextMaxChars;
|
|
331
|
-
}
|
|
332
|
-
if (config.advisorPlanGate !== undefined) {
|
|
333
|
-
advisorPlanGateRef = config.advisorPlanGate;
|
|
334
|
-
}
|
|
335
|
-
if (config.advisorFailureGate !== undefined) {
|
|
336
|
-
advisorFailureGateRef = config.advisorFailureGate;
|
|
337
|
-
}
|
|
338
|
-
if (config.advisorCompletionGate !== undefined) {
|
|
339
|
-
advisorCompletionGateRef = config.advisorCompletionGate;
|
|
340
|
-
}
|
|
341
|
-
if (config.advisorCustomInvocation !== undefined) {
|
|
342
|
-
advisorCustomInvocationRef = config.advisorCustomInvocation || undefined;
|
|
343
|
-
}
|
|
344
|
-
if (config.advisorCollapseResponses !== undefined) {
|
|
345
|
-
advisorCollapseResponsesRef = config.advisorCollapseResponses;
|
|
346
|
-
}
|
|
347
|
-
if (config.advisorBlockOnBlocked !== undefined) {
|
|
348
|
-
advisorBlockOnBlockedRef = config.advisorBlockOnBlocked;
|
|
349
|
-
}
|
|
350
|
-
if (config.advisorAutoLoopGate !== undefined) {
|
|
351
|
-
advisorAutoLoopGateRef = config.advisorAutoLoopGate;
|
|
352
|
-
}
|
|
353
|
-
if (config.advisorLoopThreshold !== undefined) {
|
|
354
|
-
advisorLoopThresholdRef = config.advisorLoopThreshold;
|
|
355
|
-
}
|
|
356
|
-
if (config.advisorMaxCallsPerSession !== undefined) {
|
|
357
|
-
advisorMaxCallsPerSessionRef = config.advisorMaxCallsPerSession;
|
|
358
|
-
}
|
|
359
|
-
if (config.advisorSessionSummary !== undefined) {
|
|
360
|
-
advisorSessionSummaryRef = config.advisorSessionSummary;
|
|
361
|
-
}
|
|
362
|
-
if (config.gateFailureMode !== undefined) {
|
|
363
|
-
advisorFailureModeRef = config.gateFailureMode;
|
|
364
|
-
}
|
|
365
|
-
if (config.advisorHerdrIntegration !== undefined) {
|
|
366
|
-
advisorHerdrIntegrationRef = config.advisorHerdrIntegration;
|
|
367
|
-
}
|
|
368
|
-
if (config.advisorToolResultMaxLines !== undefined) {
|
|
369
|
-
advisorToolResultMaxLinesRef = config.advisorToolResultMaxLines;
|
|
370
|
-
}
|
|
371
|
-
if (config.advisorToolResultMaxBytes !== undefined) {
|
|
372
|
-
advisorToolResultMaxBytesRef = config.advisorToolResultMaxBytes;
|
|
373
|
-
}
|
|
374
|
-
return path;
|
|
420
|
+
const path = configPaths(ctx).find(
|
|
421
|
+
(candidate): candidate is string =>
|
|
422
|
+
candidate !== null && existsSync(candidate)
|
|
423
|
+
);
|
|
424
|
+
if (!path) {
|
|
425
|
+
return null;
|
|
375
426
|
}
|
|
376
|
-
|
|
427
|
+
applyConfig(readConfig(path));
|
|
428
|
+
return path;
|
|
377
429
|
};
|
|
378
430
|
|
|
379
431
|
export const saveConfig = (ctx: ExtensionContext) => {
|
|
@@ -391,6 +443,9 @@ export const saveConfig = (ctx: ExtensionContext) => {
|
|
|
391
443
|
} catch {
|
|
392
444
|
/* replace a missing or malformed file */
|
|
393
445
|
}
|
|
446
|
+
if (advisorMaxCallsPerSessionRef === undefined) {
|
|
447
|
+
existing.advisorMaxCallsPerSession = undefined;
|
|
448
|
+
}
|
|
394
449
|
const data = {
|
|
395
450
|
...existing,
|
|
396
451
|
advisor: advisorRef,
|
|
@@ -423,7 +478,7 @@ export const parseArgs = (args: string): string | undefined => {
|
|
|
423
478
|
let nextExecutor = executorRef;
|
|
424
479
|
let nextAdvisor = advisorRef;
|
|
425
480
|
let nextContextMaxChars = contextMaxCharsRef;
|
|
426
|
-
for (const token of args.trim().split(
|
|
481
|
+
for (const token of args.trim().split(ARGUMENT_WHITESPACE).filter(Boolean)) {
|
|
427
482
|
const [key, value] = token.split("=");
|
|
428
483
|
if (key === "executor" && value) {
|
|
429
484
|
nextExecutor = value;
|
package/src/conversation.ts
CHANGED
|
@@ -69,30 +69,49 @@ export const capToolResult = (
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
const marker = "[... omitted tool-result section ...]";
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
for (const line of lines.slice(tailStart)) {
|
|
89
|
-
const next = tailBytes + byteLength(line) + (tail.length ? 1 : 0);
|
|
90
|
-
if (tail.length && next > Math.floor(availableBytes / 2)) {
|
|
91
|
-
break;
|
|
92
|
-
}
|
|
93
|
-
tail.push(line);
|
|
94
|
-
tailBytes = next;
|
|
72
|
+
const markerBytes = byteLength(marker);
|
|
73
|
+
if (maxBytes < markerBytes || maxLines === 1) {
|
|
74
|
+
const content = [...marker].reduce(
|
|
75
|
+
(result, character) =>
|
|
76
|
+
byteLength(result + character) <= maxBytes
|
|
77
|
+
? result + character
|
|
78
|
+
: result,
|
|
79
|
+
""
|
|
80
|
+
);
|
|
81
|
+
return {
|
|
82
|
+
content,
|
|
83
|
+
omittedLines: totalLines,
|
|
84
|
+
totalBytes,
|
|
85
|
+
totalLines,
|
|
86
|
+
truncated: true,
|
|
87
|
+
};
|
|
95
88
|
}
|
|
89
|
+
const headCount = Math.floor((maxLines - 1) / 2);
|
|
90
|
+
const tailCount = maxLines - 1 - headCount;
|
|
91
|
+
const collect = (
|
|
92
|
+
candidates: string[],
|
|
93
|
+
maxEntries: number,
|
|
94
|
+
maxContentBytes: number
|
|
95
|
+
) => {
|
|
96
|
+
const selected: string[] = [];
|
|
97
|
+
let used = 0;
|
|
98
|
+
for (const line of candidates.slice(0, maxEntries)) {
|
|
99
|
+
const next = used + byteLength(line) + (selected.length ? 1 : 0);
|
|
100
|
+
if (next > maxContentBytes) {
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
selected.push(line);
|
|
104
|
+
used = next;
|
|
105
|
+
}
|
|
106
|
+
return selected;
|
|
107
|
+
};
|
|
108
|
+
const availableBytes = maxBytes - markerBytes - 2;
|
|
109
|
+
const head = collect(lines, headCount, Math.floor(availableBytes / 2));
|
|
110
|
+
const tail = collect(
|
|
111
|
+
lines.slice(Math.max(head.length, lines.length - tailCount)),
|
|
112
|
+
tailCount,
|
|
113
|
+
availableBytes - byteLength(head.join("\n"))
|
|
114
|
+
);
|
|
96
115
|
const content = [...head, marker, ...tail].join("\n");
|
|
97
116
|
return {
|
|
98
117
|
content,
|
|
@@ -150,7 +169,7 @@ const conversationEntry = (
|
|
|
150
169
|
if (entry.type !== "message" || !isRecord(entry.message)) {
|
|
151
170
|
return;
|
|
152
171
|
}
|
|
153
|
-
const message = entry
|
|
172
|
+
const { message } = entry;
|
|
154
173
|
if (message.role === "user") {
|
|
155
174
|
const text = textFrom(message.content);
|
|
156
175
|
return text ? `User: ${text}` : undefined;
|
package/src/herdr.ts
CHANGED
|
@@ -171,10 +171,11 @@ export class HerdrAdvisorBlock {
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
clear() {
|
|
174
|
-
|
|
174
|
+
const wasBlocked: boolean = this.#blocked;
|
|
175
|
+
this.#blocked = false;
|
|
176
|
+
if (!wasBlocked) {
|
|
175
177
|
return;
|
|
176
178
|
}
|
|
177
|
-
this.#blocked = false;
|
|
178
179
|
if (!this.enabled()) {
|
|
179
180
|
return;
|
|
180
181
|
}
|
|
@@ -196,10 +197,6 @@ export class HerdrAdvisorBlock {
|
|
|
196
197
|
}
|
|
197
198
|
}
|
|
198
199
|
|
|
199
|
-
private isBlocked() {
|
|
200
|
-
return this.#blocked;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
200
|
private safeReport(labels: { blocked: string }) {
|
|
204
201
|
try {
|
|
205
202
|
this.report({
|
package/src/session-state.ts
CHANGED
|
@@ -19,10 +19,18 @@ export interface AdvisorInvocationRecord {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const WHITESPACE = /\s/;
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
const TIMESTAMP_KEYS = new Set([
|
|
23
|
+
"createdat",
|
|
24
|
+
"date",
|
|
25
|
+
"datetime",
|
|
26
|
+
"time",
|
|
27
|
+
"timestamp",
|
|
28
|
+
"updatedat",
|
|
29
|
+
]);
|
|
30
|
+
const REQUEST_ID_KEYS = new Set(["correlationid", "requestid", "traceid"]);
|
|
31
|
+
const normalizedKey = (key: string) => key.replace(/[-_]/g, "").toLowerCase();
|
|
32
|
+
const isVolatileKey = (key: string, keys: Set<string>) =>
|
|
33
|
+
keys.has(normalizedKey(key));
|
|
26
34
|
|
|
27
35
|
const normalizeShellWhitespace = (command: string) => {
|
|
28
36
|
let result = "";
|
|
@@ -67,10 +75,10 @@ export const normalizeToolInput = (
|
|
|
67
75
|
): unknown => {
|
|
68
76
|
const visit = (value: unknown, key?: string): unknown => {
|
|
69
77
|
if (typeof value === "string") {
|
|
70
|
-
if (key && isVolatileKey(key,
|
|
78
|
+
if (key && isVolatileKey(key, TIMESTAMP_KEYS)) {
|
|
71
79
|
return "<timestamp>";
|
|
72
80
|
}
|
|
73
|
-
if (key && isVolatileKey(key,
|
|
81
|
+
if (key && isVolatileKey(key, REQUEST_ID_KEYS)) {
|
|
74
82
|
return "<request-id>";
|
|
75
83
|
}
|
|
76
84
|
const normalized = normalizeString(value);
|