@awebai/oats 0.25.9 → 0.26.0
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/README.md +8 -6
- package/bin/oats.mjs +576 -1714
- package/capabilities/oats-authoring/oats-package.json +2 -2
- package/capabilities/oats-authoring/oats.json +2 -2
- package/capabilities/oats-authoring/skills/integration-authoring/SKILL.md +46 -25
- package/capabilities/oats-authoring/skills/soul-craft/SKILL.md +13 -6
- package/capabilities/oats-aweb/bin/oats-aweb.mjs +279 -93
- package/capabilities/oats-aweb/injects/aweb.md +7 -2
- package/capabilities/oats-aweb/lib/binding-wire.mjs +89 -13
- package/capabilities/oats-aweb/lib/captured-native.mjs +1 -1
- package/capabilities/oats-aweb/lib/grant-custody.mjs +38 -0
- package/capabilities/oats-aweb/oats.json +8 -4
- package/capabilities/oats-jira/bin/oats-jira.mjs +4 -4
- package/capabilities/oats-jira/oats.json +2 -2
- package/capabilities/oats-jira/skills/jira-tasks/SKILL.md +6 -3
- package/capabilities/oats-linear/bin/oats-linear-hook.mjs +6 -4
- package/capabilities/oats-linear/oats.json +2 -2
- package/capabilities/oats-linear/skills/linear-tasks/SKILL.md +6 -0
- package/capabilities/oats-review/oats.json +3 -2
- package/docs/capabilities.md +218 -47
- package/docs/capability-manifest.schema.json +13 -4
- package/docs/configuration.md +17 -5
- package/docs/conventions.md +16 -26
- package/docs/design/2026-09-08-expert-assisted-deployment-proposal.md +1 -1
- package/docs/design/2026-09-13-knowledge-and-memory-direction.md +3 -3
- package/docs/design/2026-09-14-portable-souls-and-git-workspaces.md +2 -2
- package/docs/design/2026-09-15-portable-souls-handoff.md +2 -2
- package/docs/design/2026-09-15-portable-souls-implementation.md +1 -1
- package/docs/design/2026-09-20-redesign-program-board.md +2 -2
- package/docs/design/2026-09-23-workspace-module-contracts.md +1 -1
- package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +1 -1
- package/docs/design/2026-09-24-desktop-phase-f-boundary.md +34 -1
- package/docs/design/2026-09-24-phase-d-plan.md +57 -0
- package/docs/design/2026-09-25-teams-contract.md +226 -0
- package/docs/design/README.md +3 -3
- package/docs/design/launch-configurations.md +20 -16
- package/docs/design/operations-contract.md +27 -10
- package/docs/desktop-cli-api.md +537 -261
- package/docs/desktop-instance-start.md +1 -1
- package/docs/desktop.md +7 -13
- package/docs/execution-targets.md +16 -18
- package/docs/first-team.md +14 -17
- package/docs/implementation.md +28 -59
- package/docs/integrations.md +64 -33
- package/docs/knowledge-capability-authoring.md +1 -1
- package/docs/knowledge-reference/package-craft.md +10 -8
- package/docs/knowledge-theory.md +1 -1
- package/docs/knowledge.md +10 -11
- package/docs/layers.md +16 -17
- package/docs/oats-local.schema.json +29 -1
- package/docs/oats-membership.schema.json +5 -3
- package/docs/oats-package.schema.json +2 -2
- package/docs/oats-workspace.schema.json +1 -1
- package/docs/{official-marketplace.md → official-catalog.md} +15 -16
- package/docs/packages.md +75 -52
- package/docs/release-notes/v0.22.0.md +1 -1
- package/docs/release-notes/v0.23.1.md +1 -1
- package/docs/release-notes/v0.26.0.md +670 -0
- package/docs/schedules.md +48 -126
- package/docs/soul.schema.json +11 -4
- package/docs/souls-and-instances.md +56 -43
- package/docs/workspaces.md +80 -58
- package/injects/instance-boundary.md +1 -1
- package/injects/work-attached.md +1 -1
- package/injects/work-workspace.md +2 -2
- package/lib/{portable-files.mjs → bounded-read.mjs} +6 -6
- package/lib/{portable-values.mjs → canonical-json.mjs} +3 -12
- package/lib/capability-contract.mjs +110 -0
- package/lib/config-data.mjs +2 -2
- package/lib/core.mjs +700 -4824
- package/lib/digest.mjs +12 -0
- package/lib/instance-inspect.mjs +396 -0
- package/lib/instance-lifecycle.mjs +3 -4
- package/lib/instance-resolution.mjs +212 -26
- package/lib/instruction-composition.mjs +0 -20
- package/lib/materialize.mjs +6 -4
- package/lib/operator-dispatch.mjs +33 -13
- package/lib/packages.mjs +25 -190
- package/lib/provider-binding.mjs +4 -2
- package/lib/provider-reasons.mjs +3 -68
- package/lib/resolve.mjs +204 -68
- package/lib/schedule.mjs +97 -272
- package/lib/servers.mjs +13 -13
- package/lib/{portable-shape.mjs → shape.mjs} +4 -3
- package/lib/tree-copy.mjs +44 -0
- package/lib/workspace.mjs +125 -20
- package/package-catalog.json +6 -6
- package/package.json +1 -1
- package/skills/integration-authoring/SKILL.md +48 -40
- package/skills/oats-getting-started/SKILL.md +105 -110
- package/skills/oats-support/SKILL.md +2 -2
- package/skills/soul-craft/SKILL.md +13 -6
- package/bin/oats-pi-sdk-host.mjs +0 -17
- package/docs/2026-09-03-architecture-proposal.md +0 -642
- package/docs/artifact-approvals.schema.json +0 -7
- package/docs/captured-invocation-context.schema.json +0 -7
- package/docs/captured-resolution.schema.json +0 -7
- package/docs/design/package-engine-contract.md +0 -813
- package/docs/design/package-runtime-api.md +0 -588
- package/docs/desktop-succession.md +0 -57
- package/docs/execution-capsule.schema.json +0 -108
- package/docs/first-team-demo.md +0 -92
- package/docs/knowledge-migration.md +0 -147
- package/docs/migration-from-oas.md +0 -103
- package/docs/oats-config.schema.json +0 -172
- package/docs/oats-lock-v3.schema.json +0 -7
- package/docs/oats-lock.schema.json +0 -175
- package/docs/operating-team-migration.md +0 -470
- package/docs/portable.schema.json +0 -2512
- package/docs/provider-check-input.schema.json +0 -7
- package/docs/rebuild-to-v2.md +0 -511
- package/docs/workspace-adoption.md +0 -74
- package/injects/framework-workspace.md +0 -7
- package/injects/local-soul.md +0 -19
- package/injects/oats-portable.md +0 -20
- package/injects/oats.md +0 -11
- package/injects/portable-instance-boundary.md +0 -39
- package/injects/portable-work-directory.md +0 -29
- package/lib/artifact-approvals.mjs +0 -120
- package/lib/artifact-tree.mjs +0 -141
- package/lib/capability-artifacts.mjs +0 -179
- package/lib/capability-execution.mjs +0 -15
- package/lib/capability-inputs.mjs +0 -39
- package/lib/capability-provenance.mjs +0 -231
- package/lib/captured-action-shape.mjs +0 -21
- package/lib/captured-admission-shape.mjs +0 -20
- package/lib/captured-binding-file.mjs +0 -36
- package/lib/captured-dispatch.mjs +0 -66
- package/lib/captured-instance-index.mjs +0 -277
- package/lib/captured-invocation-context.mjs +0 -130
- package/lib/captured-launch-request.mjs +0 -66
- package/lib/captured-operation-process.mjs +0 -15
- package/lib/captured-pi-custody.mjs +0 -29
- package/lib/captured-pi-host.mjs +0 -167
- package/lib/captured-pi-outcome.mjs +0 -172
- package/lib/captured-resolutions.mjs +0 -275
- package/lib/captured-scaffold.mjs +0 -87
- package/lib/captured-selector.mjs +0 -28
- package/lib/captured-session-backend.mjs +0 -52
- package/lib/captured-source-receipt-file.mjs +0 -72
- package/lib/helper-injection-policy.mjs +0 -104
- package/lib/legacy-lock-codec.mjs +0 -106
- package/lib/manifest-settings.mjs +0 -84
- package/lib/package-closure.mjs +0 -48
- package/lib/package-materialization.mjs +0 -83
- package/lib/pi-sdk-host.mjs +0 -229
- package/lib/portable-artifacts.mjs +0 -115
- package/lib/portable-choices.mjs +0 -82
- package/lib/portable-composition.mjs +0 -136
- package/lib/portable-digest.mjs +0 -105
- package/lib/portable-identity.mjs +0 -40
- package/lib/portable-lock.mjs +0 -117
- package/lib/portable-onboarding-request.mjs +0 -49
- package/lib/portable-onboarding.mjs +0 -256
- package/lib/portable-package-preparation.mjs +0 -188
- package/lib/portable-policy.mjs +0 -44
- package/lib/portable-soul.mjs +0 -42
- package/lib/portable-state.mjs +0 -80
- package/lib/prepare-composition.mjs +0 -170
- package/lib/prepared-bindings.mjs +0 -92
- package/lib/prepared-resources.mjs +0 -127
- package/lib/provider-binding-broker.mjs +0 -65
- package/lib/provider-binding-wire.mjs +0 -116
- package/lib/readiness.mjs +0 -225
- package/lib/repository-observation.mjs +0 -226
- package/lib/resolution-shape.mjs +0 -393
- package/lib/schedule-capsule.mjs +0 -206
- package/lib/soul-constraints.mjs +0 -40
- package/lib/source-projection.mjs +0 -84
- package/lib/source-spec.mjs +0 -189
- package/lib/workspace-definition.mjs +0 -126
- package/lib/workspace-discovery.mjs +0 -146
- package/skills/oats/SKILL.md +0 -162
- package/skills/oats-config/SKILL.md +0 -164
- package/skills/oats-packages/SKILL.md +0 -184
- package/skills/oats-portable/SKILL.md +0 -115
- package/skills/oats-portable-artifacts/SKILL.md +0 -63
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
2
3
|
import { isAbsolute, join, resolve } from 'node:path';
|
|
3
4
|
import { TextDecoder } from 'node:util';
|
|
4
5
|
import { assessCapturedSessionReadiness } from './session-readiness.mjs';
|
|
6
|
+
import { custodyPreflight } from './grant-custody.mjs';
|
|
5
7
|
import {
|
|
6
8
|
MESSAGING_CONTRACT,
|
|
7
9
|
MESSAGING_CONTRACT_VERSION,
|
|
@@ -78,21 +80,24 @@ export function parseBindingJson(bytes,limits=BINDING_WIRE_LIMITS) {
|
|
|
78
80
|
let text;try{text=new TextDecoder('utf-8',{fatal:true}).decode(bytes);}catch{wireError('invalid-binding');}
|
|
79
81
|
return new StrictJsonParser(text,limits).parse();
|
|
80
82
|
}
|
|
81
|
-
function settings(value) {
|
|
83
|
+
function settings(value,{phase}={}) {
|
|
82
84
|
if(!obj(value)) wireError('invalid-binding');
|
|
83
|
-
if(Object.hasOwn(value,'identity')) wireError('provider-not-qualified');
|
|
84
|
-
keys(value,['delivery','team','root','roots'],[]);
|
|
85
|
+
if(phase!=='check' && Object.hasOwn(value,'identity')) wireError('provider-not-qualified');
|
|
86
|
+
keys(value,phase==='check'?['delivery','team','root','roots','identity','residents']:['delivery','team','root','roots'],[]);
|
|
85
87
|
if(value.delivery!==undefined && !['channel','session'].includes(value.delivery)) wireError('needs-configuration');
|
|
86
88
|
if(value.team!==undefined && (typeof value.team!=='string' || !value.team.trim())) wireError('needs-configuration');
|
|
87
89
|
if(value.root!==undefined && (typeof value.root!=='string' || !value.root.trim())) wireError('needs-configuration');
|
|
88
90
|
if(value.roots!==undefined && !obj(value.roots)) wireError('needs-configuration');
|
|
89
91
|
if(obj(value.roots)) for(const [team,root] of Object.entries(value.roots)) if(!team || typeof root!=='string' || !root.trim()) wireError('needs-configuration');
|
|
92
|
+
if(value.identity!==undefined && !obj(value.identity)) wireError('needs-configuration');
|
|
93
|
+
if(value.residents!==undefined && !obj(value.residents)) wireError('needs-configuration');
|
|
94
|
+
if(obj(value.residents)) for(const [name,custody] of Object.entries(value.residents)) if(!name || typeof custody!=='string' || !custody.trim()) wireError('needs-configuration');
|
|
90
95
|
return value;
|
|
91
96
|
}
|
|
92
97
|
function request(value,phase) {
|
|
93
98
|
keys(value,['schemaVersion','phase','slot','capability','settings','input'],['schemaVersion','phase','slot','capability','settings','input']);
|
|
94
99
|
if(value.schemaVersion!==1 || value.phase!==phase || value.slot!==SLOT || value.capability!==CAPABILITY) wireError('invalid-binding');
|
|
95
|
-
settings(value.settings);return value;
|
|
100
|
+
settings(value.settings,{phase});return value;
|
|
96
101
|
}
|
|
97
102
|
function declaration(value) {
|
|
98
103
|
keys(value,['kind','value','origin','origins'],['kind','value','origin','origins']);
|
|
@@ -149,25 +154,96 @@ function binding(value) {
|
|
|
149
154
|
}
|
|
150
155
|
function checkResult(message) {return {status:'needs-configuration',problems:[{code:'needs-configuration',message}]};}
|
|
151
156
|
function checkProblems(problems) {return problems.length?{status:'needs-configuration',problems}:null;}
|
|
152
|
-
function
|
|
153
|
-
|
|
154
|
-
|
|
157
|
+
function workspaceReadinessContext(value) {
|
|
158
|
+
keys(value,['kind','workspace','deployment','soul','team','instance','home'],['kind','workspace','deployment','soul']);
|
|
159
|
+
if(value.kind!=='workspace' || typeof value.workspace!=='string' || !value.workspace.trim() || typeof value.deployment!=='string' || !value.deployment.trim() || typeof value.soul!=='string' || !value.soul.trim()) wireError('invalid-binding');
|
|
160
|
+
if(value.team!==null && value.team!==undefined && (typeof value.team!=='string' || !value.team.trim())) wireError('invalid-binding');
|
|
161
|
+
if(value.instance!==null && value.instance!==undefined && (typeof value.instance!=='string' || !value.instance.trim())) wireError('invalid-binding');
|
|
162
|
+
if(value.home!==null && value.home!==undefined && (typeof value.home!=='string' || !value.home.trim())) wireError('invalid-binding');
|
|
163
|
+
return value;
|
|
164
|
+
}
|
|
165
|
+
function yamlScalar(text,key){const m=String(text).match(new RegExp(`^${key}:\\s*["']?([^"'\\n#]+)["']?\\s*$`,'m'));return m?m[1].trim():undefined;}
|
|
166
|
+
export const CUSTODY_ATTACH_MIN = '1.36.3';
|
|
167
|
+
export function grantYamlCustodySocket(text) {
|
|
168
|
+
const lines=String(text??'').split(/\r?\n/);let inCustody=false,baseIndent=0;
|
|
169
|
+
for(const line of lines) {
|
|
170
|
+
const custody=/^(\s*)custody:\s*(?:#.*)?$/.exec(line);if(custody){inCustody=true;baseIndent=custody[1].length;continue;}
|
|
171
|
+
if(inCustody){const ind=/^(\s*)/.exec(line)?.[1].length||0;if(line.trim()&&ind<=baseIndent)inCustody=false;const socket=/^\s*socket_path:\s*["']?([^"'\n#]+)["']?\s*$/.exec(line);if(socket)return socket[1].trim();}
|
|
172
|
+
}
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
function newestGrantHome(home) {
|
|
176
|
+
if(typeof home!=='string'||!home.trim()) return null;
|
|
177
|
+
try {
|
|
178
|
+
const dirs=readdirSync(home).filter((name)=>name==='.aweb-identity'||/^\.aweb-identity-\d+$/.test(name)).map((name)=>join(home,name)).filter((p)=>{try{return statSync(p).isDirectory();}catch{return false;}}).map((p)=>{try{return {path:p,mtime:statSync(p).mtimeMs};}catch{return {path:p,mtime:0};}}).sort((a,b)=>b.mtime-a.mtime||b.path.localeCompare(a.path));
|
|
179
|
+
return dirs[0]?.path||null;
|
|
180
|
+
} catch {return null;}
|
|
181
|
+
}
|
|
182
|
+
function grantAttachmentProblem(home) {
|
|
183
|
+
const grantHome=newestGrantHome(home);
|
|
184
|
+
if(!grantHome) return null;
|
|
185
|
+
const grantYaml=join(grantHome,'grant.yaml');
|
|
186
|
+
if(!existsSync(grantYaml)) return null;
|
|
187
|
+
let text;try{text=readFileSync(grantYaml,'utf8');}catch{return null;}
|
|
188
|
+
if(grantYamlCustodySocket(text)) return null;
|
|
189
|
+
const id=yamlScalar(text,'grant_id')||'<unknown>';
|
|
190
|
+
return {code:'custody',message:`grant ${id} is not attached to custody; retire and respawn on aw >= ${CUSTODY_ATTACH_MIN}`};
|
|
191
|
+
}
|
|
192
|
+
function activeTeamAt(root){try{return yamlScalar(readFileSync(join(resolve(root),'.aw','teams.yaml'),'utf8'),'active_team')||yamlScalar(readFileSync(join(resolve(root),'.aw','teams.yaml'),'utf8'),'active');}catch{return undefined;}}
|
|
193
|
+
function teamFromSettings(settings,candidate,{env=process.env}={}) {
|
|
194
|
+
const configured=typeof settings.team==='string' && settings.team.trim()?settings.team.trim():(env.OATS_TEAM_ID || env.OATS_TEAM_NAME || undefined);
|
|
195
|
+
if(configured || env.OATS_TEAM_LABEL) return configured;
|
|
196
|
+
return candidate?.root && isAbsolute(candidate.root) ? activeTeamAt(candidate.root) : undefined;
|
|
197
|
+
}
|
|
198
|
+
function classicEnv(env=process.env) {return !!env.OATS_TEAM_SCOPE && !(env.OATS_WORKSPACE_KEY || env.OATS_WORKSPACE_NAME || env.OATS_TEAM_LABEL);}
|
|
199
|
+
function rootCandidate(settings,team,{deployment,env=process.env}={}) {
|
|
155
200
|
const roots=obj(settings.roots)?settings.roots:{};
|
|
156
201
|
if(team && typeof roots[team]==='string' && roots[team].trim()) return {root:roots[team].trim(),key:`settings.oats.aweb.roots[${JSON.stringify(team)}]`,declared:true};
|
|
157
202
|
if(typeof settings.root==='string' && settings.root.trim()) return {root:settings.root.trim(),key:'settings.oats.aweb.root',declared:true};
|
|
158
|
-
const candidates=classicEnv()?[
|
|
203
|
+
const candidates=classicEnv(env)?[env.OATS_TEAM_SCOPE,env.OATS_WORKSPACE].filter(Boolean):[env.OATS_WORKSPACE || deployment || env.OATS_TEAM_SCOPE || process.cwd()];
|
|
159
204
|
for(const root of candidates) if(isAbsolute(root) && existsSync(join(resolve(root),'.aw'))) return {root,key:'settings.oats.aweb.root',declared:false};
|
|
160
205
|
return {root:candidates[0] || process.cwd(),key:'settings.oats.aweb.root',declared:false};
|
|
161
206
|
}
|
|
162
|
-
function
|
|
163
|
-
const team
|
|
207
|
+
function readinessDetails(settings,{deployment,env=process.env}={}) {
|
|
208
|
+
const initialTeam=typeof settings.team==='string' && settings.team.trim()?settings.team.trim():(env.OATS_TEAM_ID || env.OATS_TEAM_NAME || undefined);
|
|
209
|
+
const candidate=rootCandidate(settings,initialTeam,{deployment,env}),team=teamFromSettings(settings,candidate,{env}),problems=[];
|
|
164
210
|
if(!candidate.root || !isAbsolute(candidate.root) || !existsSync(join(resolve(candidate.root),'.aw'))) problems.push({code:'needs-configuration',message:`no messaging root at ${candidate.root?resolve(candidate.root):process.cwd()}: run oats aweb setup there or set ${candidate.key}`});
|
|
165
211
|
if(!team) problems.push({code:'needs-configuration',message:'no team: set messaging.byTeam.<label>.team in the workspace file or settings.oats.aweb.team'});
|
|
166
|
-
return checkProblems(problems) || {status:'ready',problems:[]};
|
|
212
|
+
return {team,candidate,result:checkProblems(problems) || {status:'ready',problems:[]}};
|
|
213
|
+
}
|
|
214
|
+
function readinessFromSettings(settings,options) {return readinessDetails(settings,options).result;}
|
|
215
|
+
function runAw(argv,cwd,{unsetEnv=[],timeout=60000}={}) {
|
|
216
|
+
const env={...process.env};for(const name of unsetEnv) delete env[name];
|
|
217
|
+
try {return execFileSync(argv[0],argv.slice(1),{cwd,env,encoding:'utf8',stdio:['ignore','pipe','pipe'],timeout}).trim();}
|
|
218
|
+
catch(e) {throw new Error(`${argv.slice(0,3).join(' ')} failed${e.status===undefined?'':` (exit ${e.status})`}`);}
|
|
219
|
+
}
|
|
220
|
+
function workspaceReadinessPhase(req) {
|
|
221
|
+
const ctx=workspaceReadinessContext(req.input.context);
|
|
222
|
+
if(!obj(req.input.action) || req.input.action.kind!=='readiness') wireError('invalid-binding');
|
|
223
|
+
const details=readinessDetails(req.settings,{deployment:ctx.deployment}),problems=[...details.result.problems],warnings=[];
|
|
224
|
+
const identity=obj(req.settings.identity)?req.settings.identity:{},mode=identity.mode===undefined || identity.mode===null || identity.mode===''?'local':String(identity.mode);
|
|
225
|
+
if(mode==='global') {
|
|
226
|
+
const grantProblem=grantAttachmentProblem(ctx.home);if(grantProblem) problems.push(grantProblem);
|
|
227
|
+
const resident=typeof identity.resident==='string' && identity.resident.trim()?identity.resident.trim():undefined;
|
|
228
|
+
const residents=obj(req.settings.residents)?req.settings.residents:{};
|
|
229
|
+
const custody=resident?residents[resident]:undefined;
|
|
230
|
+
if(!resident) problems.push({code:'custody',message:'identity.mode "global" requires identity.resident; set oats-local.yaml settings.oats.aweb.residents.<name> to the absolute custody directory for that resident identity'});
|
|
231
|
+
else if(typeof custody!=='string' || !isAbsolute(custody) || !existsSync(join(custody,'.aw','identity.yaml'))) problems.push({code:'custody',message:`identity.mode "global" resident ${JSON.stringify(resident)} is not resolvable; set oats-local.yaml settings.oats.aweb.residents.${resident} to an absolute custody directory whose .aw/identity.yaml exists`});
|
|
232
|
+
else if(details.team) {
|
|
233
|
+
try {
|
|
234
|
+
const preflight=custodyPreflight({custody,resident,team:details.team,e2eeRequired:identity.e2ee!==false,fatalOnError:false,runAw:(argv,cwd,options={})=>runAw(argv,cwd,{...options,timeout:20000})});
|
|
235
|
+
for(const message of preflight.warnings) warnings.push({code:'e2ee-disabled',message});
|
|
236
|
+
}
|
|
237
|
+
catch(e) {problems.push({code:'custody',message:e.message});}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const result=checkProblems(problems) || {status:'ready',problems:[]};
|
|
241
|
+
return {...result,warnings};
|
|
167
242
|
}
|
|
168
243
|
function checkPhase(req) {
|
|
169
|
-
keys(req.input,['binding','context','action','invocation'],['
|
|
244
|
+
keys(req.input,['binding','context','action','invocation'],['context','action']);
|
|
170
245
|
if(!obj(req.input.action) || typeof req.input.action.kind!=='string') wireError('invalid-binding');
|
|
246
|
+
if(!Object.hasOwn(req.input,'binding')) return workspaceReadinessPhase(req);
|
|
171
247
|
const current=validateAwebBinding(binding(req.input.binding)),selectedContext=context(req.input.context,{request:true});
|
|
172
248
|
if(!same(selectedContext,current.payload.context)) wireError('invalid-binding');
|
|
173
249
|
const invocation=Object.hasOwn(req.input,'invocation')?validateAwebInvocationContext(req.input.invocation,current,{context:req.input.context,action:req.input.action}):null;
|
|
@@ -26,7 +26,7 @@ export function runCapturedNative({selected,event,settings,run,env=process.env})
|
|
|
26
26
|
const refusal=(code,message)=>({exitCode:1,output:{...(meta?{meta}:{}),status:'needs-configuration',problems:[{code,message}],warning:`oats-aweb: ${message}; retain the home and any recorded partial effects`}});
|
|
27
27
|
try{
|
|
28
28
|
assertCurrent();
|
|
29
|
-
if(!/^[a-z0-9][a-z0-
|
|
29
|
+
if(!/^[a-z0-9][a-z0-9_-]{0,63}$/i.test(name)||!team)fail('needs-configuration','exact native alias and private-team selection are required');
|
|
30
30
|
if(settings.delivery!=='session')fail('needs-configuration','captured delivery must be session');
|
|
31
31
|
if(binding.payload.wider.length)fail('needs-configuration','selected wider memberships need their explicitly qualified native setup; they were not omitted');
|
|
32
32
|
// A global override may select a different actor even with an exact cwd.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export function parseAwJson(text, what) {
|
|
2
|
+
const trimmed = String(text ?? '').trim();
|
|
3
|
+
if (!trimmed) throw new Error(`${what} returned no JSON result`);
|
|
4
|
+
try { return JSON.parse(trimmed); } catch { /* may have progress before JSON */ }
|
|
5
|
+
const lines = trimmed.split(/\r?\n/);
|
|
6
|
+
for (let i = 0; i < lines.length; i++) {
|
|
7
|
+
if (!lines[i].trimStart().startsWith('{')) continue;
|
|
8
|
+
try { return JSON.parse(lines.slice(i).join('\n')); } catch { /* keep looking */ }
|
|
9
|
+
}
|
|
10
|
+
throw new Error(`${what} returned no JSON result`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function custodyPreflight({ custody, resident, team, e2eeRequired = true, runAw, fatalOnError = true, fatal }) {
|
|
14
|
+
const failNow = (message) => { if (fatalOnError && typeof fatal === 'function') fatal(message); throw new Error(message); };
|
|
15
|
+
let status;
|
|
16
|
+
try { status = parseAwJson(runAw(['aw', 'custody', 'status', '--json'], custody, { unsetEnv: ['AWEB_IDENTITY_HOME'] }), 'aw custody status'); }
|
|
17
|
+
catch (e) { failNow(`custody preflight failed for ${resident}: aw custody status --json could not run (${e.message || e}); start aw custody serve for ${resident}`); }
|
|
18
|
+
const state = String(status.status || 'unknown');
|
|
19
|
+
const firstError = Array.isArray(status.errors) && status.errors.length ? status.errors[0] : undefined;
|
|
20
|
+
const firstErrorCode = typeof firstError === 'string' ? firstError : firstError?.code;
|
|
21
|
+
const code = firstErrorCode ? ` error=${firstErrorCode}` : '';
|
|
22
|
+
const fail = (why) => failNow(`custody preflight failed for ${resident}: status=${state}${code}; ${why}; start aw custody serve for ${resident}`);
|
|
23
|
+
if (state !== 'running') fail('custody service is not running');
|
|
24
|
+
const teamRow = (Array.isArray(status.teams) ? status.teams : []).find((t) => t && (t.team_id || t.id) === team);
|
|
25
|
+
if (!teamRow) fail(`team ${team} is not present in custody status`);
|
|
26
|
+
if (teamRow.ready !== true) fail(`team ${team} is not ready in custody status`);
|
|
27
|
+
if (teamRow.certificate_present === false) fail(`team ${team} certificate is not present in custody status`);
|
|
28
|
+
if (teamRow.grant_status_endpoint_ready !== undefined && teamRow.grant_status_endpoint_ready !== true) fail(`the aweb server serving team ${team} does not provide grant status yet; hosted grants wait for that deployment`);
|
|
29
|
+
if (status.keys?.signing_ready !== true) fail('keys.signing_ready is false');
|
|
30
|
+
const ops = new Set(Array.isArray(status.ops) ? status.ops.map(String) : []);
|
|
31
|
+
const requiredOps = ['sign_plain_message.v1', ...(e2eeRequired ? ['unwrap_e2ee_message.v1', 'create_e2ee_envelope.v1'] : [])];
|
|
32
|
+
const missingOps = requiredOps.filter((op) => !ops.has(op));
|
|
33
|
+
if (missingOps.length) fail(`required custody operations are missing: ${missingOps.join(', ')}`);
|
|
34
|
+
if (e2eeRequired && status.keys?.encryption_ready !== true) fail('keys.encryption_ready is false');
|
|
35
|
+
const warnings = [];
|
|
36
|
+
if (!e2eeRequired && status.keys?.encryption_ready !== true) warnings.push('E2E encryption is disabled for this grant and custody encryption is not ready; encrypted mail/chat will not be available in this session.');
|
|
37
|
+
return { status, warnings };
|
|
38
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"capability": "oats.aweb",
|
|
3
3
|
"command": "aweb",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.13.1",
|
|
5
5
|
"compatibility": {
|
|
6
|
-
"oats": ">=0.
|
|
6
|
+
"oats": ">=0.25.6"
|
|
7
7
|
},
|
|
8
8
|
"layer": "messaging",
|
|
9
9
|
"description": "Messaging layer via aweb: per-instance team identities + native aw mail/chat skills + cross-machine team roster.",
|
|
@@ -121,7 +121,8 @@
|
|
|
121
121
|
"command": "bin/oats-aweb.mjs spawn",
|
|
122
122
|
"required": true
|
|
123
123
|
},
|
|
124
|
-
"retire": "bin/oats-aweb.mjs retire"
|
|
124
|
+
"retire": "bin/oats-aweb.mjs retire",
|
|
125
|
+
"launch": "bin/oats-aweb.mjs launch"
|
|
125
126
|
},
|
|
126
127
|
"environment": [
|
|
127
128
|
"AWEB_DELIVERY",
|
|
@@ -140,18 +141,21 @@
|
|
|
140
141
|
"description": "Target aweb team id for identity lifecycle. In workspace v2 spawns this payload value wins over OATS_TEAM_ID/OATS_TEAM_NAME; if both are set and differ the hook warns and uses this setting."
|
|
141
142
|
},
|
|
142
143
|
"root": {
|
|
144
|
+
"hostOnly": true,
|
|
143
145
|
"description": "Host-owned absolute directory whose .aw is the aweb minting root. Put this only in oats-local.yaml settings.oats.aweb.root; use oats aweb setup to initialize it."
|
|
144
146
|
},
|
|
145
147
|
"roots": {
|
|
148
|
+
"hostOnly": true,
|
|
146
149
|
"description": "Host-owned map of aweb team id to absolute directory whose .aw is that team's minting root, for deployments that mint into several teams. Put this only in oats-local.yaml settings.oats.aweb.roots."
|
|
147
150
|
},
|
|
148
151
|
"identity": {
|
|
149
152
|
"default": {
|
|
150
153
|
"mode": "local"
|
|
151
154
|
},
|
|
152
|
-
"description": "Identity selection. mode=local (default) mints an instance-local team identity or uses the existing identity.source retained-seat flow. mode=global acts as a named resident identity through a session grant; requires identity.resident
|
|
155
|
+
"description": "Identity selection. mode=local (default) mints an instance-local team identity or uses the existing identity.source retained-seat flow. mode=global acts as a named resident identity through a session grant; requires identity.resident. Optional global fields: profile (normal|reviewer), scopes (explicit concrete grant scope list, wins over profile), ttl, e2ee (default true), renew (off|launch, default off)."
|
|
153
156
|
},
|
|
154
157
|
"residents": {
|
|
158
|
+
"hostOnly": true,
|
|
155
159
|
"description": "Host-owned map for global mode: resident name to absolute custody directory whose .aw holds the resident root keys and team certificate. Put this only in oats-local.yaml settings.oats.aweb.residents; committed workspace or soul files must never carry custody paths."
|
|
156
160
|
}
|
|
157
161
|
},
|
|
@@ -27,13 +27,13 @@ if (event === "spawn") {
|
|
|
27
27
|
const site = settings.site;
|
|
28
28
|
const project = settings.project;
|
|
29
29
|
const where = site && project ? `project ${project} on ${site}`
|
|
30
|
-
: site ? `site ${site} (project unset — ask your human
|
|
31
|
-
: project ? `project ${project} (site unset — ask your human
|
|
32
|
-
: `your deployment's Jira (site/project not configured — ask your human
|
|
30
|
+
: site ? `site ${site} (project unset — ask your human)`
|
|
31
|
+
: project ? `project ${project} (site unset — ask your human)`
|
|
32
|
+
: `your deployment's Jira (site/project not configured — ask your human to set tasks: { site, project } in the soul's soul.yaml, or settings.oats.jira.{site,project} in the deployment's oats-local.yaml)`;
|
|
33
33
|
out({
|
|
34
34
|
meta: { label, ...(site ? { site } : {}), ...(project ? { project } : {}) },
|
|
35
35
|
brief: `Tasks: Jira — ${where}. Your Jira identity is the label "${label}" (never the assignee field). Load the jira-tasks skill before touching tickets.`,
|
|
36
|
-
...(site && project ? {} : { warning: `oats-jira: settings incomplete (site: ${site || "unset"}, project: ${project || "unset"}) — set
|
|
36
|
+
...(site && project ? {} : { warning: `oats-jira: settings incomplete (site: ${site || "unset"}, project: ${project || "unset"}) — set tasks: { site, project } in the soul's soul.yaml, or settings.oats.jira.{site,project} in the deployment's oats-local.yaml` }),
|
|
37
37
|
});
|
|
38
38
|
} else {
|
|
39
39
|
warn(`unknown event "${event}" (expected spawn)`);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"capability": "oats.jira",
|
|
3
3
|
"command": "jira",
|
|
4
|
-
"version": "1.0.
|
|
5
|
-
"compatibility": { "oats": ">=0.
|
|
4
|
+
"version": "1.0.1",
|
|
5
|
+
"compatibility": { "oats": ">=0.26.0" },
|
|
6
6
|
"layer": "tasks",
|
|
7
7
|
"description": "Tasks layer via Jira: acli-based epic/story/task protocol, agent roster in epic descriptions, label-based agent identity.",
|
|
8
8
|
"requires": [
|
|
@@ -29,12 +29,15 @@ tracking and the agent roster. Agents do not know about projects — you know
|
|
|
29
29
|
|
|
30
30
|
## Site and project (from your deployment, never hardcoded)
|
|
31
31
|
|
|
32
|
-
Your Jira **site** and **project key** come from the
|
|
33
|
-
|
|
32
|
+
Your Jira **site** and **project key** come from the tasks payload OATS merged
|
|
33
|
+
for this instance: the soul's `soul.yaml` `tasks: { site, project }`, the
|
|
34
|
+
deployment's `oats-local.yaml` `settings.oats.jira.{site,project}`, or a
|
|
35
|
+
spawn's `--provider oats.jira key=value`. Find them, in order:
|
|
34
36
|
|
|
35
37
|
1. Your `TASK.md` briefing — the spawn hook writes a
|
|
36
38
|
`Tasks: Jira — project <KEY> on <site>` line.
|
|
37
|
-
2.
|
|
39
|
+
2. `./instance.json` in your instance home — `providers["oats.jira"]` is the
|
|
40
|
+
merged payload this instance received.
|
|
38
41
|
3. Ask your human.
|
|
39
42
|
|
|
40
43
|
Below, `<PROJECT>` means that project key. If site or project are unset,
|
|
@@ -10,23 +10,25 @@ if (event !== "spawn") output({ warning: `oats-linear: unknown event "${event}"
|
|
|
10
10
|
|
|
11
11
|
let settings = {};
|
|
12
12
|
try { settings = JSON.parse(process.env.OATS_SETTINGS || "{}"); }
|
|
13
|
-
catch { output({ warning: "oats-linear:
|
|
13
|
+
catch { output({ warning: "oats-linear: the tasks settings payload (OATS_SETTINGS) is not valid JSON" }); }
|
|
14
14
|
|
|
15
15
|
const instance = process.env.OATS_INSTANCE || "unknown-instance";
|
|
16
16
|
const team = settings.team;
|
|
17
17
|
const project = settings.project;
|
|
18
18
|
const label = `agent-${instance}`;
|
|
19
|
+
// Where team lives under the workspace model (0.26): the soul's tasks payload or this machine's settings.
|
|
20
|
+
const TEAM_HOMES = "tasks: { team } in the soul's soul.yaml, or settings.oats.linear.team in the deployment's oats-local.yaml";
|
|
19
21
|
const target = team
|
|
20
22
|
? `team ${team}${project ? `, default project ${project}` : ""}`
|
|
21
|
-
:
|
|
23
|
+
: `team unset — ask your human to set ${TEAM_HOMES}`;
|
|
22
24
|
const warnings = [];
|
|
23
|
-
if (!team) warnings.push(
|
|
25
|
+
if (!team) warnings.push(`settings.team is unset (set ${TEAM_HOMES})`);
|
|
24
26
|
if (!process.env.LINEAR_API_KEY) warnings.push("LINEAR_API_KEY is not in the spawn environment");
|
|
25
27
|
|
|
26
28
|
output({
|
|
27
29
|
meta: { label, ...(team ? { team } : {}), ...(project ? { project } : {}) },
|
|
28
30
|
brief: `Tasks: Linear — ${target}. Your agent identity is label "${label}"; keep the human assignee unchanged. Load the linear-tasks skill before touching issues.`,
|
|
29
31
|
...(warnings.length ? {
|
|
30
|
-
warning: `oats-linear: ${warnings.join("; ")} — see
|
|
32
|
+
warning: `oats-linear: ${warnings.join("; ")} — see the linear-tasks skill`,
|
|
31
33
|
} : {}),
|
|
32
34
|
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"capability": "oats.linear",
|
|
3
3
|
"command": "linear",
|
|
4
|
-
"version": "1.0.
|
|
5
|
-
"compatibility": { "oats": ">=0.
|
|
4
|
+
"version": "1.0.1",
|
|
5
|
+
"compatibility": { "oats": ">=0.26.0" },
|
|
6
6
|
"layer": "tasks",
|
|
7
7
|
"description": "Tasks layer via Linear: JSON-first GraphQL commands, project/issue/sub-issue workflow, label-based agent identity.",
|
|
8
8
|
"requires": [],
|
|
@@ -23,6 +23,12 @@ Get the target from the `Tasks: Linear` line in your `TASK.md` briefing:
|
|
|
23
23
|
- **project** is an optional deployment default. Do not invent one when unset.
|
|
24
24
|
- **alias** is your exact OATS instance name; your label is `agent-<alias>`.
|
|
25
25
|
|
|
26
|
+
team and project come from the tasks payload OATS merged for this instance —
|
|
27
|
+
the soul's `soul.yaml` `tasks: { team, project }`, the deployment's
|
|
28
|
+
`oats-local.yaml` `settings.oats.linear.*`, or a spawn's `--provider` — and
|
|
29
|
+
`./instance.json` in your instance home records it as `providers["oats.linear"]`.
|
|
30
|
+
If team is unset, stop and ask your human to set it there.
|
|
31
|
+
|
|
26
32
|
Before the first operation, run:
|
|
27
33
|
|
|
28
34
|
```bash
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"capability": "oats.review",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "1.2.1",
|
|
5
|
+
"compatibility": { "oats": ">=0.26.0" },
|
|
5
6
|
"description": "Post-commit review discipline: a fresh reviewer agent (defined by this capability) reviews each new commit's diff, reports its verdict to its spawner over the deployment's messaging layer (or in its transcript when there is none), and retires — plus the shared developer delivery discipline.",
|
|
6
7
|
"requires": [],
|
|
7
8
|
"agents": ["agents/reviewer"],
|