Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions marketplace/.agents/plugins/provenance.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"packageVersion": "0.1.0",
"pluginVersion": "0.1.0",
"sourceRef": "15785039498ab0c63d36890a7783cfe6a8c101e7",
"sourceSha": "15785039498ab0c63d36890a7783cfe6a8c101e7",
"sourceRef": "main",
"sourceSha": "07d079a85624bc1cdfc5b37f659e4c1faa9547b2",
"dependencyLock": {
"file": "npm-shrinkwrap.json",
"sha256": "fa927194e6ca0b25c1d3f428859b2ab4798b8eabb4d31bc87188d73c630f9938"
Expand Down
15 changes: 11 additions & 4 deletions marketplace/plugins/zcode/scripts/lib/codex-config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export async function runSetup(input) {
catch (error) { if (error?.code === 'ZCODE_NOT_FOUND' || error?.code === 'ZCODE_VERSION_UNSUPPORTED') return reportAndPersist(input, { path: null, version: null }, { ready: false }, error.code === 'ZCODE_NOT_FOUND' ? 'missing' : 'outdated', error.message, false); throw error; }
const hooks = await client.request('hooks/list', { cwds: [cwd] }); const inspected = await validateHooks(hooks, cwd, pluginRoot, hooksPath);
if (!inspected.ok) return reportAndPersist(input, discovery, { ready: false }, 'untrusted', inspected.reason, false);
const auth = await diagnoseZCodeAuth({ workspace: cwd, discovery, env: input.env }); if (!auth.ready) return reportAndPersist(input, discovery, auth, 'unauthenticated', auth.reason, false);
const auth = await diagnoseZCodeAuth({ workspace: cwd, discovery, env: input.env }); if (!auth.ready) return reportAndPersist(input, discovery, auth, auth.status === 'incompatible' ? 'incompatible' : 'unauthenticated', auth.reason, false);
const edits = []; if (config?.config?.features?.hooks !== true) edits.push({ keyPath: 'features.hooks', value: true, mergeStrategy: 'upsert' }); const trust = {}; for (const hook of inspected.hooks) if (!['trusted', 'managed'].includes(hook.trustStatus)) trust[hook.key] = { trusted_hash: hook.currentHash }; if (Object.keys(trust).length) edits.push({ keyPath: 'hooks.state', value: trust, mergeStrategy: 'upsert' });
const packageJson = JSON.parse(await readFile(join(pluginRoot, 'package.json'), 'utf8'));
const template = await readFile(join(pluginRoot, 'agents', 'zcode-rescue.toml.template'), 'utf8');
Expand Down Expand Up @@ -159,16 +159,23 @@ export async function diagnoseZCodeAuth(input) {
let client; let sessionId;
try {
client = await (input.createClient ?? createZCodeClient)({ workspace: input.workspace, launch: input.discovery.launch, env: input.env, requestTimeoutMs: input.requestTimeoutMs ?? 2_000 });
const snapshot = await client.createSession({ workspace: input.workspace });
const create = client.createSessionForSetupAuthProbe ?? client.createSession;
const snapshot = await create.call(client, { workspace: input.workspace });
sessionId = snapshot.session.sessionId;
return { ready: true, status: 'authenticated' };
} catch (error) {
if (error?.code === 'ZCODE_REQUEST_FAILED' && error.details?.method === 'session/create' && error.details.remoteCode === 'model_config_missing') {
return { ready: false, status: 'unauthenticated', reason: 'ZCode CLI model provider is not configured.', remedy: 'Configure an API-key provider in ZCode CLI, then run $zcode:setup again.' };
}
return { ready: false, status: 'unauthenticated', reason: 'ZCode session/create could not prove model authentication.', remedy: 'Authenticate with ZCode, then run $zcode:setup again.' };
if (error?.code === 'ZCODE_REQUEST_FAILED') {
return { ready: false, status: 'unauthenticated', reason: 'ZCode session/create could not prove model authentication.', remedy: 'Authenticate with ZCode, then run $zcode:setup again.' };
}
if (error?.code === 'ZCODE_OUTPUT_INVALID' || error?.category === 'protocol') {
return { ready: false, status: 'incompatible', reason: 'ZCode session/create returned a snapshot incompatible with the setup probe contract.', remedy: 'Upgrade ZCode to a compatible protocol version, then run $zcode:setup again.' };
}
return { ready: false, status: 'incompatible', reason: 'ZCode session/create could not be verified by the setup authentication probe.', remedy: 'Check the ZCode CLI protocol and rerun $zcode:setup.' };
} finally {
if (sessionId) await client?.stopSession(sessionId).catch(() => {});
if (sessionId && typeof client?.stopSession === 'function') await client.stopSession(sessionId).catch(() => {});
await client?.close().catch(() => {});
}
}
Expand Down
14 changes: 12 additions & 2 deletions marketplace/plugins/zcode/scripts/lib/zcode-client.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { readdir, realpath } from 'node:fs/promises';
import { PluginError } from './errors.mjs';
import { isBoundedPublicIdentifier, isSafeIdentifier } from './identifier.mjs';
import { closeProtocolUntil, connectZCodeBroker, MAX_DRAIN_TIMEOUT_MS, spawnZCodeProtocol } from './zcode-protocol.mjs';
import { validSessionInfo, validSnapshot as snapshotValid } from './zcode-schema.mjs';
import { validSessionInfo, validSetupAuthProbeSnapshot, validSnapshot as snapshotValid } from './zcode-schema.mjs';
import { brokerEndpointFor, brokerIdentityNameForWireOptions, ensureZCodeBroker, inspectBrokerIdentity, MAX_BROKER_IDLE_TIMEOUT_MS, MIN_BROKER_IDLE_TIMEOUT_MS, prioritizeBrokerOwnership } from '../zcode-broker.mjs';
import { resolveWorkspaceStorage } from './workspace.mjs';

Expand All @@ -22,6 +22,16 @@ export class ZCodeClient {

/** @param {{workspace:string,sessionId?:string,model?:{providerId:string,modelId:string,variant?:string},importedHistory?:{title?:string,createdAt?:number,updatedAt?:number,messages:Array<{role:'user'|'assistant',content:string,timestamp?:number}>}}} input */
async createSession(input) {
return this.createSessionValidated(input, snapshotValid);
}

/** @param {{workspace:string,sessionId?:string,model?:{providerId:string,modelId:string,variant?:string},importedHistory?:{title?:string,createdAt?:number,updatedAt?:number,messages:Array<{role:'user'|'assistant',content:string,timestamp?:number}>}}} input Setup-only compatibility probe; formal runtime callers must use createSession(). */
async createSessionForSetupAuthProbe(input) {
return this.createSessionValidated(input, validSetupAuthProbeSnapshot);
}

/** @param {{workspace:string,sessionId?:string,model?:{providerId:string,modelId:string,variant?:string},importedHistory?:{title?:string,createdAt?:number,updatedAt?:number,messages:Array<{role:'user'|'assistant',content:string,timestamp?:number}>}}} input @param {(value:any,sessionId:string,workspace:string)=>boolean} validator */
async createSessionValidated(input, validator) {
requireExactObject(input, ['workspace'], ['sessionId', 'model', 'importedHistory']);
requireString(input.workspace);
if (input.sessionId !== undefined) requireSessionId(input.sessionId);
Expand All @@ -39,7 +49,7 @@ export class ZCodeClient {
if (input.importedHistory !== undefined) params.importedHistory = normalizeImportedHistory(input.importedHistory);
const result = await this.protocol.request('session/create', params);
if (!plainObject(result) || !plainObject(result.session) || !isSafeIdentifier(result.session.sessionId) || input.sessionId && result.session.sessionId !== input.sessionId) throw outputError('session/create');
validateSnapshot(result, result.session.sessionId, workspacePath, 'session/create');
if (!validator(result, result.session.sessionId, workspacePath)) throw outputError('session/create');
this.sessionWorkspaces.set(result.session.sessionId, workspacePath);
if (plainObject(result.settings?.model) && Array.isArray(result.settings.model.available)) this.sessionCatalogs.set(result.session.sessionId, result.settings.model);
return result;
Expand Down
26 changes: 23 additions & 3 deletions marketplace/plugins/zcode/scripts/lib/zcode-schema.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,31 @@ function validSnapshotRelations(value, sessionId, workspacePath) {
&& value.messages.every((/** @type {any} */ message) => message.info.sessionId === sessionId && message.parts.every((/** @type {any} */ part) => part.sessionId === sessionId && part.messageId === message.info.messageId));
}

/** Kne */
export function validSnapshot(/** @type {any} */ value, /** @type {string} */ sessionId, /** @type {string} */ workspacePath) {
/** Validate a complete runtime snapshot with strict session identity relations. */
function validSnapshotEnvelope(/** @type {any} */ value) {
return exact(value, ['protocol', 'session', 'settings', 'projection', 'runtime', 'messages'], ['goalStats', 'todos', 'todoGroups', 'slashCommands'])
&& exact(value.protocol, ['name', 'version']) && value.protocol.name === 'ZCode Protocol' && value.protocol.version === 1
&& validSessionInfo(value.session) && validSettings(value.settings) && validProjection(value.projection) && validRuntime(value.runtime) && arrayOf(value.messages, validMessage)
&& validSnapshotRelations(value, sessionId, workspacePath)
&& optional(value.goalStats, validGoalStats) && optional(value.todos, (items) => arrayOf(items, validTodo)) && optional(value.todoGroups, (items) => arrayOf(items, validTodoGroup)) && optional(value.slashCommands, (items) => arrayOf(items, validSlashCommand));
}

export function validSnapshot(/** @type {any} */ value, /** @type {string} */ sessionId, /** @type {string} */ workspacePath) {
return validSnapshotEnvelope(value) && validSnapshotRelations(value, sessionId, workspacePath);
}

/**
* Setup-only compatibility validation for ZCode 0.16.1's empty projection.
* The normal validSnapshot relation remains strict for all runtime paths.
* @param {any} value @param {string} sessionId @param {string} workspacePath
*/
export function validSetupAuthProbeSnapshot(value, sessionId, workspacePath) {
return validSnapshotEnvelope(value)
&& text(sessionId) && value.session.sessionId === sessionId
&& value.session.workspace.workspacePath === workspacePath && value.session.workspace.workspaceKey === workspacePath
&& value.session.status === 'idle'
&& value.projection.sessionId === 'unknown' && value.projection.status === 'idle'
&& (value.session.target === undefined || value.session.target === null)
&& (value.projection.target === undefined || value.projection.target === null)
&& value.projection.pendingPermissions.length === 0 && value.projection.activeToolCalls.length === 0 && value.projection.backgroundJobs.length === 0
&& value.runtime.eventSeq === 0 && value.runtime.pendingRequestIds.length === 0 && value.messages.length === 0;
}
15 changes: 11 additions & 4 deletions scripts/lib/codex-config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export async function runSetup(input) {
catch (error) { if (error?.code === 'ZCODE_NOT_FOUND' || error?.code === 'ZCODE_VERSION_UNSUPPORTED') return reportAndPersist(input, { path: null, version: null }, { ready: false }, error.code === 'ZCODE_NOT_FOUND' ? 'missing' : 'outdated', error.message, false); throw error; }
const hooks = await client.request('hooks/list', { cwds: [cwd] }); const inspected = await validateHooks(hooks, cwd, pluginRoot, hooksPath);
if (!inspected.ok) return reportAndPersist(input, discovery, { ready: false }, 'untrusted', inspected.reason, false);
const auth = await diagnoseZCodeAuth({ workspace: cwd, discovery, env: input.env }); if (!auth.ready) return reportAndPersist(input, discovery, auth, 'unauthenticated', auth.reason, false);
const auth = await diagnoseZCodeAuth({ workspace: cwd, discovery, env: input.env }); if (!auth.ready) return reportAndPersist(input, discovery, auth, auth.status === 'incompatible' ? 'incompatible' : 'unauthenticated', auth.reason, false);
const edits = []; if (config?.config?.features?.hooks !== true) edits.push({ keyPath: 'features.hooks', value: true, mergeStrategy: 'upsert' }); const trust = {}; for (const hook of inspected.hooks) if (!['trusted', 'managed'].includes(hook.trustStatus)) trust[hook.key] = { trusted_hash: hook.currentHash }; if (Object.keys(trust).length) edits.push({ keyPath: 'hooks.state', value: trust, mergeStrategy: 'upsert' });
const packageJson = JSON.parse(await readFile(join(pluginRoot, 'package.json'), 'utf8'));
const template = await readFile(join(pluginRoot, 'agents', 'zcode-rescue.toml.template'), 'utf8');
Expand Down Expand Up @@ -159,16 +159,23 @@ export async function diagnoseZCodeAuth(input) {
let client; let sessionId;
try {
client = await (input.createClient ?? createZCodeClient)({ workspace: input.workspace, launch: input.discovery.launch, env: input.env, requestTimeoutMs: input.requestTimeoutMs ?? 2_000 });
const snapshot = await client.createSession({ workspace: input.workspace });
const create = client.createSessionForSetupAuthProbe ?? client.createSession;
const snapshot = await create.call(client, { workspace: input.workspace });
sessionId = snapshot.session.sessionId;
return { ready: true, status: 'authenticated' };
} catch (error) {
if (error?.code === 'ZCODE_REQUEST_FAILED' && error.details?.method === 'session/create' && error.details.remoteCode === 'model_config_missing') {
return { ready: false, status: 'unauthenticated', reason: 'ZCode CLI model provider is not configured.', remedy: 'Configure an API-key provider in ZCode CLI, then run $zcode:setup again.' };
}
return { ready: false, status: 'unauthenticated', reason: 'ZCode session/create could not prove model authentication.', remedy: 'Authenticate with ZCode, then run $zcode:setup again.' };
if (error?.code === 'ZCODE_REQUEST_FAILED') {
return { ready: false, status: 'unauthenticated', reason: 'ZCode session/create could not prove model authentication.', remedy: 'Authenticate with ZCode, then run $zcode:setup again.' };
}
if (error?.code === 'ZCODE_OUTPUT_INVALID' || error?.category === 'protocol') {
return { ready: false, status: 'incompatible', reason: 'ZCode session/create returned a snapshot incompatible with the setup probe contract.', remedy: 'Upgrade ZCode to a compatible protocol version, then run $zcode:setup again.' };
}
return { ready: false, status: 'incompatible', reason: 'ZCode session/create could not be verified by the setup authentication probe.', remedy: 'Check the ZCode CLI protocol and rerun $zcode:setup.' };
} finally {
if (sessionId) await client?.stopSession(sessionId).catch(() => {});
if (sessionId && typeof client?.stopSession === 'function') await client.stopSession(sessionId).catch(() => {});
await client?.close().catch(() => {});
}
}
Expand Down
14 changes: 12 additions & 2 deletions scripts/lib/zcode-client.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { readdir, realpath } from 'node:fs/promises';
import { PluginError } from './errors.mjs';
import { isBoundedPublicIdentifier, isSafeIdentifier } from './identifier.mjs';
import { closeProtocolUntil, connectZCodeBroker, MAX_DRAIN_TIMEOUT_MS, spawnZCodeProtocol } from './zcode-protocol.mjs';
import { validSessionInfo, validSnapshot as snapshotValid } from './zcode-schema.mjs';
import { validSessionInfo, validSetupAuthProbeSnapshot, validSnapshot as snapshotValid } from './zcode-schema.mjs';
import { brokerEndpointFor, brokerIdentityNameForWireOptions, ensureZCodeBroker, inspectBrokerIdentity, MAX_BROKER_IDLE_TIMEOUT_MS, MIN_BROKER_IDLE_TIMEOUT_MS, prioritizeBrokerOwnership } from '../zcode-broker.mjs';
import { resolveWorkspaceStorage } from './workspace.mjs';

Expand All @@ -22,6 +22,16 @@ export class ZCodeClient {

/** @param {{workspace:string,sessionId?:string,model?:{providerId:string,modelId:string,variant?:string},importedHistory?:{title?:string,createdAt?:number,updatedAt?:number,messages:Array<{role:'user'|'assistant',content:string,timestamp?:number}>}}} input */
async createSession(input) {
return this.createSessionValidated(input, snapshotValid);
}

/** @param {{workspace:string,sessionId?:string,model?:{providerId:string,modelId:string,variant?:string},importedHistory?:{title?:string,createdAt?:number,updatedAt?:number,messages:Array<{role:'user'|'assistant',content:string,timestamp?:number}>}}} input Setup-only compatibility probe; formal runtime callers must use createSession(). */
async createSessionForSetupAuthProbe(input) {
return this.createSessionValidated(input, validSetupAuthProbeSnapshot);
}

/** @param {{workspace:string,sessionId?:string,model?:{providerId:string,modelId:string,variant?:string},importedHistory?:{title?:string,createdAt?:number,updatedAt?:number,messages:Array<{role:'user'|'assistant',content:string,timestamp?:number}>}}} input @param {(value:any,sessionId:string,workspace:string)=>boolean} validator */
async createSessionValidated(input, validator) {
requireExactObject(input, ['workspace'], ['sessionId', 'model', 'importedHistory']);
requireString(input.workspace);
if (input.sessionId !== undefined) requireSessionId(input.sessionId);
Expand All @@ -39,7 +49,7 @@ export class ZCodeClient {
if (input.importedHistory !== undefined) params.importedHistory = normalizeImportedHistory(input.importedHistory);
const result = await this.protocol.request('session/create', params);
if (!plainObject(result) || !plainObject(result.session) || !isSafeIdentifier(result.session.sessionId) || input.sessionId && result.session.sessionId !== input.sessionId) throw outputError('session/create');
validateSnapshot(result, result.session.sessionId, workspacePath, 'session/create');
if (!validator(result, result.session.sessionId, workspacePath)) throw outputError('session/create');
this.sessionWorkspaces.set(result.session.sessionId, workspacePath);
if (plainObject(result.settings?.model) && Array.isArray(result.settings.model.available)) this.sessionCatalogs.set(result.session.sessionId, result.settings.model);
return result;
Expand Down
26 changes: 23 additions & 3 deletions scripts/lib/zcode-schema.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,31 @@ function validSnapshotRelations(value, sessionId, workspacePath) {
&& value.messages.every((/** @type {any} */ message) => message.info.sessionId === sessionId && message.parts.every((/** @type {any} */ part) => part.sessionId === sessionId && part.messageId === message.info.messageId));
}

/** Kne */
export function validSnapshot(/** @type {any} */ value, /** @type {string} */ sessionId, /** @type {string} */ workspacePath) {
/** Validate a complete runtime snapshot with strict session identity relations. */
function validSnapshotEnvelope(/** @type {any} */ value) {
return exact(value, ['protocol', 'session', 'settings', 'projection', 'runtime', 'messages'], ['goalStats', 'todos', 'todoGroups', 'slashCommands'])
&& exact(value.protocol, ['name', 'version']) && value.protocol.name === 'ZCode Protocol' && value.protocol.version === 1
&& validSessionInfo(value.session) && validSettings(value.settings) && validProjection(value.projection) && validRuntime(value.runtime) && arrayOf(value.messages, validMessage)
&& validSnapshotRelations(value, sessionId, workspacePath)
&& optional(value.goalStats, validGoalStats) && optional(value.todos, (items) => arrayOf(items, validTodo)) && optional(value.todoGroups, (items) => arrayOf(items, validTodoGroup)) && optional(value.slashCommands, (items) => arrayOf(items, validSlashCommand));
}

export function validSnapshot(/** @type {any} */ value, /** @type {string} */ sessionId, /** @type {string} */ workspacePath) {
return validSnapshotEnvelope(value) && validSnapshotRelations(value, sessionId, workspacePath);
}

/**
* Setup-only compatibility validation for ZCode 0.16.1's empty projection.
* The normal validSnapshot relation remains strict for all runtime paths.
* @param {any} value @param {string} sessionId @param {string} workspacePath
*/
export function validSetupAuthProbeSnapshot(value, sessionId, workspacePath) {
return validSnapshotEnvelope(value)
&& text(sessionId) && value.session.sessionId === sessionId
&& value.session.workspace.workspacePath === workspacePath && value.session.workspace.workspaceKey === workspacePath
&& value.session.status === 'idle'
&& value.projection.sessionId === 'unknown' && value.projection.status === 'idle'
&& (value.session.target === undefined || value.session.target === null)
&& (value.projection.target === undefined || value.projection.target === null)
&& value.projection.pendingPermissions.length === 0 && value.projection.activeToolCalls.length === 0 && value.projection.backgroundJobs.length === 0
&& value.runtime.eventSeq === 0 && value.runtime.pendingRequestIds.length === 0 && value.messages.length === 0;
}
Loading
Loading