create-principles-disciple 1.132.3 → 1.132.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/codex-adapter/dist/codec/index.d.ts +6 -0
- package/codex-adapter/dist/codec/index.js +5 -0
- package/codex-adapter/dist/codec/input-decoder.d.ts +13 -0
- package/codex-adapter/dist/codec/input-decoder.js +103 -0
- package/codex-adapter/dist/codec/output-encoder.d.ts +32 -0
- package/codex-adapter/dist/codec/output-encoder.js +51 -0
- package/codex-adapter/dist/host-adapter.d.ts +30 -0
- package/codex-adapter/dist/host-adapter.js +28 -0
- package/codex-adapter/dist/index.d.ts +25 -0
- package/codex-adapter/dist/index.js +19 -0
- package/codex-adapter/dist/ingestion/admission.d.ts +54 -0
- package/codex-adapter/dist/ingestion/admission.js +137 -0
- package/codex-adapter/dist/ingestion/catch-up.d.ts +41 -0
- package/codex-adapter/dist/ingestion/catch-up.js +94 -0
- package/codex-adapter/dist/ingestion/codex-home.d.ts +19 -0
- package/codex-adapter/dist/ingestion/codex-home.js +43 -0
- package/codex-adapter/dist/ingestion/codex-version.d.ts +30 -0
- package/codex-adapter/dist/ingestion/codex-version.js +53 -0
- package/codex-adapter/dist/ingestion/ingestion-fields.d.ts +17 -0
- package/codex-adapter/dist/ingestion/ingestion-fields.js +28 -0
- package/codex-adapter/dist/ingestion/ingestion.d.ts +71 -0
- package/codex-adapter/dist/ingestion/ingestion.js +248 -0
- package/codex-adapter/dist/ingestion/transcript-decoder.d.ts +72 -0
- package/codex-adapter/dist/ingestion/transcript-decoder.js +485 -0
- package/codex-adapter/dist/ingestion/transcript-locate.d.ts +13 -0
- package/codex-adapter/dist/ingestion/transcript-locate.js +75 -0
- package/codex-adapter/dist/ingestion/transcript-path.d.ts +25 -0
- package/codex-adapter/dist/ingestion/transcript-path.js +87 -0
- package/codex-adapter/dist/pd-hook.d.ts +9 -0
- package/codex-adapter/dist/pd-hook.js +159 -0
- package/codex-adapter/dist/tool-semantics.d.ts +20 -0
- package/codex-adapter/dist/tool-semantics.js +27 -0
- package/codex-adapter/dist/worker/workspace-worker.d.ts +59 -0
- package/codex-adapter/dist/worker/workspace-worker.js +270 -0
- package/codex-adapter/package.json +52 -0
- package/console/dist/server/routes/update.d.ts +7 -0
- package/console/dist/server/routes/update.js +200 -42
- package/console/dist/server/utils/installed-layout.d.ts +7 -0
- package/console/dist/server/utils/installed-layout.js +9 -0
- package/console/dist/types.d.ts +1 -0
- package/console/dist/ui/App.js +16 -0
- package/console/dist/ui/api.d.ts +11 -0
- package/console/dist/ui/api.js +18 -3
- package/console/dist/ui/pages/failed-tasks/FailedTasksPage.js +14 -56
- package/console/dist/web/assets/app.js +27 -50
- package/core/dist/runtime-v2/__tests__/evidence-sanitizer.test.js +55 -0
- package/core/dist/runtime-v2/__tests__/evidence-sanitizer.test.js.map +1 -1
- package/core/dist/runtime-v2/evidence-sanitizer.d.ts.map +1 -1
- package/core/dist/runtime-v2/evidence-sanitizer.js +17 -3
- package/core/dist/runtime-v2/evidence-sanitizer.js.map +1 -1
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +12 -0
- package/dist/i18n.js.map +1 -1
- package/dist/installer.d.ts +32 -0
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +234 -8
- package/dist/installer.js.map +1 -1
- package/dist/installers/codex-host-installer.d.ts.map +1 -1
- package/dist/installers/codex-host-installer.js +5 -3
- package/dist/installers/codex-host-installer.js.map +1 -1
- package/package.json +2 -1
- package/plugin/dist/bundle.js +327 -327
- package/release-manager/dist/i18n.d.ts.map +1 -1
- package/release-manager/dist/i18n.js +12 -0
- package/release-manager/dist/i18n.js.map +1 -1
- package/release-manager/dist/installer.d.ts +32 -0
- package/release-manager/dist/installer.d.ts.map +1 -1
- package/release-manager/dist/installer.js +234 -8
- package/release-manager/dist/installer.js.map +1 -1
- package/release-manager/dist/installers/codex-host-installer.d.ts.map +1 -1
- package/release-manager/dist/installers/codex-host-installer.js +5 -3
- package/release-manager/dist/installers/codex-host-installer.js.map +1 -1
- package/release-manager/package.json +2 -1
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codec barrel — input decoder + output encoder for Codex CLI.
|
|
3
|
+
*/
|
|
4
|
+
export { decodeCodexInput, CodexDecoderError, CODEX_EVENT_PRE_TOOL_USE, CODEX_EVENT_POST_TOOL_USE, CODEX_EVENT_USER_PROMPT_SUBMIT, CODEX_EVENT_SESSION_START, CODEX_EVENT_SESSION_END, } from './input-decoder.js';
|
|
5
|
+
export { encodeCodexOutput, codexOutputFieldsAreWhitelisted, CodexEncoderError, } from './output-encoder.js';
|
|
6
|
+
export type { CodexPreToolUseOutput, CodexPostToolUseOutput, CodexUserPromptSubmitOutput, CodexSessionStartOutput, CodexHookOutput, } from './output-encoder.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codec barrel — input decoder + output encoder for Codex CLI.
|
|
3
|
+
*/
|
|
4
|
+
export { decodeCodexInput, CodexDecoderError, CODEX_EVENT_PRE_TOOL_USE, CODEX_EVENT_POST_TOOL_USE, CODEX_EVENT_USER_PROMPT_SUBMIT, CODEX_EVENT_SESSION_START, CODEX_EVENT_SESSION_END, } from './input-decoder.js';
|
|
5
|
+
export { encodeCodexOutput, codexOutputFieldsAreWhitelisted, CodexEncoderError, } from './output-encoder.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { HostEvent } from '@principles/core/host';
|
|
2
|
+
export declare const CODEX_EVENT_PRE_TOOL_USE = "PreToolUse";
|
|
3
|
+
export declare const CODEX_EVENT_POST_TOOL_USE = "PostToolUse";
|
|
4
|
+
export declare const CODEX_EVENT_USER_PROMPT_SUBMIT = "UserPromptSubmit";
|
|
5
|
+
export declare const CODEX_EVENT_SESSION_START = "SessionStart";
|
|
6
|
+
export declare const CODEX_EVENT_STOP = "Stop";
|
|
7
|
+
export declare const CODEX_EVENT_SESSION_END = "SessionEnd";
|
|
8
|
+
export declare class CodexDecoderError extends Error {
|
|
9
|
+
readonly reason: string;
|
|
10
|
+
readonly nextAction: string;
|
|
11
|
+
constructor(reason: string, nextAction: string);
|
|
12
|
+
}
|
|
13
|
+
export declare function decodeCodexInput(raw: unknown): HostEvent;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { CODEX_INGESTION_MIN_VERSION } from '../ingestion/codex-version.js';
|
|
2
|
+
// Two contract baselines coexist deliberately (G1 probe report §9): the
|
|
3
|
+
// four pre-ingestion events pin their payload contract to codex-cli 0.147.0,
|
|
4
|
+
// while the Stop / ingestion-side fields follow the ingestion baseline
|
|
5
|
+
// (CODEX_INGESTION_MIN_VERSION = 0.148.0). Each message names the baseline
|
|
6
|
+
// that defines it — never mix them.
|
|
7
|
+
const FOUR_EVENT_CONTRACT_VERSION = '0.147.0';
|
|
8
|
+
export const CODEX_EVENT_PRE_TOOL_USE = 'PreToolUse';
|
|
9
|
+
export const CODEX_EVENT_POST_TOOL_USE = 'PostToolUse';
|
|
10
|
+
export const CODEX_EVENT_USER_PROMPT_SUBMIT = 'UserPromptSubmit';
|
|
11
|
+
export const CODEX_EVENT_SESSION_START = 'SessionStart';
|
|
12
|
+
export const CODEX_EVENT_STOP = 'Stop';
|
|
13
|
+
export const CODEX_EVENT_SESSION_END = 'SessionEnd';
|
|
14
|
+
const EVENT_KINDS = new Map([
|
|
15
|
+
[CODEX_EVENT_PRE_TOOL_USE, 'before_tool_call'],
|
|
16
|
+
[CODEX_EVENT_POST_TOOL_USE, 'after_tool_call'],
|
|
17
|
+
[CODEX_EVENT_USER_PROMPT_SUBMIT, 'before_prompt_build'],
|
|
18
|
+
[CODEX_EVENT_SESSION_START, 'session_start'],
|
|
19
|
+
[CODEX_EVENT_STOP, 'turn_complete'],
|
|
20
|
+
]);
|
|
21
|
+
export class CodexDecoderError extends Error {
|
|
22
|
+
reason;
|
|
23
|
+
nextAction;
|
|
24
|
+
constructor(reason, nextAction) {
|
|
25
|
+
super(`Codex input decode failed: ${reason}`);
|
|
26
|
+
this.reason = reason;
|
|
27
|
+
this.nextAction = nextAction;
|
|
28
|
+
this.name = 'CodexDecoderError';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function isRecord(value) {
|
|
32
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
33
|
+
}
|
|
34
|
+
function own(value, key) {
|
|
35
|
+
return Object.hasOwn(value, key) ? Object.getOwnPropertyDescriptor(value, key)?.value : undefined;
|
|
36
|
+
}
|
|
37
|
+
function requiredString(value, key) {
|
|
38
|
+
const candidate = own(value, key);
|
|
39
|
+
if (typeof candidate !== 'string' || candidate.trim().length === 0) {
|
|
40
|
+
throw new CodexDecoderError(`missing or malformed required field "${key}"`, `Use the exact codex-cli ${FOUR_EVENT_CONTRACT_VERSION} ${key} field.`);
|
|
41
|
+
}
|
|
42
|
+
return candidate;
|
|
43
|
+
}
|
|
44
|
+
function requiredNullableString(value, key) {
|
|
45
|
+
if (!Object.hasOwn(value, key))
|
|
46
|
+
throw new CodexDecoderError(`missing required field "${key}"`, `Use the exact codex-cli ${FOUR_EVENT_CONTRACT_VERSION} ${key} field.`);
|
|
47
|
+
const candidate = own(value, key);
|
|
48
|
+
if (candidate !== null && typeof candidate !== 'string') {
|
|
49
|
+
throw new CodexDecoderError(`malformed required field "${key}"`, `${key} must be a string or null.`);
|
|
50
|
+
}
|
|
51
|
+
return candidate;
|
|
52
|
+
}
|
|
53
|
+
function requiredUnknown(value, key) {
|
|
54
|
+
if (!Object.hasOwn(value, key))
|
|
55
|
+
throw new CodexDecoderError(`missing required field "${key}"`, `Use the exact codex-cli ${FOUR_EVENT_CONTRACT_VERSION} ${key} field.`);
|
|
56
|
+
return own(value, key);
|
|
57
|
+
}
|
|
58
|
+
function validateCommon(raw, needsTurn) {
|
|
59
|
+
const sessionId = requiredString(raw, 'session_id');
|
|
60
|
+
requiredNullableString(raw, 'transcript_path');
|
|
61
|
+
const workspaceDir = requiredString(raw, 'cwd');
|
|
62
|
+
requiredString(raw, 'model');
|
|
63
|
+
requiredString(raw, 'permission_mode');
|
|
64
|
+
const turnId = needsTurn ? requiredString(raw, 'turn_id') : undefined;
|
|
65
|
+
return { workspaceDir, sessionId, ...(turnId ? { turnId } : {}) };
|
|
66
|
+
}
|
|
67
|
+
export function decodeCodexInput(raw) {
|
|
68
|
+
if (!isRecord(raw))
|
|
69
|
+
throw new CodexDecoderError('stdin payload is not a JSON object', 'Run this executable only as a Codex command hook.');
|
|
70
|
+
const eventName = requiredString(raw, 'hook_event_name');
|
|
71
|
+
const kind = EVENT_KINDS.get(eventName);
|
|
72
|
+
if (!kind)
|
|
73
|
+
throw new CodexDecoderError(`unknown hook_event_name "${eventName}"`, 'Configure only PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, or Stop.');
|
|
74
|
+
const common = validateCommon(raw, kind !== 'session_start');
|
|
75
|
+
let context;
|
|
76
|
+
let rawPayload = raw;
|
|
77
|
+
if (kind === 'before_tool_call' || kind === 'after_tool_call') {
|
|
78
|
+
const toolName = requiredString(raw, 'tool_name');
|
|
79
|
+
const toolInput = requiredUnknown(raw, 'tool_input');
|
|
80
|
+
requiredString(raw, 'tool_use_id');
|
|
81
|
+
const toolOutput = kind === 'after_tool_call' ? requiredUnknown(raw, 'tool_response') : undefined;
|
|
82
|
+
context = { ...common, toolName, toolInput, ...(kind === 'after_tool_call' ? { toolOutput } : {}) };
|
|
83
|
+
rawPayload = { toolInput: { toolName, params: toolInput } };
|
|
84
|
+
}
|
|
85
|
+
else if (kind === 'before_prompt_build') {
|
|
86
|
+
context = { ...common, promptContent: requiredString(raw, 'prompt') };
|
|
87
|
+
}
|
|
88
|
+
else if (kind === 'turn_complete') {
|
|
89
|
+
// Stop is the G1-verified turn-complete event (probe report §2). Its
|
|
90
|
+
// payload carries stop_hook_active (required boolean); PD consumes the
|
|
91
|
+
// already-flushed transcript, never last_assistant_message directly.
|
|
92
|
+
const stopHookActive = own(raw, 'stop_hook_active');
|
|
93
|
+
if (typeof stopHookActive !== 'boolean') {
|
|
94
|
+
throw new CodexDecoderError('missing or malformed required field "stop_hook_active"', `Use the exact codex-cli ${CODEX_INGESTION_MIN_VERSION} stop_hook_active field.`);
|
|
95
|
+
}
|
|
96
|
+
context = { ...common };
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
context = { ...common, source: requiredString(raw, 'source') };
|
|
100
|
+
}
|
|
101
|
+
const source = `codex:${eventName.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase()}`;
|
|
102
|
+
return { kind, context, rawPayload, source };
|
|
103
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { HostEventResult } from '@principles/core/host';
|
|
2
|
+
type HookName = 'PreToolUse' | 'PostToolUse' | 'UserPromptSubmit' | 'SessionStart';
|
|
3
|
+
interface HookSpecificOutput {
|
|
4
|
+
hookEventName: HookName;
|
|
5
|
+
permissionDecision?: 'deny';
|
|
6
|
+
permissionDecisionReason?: string;
|
|
7
|
+
additionalContext?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface CodexPreToolUseOutput {
|
|
10
|
+
hookSpecificOutput: HookSpecificOutput;
|
|
11
|
+
}
|
|
12
|
+
export interface CodexPostToolUseOutput {
|
|
13
|
+
hookSpecificOutput: HookSpecificOutput;
|
|
14
|
+
}
|
|
15
|
+
export interface CodexUserPromptSubmitOutput {
|
|
16
|
+
hookSpecificOutput: HookSpecificOutput;
|
|
17
|
+
}
|
|
18
|
+
export interface CodexSessionStartOutput {
|
|
19
|
+
hookSpecificOutput: HookSpecificOutput;
|
|
20
|
+
}
|
|
21
|
+
export type CodexHookOutput = CodexPreToolUseOutput | CodexPostToolUseOutput | CodexUserPromptSubmitOutput | CodexSessionStartOutput;
|
|
22
|
+
export declare class CodexEncoderError extends Error {
|
|
23
|
+
readonly reason: string;
|
|
24
|
+
readonly nextAction: string;
|
|
25
|
+
constructor(reason: string, nextAction: string);
|
|
26
|
+
}
|
|
27
|
+
export declare function encodeCodexOutput(result: HostEventResult, kind: string): CodexHookOutput;
|
|
28
|
+
export declare function codexOutputFieldsAreWhitelisted(output: unknown): {
|
|
29
|
+
ok: boolean;
|
|
30
|
+
violators: string[];
|
|
31
|
+
};
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export class CodexEncoderError extends Error {
|
|
2
|
+
reason;
|
|
3
|
+
nextAction;
|
|
4
|
+
constructor(reason, nextAction) {
|
|
5
|
+
super(`Codex output encode failed: ${reason}`);
|
|
6
|
+
this.reason = reason;
|
|
7
|
+
this.nextAction = nextAction;
|
|
8
|
+
this.name = 'CodexEncoderError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function nonEmpty(value) { return typeof value === 'string' && value.trim().length > 0; }
|
|
12
|
+
export function encodeCodexOutput(result, kind) {
|
|
13
|
+
if (result.modifiedInput !== undefined)
|
|
14
|
+
throw new CodexEncoderError('modifiedInput is unsupported by the PD Codex adapter', 'Deny or allow the original input.');
|
|
15
|
+
if (result.additionalContext !== undefined && !nonEmpty(result.additionalContext))
|
|
16
|
+
throw new CodexEncoderError('additionalContext must be non-empty', 'Drop or populate additionalContext.');
|
|
17
|
+
const names = new Map([['before_tool_call', 'PreToolUse'], ['after_tool_call', 'PostToolUse'], ['before_prompt_build', 'UserPromptSubmit'], ['session_start', 'SessionStart']]);
|
|
18
|
+
const hookEventName = names.get(kind);
|
|
19
|
+
if (!hookEventName)
|
|
20
|
+
throw new CodexEncoderError(`unknown event kind "${kind}"`, 'Encode only a supported Codex hook event.');
|
|
21
|
+
if (result.decision === 'deny' && kind !== 'before_tool_call')
|
|
22
|
+
throw new CodexEncoderError(`deny is unsupported for ${kind}`, 'Return the route-compatible host result.');
|
|
23
|
+
const hookSpecificOutput = { hookEventName };
|
|
24
|
+
if (result.decision === 'deny') {
|
|
25
|
+
if (!nonEmpty(result.reason))
|
|
26
|
+
throw new CodexEncoderError('deny requires a non-empty reason', 'Supply an operator-readable reason.');
|
|
27
|
+
hookSpecificOutput.permissionDecision = 'deny';
|
|
28
|
+
hookSpecificOutput.permissionDecisionReason = result.reason.trim();
|
|
29
|
+
}
|
|
30
|
+
else if (result.reason !== undefined) {
|
|
31
|
+
throw new CodexEncoderError('reason without deny is unsupported', 'Drop reason for a non-deny decision.');
|
|
32
|
+
}
|
|
33
|
+
if (result.additionalContext !== undefined)
|
|
34
|
+
hookSpecificOutput.additionalContext = result.additionalContext;
|
|
35
|
+
return { hookSpecificOutput };
|
|
36
|
+
}
|
|
37
|
+
const TOP_LEVEL = new Set(['hookSpecificOutput']);
|
|
38
|
+
const NESTED = new Set(['hookEventName', 'permissionDecision', 'permissionDecisionReason', 'additionalContext']);
|
|
39
|
+
export function codexOutputFieldsAreWhitelisted(output) {
|
|
40
|
+
if (typeof output !== 'object' || output === null || Array.isArray(output))
|
|
41
|
+
return { ok: false, violators: ['<not-an-object>'] };
|
|
42
|
+
const top = Object.keys(output).filter((key) => !TOP_LEVEL.has(key));
|
|
43
|
+
const descriptor = Object.getOwnPropertyDescriptor(output, 'hookSpecificOutput')?.value;
|
|
44
|
+
if (typeof descriptor !== 'object' || descriptor === null || Array.isArray(descriptor))
|
|
45
|
+
return { ok: false, violators: [...top, 'hookSpecificOutput'] };
|
|
46
|
+
const nested = Object.keys(descriptor).filter((key) => !NESTED.has(key)).map((key) => `hookSpecificOutput.${key}`);
|
|
47
|
+
const name = Object.getOwnPropertyDescriptor(descriptor, 'hookEventName')?.value;
|
|
48
|
+
if (!['PreToolUse', 'PostToolUse', 'UserPromptSubmit', 'SessionStart'].includes(String(name)))
|
|
49
|
+
nested.push('hookSpecificOutput.hookEventName');
|
|
50
|
+
return { ok: top.length + nested.length === 0, violators: [...top, ...nested] };
|
|
51
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CodexHooksHostAdapter — implements HostAdapter for OpenAI Codex CLI (ADR-0020 §2.5)
|
|
3
|
+
*
|
|
4
|
+
* Codex CLI spawns `pd-hook.js` as a subprocess for each hook event, writes
|
|
5
|
+
* the event payload as JSON on stdin, and reads the output as JSON on stdout.
|
|
6
|
+
* This adapter owns:
|
|
7
|
+
* 1. Decoding the raw stdin JSON into a unified HostEvent (via codec/input-decoder).
|
|
8
|
+
* 2. Encoding a unified HostEventResult into Codex's camelCase stdout JSON
|
|
9
|
+
* (via codec/output-encoder).
|
|
10
|
+
*
|
|
11
|
+
* The adapter does NOT own hook business logic (pain detection, principle
|
|
12
|
+
* injection, gate enforcement). Those run in the openclaw-plugin/core code and
|
|
13
|
+
* are invoked by `pd-hook.js` after decodeEvent() and before encodeOutput().
|
|
14
|
+
*
|
|
15
|
+
* MVP scope (ADR-0014 / ADR-0020):
|
|
16
|
+
* - Subscribes to 4 events: before_tool_call, after_tool_call, before_prompt_build, session_start.
|
|
17
|
+
* - session_end is deferred (observe-only, no MVP-Core activation path uses it).
|
|
18
|
+
* - hostKind = 'subprocess' (Codex spawns pd-hook.js; OpenClaw is 'inprocess').
|
|
19
|
+
*
|
|
20
|
+
* Feature flag: when `host.codex.enabled = false` (default), `pd-hook.js`
|
|
21
|
+
* short-circuits to `{} + exit 0` before invoking this adapter (rc-9).
|
|
22
|
+
*/
|
|
23
|
+
import type { HostAdapter, HostEvent, HostEventKind, HostEventResult } from '@principles/core/host';
|
|
24
|
+
export declare class CodexHooksHostAdapter implements HostAdapter {
|
|
25
|
+
readonly hostId = "codex";
|
|
26
|
+
readonly hostKind: 'subprocess';
|
|
27
|
+
subscribedEvents(): readonly HostEventKind[];
|
|
28
|
+
decodeEvent(raw: unknown): HostEvent;
|
|
29
|
+
encodeOutput(result: HostEventResult, kind: HostEventKind): unknown;
|
|
30
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { decodeCodexInput, encodeCodexOutput } from './codec/index.js';
|
|
2
|
+
const SUBSCRIBED_EVENTS = [
|
|
3
|
+
'before_tool_call',
|
|
4
|
+
'after_tool_call',
|
|
5
|
+
'before_prompt_build',
|
|
6
|
+
'session_start',
|
|
7
|
+
// Stop = turn_complete is the G1-verified turn-complete event (probe
|
|
8
|
+
// report §2); it drives bounded governance-observation ingestion, not a
|
|
9
|
+
// dispatch route. session_end stays deferred (SPEC §8: never register both
|
|
10
|
+
// Stop and SessionEnd for turn completion).
|
|
11
|
+
'turn_complete',
|
|
12
|
+
];
|
|
13
|
+
export class CodexHooksHostAdapter {
|
|
14
|
+
hostId = 'codex';
|
|
15
|
+
hostKind = 'subprocess';
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
17
|
+
subscribedEvents() {
|
|
18
|
+
return SUBSCRIBED_EVENTS;
|
|
19
|
+
}
|
|
20
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
21
|
+
decodeEvent(raw) {
|
|
22
|
+
return decodeCodexInput(raw);
|
|
23
|
+
}
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
25
|
+
encodeOutput(result, kind) {
|
|
26
|
+
return encodeCodexOutput(result, kind);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @principles/codex-adapter — Codex CLI Host Adapter (ADR-0020)
|
|
3
|
+
*
|
|
4
|
+
* Implements HostAdapter for OpenAI Codex CLI's stdin/stdout JSON hook model.
|
|
5
|
+
* This package is INDEPENDENT of `packages/openclaw-plugin/` — the two hosts
|
|
6
|
+
* have different extension models (Codex: subprocess; OpenClaw: in-process).
|
|
7
|
+
*/
|
|
8
|
+
export { CodexHooksHostAdapter } from './host-adapter.js';
|
|
9
|
+
export { processHookInvocation } from './pd-hook.js';
|
|
10
|
+
export type { PdHookResult } from './pd-hook.js';
|
|
11
|
+
export * from './codec/index.js';
|
|
12
|
+
export { resolveCodexHome, canonicalizePath } from './ingestion/codex-home.js';
|
|
13
|
+
export { validateCodexTranscriptPath } from './ingestion/transcript-path.js';
|
|
14
|
+
export type { TranscriptPathValidation, TranscriptFileIdentity } from './ingestion/transcript-path.js';
|
|
15
|
+
export { classifyCodexVersion, CODEX_INGESTION_MIN_VERSION, CODEX_INGESTION_VERIFIED_VERSION } from './ingestion/codex-version.js';
|
|
16
|
+
export { ingestCodexConversation, setCodexTranscriptPortForTest, ingestCodexTranscriptFromPath } from './ingestion/ingestion.js';
|
|
17
|
+
export type { CodexIngestionOptions, CodexIngestionOutcome, CodexTranscriptFromPathArgs } from './ingestion/ingestion.js';
|
|
18
|
+
export { decodeTranscriptWindow, createNodeTranscriptPort, TranscriptReplacedError, CODEX_INGESTION_MAX_BATCH_BYTES, CODEX_INGESTION_MAX_BATCH_RECORDS } from './ingestion/transcript-decoder.js';
|
|
19
|
+
export type { TranscriptPort, TranscriptExpectedIdentity, DecodedDelta, TranscriptDecodeStop } from './ingestion/transcript-decoder.js';
|
|
20
|
+
export { catchUpCodexIngestion, CODEX_CATCH_UP_NEXT_ACTION } from './ingestion/catch-up.js';
|
|
21
|
+
export type { CodexCatchUpOptions, CodexCatchUpResult, CodexCatchUpRolloutResult } from './ingestion/catch-up.js';
|
|
22
|
+
export { locateCodexTranscriptByRolloutIdentity } from './ingestion/transcript-locate.js';
|
|
23
|
+
export type { CodexTranscriptLookup } from './ingestion/transcript-locate.js';
|
|
24
|
+
export { runCodexWorkspaceWorkerCycle, computeCodexWorkerStatusMode } from './worker/workspace-worker.js';
|
|
25
|
+
export type { CodexWorkerMode, CodexWorkerCycleResult, CodexWorkerCycleStepReport, CodexWorkerCycleOptions, CodexWorkerStatusEvaluation } from './worker/workspace-worker.js';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @principles/codex-adapter — Codex CLI Host Adapter (ADR-0020)
|
|
3
|
+
*
|
|
4
|
+
* Implements HostAdapter for OpenAI Codex CLI's stdin/stdout JSON hook model.
|
|
5
|
+
* This package is INDEPENDENT of `packages/openclaw-plugin/` — the two hosts
|
|
6
|
+
* have different extension models (Codex: subprocess; OpenClaw: in-process).
|
|
7
|
+
*/
|
|
8
|
+
export { CodexHooksHostAdapter } from './host-adapter.js';
|
|
9
|
+
export { processHookInvocation } from './pd-hook.js';
|
|
10
|
+
export * from './codec/index.js';
|
|
11
|
+
export { resolveCodexHome, canonicalizePath } from './ingestion/codex-home.js';
|
|
12
|
+
export { validateCodexTranscriptPath } from './ingestion/transcript-path.js';
|
|
13
|
+
export { classifyCodexVersion, CODEX_INGESTION_MIN_VERSION, CODEX_INGESTION_VERIFIED_VERSION } from './ingestion/codex-version.js';
|
|
14
|
+
export { ingestCodexConversation, setCodexTranscriptPortForTest, ingestCodexTranscriptFromPath } from './ingestion/ingestion.js';
|
|
15
|
+
export { decodeTranscriptWindow, createNodeTranscriptPort, TranscriptReplacedError, CODEX_INGESTION_MAX_BATCH_BYTES, CODEX_INGESTION_MAX_BATCH_RECORDS } from './ingestion/transcript-decoder.js';
|
|
16
|
+
// PRI-624 Slice C: bounded catch-up + per-workspace worker cycle.
|
|
17
|
+
export { catchUpCodexIngestion, CODEX_CATCH_UP_NEXT_ACTION } from './ingestion/catch-up.js';
|
|
18
|
+
export { locateCodexTranscriptByRolloutIdentity } from './ingestion/transcript-locate.js';
|
|
19
|
+
export { runCodexWorkspaceWorkerCycle, computeCodexWorkerStatusMode } from './worker/workspace-worker.js';
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex governance admission orchestration (Codex Governance Closure Slice B,
|
|
3
|
+
* PRI-623; SPEC §12/§13).
|
|
4
|
+
*
|
|
5
|
+
* Owns ONLY the Codex facts: mapping live hook payloads and decoded transcript
|
|
6
|
+
* observations onto the host-neutral admission candidates of
|
|
7
|
+
* `@principles/host-runtime` (`admitGovernanceSignals`), then driving the
|
|
8
|
+
* admitted pains through the existing continuation seams —
|
|
9
|
+
* `ensureGovernanceDiagnosticianTask` (Runtime V2 async enqueue, never an
|
|
10
|
+
* LLM) and `promoteAdmittedGovernanceEvidence` (Slice A's ≤12 + trigger +
|
|
11
|
+
* next-assistant window with its durable pending tail).
|
|
12
|
+
*
|
|
13
|
+
* No Codex JSONL knowledge crosses into host-runtime; no second pain or task
|
|
14
|
+
* authority exists here.
|
|
15
|
+
*/
|
|
16
|
+
import { type GovernanceSignalCandidate } from '@principles/host-runtime';
|
|
17
|
+
import type { GovernanceObservationInput } from '@principles/host-runtime';
|
|
18
|
+
import type { PayloadFields } from './ingestion-fields.js';
|
|
19
|
+
export interface GovernanceAdmissionDegradation {
|
|
20
|
+
readonly reason: string;
|
|
21
|
+
readonly nextAction: string;
|
|
22
|
+
}
|
|
23
|
+
export interface GovernanceAdmissionRun {
|
|
24
|
+
/** Bounded structured degradations (only real failures; duplicate / rate-limited / ordinary conversation are normal outcomes and stay silent, SPEC §20/§21). */
|
|
25
|
+
readonly degradations: readonly GovernanceAdmissionDegradation[];
|
|
26
|
+
}
|
|
27
|
+
/** Live UserPromptSubmit payload → correction candidate (logical key mirrors the observation). */
|
|
28
|
+
export declare function buildLiveCorrectionCandidate(args: {
|
|
29
|
+
fields: PayloadFields;
|
|
30
|
+
rolloutIdentity: string;
|
|
31
|
+
}): GovernanceSignalCandidate | null;
|
|
32
|
+
/** Live PostToolUse payload → tool-failure candidate (logical key mirrors the observation). */
|
|
33
|
+
export declare function buildLiveToolCandidate(args: {
|
|
34
|
+
fields: PayloadFields;
|
|
35
|
+
rolloutIdentity: string;
|
|
36
|
+
}): GovernanceSignalCandidate | null;
|
|
37
|
+
/**
|
|
38
|
+
* Decoded transcript observations → candidates. User turns carry visibleText;
|
|
39
|
+
* tool calls carry the CommandExecution facts projection. The logical keys are
|
|
40
|
+
* the decoded ones, so a replay of an already-admitted live event is an
|
|
41
|
+
* observation-level no-op (SPEC §10).
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildTranscriptCandidates(observations: readonly GovernanceObservationInput[]): readonly GovernanceSignalCandidate[];
|
|
44
|
+
/**
|
|
45
|
+
* Run admission for the given candidates and drive every admitted pain
|
|
46
|
+
* through task-ensure + evidence promotion. Exactly-once is owned by the
|
|
47
|
+
* host-runtime seams; this orchestrator only sequences them and surfaces
|
|
48
|
+
* bounded degradations (duplicate / rate-limited / ordinary conversation are
|
|
49
|
+
* normal outcomes and produce no noise, SPEC §20/§21).
|
|
50
|
+
*/
|
|
51
|
+
export declare function runGovernanceAdmission(args: {
|
|
52
|
+
workspaceDir: string;
|
|
53
|
+
candidates: readonly GovernanceSignalCandidate[];
|
|
54
|
+
}): Promise<GovernanceAdmissionRun>;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex governance admission orchestration (Codex Governance Closure Slice B,
|
|
3
|
+
* PRI-623; SPEC §12/§13).
|
|
4
|
+
*
|
|
5
|
+
* Owns ONLY the Codex facts: mapping live hook payloads and decoded transcript
|
|
6
|
+
* observations onto the host-neutral admission candidates of
|
|
7
|
+
* `@principles/host-runtime` (`admitGovernanceSignals`), then driving the
|
|
8
|
+
* admitted pains through the existing continuation seams —
|
|
9
|
+
* `ensureGovernanceDiagnosticianTask` (Runtime V2 async enqueue, never an
|
|
10
|
+
* LLM) and `promoteAdmittedGovernanceEvidence` (Slice A's ≤12 + trigger +
|
|
11
|
+
* next-assistant window with its durable pending tail).
|
|
12
|
+
*
|
|
13
|
+
* No Codex JSONL knowledge crosses into host-runtime; no second pain or task
|
|
14
|
+
* authority exists here.
|
|
15
|
+
*/
|
|
16
|
+
import { admitGovernanceSignals, ensureGovernanceContinuation, } from '@principles/host-runtime';
|
|
17
|
+
import { isRecord, own } from './ingestion-fields.js';
|
|
18
|
+
/** The live host-event source used by the identity derivation (parity with the dispatch handler). */
|
|
19
|
+
const CODEX_POST_TOOL_USE_SOURCE = 'codex:post_tool_use';
|
|
20
|
+
/** Live UserPromptSubmit payload → correction candidate (logical key mirrors the observation). */
|
|
21
|
+
export function buildLiveCorrectionCandidate(args) {
|
|
22
|
+
const { fields } = args;
|
|
23
|
+
if (fields.turnId === null || fields.prompt === null)
|
|
24
|
+
return null;
|
|
25
|
+
return {
|
|
26
|
+
kind: 'user_correction',
|
|
27
|
+
hostKind: 'codex',
|
|
28
|
+
logicalObservationKey: `codex|${args.rolloutIdentity}|${fields.turnId}|user`,
|
|
29
|
+
rolloutIdentity: args.rolloutIdentity,
|
|
30
|
+
rootSessionId: fields.sessionId,
|
|
31
|
+
hostTurnId: fields.turnId,
|
|
32
|
+
text: fields.prompt,
|
|
33
|
+
observedAt: new Date().toISOString(),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** Live PostToolUse payload → tool-failure candidate (logical key mirrors the observation). */
|
|
37
|
+
export function buildLiveToolCandidate(args) {
|
|
38
|
+
const { fields } = args;
|
|
39
|
+
if (fields.turnId === null || fields.toolUseId === null)
|
|
40
|
+
return null;
|
|
41
|
+
return {
|
|
42
|
+
kind: 'tool_failure',
|
|
43
|
+
hostKind: 'codex',
|
|
44
|
+
logicalObservationKey: `codex|${args.rolloutIdentity}|${fields.toolUseId}`,
|
|
45
|
+
rolloutIdentity: args.rolloutIdentity,
|
|
46
|
+
rootSessionId: fields.sessionId,
|
|
47
|
+
hostTurnId: fields.turnId,
|
|
48
|
+
toolName: fields.toolName ?? '',
|
|
49
|
+
source: CODEX_POST_TOOL_USE_SOURCE,
|
|
50
|
+
...(fields.toolInput !== undefined ? { toolInput: fields.toolInput } : {}),
|
|
51
|
+
...(fields.toolResponse !== undefined ? { toolOutput: fields.toolResponse } : {}),
|
|
52
|
+
observedAt: new Date().toISOString(),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Decoded transcript observations → candidates. User turns carry visibleText;
|
|
57
|
+
* tool calls carry the CommandExecution facts projection. The logical keys are
|
|
58
|
+
* the decoded ones, so a replay of an already-admitted live event is an
|
|
59
|
+
* observation-level no-op (SPEC §10).
|
|
60
|
+
*/
|
|
61
|
+
export function buildTranscriptCandidates(observations) {
|
|
62
|
+
const candidates = [];
|
|
63
|
+
for (const observation of observations) {
|
|
64
|
+
if (observation.kind === 'user_turn') {
|
|
65
|
+
if (observation.visibleText === undefined)
|
|
66
|
+
continue;
|
|
67
|
+
candidates.push({
|
|
68
|
+
kind: 'user_correction',
|
|
69
|
+
hostKind: 'codex',
|
|
70
|
+
logicalObservationKey: observation.logicalObservationKey,
|
|
71
|
+
rolloutIdentity: observation.rolloutIdentity,
|
|
72
|
+
rootSessionId: observation.rootSessionId,
|
|
73
|
+
hostTurnId: observation.hostTurnId,
|
|
74
|
+
text: observation.visibleText,
|
|
75
|
+
observedAt: observation.observedAt,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
else if (observation.kind === 'tool_call') {
|
|
79
|
+
const facts = observation.toolFacts;
|
|
80
|
+
if (!isRecord(facts))
|
|
81
|
+
continue;
|
|
82
|
+
const toolName = own(facts, 'toolName');
|
|
83
|
+
const exitCode = own(facts, 'exitCode');
|
|
84
|
+
candidates.push({
|
|
85
|
+
kind: 'tool_failure',
|
|
86
|
+
hostKind: 'codex',
|
|
87
|
+
logicalObservationKey: observation.logicalObservationKey,
|
|
88
|
+
rolloutIdentity: observation.rolloutIdentity,
|
|
89
|
+
rootSessionId: observation.rootSessionId,
|
|
90
|
+
hostTurnId: observation.hostTurnId,
|
|
91
|
+
toolName: typeof toolName === 'string' ? toolName : '',
|
|
92
|
+
source: CODEX_POST_TOOL_USE_SOURCE,
|
|
93
|
+
toolInput: own(facts, 'command') ?? null,
|
|
94
|
+
...(exitCode !== undefined ? { toolOutput: { exitCode, stdout: own(facts, 'stdout') ?? null, stderr: own(facts, 'stderr') ?? null } } : {}),
|
|
95
|
+
observedAt: observation.observedAt,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return candidates;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Run admission for the given candidates and drive every admitted pain
|
|
103
|
+
* through task-ensure + evidence promotion. Exactly-once is owned by the
|
|
104
|
+
* host-runtime seams; this orchestrator only sequences them and surfaces
|
|
105
|
+
* bounded degradations (duplicate / rate-limited / ordinary conversation are
|
|
106
|
+
* normal outcomes and produce no noise, SPEC §20/§21).
|
|
107
|
+
*/
|
|
108
|
+
export async function runGovernanceAdmission(args) {
|
|
109
|
+
const { workspaceDir, candidates } = args;
|
|
110
|
+
if (candidates.length === 0)
|
|
111
|
+
return { degradations: [] };
|
|
112
|
+
const degradations = [];
|
|
113
|
+
const push = (reason, nextAction) => {
|
|
114
|
+
if (degradations.length < 4)
|
|
115
|
+
degradations.push({ reason: reason.slice(0, 300), nextAction: nextAction.slice(0, 300) });
|
|
116
|
+
};
|
|
117
|
+
const admitted = admitGovernanceSignals({ workspaceDir, candidates });
|
|
118
|
+
if (!admitted.ok) {
|
|
119
|
+
push(admitted.reason, admitted.nextAction);
|
|
120
|
+
return { degradations };
|
|
121
|
+
}
|
|
122
|
+
for (const outcome of admitted.outcomes) {
|
|
123
|
+
if (!('disposition' in outcome))
|
|
124
|
+
continue;
|
|
125
|
+
if (outcome.disposition !== 'admitted' && outcome.disposition !== 'already_admitted')
|
|
126
|
+
continue;
|
|
127
|
+
const cont = await ensureGovernanceContinuation({
|
|
128
|
+
workspaceDir,
|
|
129
|
+
logicalObservationKey: outcome.logicalObservationKey,
|
|
130
|
+
canonicalPainId: outcome.canonicalPainId,
|
|
131
|
+
});
|
|
132
|
+
if (!cont.ok) {
|
|
133
|
+
push(cont.reason, cont.nextAction);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return { degradations };
|
|
137
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type CodexIngestionOutcome } from './ingestion.js';
|
|
2
|
+
import type { TranscriptPort } from './transcript-decoder.js';
|
|
3
|
+
export interface CodexCatchUpOptions {
|
|
4
|
+
readonly workspaceDir: string;
|
|
5
|
+
readonly env?: {
|
|
6
|
+
CODEX_HOME?: string | undefined;
|
|
7
|
+
};
|
|
8
|
+
readonly now?: Date;
|
|
9
|
+
readonly port?: TranscriptPort;
|
|
10
|
+
/** Bounded per invocation (default 8 rollouts × the 1 MiB batch bound). */
|
|
11
|
+
readonly maxRollouts?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface CodexCatchUpRolloutResult {
|
|
14
|
+
readonly rolloutIdentity: string;
|
|
15
|
+
readonly outcome: CodexIngestionOutcome;
|
|
16
|
+
/** Admission degradations from this rollout's candidates (bounded by runGovernanceAdmission). */
|
|
17
|
+
readonly admissionDegradations: readonly {
|
|
18
|
+
reason: string;
|
|
19
|
+
nextAction: string;
|
|
20
|
+
}[];
|
|
21
|
+
}
|
|
22
|
+
export type CodexCatchUpResult = {
|
|
23
|
+
status: 'skipped';
|
|
24
|
+
reason: string;
|
|
25
|
+
nextAction: string;
|
|
26
|
+
} | {
|
|
27
|
+
status: 'ok' | 'degraded';
|
|
28
|
+
/** Rollouts processed this invocation (bounded). */
|
|
29
|
+
readonly rollouts: readonly CodexCatchUpRolloutResult[];
|
|
30
|
+
/** Processed rollouts that still carry lag (degraded or lagBytes > 0). */
|
|
31
|
+
readonly remainingLagRollouts: readonly string[];
|
|
32
|
+
/** Checkpoints not examined this invocation because of the bound (rotation converges them over repeated passes). */
|
|
33
|
+
readonly unexaminedRollouts: readonly string[];
|
|
34
|
+
};
|
|
35
|
+
export declare const CODEX_CATCH_UP_NEXT_ACTION = "Set features.codex_conversation_ingestion.enabled=true in the selected Workspace .pd/config.yaml to enable bounded conversation ingestion.";
|
|
36
|
+
/**
|
|
37
|
+
* Perform one bounded catch-up pass. `remainingLagRollouts` is derived from
|
|
38
|
+
* each processed rollout's reported lag plus the checkpoints left unprocessed
|
|
39
|
+
* by the bound — callers (worker cycle / CLI) repeat passes to converge.
|
|
40
|
+
*/
|
|
41
|
+
export declare function catchUpCodexIngestion(options: CodexCatchUpOptions): Promise<CodexCatchUpResult>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex ingestion catch-up (PRI-624 Slice C; SPEC §13/§15).
|
|
3
|
+
*
|
|
4
|
+
* Bounded non-destructive catch-up of transcript lag for rollouts the
|
|
5
|
+
* authenticated Workspace hook already delivered: every checkpoint in the
|
|
6
|
+
* workspace trajectory database names a rollout that once arrived with an
|
|
7
|
+
* authenticated transcript_path. For each (bounded) checkpoint we resolve the
|
|
8
|
+
* rollout's transcript by EXACT uuid (never a session scan), re-authorize the
|
|
9
|
+
* path through the same validator the hook uses, run the same bounded
|
|
10
|
+
* incremental delta from the checkpoint, and feed any admission candidates
|
|
11
|
+
* through the same Slice B admission/continuation pass the hook runs.
|
|
12
|
+
*
|
|
13
|
+
* Gated by `codex_conversation_ingestion`: flag-off performs ZERO transcript
|
|
14
|
+
* filesystem I/O — no checkpoint-to-path resolution, no sessions-tree lookup,
|
|
15
|
+
* no reads (SPEC §10 hard privacy invariant; the port spy test covers the
|
|
16
|
+
* lookup path too).
|
|
17
|
+
*/
|
|
18
|
+
import { computeFeatureFlagsFromConfig } from '@principles/core/runtime-v2';
|
|
19
|
+
import { loadPdConfigForPlugin, listGovernanceCheckpoints } from '@principles/host-runtime';
|
|
20
|
+
import { resolveCodexHome } from './codex-home.js';
|
|
21
|
+
import { locateCodexTranscriptByRolloutIdentity } from './transcript-locate.js';
|
|
22
|
+
import { ingestCodexTranscriptFromPath } from './ingestion.js';
|
|
23
|
+
import { runGovernanceAdmission } from './admission.js';
|
|
24
|
+
const DEFAULT_MAX_ROLLOUTS = 8;
|
|
25
|
+
export const CODEX_CATCH_UP_NEXT_ACTION = 'Set features.codex_conversation_ingestion.enabled=true in the selected Workspace .pd/config.yaml to enable bounded conversation ingestion.';
|
|
26
|
+
/**
|
|
27
|
+
* Perform one bounded catch-up pass. `remainingLagRollouts` is derived from
|
|
28
|
+
* each processed rollout's reported lag plus the checkpoints left unprocessed
|
|
29
|
+
* by the bound — callers (worker cycle / CLI) repeat passes to converge.
|
|
30
|
+
*/
|
|
31
|
+
export async function catchUpCodexIngestion(options) {
|
|
32
|
+
const { workspaceDir } = options;
|
|
33
|
+
// Flag gate FIRST — before resolveCodexHome, checkpoint listing, any FS I/O.
|
|
34
|
+
const config = loadPdConfigForPlugin(workspaceDir);
|
|
35
|
+
if (!config.ok) {
|
|
36
|
+
const [first] = config.errors;
|
|
37
|
+
return { status: 'skipped', reason: `pd_config_invalid:${first?.reason ?? 'unknown'}`, nextAction: first?.nextAction ?? 'Repair .pd/config.yaml.' };
|
|
38
|
+
}
|
|
39
|
+
const { flags } = computeFeatureFlagsFromConfig(config.effective);
|
|
40
|
+
if (flags['host.codex']?.enabled !== true) {
|
|
41
|
+
return { status: 'skipped', reason: 'host.codex_disabled', nextAction: 'Set features.host.codex.enabled=true in the selected Workspace to enable PD.' };
|
|
42
|
+
}
|
|
43
|
+
if (flags.codex_conversation_ingestion?.enabled !== true) {
|
|
44
|
+
return { status: 'skipped', reason: 'feature_disabled', nextAction: CODEX_CATCH_UP_NEXT_ACTION };
|
|
45
|
+
}
|
|
46
|
+
const home = resolveCodexHome(options.env);
|
|
47
|
+
if (!home.ok)
|
|
48
|
+
return { status: 'skipped', reason: home.reason, nextAction: home.nextAction };
|
|
49
|
+
const listed = listGovernanceCheckpoints({ workspaceDir, hostKind: 'codex' });
|
|
50
|
+
if (!listed.ok)
|
|
51
|
+
return { status: 'skipped', reason: listed.reason, nextAction: listed.nextAction };
|
|
52
|
+
const maxRollouts = Math.max(1, Math.min(options.maxRollouts ?? DEFAULT_MAX_ROLLOUTS, 32));
|
|
53
|
+
const checkpoints = listed.checkpoints.slice(0, maxRollouts);
|
|
54
|
+
const unprocessed = listed.checkpoints.slice(maxRollouts);
|
|
55
|
+
const rollouts = [];
|
|
56
|
+
const remainingLagRollouts = [];
|
|
57
|
+
for (const checkpoint of checkpoints) {
|
|
58
|
+
const located = locateCodexTranscriptByRolloutIdentity(home.home, checkpoint.rolloutIdentity);
|
|
59
|
+
if (!located.ok) {
|
|
60
|
+
remainingLagRollouts.push(checkpoint.rolloutIdentity);
|
|
61
|
+
rollouts.push({
|
|
62
|
+
rolloutIdentity: checkpoint.rolloutIdentity,
|
|
63
|
+
outcome: { status: 'degraded', reason: located.reason, nextAction: located.nextAction, warnings: [] },
|
|
64
|
+
admissionDegradations: [],
|
|
65
|
+
});
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const outcome = ingestCodexTranscriptFromPath({
|
|
69
|
+
workspaceDir,
|
|
70
|
+
transcriptPath: located.transcriptPath,
|
|
71
|
+
fallbackRootSessionId: checkpoint.rootSessionId,
|
|
72
|
+
env: options.env,
|
|
73
|
+
now: options.now,
|
|
74
|
+
port: options.port,
|
|
75
|
+
});
|
|
76
|
+
let admissionDegradations = [];
|
|
77
|
+
if (outcome.status === 'ok' && outcome.admissionCandidates.length > 0) {
|
|
78
|
+
const admission = await runGovernanceAdmission({ workspaceDir, candidates: outcome.admissionCandidates });
|
|
79
|
+
admissionDegradations = [...admission.degradations];
|
|
80
|
+
}
|
|
81
|
+
if (outcome.status !== 'ok' || outcome.lagBytes > 0) {
|
|
82
|
+
remainingLagRollouts.push(checkpoint.rolloutIdentity);
|
|
83
|
+
}
|
|
84
|
+
rollouts.push({ rolloutIdentity: checkpoint.rolloutIdentity, outcome, admissionDegradations });
|
|
85
|
+
}
|
|
86
|
+
const unexaminedRollouts = unprocessed.map((leftover) => leftover.rolloutIdentity);
|
|
87
|
+
const anyDegraded = rollouts.some((entry) => entry.outcome.status === 'degraded' || entry.admissionDegradations.length > 0);
|
|
88
|
+
return {
|
|
89
|
+
status: anyDegraded ? 'degraded' : 'ok',
|
|
90
|
+
rollouts,
|
|
91
|
+
remainingLagRollouts,
|
|
92
|
+
unexaminedRollouts,
|
|
93
|
+
};
|
|
94
|
+
}
|