Skip to content

Commit 344d113

Browse files
authored
Merge pull request #18 from vitry/fix/setup-empty-session-auth-probe
fix: support ZCode 0.16.1 empty-session auth probe
2 parents cb1eb1c + 5cd0e88 commit 344d113

10 files changed

Lines changed: 152 additions & 22 deletions

File tree

marketplace/.agents/plugins/provenance.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"packageVersion": "0.1.0",
33
"pluginVersion": "0.1.0",
4-
"sourceRef": "15785039498ab0c63d36890a7783cfe6a8c101e7",
5-
"sourceSha": "15785039498ab0c63d36890a7783cfe6a8c101e7",
4+
"sourceRef": "main",
5+
"sourceSha": "07d079a85624bc1cdfc5b37f659e4c1faa9547b2",
66
"dependencyLock": {
77
"file": "npm-shrinkwrap.json",
88
"sha256": "fa927194e6ca0b25c1d3f428859b2ab4798b8eabb4d31bc87188d73c630f9938"

marketplace/plugins/zcode/scripts/lib/codex-config.mjs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export async function runSetup(input) {
3434
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; }
3535
const hooks = await client.request('hooks/list', { cwds: [cwd] }); const inspected = await validateHooks(hooks, cwd, pluginRoot, hooksPath);
3636
if (!inspected.ok) return reportAndPersist(input, discovery, { ready: false }, 'untrusted', inspected.reason, false);
37-
const auth = await diagnoseZCodeAuth({ workspace: cwd, discovery, env: input.env }); if (!auth.ready) return reportAndPersist(input, discovery, auth, 'unauthenticated', auth.reason, false);
37+
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);
3838
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' });
3939
const packageJson = JSON.parse(await readFile(join(pluginRoot, 'package.json'), 'utf8'));
4040
const template = await readFile(join(pluginRoot, 'agents', 'zcode-rescue.toml.template'), 'utf8');
@@ -159,16 +159,23 @@ export async function diagnoseZCodeAuth(input) {
159159
let client; let sessionId;
160160
try {
161161
client = await (input.createClient ?? createZCodeClient)({ workspace: input.workspace, launch: input.discovery.launch, env: input.env, requestTimeoutMs: input.requestTimeoutMs ?? 2_000 });
162-
const snapshot = await client.createSession({ workspace: input.workspace });
162+
const create = client.createSessionForSetupAuthProbe ?? client.createSession;
163+
const snapshot = await create.call(client, { workspace: input.workspace });
163164
sessionId = snapshot.session.sessionId;
164165
return { ready: true, status: 'authenticated' };
165166
} catch (error) {
166167
if (error?.code === 'ZCODE_REQUEST_FAILED' && error.details?.method === 'session/create' && error.details.remoteCode === 'model_config_missing') {
167168
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.' };
168169
}
169-
return { ready: false, status: 'unauthenticated', reason: 'ZCode session/create could not prove model authentication.', remedy: 'Authenticate with ZCode, then run $zcode:setup again.' };
170+
if (error?.code === 'ZCODE_REQUEST_FAILED') {
171+
return { ready: false, status: 'unauthenticated', reason: 'ZCode session/create could not prove model authentication.', remedy: 'Authenticate with ZCode, then run $zcode:setup again.' };
172+
}
173+
if (error?.code === 'ZCODE_OUTPUT_INVALID' || error?.category === 'protocol') {
174+
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.' };
175+
}
176+
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.' };
170177
} finally {
171-
if (sessionId) await client?.stopSession(sessionId).catch(() => {});
178+
if (sessionId && typeof client?.stopSession === 'function') await client.stopSession(sessionId).catch(() => {});
172179
await client?.close().catch(() => {});
173180
}
174181
}

marketplace/plugins/zcode/scripts/lib/zcode-client.mjs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { readdir, realpath } from 'node:fs/promises';
55
import { PluginError } from './errors.mjs';
66
import { isBoundedPublicIdentifier, isSafeIdentifier } from './identifier.mjs';
77
import { closeProtocolUntil, connectZCodeBroker, MAX_DRAIN_TIMEOUT_MS, spawnZCodeProtocol } from './zcode-protocol.mjs';
8-
import { validSessionInfo, validSnapshot as snapshotValid } from './zcode-schema.mjs';
8+
import { validSessionInfo, validSetupAuthProbeSnapshot, validSnapshot as snapshotValid } from './zcode-schema.mjs';
99
import { brokerEndpointFor, brokerIdentityNameForWireOptions, ensureZCodeBroker, inspectBrokerIdentity, MAX_BROKER_IDLE_TIMEOUT_MS, MIN_BROKER_IDLE_TIMEOUT_MS, prioritizeBrokerOwnership } from '../zcode-broker.mjs';
1010
import { resolveWorkspaceStorage } from './workspace.mjs';
1111

@@ -22,6 +22,16 @@ export class ZCodeClient {
2222

2323
/** @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 */
2424
async createSession(input) {
25+
return this.createSessionValidated(input, snapshotValid);
26+
}
27+
28+
/** @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(). */
29+
async createSessionForSetupAuthProbe(input) {
30+
return this.createSessionValidated(input, validSetupAuthProbeSnapshot);
31+
}
32+
33+
/** @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 */
34+
async createSessionValidated(input, validator) {
2535
requireExactObject(input, ['workspace'], ['sessionId', 'model', 'importedHistory']);
2636
requireString(input.workspace);
2737
if (input.sessionId !== undefined) requireSessionId(input.sessionId);
@@ -39,7 +49,7 @@ export class ZCodeClient {
3949
if (input.importedHistory !== undefined) params.importedHistory = normalizeImportedHistory(input.importedHistory);
4050
const result = await this.protocol.request('session/create', params);
4151
if (!plainObject(result) || !plainObject(result.session) || !isSafeIdentifier(result.session.sessionId) || input.sessionId && result.session.sessionId !== input.sessionId) throw outputError('session/create');
42-
validateSnapshot(result, result.session.sessionId, workspacePath, 'session/create');
52+
if (!validator(result, result.session.sessionId, workspacePath)) throw outputError('session/create');
4353
this.sessionWorkspaces.set(result.session.sessionId, workspacePath);
4454
if (plainObject(result.settings?.model) && Array.isArray(result.settings.model.available)) this.sessionCatalogs.set(result.session.sessionId, result.settings.model);
4555
return result;

marketplace/plugins/zcode/scripts/lib/zcode-schema.mjs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,31 @@ function validSnapshotRelations(value, sessionId, workspacePath) {
149149
&& value.messages.every((/** @type {any} */ message) => message.info.sessionId === sessionId && message.parts.every((/** @type {any} */ part) => part.sessionId === sessionId && part.messageId === message.info.messageId));
150150
}
151151

152-
/** Kne */
153-
export function validSnapshot(/** @type {any} */ value, /** @type {string} */ sessionId, /** @type {string} */ workspacePath) {
152+
/** Validate a complete runtime snapshot with strict session identity relations. */
153+
function validSnapshotEnvelope(/** @type {any} */ value) {
154154
return exact(value, ['protocol', 'session', 'settings', 'projection', 'runtime', 'messages'], ['goalStats', 'todos', 'todoGroups', 'slashCommands'])
155155
&& exact(value.protocol, ['name', 'version']) && value.protocol.name === 'ZCode Protocol' && value.protocol.version === 1
156156
&& validSessionInfo(value.session) && validSettings(value.settings) && validProjection(value.projection) && validRuntime(value.runtime) && arrayOf(value.messages, validMessage)
157-
&& validSnapshotRelations(value, sessionId, workspacePath)
158157
&& 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));
159158
}
159+
160+
export function validSnapshot(/** @type {any} */ value, /** @type {string} */ sessionId, /** @type {string} */ workspacePath) {
161+
return validSnapshotEnvelope(value) && validSnapshotRelations(value, sessionId, workspacePath);
162+
}
163+
164+
/**
165+
* Setup-only compatibility validation for ZCode 0.16.1's empty projection.
166+
* The normal validSnapshot relation remains strict for all runtime paths.
167+
* @param {any} value @param {string} sessionId @param {string} workspacePath
168+
*/
169+
export function validSetupAuthProbeSnapshot(value, sessionId, workspacePath) {
170+
return validSnapshotEnvelope(value)
171+
&& text(sessionId) && value.session.sessionId === sessionId
172+
&& value.session.workspace.workspacePath === workspacePath && value.session.workspace.workspaceKey === workspacePath
173+
&& value.session.status === 'idle'
174+
&& value.projection.sessionId === 'unknown' && value.projection.status === 'idle'
175+
&& (value.session.target === undefined || value.session.target === null)
176+
&& (value.projection.target === undefined || value.projection.target === null)
177+
&& value.projection.pendingPermissions.length === 0 && value.projection.activeToolCalls.length === 0 && value.projection.backgroundJobs.length === 0
178+
&& value.runtime.eventSeq === 0 && value.runtime.pendingRequestIds.length === 0 && value.messages.length === 0;
179+
}

scripts/lib/codex-config.mjs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export async function runSetup(input) {
3434
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; }
3535
const hooks = await client.request('hooks/list', { cwds: [cwd] }); const inspected = await validateHooks(hooks, cwd, pluginRoot, hooksPath);
3636
if (!inspected.ok) return reportAndPersist(input, discovery, { ready: false }, 'untrusted', inspected.reason, false);
37-
const auth = await diagnoseZCodeAuth({ workspace: cwd, discovery, env: input.env }); if (!auth.ready) return reportAndPersist(input, discovery, auth, 'unauthenticated', auth.reason, false);
37+
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);
3838
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' });
3939
const packageJson = JSON.parse(await readFile(join(pluginRoot, 'package.json'), 'utf8'));
4040
const template = await readFile(join(pluginRoot, 'agents', 'zcode-rescue.toml.template'), 'utf8');
@@ -159,16 +159,23 @@ export async function diagnoseZCodeAuth(input) {
159159
let client; let sessionId;
160160
try {
161161
client = await (input.createClient ?? createZCodeClient)({ workspace: input.workspace, launch: input.discovery.launch, env: input.env, requestTimeoutMs: input.requestTimeoutMs ?? 2_000 });
162-
const snapshot = await client.createSession({ workspace: input.workspace });
162+
const create = client.createSessionForSetupAuthProbe ?? client.createSession;
163+
const snapshot = await create.call(client, { workspace: input.workspace });
163164
sessionId = snapshot.session.sessionId;
164165
return { ready: true, status: 'authenticated' };
165166
} catch (error) {
166167
if (error?.code === 'ZCODE_REQUEST_FAILED' && error.details?.method === 'session/create' && error.details.remoteCode === 'model_config_missing') {
167168
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.' };
168169
}
169-
return { ready: false, status: 'unauthenticated', reason: 'ZCode session/create could not prove model authentication.', remedy: 'Authenticate with ZCode, then run $zcode:setup again.' };
170+
if (error?.code === 'ZCODE_REQUEST_FAILED') {
171+
return { ready: false, status: 'unauthenticated', reason: 'ZCode session/create could not prove model authentication.', remedy: 'Authenticate with ZCode, then run $zcode:setup again.' };
172+
}
173+
if (error?.code === 'ZCODE_OUTPUT_INVALID' || error?.category === 'protocol') {
174+
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.' };
175+
}
176+
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.' };
170177
} finally {
171-
if (sessionId) await client?.stopSession(sessionId).catch(() => {});
178+
if (sessionId && typeof client?.stopSession === 'function') await client.stopSession(sessionId).catch(() => {});
172179
await client?.close().catch(() => {});
173180
}
174181
}

scripts/lib/zcode-client.mjs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { readdir, realpath } from 'node:fs/promises';
55
import { PluginError } from './errors.mjs';
66
import { isBoundedPublicIdentifier, isSafeIdentifier } from './identifier.mjs';
77
import { closeProtocolUntil, connectZCodeBroker, MAX_DRAIN_TIMEOUT_MS, spawnZCodeProtocol } from './zcode-protocol.mjs';
8-
import { validSessionInfo, validSnapshot as snapshotValid } from './zcode-schema.mjs';
8+
import { validSessionInfo, validSetupAuthProbeSnapshot, validSnapshot as snapshotValid } from './zcode-schema.mjs';
99
import { brokerEndpointFor, brokerIdentityNameForWireOptions, ensureZCodeBroker, inspectBrokerIdentity, MAX_BROKER_IDLE_TIMEOUT_MS, MIN_BROKER_IDLE_TIMEOUT_MS, prioritizeBrokerOwnership } from '../zcode-broker.mjs';
1010
import { resolveWorkspaceStorage } from './workspace.mjs';
1111

@@ -22,6 +22,16 @@ export class ZCodeClient {
2222

2323
/** @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 */
2424
async createSession(input) {
25+
return this.createSessionValidated(input, snapshotValid);
26+
}
27+
28+
/** @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(). */
29+
async createSessionForSetupAuthProbe(input) {
30+
return this.createSessionValidated(input, validSetupAuthProbeSnapshot);
31+
}
32+
33+
/** @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 */
34+
async createSessionValidated(input, validator) {
2535
requireExactObject(input, ['workspace'], ['sessionId', 'model', 'importedHistory']);
2636
requireString(input.workspace);
2737
if (input.sessionId !== undefined) requireSessionId(input.sessionId);
@@ -39,7 +49,7 @@ export class ZCodeClient {
3949
if (input.importedHistory !== undefined) params.importedHistory = normalizeImportedHistory(input.importedHistory);
4050
const result = await this.protocol.request('session/create', params);
4151
if (!plainObject(result) || !plainObject(result.session) || !isSafeIdentifier(result.session.sessionId) || input.sessionId && result.session.sessionId !== input.sessionId) throw outputError('session/create');
42-
validateSnapshot(result, result.session.sessionId, workspacePath, 'session/create');
52+
if (!validator(result, result.session.sessionId, workspacePath)) throw outputError('session/create');
4353
this.sessionWorkspaces.set(result.session.sessionId, workspacePath);
4454
if (plainObject(result.settings?.model) && Array.isArray(result.settings.model.available)) this.sessionCatalogs.set(result.session.sessionId, result.settings.model);
4555
return result;

scripts/lib/zcode-schema.mjs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,31 @@ function validSnapshotRelations(value, sessionId, workspacePath) {
149149
&& value.messages.every((/** @type {any} */ message) => message.info.sessionId === sessionId && message.parts.every((/** @type {any} */ part) => part.sessionId === sessionId && part.messageId === message.info.messageId));
150150
}
151151

152-
/** Kne */
153-
export function validSnapshot(/** @type {any} */ value, /** @type {string} */ sessionId, /** @type {string} */ workspacePath) {
152+
/** Validate a complete runtime snapshot with strict session identity relations. */
153+
function validSnapshotEnvelope(/** @type {any} */ value) {
154154
return exact(value, ['protocol', 'session', 'settings', 'projection', 'runtime', 'messages'], ['goalStats', 'todos', 'todoGroups', 'slashCommands'])
155155
&& exact(value.protocol, ['name', 'version']) && value.protocol.name === 'ZCode Protocol' && value.protocol.version === 1
156156
&& validSessionInfo(value.session) && validSettings(value.settings) && validProjection(value.projection) && validRuntime(value.runtime) && arrayOf(value.messages, validMessage)
157-
&& validSnapshotRelations(value, sessionId, workspacePath)
158157
&& 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));
159158
}
159+
160+
export function validSnapshot(/** @type {any} */ value, /** @type {string} */ sessionId, /** @type {string} */ workspacePath) {
161+
return validSnapshotEnvelope(value) && validSnapshotRelations(value, sessionId, workspacePath);
162+
}
163+
164+
/**
165+
* Setup-only compatibility validation for ZCode 0.16.1's empty projection.
166+
* The normal validSnapshot relation remains strict for all runtime paths.
167+
* @param {any} value @param {string} sessionId @param {string} workspacePath
168+
*/
169+
export function validSetupAuthProbeSnapshot(value, sessionId, workspacePath) {
170+
return validSnapshotEnvelope(value)
171+
&& text(sessionId) && value.session.sessionId === sessionId
172+
&& value.session.workspace.workspacePath === workspacePath && value.session.workspace.workspaceKey === workspacePath
173+
&& value.session.status === 'idle'
174+
&& value.projection.sessionId === 'unknown' && value.projection.status === 'idle'
175+
&& (value.session.target === undefined || value.session.target === null)
176+
&& (value.projection.target === undefined || value.projection.target === null)
177+
&& value.projection.pendingPermissions.length === 0 && value.projection.activeToolCalls.length === 0 && value.projection.backgroundJobs.length === 0
178+
&& value.runtime.eventSeq === 0 && value.runtime.pendingRequestIds.length === 0 && value.messages.length === 0;
179+
}

0 commit comments

Comments
 (0)