create-principles-disciple 1.145.28 → 1.145.29

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.
Files changed (37) hide show
  1. package/_release/product-identity.json +2 -2
  2. package/codex-adapter/dist/index.d.ts +4 -0
  3. package/codex-adapter/dist/index.js +2 -0
  4. package/codex-adapter/dist/ingestion/admission.js +1 -0
  5. package/codex-adapter/dist/ingestion/explicit-owner-statement.d.ts +22 -3
  6. package/codex-adapter/dist/ingestion/explicit-owner-statement.js +43 -16
  7. package/codex-adapter/dist/ingestion/turn-context.d.ts +75 -0
  8. package/codex-adapter/dist/ingestion/turn-context.js +302 -0
  9. package/codex-adapter/dist/pd-hook.js +87 -36
  10. package/codex-adapter/dist/worker/workspace-worker.js +4 -1
  11. package/codex-adapter/package.json +1 -1
  12. package/core/dist/runtime-v2/context-payload.d.ts +4 -4
  13. package/core/dist/runtime-v2/context-payload.d.ts.map +1 -1
  14. package/core/dist/runtime-v2/context-payload.js +1 -0
  15. package/core/dist/runtime-v2/context-payload.js.map +1 -1
  16. package/core/dist/runtime-v2/task-status.d.ts +1 -1
  17. package/core/package.json +1 -1
  18. package/host-runtime/dist/governance-signal-admission.d.ts +26 -7
  19. package/host-runtime/dist/governance-signal-admission.js +104 -34
  20. package/host-runtime/package.json +1 -1
  21. package/package.json +2 -2
  22. package/pd-cli/dist/commands/codex-reconcile.d.ts.map +1 -1
  23. package/pd-cli/dist/commands/codex-reconcile.js +30 -1
  24. package/pd-cli/dist/commands/codex-reconcile.js.map +1 -1
  25. package/pd-cli/dist/commands/pain-explicit.d.ts +8 -0
  26. package/pd-cli/dist/commands/pain-explicit.d.ts.map +1 -0
  27. package/pd-cli/dist/commands/pain-explicit.js +89 -0
  28. package/pd-cli/dist/commands/pain-explicit.js.map +1 -0
  29. package/pd-cli/dist/index.js +10 -0
  30. package/pd-cli/dist/index.js.map +1 -1
  31. package/pd-cli/package.json +1 -1
  32. package/plugin/dist/bundle.js +1 -1
  33. package/plugin/openclaw.plugin.json +1 -1
  34. package/plugin/package.json +1 -1
  35. package/release-manager/package.json +2 -2
  36. package/workbuddy-adapter/dist/worker/workspace-worker.js +1 -1
  37. package/workbuddy-adapter/package.json +1 -1
@@ -395,7 +395,9 @@ function tryConsumeRateLimit({ db, rootSessionId, ruleVersion, now }) {
395
395
  }
396
396
  function insertCorrectionPain({ db, candidate, workspaceDir, painId, detection, nowIso }) {
397
397
  const reason = candidate.explicitOwnerStatement === true
398
- ? `${candidate.hostKind === 'workbuddy' ? 'WorkBuddy' : 'Codex'} explicit owner statement (PD记录痛苦 prefix)`
398
+ ? candidate.explicitChannel === 'codex_explicit_agent'
399
+ ? 'Codex explicit owner statement (agent-assisted, no prefix)'
400
+ : `${candidate.hostKind === 'workbuddy' ? 'WorkBuddy' : 'Codex'} explicit owner statement (PD记录痛苦 prefix)`
399
401
  : detection !== undefined && detection.matchedTerms.length > 0
400
402
  ? `User correction detected: ${detection.matchedTerms.join(', ')}`
401
403
  : 'User correction detected';
@@ -461,7 +463,7 @@ function taskSubmissionPayload(candidate, workspaceDir, details) {
461
463
  ...(candidate.explicitOwnerStatement === true
462
464
  ? { correctionEvidence: candidate.hostKind === 'workbuddy'
463
465
  ? { text: boundCorrectionEvidenceText({ text: candidate.text, classification: 'owner_statement', channel: 'workbuddy_explicit_prefix' }, workspaceDir).text, classification: 'owner_statement', channel: 'workbuddy_explicit_prefix' }
464
- : { text: sanitizeString(candidate.text, workspaceDir), classification: 'owner_statement', channel: 'codex_explicit_prefix' } }
466
+ : { text: sanitizeString(candidate.text, workspaceDir), classification: 'owner_statement', channel: candidate.explicitChannel === 'codex_explicit_agent' ? 'codex_explicit_agent' : 'codex_explicit_prefix' } }
465
467
  : {}),
466
468
  };
467
469
  return JSON.stringify(payload);
@@ -482,6 +484,35 @@ function insertAdmissionMarker({ db, identity, canonicalPainId, ruleVersion, rea
482
484
  VALUES (?, ?, ?, ?, ?, 'admitted', ?, NULL, ?, ?, ?, ?, ?)`)
483
485
  .run(identity.hostKind, identity.logicalObservationKey, identity.rolloutIdentity, identity.rootSessionId, identity.signalKind, canonicalPainId, ruleVersion, reason.slice(0, MAX_REASON_BOUND) || null, payloadJson, nowIso, nowIso);
484
486
  }
487
+ /**
488
+ * Convergence probe for the concurrent-redelivery race: two admission
489
+ * processes passed the pre-transaction marker probe before either committed,
490
+ * so the loser's marker INSERT hits the store UNIQUE constraint and its whole
491
+ * transaction rolls back. A marker AND its canonical pain now existing means
492
+ * the idempotency contract already WON — converge on the winner's state (the
493
+ * same semantics as the task-ensure race in ensureGovernanceDiagnosticianTask)
494
+ * instead of surfacing a governance_admission_failed that contradicts the
495
+ * durable state. The probe re-reads the store fresh (rc-7); when the durable
496
+ * facts do NOT exist it returns null and the original error propagates.
497
+ */
498
+ function convergeOnAdmittedMarker(args) {
499
+ const marker = args.db.prepare('SELECT * FROM governance_signal_admissions WHERE logical_observation_key = ?').get(args.logicalObservationKey);
500
+ if (!isRecord(marker))
501
+ return null;
502
+ const canonicalPainId = rowField(marker, 'canonical_pain_id');
503
+ const taskId = rowField(marker, 'diagnostician_task_id');
504
+ if (typeof canonicalPainId !== 'string')
505
+ return null;
506
+ if (args.db.prepare('SELECT 1 FROM pain_events WHERE canonical_pain_id = ?').get(canonicalPainId) === undefined)
507
+ return null;
508
+ return {
509
+ disposition: 'already_admitted',
510
+ kind: args.kind,
511
+ logicalObservationKey: args.logicalObservationKey,
512
+ canonicalPainId,
513
+ diagnosticianTaskId: typeof taskId === 'string' ? taskId : null,
514
+ };
515
+ }
485
516
  function admitCorrection({ db, candidate, workspaceDir, now, nowIso, keywordStore, onDegradation }) {
486
517
  // SPEC-CODEX-PAIN-CLOSURE-001 CR-07: an EXPLICIT Owner statement
487
518
  // (`PD记录痛苦:<text>` in the current Codex message) does not pass through
@@ -672,10 +703,23 @@ function admitOne({ db, candidate, workspaceDir, now, nowIso, keywordStore, onDe
672
703
  diagnosticianTaskId: typeof taskId === 'string' ? taskId : null,
673
704
  };
674
705
  }
675
- if (candidate.kind === 'user_correction') {
676
- return admitCorrection({ db, candidate, workspaceDir, now, nowIso, ...(keywordStore ? { keywordStore } : {}), ...(onDegradation ? { onDegradation } : {}) });
706
+ try {
707
+ if (candidate.kind === 'user_correction') {
708
+ return admitCorrection({ db, candidate, workspaceDir, now, nowIso, ...(keywordStore ? { keywordStore } : {}), ...(onDegradation ? { onDegradation } : {}) });
709
+ }
710
+ return admitToolFailure({ db, candidate, workspaceDir, nowIso });
711
+ }
712
+ catch (error) {
713
+ // RateLimitedSignal never reaches here — admitCorrection converts it to a
714
+ // rate_limited outcome internally. A store-level failure (e.g. the loser
715
+ // of a concurrent redelivery hitting the marker UNIQUE constraint) first
716
+ // gets a fresh convergence probe; only a failure with NO durable
717
+ // idempotency facts propagates as governance_admission_failed.
718
+ const converged = convergeOnAdmittedMarker({ db, logicalObservationKey: candidate.logicalObservationKey, kind: candidate.kind });
719
+ if (converged !== null)
720
+ return converged;
721
+ throw error;
677
722
  }
678
- return admitToolFailure({ db, candidate, workspaceDir, nowIso });
679
723
  }
680
724
  /**
681
725
  * Admit governance signal candidates. Each candidate commits in its own
@@ -836,7 +880,7 @@ function isGovernanceTaskPayload(value) {
836
880
  const correctionEvidence = own(value, 'correctionEvidence');
837
881
  const hasOwnerStatement = isRecord(correctionEvidence)
838
882
  && own(correctionEvidence, 'classification') === 'owner_statement'
839
- && (own(correctionEvidence, 'channel') === 'codex_explicit_prefix' && hostKind === 'codex'
883
+ && ((own(correctionEvidence, 'channel') === 'codex_explicit_prefix' || own(correctionEvidence, 'channel') === 'codex_explicit_agent') && hostKind === 'codex'
840
884
  || own(correctionEvidence, 'channel') === 'workbuddy_explicit_prefix' && hostKind === 'workbuddy')
841
885
  && typeof own(correctionEvidence, 'text') === 'string'
842
886
  && own(correctionEvidence, 'text').length > 0;
@@ -908,7 +952,7 @@ export async function ensureGovernanceDiagnosticianTask(input) {
908
952
  const stateManager = new RuntimeStateManager({ workspaceDir });
909
953
  try {
910
954
  await stateManager.initialize();
911
- const existing = await stateManager.getTask(taskId);
955
+ let existing = await stateManager.getTask(taskId);
912
956
  if (existing === null) {
913
957
  const stateDir = path.join(workspaceDir, '.state');
914
958
  const config = loadPdConfigForPlugin(workspaceDir);
@@ -949,7 +993,16 @@ export async function ensureGovernanceDiagnosticianTask(input) {
949
993
  // worker, so release it immediately (hook processes exit anyway).
950
994
  await disposePainSignalBridgesForWorkspace(workspaceDir).catch(() => undefined);
951
995
  if (result.status === 'failed') {
952
- return { ok: false, reason: `task_submit_failed:${(result.message ?? result.failureCategory ?? 'unknown').slice(0, 160)}`, nextAction: 'inspect the diagnostician runtime profile; reconciliation retries task creation without losing the admitted pain' };
996
+ // Concurrent-ensure race (hook redelivery × worker reconcile): both
997
+ // callers passed the getTask probe before either committed, and the
998
+ // DETERMINISTIC task id makes the loser's insert hit the store PK.
999
+ // The task existing now is the idempotency contract winning, not a
1000
+ // failure — converge on it (Case-B repair semantics) instead of
1001
+ // reporting a task_submit_failed that contradicts the durable state.
1002
+ existing = await stateManager.getTask(taskId);
1003
+ if (existing === null) {
1004
+ return { ok: false, reason: `task_submit_failed:${(result.message ?? result.failureCategory ?? 'unknown').slice(0, 160)}`, nextAction: 'inspect the diagnostician runtime profile; reconciliation retries task creation without losing the admitted pain' };
1005
+ }
953
1006
  }
954
1007
  }
955
1008
  db.prepare('UPDATE governance_signal_admissions SET diagnostician_task_id = ?, updated_at = ? WHERE logical_observation_key = ?')
@@ -995,11 +1048,12 @@ export function promoteAdmittedGovernanceEvidence(input) {
995
1048
  return { ok: true, promoted: result.promoted, tailState: result.tailState };
996
1049
  }
997
1050
  /**
998
- * Given an admitted canonical pain (identified by its marker), ensure both
999
- * the Diagnostician task AND the evidence promotion exist idempotently.
1051
+ * Given an admitted canonical pain (identified by its marker), ensure the
1052
+ * Diagnostician task and, only with current conversation-ingestion consent,
1053
+ * the evidence promotion exist idempotently.
1000
1054
  * Single ensure path for both fresh admits and crash recovery (already_admitted
1001
- * redelivery). The marker row carries rollout_identity so the function is
1002
- * self-contained (caller only needs workspaceDir, logicalObservationKey, painId).
1055
+ * redelivery). The marker row carries rollout_identity; callers must also
1056
+ * provide the current conversation-ingestion consent.
1003
1057
  */
1004
1058
  export async function ensureGovernanceContinuation(input) {
1005
1059
  const workspaceDir = path.resolve(input.workspaceDir);
@@ -1016,23 +1070,26 @@ export async function ensureGovernanceContinuation(input) {
1016
1070
  if (typeof rolloutIdentity !== 'string' || rolloutIdentity.length === 0) {
1017
1071
  return { ok: false, reason: 'marker_missing_rollout_identity', nextAction: 're-admit the signal' };
1018
1072
  }
1019
- // 1. Ensure task (idempotent)
1073
+ // Promote before making a runnable task visible, so a worker racing this
1074
+ // continuation cannot begin diagnosis before the eligible evidence window
1075
+ // has been promoted. A promotion failure is retained as a degradation,
1076
+ // but never prevents the task from being durably ensured.
1077
+ const promoted = input.conversationIngestionEnabled
1078
+ ? promoteAdmittedGovernanceEvidence({
1079
+ workspaceDir,
1080
+ rolloutIdentity,
1081
+ triggerLogicalKey: input.logicalObservationKey,
1082
+ canonicalPainId: input.canonicalPainId,
1083
+ })
1084
+ : undefined;
1020
1085
  const ensured = await ensureGovernanceDiagnosticianTask({ workspaceDir, logicalObservationKey: input.logicalObservationKey, canonicalPainId: input.canonicalPainId });
1021
1086
  if (!ensured.ok) {
1022
1087
  return { ok: false, reason: ensured.reason, nextAction: ensured.nextAction };
1023
1088
  }
1024
- // 2. Ensure promotion (idempotent substrate — already promoted = no-op,
1025
- // pending tail = re-complete, never started = start now).
1026
- const promoted = promoteAdmittedGovernanceEvidence({
1027
- workspaceDir,
1028
- rolloutIdentity,
1029
- triggerLogicalKey: input.logicalObservationKey,
1030
- canonicalPainId: input.canonicalPainId,
1031
- });
1032
- if (!promoted.ok) {
1033
- return { ok: false, reason: promoted.reason, nextAction: promoted.nextAction };
1089
+ if (promoted !== undefined && !promoted.ok) {
1090
+ return { ok: false, reason: promoted.reason, nextAction: promoted.nextAction, taskId: ensured.taskId, taskCreated: ensured.created, linkRepaired: ensured.linkRepaired };
1034
1091
  }
1035
- return { ok: true, taskId: ensured.taskId, taskCreated: ensured.created, linkRepaired: ensured.linkRepaired, promoted: promoted.promoted, tailState: promoted.tailState };
1092
+ return { ok: true, taskId: ensured.taskId, taskCreated: ensured.created, linkRepaired: ensured.linkRepaired, promoted: promoted?.promoted ?? 0, tailState: promoted?.tailState ?? 'skipped' };
1036
1093
  }
1037
1094
  finally {
1038
1095
  close();
@@ -1164,10 +1221,13 @@ export async function admitBareHostPains(input) {
1164
1221
  /**
1165
1222
  * Narrow idempotent reconciliation between the trajectory admission markers
1166
1223
  * and the Runtime V2 task store (separate SQLite databases — no cross-store
1167
- * transaction exists, SPEC §13). Recovers:
1224
+ * transaction exists, SPEC §13). Current conversation-ingestion consent is
1225
+ * mandatory because it controls whether Codex observations may be read and
1226
+ * promoted; task/link recovery remains available when consent is off. Recovers:
1168
1227
  * - Case A: pain admitted, crash before task creation → create the task now;
1169
1228
  * - Case B: task exists, crash before the link write → repair the link;
1170
- * - Case C: pain+task, crash before promotion → promote evidence now;
1229
+ * - Case C: pain+task, crash before promotion → promote evidence now only
1230
+ * while conversation-ingestion consent is enabled;
1171
1231
  * - Case D: promotion started → pending tail → retry completion once; stale
1172
1232
  * tails are reported (never silently dropped).
1173
1233
  * Not a background worker — Slice C's Companion worker and the CLI call this.
@@ -1194,7 +1254,8 @@ export async function reconcileGovernanceContinuation(input) {
1194
1254
  //
1195
1255
  // Recovery needed:
1196
1256
  // Case A/B: task link missing (crash before task, or before the link write)
1197
- // Case C: promotion never started (no observation carries this painRef)
1257
+ // Case C: promotion never started (no observation carries this painRef),
1258
+ // but only while current conversation-ingestion consent is on.
1198
1259
  //
1199
1260
  // Case C/D (promotion recovery) is scoped to the hosts whose evidence
1200
1261
  // substrate it exists for: the promotion rows come from transcript
@@ -1211,14 +1272,17 @@ export async function reconcileGovernanceContinuation(input) {
1211
1272
  // not retry pending tails, so pending-tail markers do not block later
1212
1273
  // markers. Stale tails are NOT auto-recoverable (substrate refuses to
1213
1274
  // re-arm them): they are counted separately below and reported.
1275
+ const promotionRecoveryClause = input.conversationIngestionEnabled
1276
+ ? `OR (a.host_kind = 'codex'
1277
+ AND NOT EXISTS (SELECT 1 FROM governance_observations o
1278
+ WHERE o.promotion_ref = a.canonical_pain_id))`
1279
+ : '';
1214
1280
  const markers = db.prepare(`SELECT a.logical_observation_key, a.canonical_pain_id, a.host_kind
1215
1281
  FROM governance_signal_admissions a
1216
1282
  WHERE a.decision = 'admitted'
1217
1283
  AND (
1218
1284
  a.diagnostician_task_id IS NULL
1219
- OR (a.host_kind = 'codex'
1220
- AND NOT EXISTS (SELECT 1 FROM governance_observations o
1221
- WHERE o.promotion_ref = a.canonical_pain_id))
1285
+ ${promotionRecoveryClause}
1222
1286
  )
1223
1287
  ORDER BY a.id LIMIT ?`).all(limit);
1224
1288
  for (const row of markers) {
@@ -1243,7 +1307,7 @@ export async function reconcileGovernanceContinuation(input) {
1243
1307
  }
1244
1308
  continue;
1245
1309
  }
1246
- const cont = await ensureGovernanceContinuation({ workspaceDir, logicalObservationKey: key, canonicalPainId: painId });
1310
+ const cont = await ensureGovernanceContinuation({ workspaceDir, logicalObservationKey: key, canonicalPainId: painId, conversationIngestionEnabled: input.conversationIngestionEnabled });
1247
1311
  if (cont.ok) {
1248
1312
  // Only count REAL actions: a task actually created, a link actually
1249
1313
  // repaired (task existed but the marker link was missing). Healthy
@@ -1259,13 +1323,19 @@ export async function reconcileGovernanceContinuation(input) {
1259
1323
  completedTails += 1;
1260
1324
  }
1261
1325
  else {
1326
+ if (cont.taskCreated)
1327
+ tasksEnsured += 1;
1328
+ if (cont.linkRepaired)
1329
+ linksRepaired += 1;
1262
1330
  degradations.push(`${key}:${cont.reason}`);
1263
1331
  }
1264
1332
  }
1265
1333
  // Also report stale tails scoped to governance admissions.
1266
- const stale = db.prepare(`SELECT COUNT(*) AS n FROM governance_pending_promotion_tails t
1267
- WHERE t.state = 'stale'
1268
- AND EXISTS (SELECT 1 FROM governance_signal_admissions a WHERE a.canonical_pain_id = t.pain_ref)`).get();
1334
+ const stale = input.conversationIngestionEnabled
1335
+ ? db.prepare(`SELECT COUNT(*) AS n FROM governance_pending_promotion_tails t
1336
+ WHERE t.state = 'stale'
1337
+ AND EXISTS (SELECT 1 FROM governance_signal_admissions a WHERE a.canonical_pain_id = t.pain_ref)`).get()
1338
+ : { n: 0 };
1269
1339
  const staleTails = typeof rowField(stale, 'n') === 'number' ? rowField(stale, 'n') : 0;
1270
1340
  return {
1271
1341
  ok: degradations.length === 0,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@principles/host-runtime",
3
- "version": "0.13.3",
3
+ "version": "0.14.0",
4
4
  "description": "Shared host-neutral orchestration for Principles Disciple MVP-Core hook paths.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-principles-disciple",
3
- "version": "1.145.28",
3
+ "version": "1.145.29",
4
4
  "description": "Interactive CLI installer for Principles Disciple OpenClaw plugin",
5
5
  "type": "module",
6
6
  "bin": {
@@ -89,6 +89,6 @@
89
89
  "directory": "packages/create-principles-disciple"
90
90
  },
91
91
  "pd": {
92
- "bundledPluginVersion": "2.2.3"
92
+ "bundledPluginVersion": "2.2.4"
93
93
  }
94
94
  }
@@ -1 +1 @@
1
- {"version":3,"file":"codex-reconcile.d.ts","sourceRoot":"","sources":["../../src/commands/codex-reconcile.ts"],"names":[],"mappings":"AAmBA,UAAU,qBAAqB;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2CxF"}
1
+ {"version":3,"file":"codex-reconcile.d.ts","sourceRoot":"","sources":["../../src/commands/codex-reconcile.ts"],"names":[],"mappings":"AAoBA,UAAU,qBAAqB;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,OAAO,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqExF"}
@@ -15,12 +15,41 @@
15
15
  * - cli-6: degraded results include reason + nextAction.
16
16
  */
17
17
  import { resolveWorkspaceDir } from '../resolve-workspace.js';
18
- import { reconcileGovernanceContinuation } from '@principles/host-runtime';
18
+ import { loadPdConfigForPlugin, reconcileGovernanceContinuation } from '@principles/host-runtime';
19
+ import { computeFeatureFlagsFromConfig } from '@principles/core/runtime-v2';
19
20
  export async function handleCodexReconcile(options) {
20
21
  const generatedAt = new Date().toISOString();
21
22
  const workspace = resolveWorkspaceDir(options.workspace);
23
+ const config = loadPdConfigForPlugin(workspace);
24
+ if (!config.ok) {
25
+ const [first] = config.errors;
26
+ const reason = `pd_config_invalid:${first?.reason ?? 'unknown'}`;
27
+ const nextAction = first?.nextAction ?? 'Repair the Workspace .pd/config.yaml before reconciling; the conversation-ingestion consent could not be verified.';
28
+ const report = {
29
+ generatedAt,
30
+ host: 'codex',
31
+ workspace,
32
+ ok: false,
33
+ reason,
34
+ nextAction,
35
+ tasksEnsured: 0,
36
+ linksRepaired: 0,
37
+ pendingTails: 0,
38
+ completedTails: 0,
39
+ staleTails: 0,
40
+ degradations: [reason],
41
+ };
42
+ if (options.json)
43
+ console.log(JSON.stringify(report));
44
+ else
45
+ console.log([`Codex governance reconciliation (${workspace})`, ` reason: ${reason}`, ` next action: ${nextAction}`].join('\n'));
46
+ process.exitCode = 1;
47
+ return;
48
+ }
49
+ const conversationIngestionEnabled = computeFeatureFlagsFromConfig(config.effective).flags.codex_conversation_ingestion?.enabled === true;
22
50
  const result = await reconcileGovernanceContinuation({
23
51
  workspaceDir: workspace,
52
+ conversationIngestionEnabled,
24
53
  ...(typeof options.limit === 'number' ? { limit: options.limit } : {}),
25
54
  });
26
55
  const report = {
@@ -1 +1 @@
1
- {"version":3,"file":"codex-reconcile.js","sourceRoot":"","sources":["../../src/commands/codex-reconcile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,+BAA+B,EAAE,MAAM,0BAA0B,CAAC;AAuB3E,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,OAA8B;IACvE,MAAM,WAAW,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC7C,MAAM,SAAS,GAAG,mBAAmB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEzD,MAAM,MAAM,GAAG,MAAM,+BAA+B,CAAC;QACnD,YAAY,EAAE,SAAS;QACvB,GAAG,CAAC,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACvE,CAAC,CAAC;IAEH,MAAM,MAAM,GAAyB;QACnC,WAAW;QACX,IAAI,EAAE,OAAO;QACb,SAAS;QACT,EAAE,EAAE,MAAM,CAAC,EAAE;QACb,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,cAAc,EAAE,MAAM,CAAC,cAAc;QACrC,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,YAAY,EAAE,CAAC,GAAG,MAAM,CAAC,YAAY,CAAC;QACtC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,IAAI,uBAAuB,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,8EAA8E,EAAE,CAAC;KAC5L,CAAC;IAEF,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;QACpC,IAAI,CAAC,MAAM,CAAC,EAAE;YAAE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrC,OAAO;IACT,CAAC;IAED,MAAM,KAAK,GAAG;QACZ,oCAAoC,SAAS,GAAG;QAChD,yBAAyB,MAAM,CAAC,YAAY,qBAAqB,MAAM,CAAC,aAAa,GAAG;QACxF,iCAAiC,MAAM,CAAC,YAAY,kBAAkB,MAAM,CAAC,cAAc,UAAU,MAAM,CAAC,UAAU,EAAE;KACzH,CAAC;IACF,KAAK,MAAM,WAAW,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;QAC9C,KAAK,CAAC,IAAI,CAAC,kBAAkB,WAAW,EAAE,CAAC,CAAC;IAC9C,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,aAAa,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QACzC,KAAK,CAAC,IAAI,CAAC,kBAAkB,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC9B,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvC,CAAC"}
1
+ {"version":3,"file":"codex-reconcile.js","sourceRoot":"","sources":["../../src/commands/codex-reconcile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ interface ExplicitOptions {
2
+ statement?: string;
3
+ workspace?: string;
4
+ json?: boolean;
5
+ }
6
+ export declare function handlePainExplicit(opts: ExplicitOptions): Promise<void>;
7
+ export {};
8
+ //# sourceMappingURL=pain-explicit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pain-explicit.d.ts","sourceRoot":"","sources":["../../src/commands/pain-explicit.ts"],"names":[],"mappings":"AAuBA,UAAU,eAAe;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AA+BD,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAmC7E"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * pd pain explicit — agent-assisted explicit Owner pain registration from the
3
+ * LIVE Codex turn context (no `PD记录痛苦:` prefix).
4
+ *
5
+ * Usage:
6
+ * pd pain explicit --statement "<Owner 原文>" [--workspace <path>] [--json]
7
+ *
8
+ * The command takes NO lineage flags by design: the Codex hook wrote the
9
+ * current turn's host-verified identity to `<workspace>/.state/`, and the
10
+ * bridge (registerCurrentTurnOwnerStatement) re-validates it — transcript
11
+ * existence, CODEX_HOME containment, workspace match — inside the SAME
12
+ * admission chain the prefix entry uses (governance admission marker,
13
+ * explicit rate-limit bucket, diagnostician task ensure). The model never
14
+ * supplies session/rollout/turn ids, so it cannot guess or fabricate them.
15
+ *
16
+ * This is NOT `pd pain record`: that command is the unbound operator/recovery
17
+ * entry (manual pain ids, no admission marker); this one is the bound
18
+ * agent-assisted counterpart of the prefix entry.
19
+ */
20
+ import { registerCurrentTurnOwnerStatement } from '@principles/codex-adapter';
21
+ import { MAX_OWNER_STATEMENT_CHARS } from '@principles/host-runtime';
22
+ import { resolveWorkspaceDir } from '../resolve-workspace.js';
23
+ const FAILURE_MESSAGES = {
24
+ statement_empty: 'The Owner statement is empty; nothing was recorded.',
25
+ statement_too_long: `The Owner statement exceeds ${MAX_OWNER_STATEMENT_CHARS} characters; nothing was recorded (never truncated).`,
26
+ turn_context_missing: 'No live Codex turn context: the turn already completed, the session restarted, or the PD hook did not run.',
27
+ turn_context_stale: 'The live Codex turn context is stale — the turn is treated as ended; nothing was recorded.',
28
+ turn_context_invalid: 'The Codex turn context file is malformed or unsupported; nothing was recorded.',
29
+ turn_context_workspace_mismatch: 'The live turn context belongs to a different PD workspace; nothing was recorded.',
30
+ turn_context_session_mismatch: 'The live turn context does not belong to this Codex session; nothing was recorded.',
31
+ caller_session_missing: 'No host-bound Codex session identity is available; nothing was recorded.',
32
+ caller_session_conflict: 'The host-bound Codex session and thread identities conflict; nothing was recorded.',
33
+ conversation_ingestion_config_invalid: 'The workspace configuration could not be validated, so conversation-ingestion consent could not be determined; nothing was recorded.',
34
+ transcript_unavailable: 'The Codex transcript for the live turn is unavailable; nothing was recorded.',
35
+ lineage_invalid: 'The live turn context failed lineage validation; nothing was recorded.',
36
+ rate_limited: 'The per-root-session explicit statement quota (5/hour) is exhausted; nothing was recorded.',
37
+ continuation_failed: 'The governance admission or diagnostician-task continuation failed; nothing was recorded.',
38
+ workspace_unresolvable: 'No PD workspace could be resolved for this invocation.',
39
+ };
40
+ function emitFailure(opts, failure) {
41
+ if (opts.json) {
42
+ console.log(JSON.stringify({ status: 'failed', reason: failure.reason, message: failure.message, nextAction: failure.nextAction }, null, 2));
43
+ }
44
+ else {
45
+ console.error(`Error: ${failure.message}`);
46
+ console.error(`Reason: ${failure.reason}`);
47
+ console.error(`Next action: ${failure.nextAction}`);
48
+ }
49
+ process.exit(1);
50
+ }
51
+ export async function handlePainExplicit(opts) {
52
+ let workspaceDir;
53
+ try {
54
+ workspaceDir = resolveWorkspaceDir(opts.workspace);
55
+ }
56
+ catch (error) {
57
+ const detail = error instanceof Error ? error.message : String(error);
58
+ emitFailure(opts, { reason: 'workspace_unresolvable', message: detail.slice(0, 400), nextAction: 'Pass --workspace explicitly or run from a directory inside a PD workspace.' });
59
+ }
60
+ const result = await registerCurrentTurnOwnerStatement({
61
+ workspaceDir,
62
+ statementText: opts.statement ?? '',
63
+ });
64
+ if (result.status !== 'received') {
65
+ emitFailure(opts, {
66
+ reason: result.reason,
67
+ message: FAILURE_MESSAGES[result.reason] ?? 'The explicit registration was rejected; nothing was recorded.',
68
+ nextAction: result.nextAction,
69
+ });
70
+ }
71
+ if (opts.json) {
72
+ console.log(JSON.stringify({
73
+ status: 'received',
74
+ disposition: result.disposition,
75
+ painId: result.painId,
76
+ taskId: result.taskId,
77
+ logicalObservationKey: result.logicalObservationKey,
78
+ ...(result.promotionDegraded !== undefined ? { promotionDegraded: result.promotionDegraded } : {}),
79
+ }, null, 2));
80
+ }
81
+ else {
82
+ const duplicateNote = result.disposition === 'already_admitted' ? ' (duplicate submission merged into the same signal)' : '';
83
+ console.log(`Pain registered${duplicateNote}: painId=${result.painId} taskId=${result.taskId}`);
84
+ if (result.promotionDegraded !== undefined) {
85
+ console.error(`Warning: session-evidence promotion degraded: ${result.promotionDegraded}`);
86
+ }
87
+ }
88
+ }
89
+ //# sourceMappingURL=pain-explicit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pain-explicit.js","sourceRoot":"","sources":["../../src/commands/pain-explicit.ts"],"names":[],"mappings":"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"}
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import { Command, Option } from 'commander';
9
9
  import { handlePainRecord } from './commands/pain-record.js';
10
+ import { handlePainExplicit } from './commands/pain-explicit.js';
10
11
  import { registerPainRetryCommand } from './commands/pain-retry.js';
11
12
  import { handlePainRecall, parsePainRecallLimit } from './commands/pain-recall.js';
12
13
  import { handlePainEvidence } from './commands/pain-evidence.js';
@@ -121,6 +122,15 @@ painCmd
121
122
  .action(async (opts) => {
122
123
  await handlePainRecord(opts);
123
124
  });
125
+ painCmd
126
+ .command('explicit')
127
+ .description('Register an explicit Owner pain statement from the live Codex turn context (agent-assisted, no prefix; same admission chain as PD记录痛苦: — takes no lineage flags)')
128
+ .option('--statement <text>', 'Owner verbatim statement (1-500 chars, required; rejected when longer — never truncated)')
129
+ .option('-w, --workspace <path>', 'Workspace directory')
130
+ .option('--json', 'Output raw JSON')
131
+ .action(async (opts) => {
132
+ await handlePainExplicit(opts);
133
+ });
124
134
  registerPainRetryCommand(painCmd);
125
135
  painCmd
126
136
  .command('list')