@principles/workbuddy-adapter 0.2.3 → 0.2.5
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/codec/input-decoder.js +16 -4
- package/dist/pd-hook.js +73 -2
- package/package.json +3 -3
|
@@ -180,6 +180,18 @@ export function decodeWorkbuddyInput(raw) {
|
|
|
180
180
|
// is ever fabricated when missing (rc-6).
|
|
181
181
|
const turnId = consumedOptionalString(raw, 'generation_id');
|
|
182
182
|
const base = { sessionId, ...(turnId !== undefined ? { turnId } : {}) };
|
|
183
|
+
// SPEC-PD-MODEL-AWARE-PROMPT-BUDGET-001 §5.3: `model` is an OBSERVED
|
|
184
|
+
// OPTIONAL field of the frozen host contract — the only model identity
|
|
185
|
+
// this host exposes (no capacity field exists). Consumed SOFTLY (absent or
|
|
186
|
+
// malformed → undefined, fixed-budget fallback) rather than
|
|
187
|
+
// consumedOptionalString's fail-loud: SPEC §8.6 forbids metadata failure
|
|
188
|
+
// from blocking the host conversation, and a bad model string degrades to
|
|
189
|
+
// the legacy budget instead of erroring the hook. Dynamic support is
|
|
190
|
+
// therefore catalog-unambiguous-match-only — never a guessed capacity.
|
|
191
|
+
const rawModel = own(raw, 'model');
|
|
192
|
+
const modelId = typeof rawModel === 'string' && rawModel.trim().length > 0 ? rawModel : undefined;
|
|
193
|
+
const modelContext = modelId !== undefined ? { modelId } : undefined;
|
|
194
|
+
const baseWithContext = modelContext !== undefined ? { ...base, modelContext } : base;
|
|
183
195
|
let context;
|
|
184
196
|
let rawPayload = raw;
|
|
185
197
|
if (kind === 'session_start') {
|
|
@@ -190,12 +202,12 @@ export function decodeWorkbuddyInput(raw) {
|
|
|
190
202
|
requiredString(raw, 'permission_mode');
|
|
191
203
|
requiredString(raw, 'client');
|
|
192
204
|
requiredString(raw, 'version');
|
|
193
|
-
context = { ...
|
|
205
|
+
context = { ...baseWithContext, workspaceDir: '', source };
|
|
194
206
|
}
|
|
195
207
|
else if (kind === 'before_prompt_build') {
|
|
196
208
|
const cwd = requiredAbsoluteCwd(raw);
|
|
197
209
|
validateOptionalStringType(raw, 'permission_mode');
|
|
198
|
-
context = { ...
|
|
210
|
+
context = { ...baseWithContext, workspaceDir: cwd, promptContent: requiredString(raw, 'prompt') };
|
|
199
211
|
}
|
|
200
212
|
else {
|
|
201
213
|
const cwd = requiredAbsoluteCwd(raw);
|
|
@@ -218,10 +230,10 @@ export function decodeWorkbuddyInput(raw) {
|
|
|
218
230
|
const thrownError = requiredString(raw, 'error');
|
|
219
231
|
toolOutput = { result: raw, error: thrownError };
|
|
220
232
|
}
|
|
221
|
-
context = { ...
|
|
233
|
+
context = { ...baseWithContext, workspaceDir: cwd, toolName, ...(callId !== undefined ? { toolCallId: callId } : {}), toolInput, toolOutput };
|
|
222
234
|
}
|
|
223
235
|
else {
|
|
224
|
-
context = { ...
|
|
236
|
+
context = { ...baseWithContext, workspaceDir: cwd, toolName, ...(callId !== undefined ? { toolCallId: callId } : {}), toolInput };
|
|
225
237
|
}
|
|
226
238
|
// The shared production gate reads its tool tuple from rawPayload.toolInput
|
|
227
239
|
// (production-rulehost-gate readToolInput contract).
|
package/dist/pd-hook.js
CHANGED
|
@@ -37,7 +37,7 @@ import path from 'node:path';
|
|
|
37
37
|
import process from 'node:process';
|
|
38
38
|
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
39
39
|
import { appendEventLogLine, redactTelemetryString, isRuleHostEvaluatedEventData } from '@principles/core/runtime-v2';
|
|
40
|
-
import { createProductionHostRuntime, loadPdConfigForPlugin, parseOwnerStatementPrefix } from '@principles/host-runtime';
|
|
40
|
+
import { createProductionHostRuntime, loadPdConfigForPlugin, parseOwnerStatementPrefix, assessWorkbuddyLiveKeyword, admitTrustedWorkbuddyLiveCorrection, ensureGovernanceDiagnosticianTask } from '@principles/host-runtime';
|
|
41
41
|
import { computeFeatureFlagsFromConfig } from '@principles/core/runtime-v2';
|
|
42
42
|
import { WorkBuddyHooksHostAdapter } from './host-adapter.js';
|
|
43
43
|
import { WORKBUDDY_TOOL_SEMANTICS } from './tool-semantics.js';
|
|
@@ -255,6 +255,76 @@ export async function processWorkbuddyHookInvocation(rawStdin, channelEventName,
|
|
|
255
255
|
const ownerReceipt = channelEventName === 'UserPromptSubmit'
|
|
256
256
|
? await recordWorkbuddyOwnerStatement(event, resolution.workspaceDir)
|
|
257
257
|
: undefined;
|
|
258
|
+
// SPEC-PD-CROSS-HOST-KEYWORD-PAIN-001 P0: only a REAL current-user
|
|
259
|
+
// UserPromptSubmit may enter this quiet, default-OFF live keyword path.
|
|
260
|
+
// It does not inspect transcripts or alter normal hook stdout / exit codes.
|
|
261
|
+
const autoDiagnostics = [];
|
|
262
|
+
if (channelEventName === 'UserPromptSubmit' &&
|
|
263
|
+
event.kind === 'before_prompt_build' &&
|
|
264
|
+
flags.workbuddy_live_keyword_pain?.enabled === true) {
|
|
265
|
+
try {
|
|
266
|
+
const raw = parsed;
|
|
267
|
+
const prompt = event.context.promptContent;
|
|
268
|
+
const fromOwner = Object.getOwnPropertyDescriptor(raw, 'is_internal_continuation')?.value === false;
|
|
269
|
+
const nativeId = event.context.turnId;
|
|
270
|
+
const explicitReserved = typeof prompt === 'string' && prompt.trimStart().startsWith('PD记录痛苦');
|
|
271
|
+
if (fromOwner && nativeId !== undefined && nativeId.trim().length > 0 &&
|
|
272
|
+
typeof prompt === 'string' && !explicitReserved) {
|
|
273
|
+
const matched = assessWorkbuddyLiveKeyword(prompt, event.context.sessionId);
|
|
274
|
+
if (matched.matched) {
|
|
275
|
+
const admitted = admitTrustedWorkbuddyLiveCorrection({
|
|
276
|
+
workspaceDir: resolution.workspaceDir,
|
|
277
|
+
rawEvent: parsed,
|
|
278
|
+
});
|
|
279
|
+
if (!admitted.ok) {
|
|
280
|
+
autoDiagnostics.push(diagnostic('degraded', 'workbuddy_keyword_admission_' + admitted.reason, admitted.nextAction));
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
for (const outcome of admitted.outcomes) {
|
|
284
|
+
if (!('disposition' in outcome)) {
|
|
285
|
+
autoDiagnostics.push(diagnostic('degraded', 'workbuddy_keyword_admission_rejected', outcome.reason));
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (outcome.disposition === 'admitted' || outcome.disposition === 'already_admitted') {
|
|
289
|
+
const ensured = await ensureGovernanceDiagnosticianTask({
|
|
290
|
+
workspaceDir: resolution.workspaceDir,
|
|
291
|
+
logicalObservationKey: outcome.logicalObservationKey,
|
|
292
|
+
canonicalPainId: outcome.canonicalPainId,
|
|
293
|
+
});
|
|
294
|
+
if (!ensured.ok) {
|
|
295
|
+
autoDiagnostics.push(diagnostic('degraded', 'workbuddy_keyword_task_' + ensured.reason, ensured.nextAction));
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
// Metadata only: never log the raw prompt or fabricate a
|
|
299
|
+
// completed diagnosis, approval or activation.
|
|
300
|
+
try {
|
|
301
|
+
appendEventLogLine(path.join(resolution.workspaceDir, '.state'), {
|
|
302
|
+
ts: new Date().toISOString(),
|
|
303
|
+
type: 'workbuddy_keyword_pain_admitted',
|
|
304
|
+
category: 'detected',
|
|
305
|
+
sessionId: event.context.sessionId,
|
|
306
|
+
data: { hostKind: 'workbuddy', outcome: outcome.disposition,
|
|
307
|
+
painId: outcome.canonicalPainId, taskId: ensured.taskId },
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
catch { /* Evidence-log failure must not block the prompt. */ }
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
else if (outcome.disposition === 'rate_limited') {
|
|
314
|
+
autoDiagnostics.push(diagnostic('degraded', 'workbuddy_keyword_rate_limited', 'Per-session strong signal limit reached; no new Pain was created.'));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
else if (matched.reason === 'prompt_too_large') {
|
|
320
|
+
autoDiagnostics.push(diagnostic('degraded', 'workbuddy_keyword_prompt_too_large', 'Oversized prompt was not scanned or persisted as a keyword signal.'));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
autoDiagnostics.push(diagnostic('degraded', 'workbuddy_keyword_unexpected:' + errorMessage(error), 'The current prompt was preserved; inspect the governance workspace.'));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
258
328
|
const receiptOutput = (output) => ownerReceipt === undefined ? output : {
|
|
259
329
|
hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: [ownerReceipt, output?.hookSpecificOutput.additionalContext].filter(Boolean).join('\n\n') },
|
|
260
330
|
};
|
|
@@ -360,6 +430,7 @@ export async function processWorkbuddyHookInvocation(rawStdin, channelEventName,
|
|
|
360
430
|
...annotateContextWarnings(result.warnings ?? []).slice(0, 16).map((warning) => diagnostic('degraded', warning, 'Inspect the PD governance workspace state and retry; the hook stayed permissive (allow+warnings is degraded health, not fail-closed).')),
|
|
361
431
|
...evaluationDiagnostics,
|
|
362
432
|
...evidenceDiagnostics,
|
|
433
|
+
...autoDiagnostics,
|
|
363
434
|
];
|
|
364
435
|
return { stdout: receiptOutput(encoded), exitCode: 0, stderr: [...stderr, ...(ownerReceipt === undefined ? [] : [ownerReceipt])] };
|
|
365
436
|
}
|
|
@@ -373,7 +444,7 @@ export async function processWorkbuddyHookInvocation(rawStdin, channelEventName,
|
|
|
373
444
|
}
|
|
374
445
|
// UserPromptSubmit: protect the prompt (exit 2 clears it — P02).
|
|
375
446
|
// after_tool_call: the tool already ran — report evidence degradation only.
|
|
376
|
-
return { stdout: receiptOutput(null), exitCode: 0, stderr: [diagnostic('degraded', reason, nextAction), ...(ownerReceipt === undefined ? [] : [ownerReceipt])] };
|
|
447
|
+
return { stdout: receiptOutput(null), exitCode: 0, stderr: [diagnostic('degraded', reason, nextAction), ...autoDiagnostics, ...(ownerReceipt === undefined ? [] : [ownerReceipt])] };
|
|
377
448
|
}
|
|
378
449
|
}
|
|
379
450
|
// ─── Process-boundary belt (PRI-943 pattern, WorkBuddy exit-code semantics) ──
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@principles/workbuddy-adapter",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "WorkBuddy host adapter for Principles Disciple — implements the HostAdapter interface for WorkBuddy's Git Bash command-hook model (ADR-0020 third host, SPEC workbuddy-host-adapter v0.3.0).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"lint": "eslint \"src/**/*.ts\""
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@principles/core": "^1.
|
|
46
|
-
"@principles/host-runtime": "^0.
|
|
45
|
+
"@principles/core": "^1.295.0",
|
|
46
|
+
"@principles/host-runtime": "^0.11.0",
|
|
47
47
|
"@principles/install-layout": "^0.4.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|