@cleocode/contracts 2026.6.13 → 2026.6.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/operation-def.test.js +4 -1
- package/dist/__tests__/operation-def.test.js.map +1 -1
- package/dist/__tests__/provider-def.test.d.ts +12 -0
- package/dist/__tests__/provider-def.test.d.ts.map +1 -0
- package/dist/__tests__/provider-def.test.js +84 -0
- package/dist/__tests__/provider-def.test.js.map +1 -0
- package/dist/__tests__/service-provider.test.d.ts +17 -0
- package/dist/__tests__/service-provider.test.d.ts.map +1 -0
- package/dist/__tests__/service-provider.test.js +108 -0
- package/dist/__tests__/service-provider.test.js.map +1 -0
- package/dist/boundary.d.ts +52 -0
- package/dist/boundary.d.ts.map +1 -1
- package/dist/boundary.js +23 -0
- package/dist/boundary.js.map +1 -1
- package/dist/branch-lock.d.ts +4 -0
- package/dist/branch-lock.d.ts.map +1 -1
- package/dist/branch-lock.js.map +1 -1
- package/dist/conduit.d.ts +171 -0
- package/dist/conduit.d.ts.map +1 -1
- package/dist/config/index.d.ts +23 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +22 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config.d.ts +109 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/dispatch/__tests__/tasks-gateway-crud-ops.test.d.ts +21 -0
- package/dist/dispatch/__tests__/tasks-gateway-crud-ops.test.d.ts.map +1 -0
- package/dist/dispatch/__tests__/tasks-gateway-crud-ops.test.js +84 -0
- package/dist/dispatch/__tests__/tasks-gateway-crud-ops.test.js.map +1 -0
- package/dist/dispatch/identity.d.ts +1 -1
- package/dist/dispatch/identity.d.ts.map +1 -1
- package/dist/dispatch/identity.js +20 -0
- package/dist/dispatch/identity.js.map +1 -1
- package/dist/dispatch/operation-def.d.ts +20 -0
- package/dist/dispatch/operation-def.d.ts.map +1 -1
- package/dist/dispatch/operations-registry.d.ts.map +1 -1
- package/dist/dispatch/operations-registry.js +1030 -46
- package/dist/dispatch/operations-registry.js.map +1 -1
- package/dist/index.d.ts +17 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -2
- package/dist/index.js.map +1 -1
- package/dist/lease-ipc/__tests__/freeze.test.js +92 -0
- package/dist/lease-ipc/__tests__/freeze.test.js.map +1 -1
- package/dist/lease-ipc/index.d.ts +2 -2
- package/dist/lease-ipc/index.d.ts.map +1 -1
- package/dist/lease-ipc/index.js +1 -1
- package/dist/lease-ipc/index.js.map +1 -1
- package/dist/lease-ipc/messages.d.ts +167 -3
- package/dist/lease-ipc/messages.d.ts.map +1 -1
- package/dist/lease-ipc/messages.js +93 -0
- package/dist/lease-ipc/messages.js.map +1 -1
- package/dist/llm/catalog-schema.d.ts +242 -0
- package/dist/llm/catalog-schema.d.ts.map +1 -0
- package/dist/llm/catalog-schema.js +153 -0
- package/dist/llm/catalog-schema.js.map +1 -0
- package/dist/llm/catalog-schema.json +139 -0
- package/dist/llm/index.d.ts +15 -0
- package/dist/llm/index.d.ts.map +1 -0
- package/dist/llm/index.js +14 -0
- package/dist/llm/index.js.map +1 -0
- package/dist/llm/oauth.d.ts +16 -2
- package/dist/llm/oauth.d.ts.map +1 -1
- package/dist/llm/onboarding.d.ts +126 -0
- package/dist/llm/onboarding.d.ts.map +1 -0
- package/dist/llm/onboarding.js +34 -0
- package/dist/llm/onboarding.js.map +1 -0
- package/dist/llm/provider-def.d.ts +254 -0
- package/dist/llm/provider-def.d.ts.map +1 -0
- package/dist/llm/provider-def.js +93 -0
- package/dist/llm/provider-def.js.map +1 -0
- package/dist/llm/system-resolver.d.ts +42 -0
- package/dist/llm/system-resolver.d.ts.map +1 -1
- package/dist/operations/admin.d.ts +124 -1
- package/dist/operations/admin.d.ts.map +1 -1
- package/dist/operations/entities.d.ts +110 -0
- package/dist/operations/entities.d.ts.map +1 -0
- package/dist/operations/entities.js +695 -0
- package/dist/operations/entities.js.map +1 -0
- package/dist/operations/llm.d.ts +16 -1
- package/dist/operations/llm.d.ts.map +1 -1
- package/dist/operations/output-contracts-data.d.ts.map +1 -1
- package/dist/operations/output-contracts-data.js +230 -0
- package/dist/operations/output-contracts-data.js.map +1 -1
- package/dist/operations/service.d.ts +68 -0
- package/dist/operations/service.d.ts.map +1 -0
- package/dist/operations/service.js +336 -0
- package/dist/operations/service.js.map +1 -0
- package/dist/operations/tasks.d.ts +109 -0
- package/dist/operations/tasks.d.ts.map +1 -1
- package/dist/operations/tasks.js.map +1 -1
- package/dist/operations/worktree.d.ts +11 -1
- package/dist/operations/worktree.d.ts.map +1 -1
- package/dist/playbook.d.ts +56 -0
- package/dist/playbook.d.ts.map +1 -1
- package/dist/release/plan.d.ts +5 -5
- package/dist/spawn.d.ts +70 -0
- package/dist/spawn.d.ts.map +1 -1
- package/dist/spawn.js +1 -0
- package/dist/spawn.js.map +1 -1
- package/dist/vault/service-provider.d.ts +343 -0
- package/dist/vault/service-provider.d.ts.map +1 -0
- package/dist/vault/service-provider.js +857 -0
- package/dist/vault/service-provider.js.map +1 -0
- package/dist/workgraph.d.ts +12 -12
- package/package.json +10 -2
- package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
- package/src/__tests__/operation-def.test.ts +6 -1
- package/src/__tests__/provider-def.test.ts +93 -0
- package/src/__tests__/service-provider.test.ts +129 -0
- package/src/boundary.ts +69 -0
- package/src/branch-lock.ts +4 -0
- package/src/conduit.ts +200 -0
- package/src/config/index.ts +35 -0
- package/src/config.ts +116 -1
- package/src/dispatch/__tests__/tasks-gateway-crud-ops.test.ts +99 -0
- package/src/dispatch/identity.ts +20 -0
- package/src/dispatch/operation-def.ts +21 -0
- package/src/dispatch/operations-registry.ts +1062 -36
- package/src/index.ts +147 -2
- package/src/lease-ipc/__tests__/freeze.test.ts +96 -0
- package/src/lease-ipc/index.ts +12 -0
- package/src/lease-ipc/messages.ts +117 -0
- package/src/llm/catalog-schema.json +139 -0
- package/src/llm/catalog-schema.ts +184 -0
- package/src/llm/index.ts +29 -0
- package/src/llm/oauth.ts +16 -2
- package/src/llm/onboarding.ts +131 -0
- package/src/llm/provider-def.ts +291 -0
- package/src/llm/system-resolver.ts +45 -0
- package/src/operations/admin.ts +144 -0
- package/src/operations/entities.ts +758 -0
- package/src/operations/llm.ts +16 -0
- package/src/operations/output-contracts-data.ts +264 -0
- package/src/operations/service.ts +360 -0
- package/src/operations/tasks.ts +119 -0
- package/src/operations/worktree.ts +11 -0
- package/src/playbook.ts +56 -0
- package/src/spawn.ts +79 -0
- package/src/vault/service-provider.ts +1137 -0
|
@@ -68,6 +68,8 @@ type _OperationDefShape = {
|
|
|
68
68
|
requiredParams: string[];
|
|
69
69
|
params?: ParamDef[];
|
|
70
70
|
mcpExposed?: boolean;
|
|
71
|
+
// T11921 — opt-in streaming marker for the gateway HTTP/SSE transport.
|
|
72
|
+
streaming?: boolean;
|
|
71
73
|
// T11692 (DHQ-033 / DHQ-057) — optional schema-first input/output contracts.
|
|
72
74
|
inputSchema?: OperationInputContract<unknown>;
|
|
73
75
|
outputSchema?: OperationOutputContract;
|
|
@@ -103,7 +105,10 @@ describe('dispatch/operation-def contracts', () => {
|
|
|
103
105
|
// T9954 — snapshot the current count so accidental additions/removals
|
|
104
106
|
// trip this test and force an explicit human review.
|
|
105
107
|
// T11889-D — +1 for the new 'selfimprove' domain (self-dogfooding loop verb).
|
|
106
|
-
|
|
108
|
+
// T11939 — +1 for the new 'service' domain (universal service-vault OAuth flow).
|
|
109
|
+
// T11700 — +4 for the 5-entity provider experience: 'account', 'provider',
|
|
110
|
+
// 'model', 'profile' (North-Star §2 addressable provider surface).
|
|
111
|
+
expect(CANONICAL_DOMAINS.length).toBe(29);
|
|
107
112
|
});
|
|
108
113
|
|
|
109
114
|
it('CANONICAL_DOMAINS contains the four sentinel newly-promoted domains', () => {
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T11702 — `ProviderDef` declarative contract shape.
|
|
3
|
+
*
|
|
4
|
+
* Type-level + value-level assertions that the contract is a closed, discriminated,
|
|
5
|
+
* serializable shape (the endpoint union is keyed on the `transport` literal, no
|
|
6
|
+
* `any`/`unknown` shortcut). No DB, no network.
|
|
7
|
+
*
|
|
8
|
+
* @task T11702
|
|
9
|
+
* @epic T11667
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, expect, it } from 'vitest';
|
|
13
|
+
import {
|
|
14
|
+
PROVIDER_TRANSPORTS,
|
|
15
|
+
type ProviderDef,
|
|
16
|
+
type ProviderEndpoint,
|
|
17
|
+
REQUEST_QUIRK_KINDS,
|
|
18
|
+
} from '../llm/provider-def.js';
|
|
19
|
+
|
|
20
|
+
describe('ProviderDef contract (T11702)', () => {
|
|
21
|
+
it('exposes the closed transport discriminant set', () => {
|
|
22
|
+
expect([...PROVIDER_TRANSPORTS]).toEqual([
|
|
23
|
+
'openai-completions',
|
|
24
|
+
'openai-responses',
|
|
25
|
+
'anthropic-messages',
|
|
26
|
+
'aisdk',
|
|
27
|
+
]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('exposes the closed request-quirk-kind set', () => {
|
|
31
|
+
expect(REQUEST_QUIRK_KINDS).toContain('grok-conv-id');
|
|
32
|
+
expect(REQUEST_QUIRK_KINDS).toContain('gemini-thinking-config');
|
|
33
|
+
expect(REQUEST_QUIRK_KINDS).toContain('moonshot-schema-sanitize');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('accepts a fully-populated declarative provider def with an OAuth flow + alt endpoint', () => {
|
|
37
|
+
const def: ProviderDef = {
|
|
38
|
+
id: 'openai',
|
|
39
|
+
displayName: 'OpenAI Codex (ChatGPT)',
|
|
40
|
+
aliases: ['codex', 'chatgpt', 'openai-codex'],
|
|
41
|
+
authMethods: ['api_key', 'oauth'],
|
|
42
|
+
endpoint: { transport: 'openai-completions', baseUrl: 'https://api.openai.com/v1' },
|
|
43
|
+
altEndpoints: [{ transport: 'openai-responses', baseUrl: 'https://api.openai.com/v1' }],
|
|
44
|
+
modelsDevId: 'openai',
|
|
45
|
+
oauth: {
|
|
46
|
+
mode: 'pkce',
|
|
47
|
+
clientId: 'app_test',
|
|
48
|
+
tokenEndpoint: 'https://auth.openai.com/oauth/token',
|
|
49
|
+
authorizationEndpoint: 'https://auth.openai.com/oauth/authorize',
|
|
50
|
+
},
|
|
51
|
+
requestQuirks: [{ kind: 'openrouter-pareto' }],
|
|
52
|
+
};
|
|
53
|
+
// Round-trips through JSON — the shape is serializable (no closures).
|
|
54
|
+
const roundTripped = JSON.parse(JSON.stringify(def)) as ProviderDef;
|
|
55
|
+
expect(roundTripped.id).toBe('openai');
|
|
56
|
+
expect(roundTripped.endpoint.transport).toBe('openai-completions');
|
|
57
|
+
expect(roundTripped.altEndpoints?.[0]?.transport).toBe('openai-responses');
|
|
58
|
+
expect(roundTripped.oauth?.mode).toBe('pkce');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('narrows the endpoint discriminated union on the transport literal', () => {
|
|
62
|
+
const endpoints: ProviderEndpoint[] = [
|
|
63
|
+
{ transport: 'anthropic-messages', baseUrl: 'https://api.anthropic.com' },
|
|
64
|
+
{ transport: 'aisdk', baseUrl: 'https://example.com', aiSdkProvider: 'google' },
|
|
65
|
+
];
|
|
66
|
+
for (const ep of endpoints) {
|
|
67
|
+
if (ep.transport === 'aisdk') {
|
|
68
|
+
// Only the aisdk variant carries aiSdkProvider — narrowing proves the union.
|
|
69
|
+
expect(ep.aiSdkProvider).toBe('google');
|
|
70
|
+
} else {
|
|
71
|
+
expect(ep.baseUrl.startsWith('https://')).toBe(true);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('accepts a minimal def (no optional fields)', () => {
|
|
77
|
+
const def: ProviderDef = {
|
|
78
|
+
id: 'bedrock',
|
|
79
|
+
displayName: 'AWS Bedrock',
|
|
80
|
+
aliases: ['aws-bedrock'],
|
|
81
|
+
authMethods: ['aws_sdk'],
|
|
82
|
+
endpoint: {
|
|
83
|
+
transport: 'aisdk',
|
|
84
|
+
baseUrl: 'https://bedrock.example',
|
|
85
|
+
aiSdkProvider: 'bedrock',
|
|
86
|
+
},
|
|
87
|
+
modelsDevId: 'bedrock',
|
|
88
|
+
};
|
|
89
|
+
expect(def.oauth).toBeUndefined();
|
|
90
|
+
expect(def.requestQuirks).toBeUndefined();
|
|
91
|
+
expect(def.altEndpoints).toBeUndefined();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* T11937 seed + T11938 BREADTH — service-provider registry contract.
|
|
3
|
+
*
|
|
4
|
+
* Asserts the declarative `SERVICE_PROVIDERS` registry: the two pattern seeds
|
|
5
|
+
* (github + google) keep their precise shape (T11937), and the full ~40-service
|
|
6
|
+
* census (T11938) is well-formed — every entry self-consistent on its key, every
|
|
7
|
+
* provider carrying a NON-EMPTY `hostRules` array, and every OAuth (non-`api-key`)
|
|
8
|
+
* provider carrying either a `refresh` config or an explicit re-auth shape. The
|
|
9
|
+
* registry is DATA (no runtime helper) — this test is the only consumer that
|
|
10
|
+
* exercises its shape.
|
|
11
|
+
*
|
|
12
|
+
* @task T11937
|
|
13
|
+
* @task T11938
|
|
14
|
+
* @epic T11765
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { describe, expect, it } from 'vitest';
|
|
18
|
+
import {
|
|
19
|
+
REFRESH_KINDS,
|
|
20
|
+
SERVICE_AUTH_KINDS,
|
|
21
|
+
SERVICE_PROVIDERS,
|
|
22
|
+
type ServiceProviderDef,
|
|
23
|
+
} from '../vault/service-provider.js';
|
|
24
|
+
|
|
25
|
+
describe('SERVICE_PROVIDERS registry (T11937 seed + T11938 breadth)', () => {
|
|
26
|
+
it('contains the full ~40-service census (≥ 38 providers, T11938 AC1)', () => {
|
|
27
|
+
// 2 seeds (github, google) + ~37 breadth entries = ~40 declarative providers.
|
|
28
|
+
expect(Object.keys(SERVICE_PROVIDERS).length).toBeGreaterThanOrEqual(38);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('includes a representative spread across provider families', () => {
|
|
32
|
+
const keys = Object.keys(SERVICE_PROVIDERS);
|
|
33
|
+
// Seeds + Google family + Atlassian + api-key + JWT/client-credentials.
|
|
34
|
+
for (const expected of [
|
|
35
|
+
'github',
|
|
36
|
+
'google',
|
|
37
|
+
'gmail',
|
|
38
|
+
'notion',
|
|
39
|
+
'dropbox',
|
|
40
|
+
'jira',
|
|
41
|
+
'confluence',
|
|
42
|
+
'supabase',
|
|
43
|
+
'todoist',
|
|
44
|
+
'resend',
|
|
45
|
+
'cloudflare',
|
|
46
|
+
'github-app',
|
|
47
|
+
'mongodb-atlas',
|
|
48
|
+
'vertex-ai',
|
|
49
|
+
]) {
|
|
50
|
+
expect(keys).toContain(expected);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('each entry is well-formed and self-consistent on its key', () => {
|
|
55
|
+
for (const [key, def] of Object.entries(SERVICE_PROVIDERS) as Array<
|
|
56
|
+
[string, ServiceProviderDef]
|
|
57
|
+
>) {
|
|
58
|
+
expect(def.provider).toBe(key);
|
|
59
|
+
expect(def.displayName.length).toBeGreaterThan(0);
|
|
60
|
+
expect(def.description.length).toBeGreaterThan(0);
|
|
61
|
+
expect(SERVICE_AUTH_KINDS).toContain(def.authKind);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('every provider declares a NON-EMPTY hostRules array (T11938 AC4)', () => {
|
|
66
|
+
for (const [key, def] of Object.entries(SERVICE_PROVIDERS) as Array<
|
|
67
|
+
[string, ServiceProviderDef]
|
|
68
|
+
>) {
|
|
69
|
+
expect(Array.isArray(def.hostRules), `${key} must declare hostRules`).toBe(true);
|
|
70
|
+
expect(def.hostRules.length, `${key} hostRules must be non-empty`).toBeGreaterThan(0);
|
|
71
|
+
for (const rule of def.hostRules) {
|
|
72
|
+
expect(rule.host.length).toBeGreaterThan(0);
|
|
73
|
+
expect(['bearer', 'basic-x-access-token', 'header']).toContain(rule.strategy);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('every OAuth (non api-key) provider declares a refresh config OR an oauth re-auth shape (T11938 AC4)', () => {
|
|
79
|
+
for (const [key, def] of Object.entries(SERVICE_PROVIDERS) as Array<
|
|
80
|
+
[string, ServiceProviderDef]
|
|
81
|
+
>) {
|
|
82
|
+
if (def.authKind === 'api-key') {
|
|
83
|
+
// api-key services have no token to refresh — refresh MUST be absent.
|
|
84
|
+
expect(def.refresh, `${key} (api-key) must not declare refresh`).toBeUndefined();
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
// An OAuth provider must be refreshable OR at least carry a full oauth
|
|
88
|
+
// block the flow can re-authorize against.
|
|
89
|
+
const hasRefresh = def.refresh !== undefined;
|
|
90
|
+
const hasOAuth = def.oauth !== undefined;
|
|
91
|
+
expect(hasRefresh || hasOAuth, `${key} must declare refresh or oauth`).toBe(true);
|
|
92
|
+
if (hasRefresh && def.refresh) {
|
|
93
|
+
expect(REFRESH_KINDS).toContain(def.refresh.kind);
|
|
94
|
+
expect(def.refresh.tokenUrl.length).toBeGreaterThan(0);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('declares the three special-case refresh-kind discriminants (T11938 AC3)', () => {
|
|
100
|
+
const kinds = new Set(
|
|
101
|
+
Object.values(SERVICE_PROVIDERS)
|
|
102
|
+
.map((d) => d.refresh?.kind)
|
|
103
|
+
.filter((k): k is NonNullable<typeof k> => k !== undefined),
|
|
104
|
+
);
|
|
105
|
+
expect(kinds.has('github-app')).toBe(true);
|
|
106
|
+
expect(kinds.has('client-credentials')).toBe(true);
|
|
107
|
+
expect(kinds.has('refresh-token')).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('github is a classic OAuth2 web flow with complete endpoints (T11937)', () => {
|
|
111
|
+
const gh = SERVICE_PROVIDERS['github'];
|
|
112
|
+
expect(gh).toBeDefined();
|
|
113
|
+
expect(gh?.authKind).toBe('oauth2');
|
|
114
|
+
expect(gh?.oauth?.authorizationEndpoint).toContain('github.com');
|
|
115
|
+
expect(gh?.oauth?.tokenEndpoint).toContain('github.com');
|
|
116
|
+
expect(gh?.oauth?.clientId.length).toBeGreaterThan(0);
|
|
117
|
+
expect(gh?.defaultScopes).toContain('repo');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('google is an installed-app OAuth2 + PKCE flow that requests offline access (T11937)', () => {
|
|
121
|
+
const g = SERVICE_PROVIDERS['google'];
|
|
122
|
+
expect(g).toBeDefined();
|
|
123
|
+
expect(g?.authKind).toBe('oauth2-pkce');
|
|
124
|
+
expect(g?.oauth?.authorizationEndpoint).toContain('accounts.google.com');
|
|
125
|
+
expect(g?.oauth?.tokenEndpoint).toContain('oauth2.googleapis.com');
|
|
126
|
+
// Google needs access_type=offline + prompt=consent to issue a refresh token.
|
|
127
|
+
expect(g?.oauth?.extraAuthParams?.['access_type']).toBe('offline');
|
|
128
|
+
});
|
|
129
|
+
});
|
package/src/boundary.ts
CHANGED
|
@@ -891,6 +891,75 @@ export const BOUNDARY_REGISTRY: readonly BoundaryEntry[] = [
|
|
|
891
891
|
},
|
|
892
892
|
] as const;
|
|
893
893
|
|
|
894
|
+
// ============================================================
|
|
895
|
+
// Channel Boundary Registry (T11952 · epic T11854 · saga T10419)
|
|
896
|
+
// ============================================================
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* Trust posture for an inbound channel sender.
|
|
900
|
+
*
|
|
901
|
+
* - `verified` — sender messages bypass the HITL approval gate and dispatch
|
|
902
|
+
* directly to the agent.
|
|
903
|
+
* - `unverified` — sender messages route through the resumeToken approval gate
|
|
904
|
+
* before dispatch (T11854 AC5).
|
|
905
|
+
*/
|
|
906
|
+
export type ChannelTrustPosture = 'verified' | 'unverified';
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* One row in {@link CHANNEL_BOUNDARY_REGISTRY}. Declares a messaging channel's
|
|
910
|
+
* delivery boundary: which transport it rides, whether it crosses the network,
|
|
911
|
+
* whether a platform credential is required, and the default trust posture for
|
|
912
|
+
* inbound senders.
|
|
913
|
+
*
|
|
914
|
+
* This is the channel-layer companion to the ADR-078 module {@link BoundaryEntry}.
|
|
915
|
+
* Where {@link BoundaryEntry} governs Rust/TS package layering (one row per
|
|
916
|
+
* crate/package, validated against the filesystem), a {@link ChannelBoundaryEntry}
|
|
917
|
+
* governs the conversation-surface boundary (one row per channel adapter) and is
|
|
918
|
+
* NOT filesystem-validated — channels are runtime concepts, not on-disk modules.
|
|
919
|
+
*
|
|
920
|
+
* @see T11854 AC4 — every channel registers a boundary entry in boundary.ts.
|
|
921
|
+
* @see ConduitMessage — the wire shape channels exchange.
|
|
922
|
+
*/
|
|
923
|
+
export interface ChannelBoundaryEntry {
|
|
924
|
+
/** Stable, kebab-case channel id (e.g. `'local-tui'`, `'telegram'`). */
|
|
925
|
+
channel: string;
|
|
926
|
+
/** Underlying conduit transport the adapter rides (e.g. `'local'`, `'http'`). */
|
|
927
|
+
transport: string;
|
|
928
|
+
/** Whether the channel originates outbound network traffic. */
|
|
929
|
+
networkEgress: 'none' | 'required';
|
|
930
|
+
/** Whether the channel requires a platform credential to operate. */
|
|
931
|
+
credentialRequired: boolean;
|
|
932
|
+
/** Default trust posture applied to inbound senders lacking explicit verification. */
|
|
933
|
+
defaultTrust: ChannelTrustPosture;
|
|
934
|
+
/** 1-2 sentence rationale for the channel's boundary posture. */
|
|
935
|
+
rationale: string;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* Canonical registry of messaging-channel boundaries.
|
|
940
|
+
*
|
|
941
|
+
* The Local-TUI adapter (T11952) is the first reference channel: it rides the
|
|
942
|
+
* in-process conduit `LocalTransport`, never touches the network, needs no
|
|
943
|
+
* platform credential, and treats the single local operator as a verified
|
|
944
|
+
* sender. Platform adapters (Telegram/Discord/Slack/WhatsApp — T11855) append
|
|
945
|
+
* their own entries as they land.
|
|
946
|
+
*
|
|
947
|
+
* Entries are kept alphabetical by `channel` for diff-friendliness.
|
|
948
|
+
*
|
|
949
|
+
* @see T11854 AC4
|
|
950
|
+
*/
|
|
951
|
+
export const CHANNEL_BOUNDARY_REGISTRY: readonly ChannelBoundaryEntry[] = [
|
|
952
|
+
{
|
|
953
|
+
channel: 'local-tui',
|
|
954
|
+
transport: 'local',
|
|
955
|
+
networkEgress: 'none',
|
|
956
|
+
credentialRequired: false,
|
|
957
|
+
defaultTrust: 'verified',
|
|
958
|
+
rationale:
|
|
959
|
+
'In-process stdin/stdout terminal channel riding the conduit LocalTransport (conduit.db). Fully offline, daemon-OFF, no network, no platform credential; the single local operator is a verified sender.',
|
|
960
|
+
},
|
|
961
|
+
] as const;
|
|
962
|
+
|
|
894
963
|
// ============================================================
|
|
895
964
|
// TOOLS-vs-SKILLS boundary (E3 · T11409 · SG-PACKAGE-ARCH)
|
|
896
965
|
// ============================================================
|
package/src/branch-lock.ts
CHANGED
|
@@ -102,6 +102,10 @@ export interface WorktreeCleanupResult {
|
|
|
102
102
|
removed: number;
|
|
103
103
|
/** Paths that were removed. */
|
|
104
104
|
removedPaths: string[];
|
|
105
|
+
/** Number of worktrees quarantined (dirty/unpushed — preserved, not deleted). */
|
|
106
|
+
quarantined: number;
|
|
107
|
+
/** Paths that were quarantined. */
|
|
108
|
+
quarantinedPaths: string[];
|
|
105
109
|
/** Paths that failed to remove (with reasons). */
|
|
106
110
|
errors: Array<{ path: string; reason: string }>;
|
|
107
111
|
}
|
package/src/conduit.ts
CHANGED
|
@@ -264,6 +264,206 @@ export interface Conduit {
|
|
|
264
264
|
readonly agentId: string;
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
+
// ============================================================================
|
|
268
|
+
// Channel Adapter (T11952 · epic T11854 · saga T10419)
|
|
269
|
+
// ============================================================================
|
|
270
|
+
//
|
|
271
|
+
// A ChannelAdapter is the boundary between an external conversation surface
|
|
272
|
+
// (a terminal TUI, Telegram chat, Discord channel, …) and the CLEO agent
|
|
273
|
+
// runtime. Each adapter wraps a {@link Transport} (the wire layer) and exposes
|
|
274
|
+
// a uniform, platform-agnostic contract so the Session Store + Delivery Router
|
|
275
|
+
// can route inbound user messages to the agent and route the agent's reply back
|
|
276
|
+
// to the originating channel.
|
|
277
|
+
//
|
|
278
|
+
// The Local-TUI adapter is the FIRST reference adapter (T11952): it rides the
|
|
279
|
+
// in-process {@link Transport} (conduit `LocalTransport`) and works fully
|
|
280
|
+
// offline, daemon-OFF, with no network and no platform credential. Platform
|
|
281
|
+
// adapters (Telegram/Discord/Slack/WhatsApp) implement this same contract but
|
|
282
|
+
// are credential+network gated and out of scope for T11952.
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Liveness verdict for a single channel adapter.
|
|
286
|
+
*
|
|
287
|
+
* - `connected` — the adapter is connected to its transport and ready to
|
|
288
|
+
* receive/send.
|
|
289
|
+
* - `degraded` — the adapter is reachable but operating with reduced capability
|
|
290
|
+
* (e.g. polling fallback, partial subscription).
|
|
291
|
+
* - `disconnected` — the adapter is not currently connected.
|
|
292
|
+
* - `error` — the adapter encountered a fatal error; see {@link ChannelHealth.detail}.
|
|
293
|
+
*/
|
|
294
|
+
export type ChannelHealthStatus = 'connected' | 'degraded' | 'disconnected' | 'error';
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Health snapshot for a single {@link ChannelAdapter}.
|
|
298
|
+
*
|
|
299
|
+
* Surfaced by `cleo channels list` as part of the LAFS RenderableEnvelope and
|
|
300
|
+
* consumed by the Delivery Router to skip dead channels.
|
|
301
|
+
*/
|
|
302
|
+
export interface ChannelHealth {
|
|
303
|
+
/** Stable channel identifier (matches {@link ChannelAdapter.id}). */
|
|
304
|
+
channelId: string;
|
|
305
|
+
/** Coarse liveness verdict. */
|
|
306
|
+
status: ChannelHealthStatus;
|
|
307
|
+
/** Underlying transport name (e.g. `'local'`, `'http'`, `'sse'`). */
|
|
308
|
+
transport: string;
|
|
309
|
+
/** Human-readable detail for non-`connected` states (error message, reason). */
|
|
310
|
+
detail?: string;
|
|
311
|
+
/** ISO 8601 timestamp the health was sampled. */
|
|
312
|
+
checkedAt: string;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Per-channel configuration knobs shared by every {@link ChannelAdapter}.
|
|
317
|
+
*
|
|
318
|
+
* Platform adapters extend this with credential fields; the Local-TUI adapter
|
|
319
|
+
* uses only the platform-agnostic subset.
|
|
320
|
+
*
|
|
321
|
+
* @see T11854 AC1 — `home_channel`, `require_mention`, `allowed_users`.
|
|
322
|
+
*/
|
|
323
|
+
export interface ChannelConfig {
|
|
324
|
+
/**
|
|
325
|
+
* The home channel address replies fall back to when an inbound message has
|
|
326
|
+
* no explicit reply target (e.g. the default conversation/thread id).
|
|
327
|
+
*/
|
|
328
|
+
homeChannel?: string;
|
|
329
|
+
/**
|
|
330
|
+
* When `true`, the adapter only dispatches inbound messages that explicitly
|
|
331
|
+
* mention the agent. Defaults to `false` for the Local-TUI adapter (every
|
|
332
|
+
* line is addressed to the agent).
|
|
333
|
+
*/
|
|
334
|
+
requireMention?: boolean;
|
|
335
|
+
/**
|
|
336
|
+
* Allowlist of sender identifiers permitted to reach the agent. When omitted
|
|
337
|
+
* or empty, all senders are allowed (the Local-TUI default — a single local
|
|
338
|
+
* operator).
|
|
339
|
+
*/
|
|
340
|
+
allowedUsers?: readonly string[];
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* An inbound message arriving from a channel surface, normalized into the
|
|
345
|
+
* platform-agnostic shape the Delivery Router consumes.
|
|
346
|
+
*
|
|
347
|
+
* It is a thin projection over {@link ConduitMessage} carrying the originating
|
|
348
|
+
* channel so the router can preserve session affinity.
|
|
349
|
+
*/
|
|
350
|
+
export interface InboundMsg {
|
|
351
|
+
/** Stable message id (from the underlying transport). */
|
|
352
|
+
id: string;
|
|
353
|
+
/** Channel the message originated on (the {@link ChannelAdapter.id}). */
|
|
354
|
+
channelId: string;
|
|
355
|
+
/** Sender identifier within the channel (user/agent id). */
|
|
356
|
+
from: string;
|
|
357
|
+
/** Message text content. */
|
|
358
|
+
content: string;
|
|
359
|
+
/**
|
|
360
|
+
* Channel-native conversation/thread id used for session affinity. The router
|
|
361
|
+
* keys the {@link ChannelSession} on `(channelId, sessionKey)`.
|
|
362
|
+
*/
|
|
363
|
+
sessionKey: string;
|
|
364
|
+
/** ISO 8601 timestamp the message was received. */
|
|
365
|
+
timestamp: string;
|
|
366
|
+
/** Optional structured payload carried alongside the message. */
|
|
367
|
+
payload?: Record<string, unknown>;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* An outbound reply the agent emits in response to an {@link InboundMsg}.
|
|
372
|
+
*
|
|
373
|
+
* The Delivery Router resolves the target channel + session from the originating
|
|
374
|
+
* {@link InboundMsg} (or an explicit {@link OutboundReply.channelId}) and calls
|
|
375
|
+
* {@link ChannelAdapter.send}.
|
|
376
|
+
*/
|
|
377
|
+
export interface OutboundReply {
|
|
378
|
+
/**
|
|
379
|
+
* Target channel id. When omitted, the router routes to the channel that
|
|
380
|
+
* originated the {@link InboundMsg} being replied to (session affinity).
|
|
381
|
+
*/
|
|
382
|
+
channelId?: string;
|
|
383
|
+
/**
|
|
384
|
+
* Target session/thread key. When omitted, the router uses the originating
|
|
385
|
+
* inbound message's {@link InboundMsg.sessionKey}, then the channel's
|
|
386
|
+
* {@link ChannelConfig.homeChannel} fallback.
|
|
387
|
+
*/
|
|
388
|
+
sessionKey?: string;
|
|
389
|
+
/** Reply text content. */
|
|
390
|
+
content: string;
|
|
391
|
+
/** Optional structured payload carried with the reply. */
|
|
392
|
+
payload?: Record<string, unknown>;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* A persisted per-channel session-affinity record.
|
|
397
|
+
*
|
|
398
|
+
* The Session Store tracks the most recent inbound exchange per
|
|
399
|
+
* `(channelId, sessionKey)` so the Delivery Router can route an agent reply
|
|
400
|
+
* back to the originating channel without the caller threading routing state.
|
|
401
|
+
*
|
|
402
|
+
* @see T1797 — Session Store + Delivery Router.
|
|
403
|
+
*/
|
|
404
|
+
export interface ChannelSession {
|
|
405
|
+
/** Channel the session belongs to. */
|
|
406
|
+
channelId: string;
|
|
407
|
+
/** Channel-native conversation/thread key. */
|
|
408
|
+
sessionKey: string;
|
|
409
|
+
/** Sender identifier of the last inbound message in this session. */
|
|
410
|
+
lastFrom: string;
|
|
411
|
+
/** Id of the last inbound message routed for this session. */
|
|
412
|
+
lastInboundId: string;
|
|
413
|
+
/** ISO 8601 timestamp the session was last touched. */
|
|
414
|
+
updatedAt: string;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* The platform-agnostic channel boundary contract.
|
|
419
|
+
*
|
|
420
|
+
* Every channel (Local-TUI, Telegram, Discord, Slack, WhatsApp, …) implements
|
|
421
|
+
* this interface. Adapters wrap a {@link Transport} and translate between the
|
|
422
|
+
* channel-native surface and the normalized {@link InboundMsg} /
|
|
423
|
+
* {@link OutboundReply} shapes the router consumes.
|
|
424
|
+
*
|
|
425
|
+
* Implementations:
|
|
426
|
+
* - `LocalTuiChannelAdapter` (`@cleocode/core` — rides conduit `LocalTransport`,
|
|
427
|
+
* the first reference adapter, T11952).
|
|
428
|
+
* - Platform adapters (Telegram/Discord/Slack/WhatsApp) — credential+network
|
|
429
|
+
* gated, implemented under T11855 (out of scope for T11952).
|
|
430
|
+
*
|
|
431
|
+
* @see T11854 AC1 — start/stop/onMessage/sendMessage/health.
|
|
432
|
+
*/
|
|
433
|
+
export interface ChannelAdapter {
|
|
434
|
+
/** Stable, kebab-case channel identifier (e.g. `'local-tui'`). */
|
|
435
|
+
readonly id: string;
|
|
436
|
+
/** Effective per-channel configuration. */
|
|
437
|
+
readonly config: ChannelConfig;
|
|
438
|
+
|
|
439
|
+
/** Start the adapter: connect its transport and begin receiving. */
|
|
440
|
+
start(): Promise<void>;
|
|
441
|
+
|
|
442
|
+
/** Stop the adapter: detach handlers and disconnect its transport. */
|
|
443
|
+
stop(): Promise<void>;
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Stream of inbound messages from the channel.
|
|
447
|
+
*
|
|
448
|
+
* Yields each {@link InboundMsg} as it arrives until {@link ChannelAdapter.stop}
|
|
449
|
+
* is called (which ends the iterator). Honors {@link ChannelConfig.requireMention}
|
|
450
|
+
* and {@link ChannelConfig.allowedUsers} filtering before yielding.
|
|
451
|
+
*/
|
|
452
|
+
receive(): AsyncIterable<InboundMsg>;
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Send a reply back to the channel.
|
|
456
|
+
*
|
|
457
|
+
* @param reply - The outbound reply. The adapter resolves the destination
|
|
458
|
+
* session via {@link OutboundReply.sessionKey} then
|
|
459
|
+
* {@link ChannelConfig.homeChannel}.
|
|
460
|
+
*/
|
|
461
|
+
send(reply: OutboundReply): Promise<void>;
|
|
462
|
+
|
|
463
|
+
/** Sample the adapter's current health. */
|
|
464
|
+
health(): ChannelHealth;
|
|
465
|
+
}
|
|
466
|
+
|
|
267
467
|
// ============================================================================
|
|
268
468
|
// Factory
|
|
269
469
|
// ============================================================================
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@cleocode/contracts/config` — config-domain contract surface.
|
|
3
|
+
*
|
|
4
|
+
* Subpath barrel that surfaces the `ConfigManifest` contract (the
|
|
5
|
+
* {@link ConfigManifestEntry} shape, its Zod validator
|
|
6
|
+
* {@link configManifestEntrySchema}, and the built-in manifest entries) under a
|
|
7
|
+
* stable, config-scoped import path. This is the AC3 surface for the
|
|
8
|
+
* config-as-domain work (T11917): consumers that only need the manifest contract
|
|
9
|
+
* import from `@cleocode/contracts/config` instead of pulling the full index.
|
|
10
|
+
*
|
|
11
|
+
* The types are also re-exported from the package root (`@cleocode/contracts`)
|
|
12
|
+
* for backwards compatibility — this barrel is an additive, narrower entrypoint.
|
|
13
|
+
*
|
|
14
|
+
* @packageDocumentation
|
|
15
|
+
* @module @cleocode/contracts/config
|
|
16
|
+
*
|
|
17
|
+
* @task T11917 — config-as-domain (M5/AC3)
|
|
18
|
+
* @task T9876 — ConfigManifest contract
|
|
19
|
+
* @saga T9855
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export type {
|
|
23
|
+
ConfigManifestEntry,
|
|
24
|
+
ConfigManifestScope,
|
|
25
|
+
ConfigScope,
|
|
26
|
+
DriftDetection,
|
|
27
|
+
} from './manifest.js';
|
|
28
|
+
export {
|
|
29
|
+
CLEO_CONFIG_MANIFEST,
|
|
30
|
+
CONFIG_MANIFEST_ENTRIES,
|
|
31
|
+
configManifestEntrySchema,
|
|
32
|
+
GLOBAL_CLEO_CONFIG_MANIFEST,
|
|
33
|
+
PROJECT_CONTEXT_MANIFEST,
|
|
34
|
+
PROJECT_INFO_MANIFEST,
|
|
35
|
+
} from './manifest.js';
|