@awebai/oats 0.29.4 → 0.30.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 +12 -6
- package/bin/oats.mjs +194 -50
- package/capabilities/oats-aweb/bin/oats-aweb.mjs +538 -204
- package/capabilities/oats-aweb/injects/aweb.md +1 -1
- package/capabilities/oats-aweb/lib/binding-wire.mjs +31 -22
- package/capabilities/oats-aweb/oats.json +5 -12
- package/capabilities/oats-aweb/skills/VENDORED.md +4 -4
- package/capabilities/oats-aweb/skills/aweb-team-membership/SKILL.md +1 -1
- package/capabilities/oats-aweb/skills/oats-aweb/SKILL.md +83 -13
- package/capabilities/oats-code-review/injects/reviewer.md +26 -0
- package/capabilities/oats-code-review/oats.json +16 -0
- package/capabilities/oats-code-review/skills/adversarial-review/SKILL.md +66 -0
- package/capabilities/oats-code-review/skills/review-dev-docs/SKILL.md +30 -0
- package/capabilities/oats-code-review/skills/security-review/SKILL.md +56 -0
- package/capabilities/oats-code-review/skills/simplification-review/SKILL.md +34 -0
- package/capabilities/oats-developer/injects/developer.md +38 -0
- package/capabilities/oats-developer/oats.json +17 -0
- package/capabilities/oats-developer/skills/execution-strategy/SKILL.md +43 -0
- package/capabilities/oats-developer/skills/maintain-dev-docs/SKILL.md +47 -0
- package/capabilities/oats-developer/skills/run-the-review-loop/SKILL.md +65 -0
- package/capabilities/oats-developer/skills/understand-the-spec/SKILL.md +37 -0
- package/capabilities/oats-developer/skills/worktrees/SKILL.md +36 -0
- package/capabilities/oats-engineering-expert/injects/expert.md +37 -0
- package/capabilities/oats-engineering-expert/oats.json +17 -0
- package/capabilities/oats-engineering-expert/skills/coordinate-developers/SKILL.md +37 -0
- package/capabilities/oats-engineering-expert/skills/coordinate-experts/SKILL.md +52 -0
- package/capabilities/oats-engineering-expert/skills/land-your-prs/SKILL.md +50 -0
- package/capabilities/oats-engineering-expert/skills/plan-and-spec/SKILL.md +53 -0
- package/capabilities/oats-engineering-expert/skills/verify-developer-work/SKILL.md +49 -0
- package/capabilities/oats-okf/bin/oats-okf.mjs +8 -4
- package/capabilities/oats-okf/lib/binding-wire.mjs +47 -15
- package/capabilities/oats-okf/lib/inspection.mjs +26 -7
- package/capabilities/oats-okf/lib/sources.mjs +16 -2
- package/capabilities/oats-okf/lib/worker.mjs +5 -16
- package/capabilities/oats-okf/oats.json +6 -3
- package/capabilities/oats-okf-harvest/bin/okf-harvest.mjs +2 -2
- package/capabilities/oats-okf-harvest/oats.json +3 -3
- package/capabilities/oats-okf-harvest/skills/knowledge-harvest/SKILL.md +6 -6
- package/capabilities/oats-okf-maintenance/bin/okf-maintenance.mjs +2 -2
- package/capabilities/oats-okf-maintenance/injects/maintainer.md +1 -1
- package/capabilities/oats-okf-maintenance/oats.json +2 -2
- package/capabilities/oats-okf-maintenance/skills/knowledge-review/SKILL.md +1 -1
- package/capabilities/oats-okf-maintenance/skills/okf-trigger-setup/SKILL.md +11 -23
- package/capabilities/oats-workspace-experts/injects/oats-experts.md +26 -0
- package/capabilities/oats-workspace-experts/oats.json +9 -0
- package/docs/capabilities.md +160 -171
- package/docs/capability-manifest.schema.json +6 -11
- package/docs/configuration.md +213 -64
- package/docs/design/2026-09-16-knowledge-capability-contract.md +36 -50
- package/docs/design/2026-09-23-workspace-module-contracts.md +377 -544
- package/docs/design/2026-09-26-okf-knowledge-operations.md +132 -357
- package/docs/design/2026-09-27-team-model-v2.md +97 -117
- package/docs/design/2026-09-28-automations-trust.md +38 -0
- package/docs/design/2026-09-28-soul-launch-preference.md +63 -0
- package/docs/design/HISTORY.md +65 -0
- package/docs/design/README.md +23 -54
- package/docs/desktop-cli-api.md +1787 -1777
- package/docs/desktop.md +30 -91
- package/docs/execution-targets.md +146 -292
- package/docs/first-team.md +31 -17
- package/docs/implementation.md +76 -288
- package/docs/integrations.md +118 -320
- package/docs/knowledge-capability-authoring.md +25 -52
- package/docs/knowledge-reference/acceptance.md +3 -3
- package/docs/knowledge-reference/adoption.md +1 -1
- package/docs/knowledge-reference/harvester.md +2 -2
- package/docs/knowledge-reference/package-craft.md +3 -3
- package/docs/knowledge-reference/provider-mapping.md +3 -6
- package/docs/knowledge-reference/reader-capture.md +3 -3
- package/docs/knowledge-theory.md +62 -166
- package/docs/knowledge.md +225 -404
- package/docs/layers.md +42 -97
- package/docs/oats-local.schema.json +58 -5
- package/docs/oats-membership.schema.json +1 -8
- package/docs/oats-package.schema.json +5 -5
- package/docs/oats-workspace.schema.json +8 -22
- package/docs/official-catalog.md +25 -28
- package/docs/packages.md +45 -63
- package/docs/plans/0.30-close-out.md +61 -0
- package/docs/release-lane.md +77 -0
- package/docs/release-notes/oats-framework-v1.1.3.md +10 -8
- package/docs/release-notes/v0.19.0.md +48 -147
- package/docs/release-notes/v0.19.1.md +2 -3
- package/docs/release-notes/v0.19.3.md +2 -15
- package/docs/release-notes/v0.20.0.md +0 -15
- package/docs/release-notes/v0.22.0.md +71 -138
- package/docs/release-notes/v0.22.1.md +42 -90
- package/docs/release-notes/v0.22.10.md +1 -1
- package/docs/release-notes/v0.22.11.md +1 -47
- package/docs/release-notes/v0.22.12.md +4 -13
- package/docs/release-notes/v0.22.13.md +1 -42
- package/docs/release-notes/v0.22.14.md +3 -11
- package/docs/release-notes/v0.22.15.md +1 -46
- package/docs/release-notes/v0.22.16.md +6 -8
- package/docs/release-notes/v0.22.18.md +1 -99
- package/docs/release-notes/v0.22.19.md +3 -14
- package/docs/release-notes/v0.22.2.md +6 -15
- package/docs/release-notes/v0.22.3.md +0 -1
- package/docs/release-notes/v0.22.4.md +1 -14
- package/docs/release-notes/v0.22.5.md +2 -12
- package/docs/release-notes/v0.22.6.md +0 -3
- package/docs/release-notes/v0.23.0.md +9 -25
- package/docs/release-notes/v0.23.1.md +9 -25
- package/docs/release-notes/v0.23.2.md +2 -4
- package/docs/release-notes/v0.24.0.md +56 -97
- package/docs/release-notes/v0.24.1.md +7 -11
- package/docs/release-notes/v0.24.10.md +34 -45
- package/docs/release-notes/v0.24.11.md +12 -20
- package/docs/release-notes/v0.24.12.md +35 -48
- package/docs/release-notes/v0.24.13.md +34 -41
- package/docs/release-notes/v0.24.2.md +9 -13
- package/docs/release-notes/v0.24.3.md +7 -11
- package/docs/release-notes/v0.24.4.md +6 -6
- package/docs/release-notes/v0.24.5.md +6 -10
- package/docs/release-notes/v0.24.6.md +2 -5
- package/docs/release-notes/v0.24.7.md +46 -75
- package/docs/release-notes/v0.24.8.md +58 -96
- package/docs/release-notes/v0.24.9.md +38 -54
- package/docs/release-notes/v0.25.0.md +59 -76
- package/docs/release-notes/v0.25.1.md +57 -81
- package/docs/release-notes/v0.25.2.md +51 -70
- package/docs/release-notes/v0.25.3.md +11 -13
- package/docs/release-notes/v0.25.4.md +9 -13
- package/docs/release-notes/v0.25.5.md +3 -5
- package/docs/release-notes/v0.25.6.md +20 -29
- package/docs/release-notes/v0.25.7.md +5 -7
- package/docs/release-notes/v0.25.8.md +26 -39
- package/docs/release-notes/v0.26.0.md +175 -646
- package/docs/release-notes/v0.27.0.md +4 -5
- package/docs/release-notes/v0.27.1.md +4 -6
- package/docs/release-notes/v0.27.2.md +1 -1
- package/docs/release-notes/v0.28.0.md +57 -124
- package/docs/release-notes/v0.29.0.md +89 -208
- package/docs/release-notes/v0.29.1.md +1 -1
- package/docs/release-notes/v0.29.2.md +3 -4
- package/docs/release-notes/v0.30.0.md +205 -0
- package/docs/schedules.md +280 -363
- package/docs/servers.md +99 -117
- package/docs/soul.schema.json +2 -9
- package/docs/souls-and-instances.md +145 -158
- package/docs/workspaces.md +132 -215
- package/lib/automations.mjs +21 -6
- package/lib/core.mjs +226 -74
- package/lib/instance-events.mjs +1 -1
- package/lib/instance-inspect.mjs +109 -34
- package/lib/instance-lifecycle.mjs +14 -1
- package/lib/instance-resolution.mjs +26 -27
- package/lib/launch-preference.mjs +87 -0
- package/lib/materialize.mjs +3 -3
- package/lib/resolve.mjs +29 -87
- package/lib/schedule.mjs +1 -1
- package/lib/teams-verbs.mjs +195 -0
- package/lib/teams.mjs +190 -0
- package/lib/triggers.mjs +2 -2
- package/lib/workspace.mjs +54 -147
- package/package-catalog.json +9 -15
- package/package.json +1 -1
- package/skills/oats-getting-started/SKILL.md +25 -13
- package/capabilities/oats-review/injects/review.md +0 -69
- package/capabilities/oats-review/oats.json +0 -10
- package/capabilities/oats-review/skills/code-review/SKILL.md +0 -44
- package/capabilities/oats-review/skills/security-review/SKILL.md +0 -59
- package/docs/conventions.md +0 -90
- package/docs/design/2026-09-07-architecture-reassessment.md +0 -131
- package/docs/design/2026-09-07-desktop-souls-capabilities.md +0 -50
- package/docs/design/2026-09-07-mobile-agent-management-proposal.md +0 -228
- package/docs/design/2026-09-08-expert-assisted-deployment-proposal.md +0 -558
- package/docs/design/2026-09-13-knowledge-and-memory-direction.md +0 -744
- package/docs/design/2026-09-13-knowledge-implementation.md +0 -127
- package/docs/design/2026-09-13-knowledge-location-contract.md +0 -340
- package/docs/design/2026-09-14-artifact-retention-contract.md +0 -190
- package/docs/design/2026-09-14-portable-souls-and-git-workspaces.md +0 -708
- package/docs/design/2026-09-14-portable-souls-contract-amendments.md +0 -85
- package/docs/design/2026-09-14-portable-souls-explainer.md +0 -750
- package/docs/design/2026-09-15-captured-dispatch.md +0 -127
- package/docs/design/2026-09-15-captured-resolution-records.md +0 -143
- package/docs/design/2026-09-15-package-preparation.md +0 -100
- package/docs/design/2026-09-15-portable-data-contract.md +0 -121
- package/docs/design/2026-09-15-portable-declarations.md +0 -189
- package/docs/design/2026-09-15-portable-souls-handoff.md +0 -150
- package/docs/design/2026-09-15-portable-souls-implementation.md +0 -417
- package/docs/design/2026-09-15-selection-lock-and-approval.md +0 -122
- package/docs/design/2026-09-15-source-observation.md +0 -119
- package/docs/design/2026-09-16-captured-admission.md +0 -77
- package/docs/design/2026-09-16-captured-helper-dispatch.md +0 -105
- package/docs/design/2026-09-16-captured-launch-inputs.md +0 -42
- package/docs/design/2026-09-16-command-profile-preparation.md +0 -86
- package/docs/design/2026-09-16-fresh-install-first-rollout.md +0 -47
- package/docs/design/2026-09-16-fresh-operator-walkthrough.md +0 -282
- package/docs/design/2026-09-16-messaging-capability-contract.md +0 -59
- package/docs/design/2026-09-16-portable-migration-evidence.md +0 -158
- package/docs/design/2026-09-16-portable-onboarding.md +0 -179
- package/docs/design/2026-09-16-prepare-request-transport.md +0 -26
- package/docs/design/2026-09-16-provider-binding-codecs.md +0 -98
- package/docs/design/2026-09-16-provider-binding-wire.md +0 -274
- package/docs/design/2026-09-17-capability-helper-input-contract.md +0 -95
- package/docs/design/2026-09-17-captured-backend-parity.md +0 -53
- package/docs/design/2026-09-17-captured-native-start.md +0 -58
- package/docs/design/2026-09-17-portable-boundary-hookup.md +0 -19
- package/docs/design/2026-09-17-portable-boundary-resources.md +0 -52
- package/docs/design/2026-09-17-public-captured-start.md +0 -108
- package/docs/design/2026-09-17-public-prepare-request.md +0 -90
- package/docs/design/2026-09-18-captured-pi-host.md +0 -205
- package/docs/design/2026-09-18-first-cut-release-checklist.md +0 -131
- package/docs/design/2026-09-18-herdr-protocol-compatibility.md +0 -60
- package/docs/design/2026-09-20-redesign-program-board.md +0 -142
- package/docs/design/2026-09-20-workspace-and-portable-adoption-plan.md +0 -289
- package/docs/design/2026-09-20-workspace-onboarding-public.md +0 -207
- package/docs/design/2026-09-22-desktop-parity-seams.md +0 -58
- package/docs/design/2026-09-23-simplified-workspace-model.md +0 -711
- package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +0 -65
- package/docs/design/2026-09-24-desktop-phase-f-boundary.md +0 -242
- package/docs/design/2026-09-24-phase-d-plan.md +0 -305
- package/docs/design/2026-09-25-teams-contract.md +0 -258
- package/docs/design/2026-09-26-desktop-design-brief-architecture.md +0 -241
- package/docs/design/desktop-ux-plan.md +0 -362
- package/docs/design/launch-configurations.md +0 -168
- package/docs/design/okf-mirror-provenance.md +0 -105
- package/docs/design/operations-contract.md +0 -141
- package/docs/oats-member.schema.json +0 -38
- package/skills/integration-authoring/SKILL.md +0 -84
- package/skills/oats-support/SKILL.md +0 -79
- package/skills/skill-craft/SKILL.md +0 -109
- package/skills/soul-craft/SKILL.md +0 -116
package/README.md
CHANGED
|
@@ -13,8 +13,6 @@ OATS—**Open Agent Team Specification**—is a framework for building, running
|
|
|
13
13
|
|
|
14
14
|
The [official catalog](docs/official-catalog.md) is the reviewed list of official packages; a workspace trusts a package by declaring it, and listing does not declare it for you.
|
|
15
15
|
|
|
16
|
-
**oats.aweb 1.13.1** attaches resident session-grant homes to the resident custody service during minting, verifies the written `grant.yaml` custody locator, and fails closed if an installed `aw` cannot write or prove that attachment. If a grant-sent message ever arrives as unverified, report the missing custody attachment instead of retrying.
|
|
17
|
-
|
|
18
16
|
For example, a kernel expert, a UX expert and a customer-support expert can share capabilities for learning and coordination, while each has specific capabilities for its own area of expertise.
|
|
19
17
|
|
|
20
18
|
Through your capabilities, you remain in control of:
|
|
@@ -179,7 +177,7 @@ For example, you might want:
|
|
|
179
177
|
- **Long-running instances to retain working understanding.** A customer-support instance carries its investigations, observations and unresolved questions across many tasks.
|
|
180
178
|
- **Selected learning to become shared knowledge.** Useful findings are reviewed and made available to other instances, while task-specific context stays with the instance that needs it.
|
|
181
179
|
|
|
182
|
-
Your knowledge capability determines what context an instance receives, how it builds on experience, and which learning is retained or shared. It can organise knowledge per soul, per topic or per project, centrally or alongside soul definitions, provided that it actually supports the chosen arrangement.
|
|
180
|
+
Your knowledge capability determines what context an instance receives, how it builds on experience, and which learning is retained or shared. It can organise knowledge per soul, per topic or per project, centrally or alongside soul definitions, provided that it actually supports the chosen arrangement.
|
|
183
181
|
|
|
184
182
|
The same principle applies to messaging and tasks: **OATS provides the contracts; you choose how your team works through them.**
|
|
185
183
|
|
|
@@ -210,7 +208,15 @@ Begin with a small team and a real piece of work:
|
|
|
210
208
|
4. Create instances for their assignments.
|
|
211
209
|
5. Verify that work, communication, learning and handoff behave as intended.
|
|
212
210
|
|
|
213
|
-
|
|
211
|
+
On a machine with Node.js 22+, Git and tmux, and a workspace repository to point at:
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
npm install -g @awebai/oats
|
|
215
|
+
oats onboard ~/acme --workspace git:github.com/acme/agents # this machine's deployment
|
|
216
|
+
oats spawn <soul> --task "First concrete task"
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
The [first-team guide](docs/first-team.md) walks through writing the workspace, its members and a first soul; the [release notes](docs/release-notes/) state the supported scope of each version.
|
|
214
220
|
|
|
215
221
|
Further documentation:
|
|
216
222
|
|
|
@@ -220,10 +226,10 @@ Further documentation:
|
|
|
220
226
|
- [Knowledge operations](docs/knowledge.md)
|
|
221
227
|
- [Knowledge, instances and evolving expertise](docs/knowledge-theory.md)
|
|
222
228
|
- [Packages](docs/packages.md)
|
|
223
|
-
- [
|
|
229
|
+
- [Sessions](docs/execution-targets.md)
|
|
224
230
|
- [OATS Desktop](docs/desktop.md)
|
|
225
231
|
|
|
226
|
-
This README explains the framework and its direction. Advanced mechanisms such as automatic speciation and context cloning require their own implementation and verification; the
|
|
232
|
+
This README explains the framework and its direction. Advanced mechanisms such as automatic speciation and context cloning require their own implementation and verification; the model is not a claim that every feature or capability combination already works.
|
|
227
233
|
|
|
228
234
|
## Contributing and releases
|
|
229
235
|
|
package/bin/oats.mjs
CHANGED
|
@@ -27,12 +27,14 @@ import {
|
|
|
27
27
|
LAYERS, OATS_VERSION, manifestOperations, upgradeHomeMeta,
|
|
28
28
|
capabilityManifests, capabilityTrust, capabilityExecutablePath,
|
|
29
29
|
officialPackageCatalog, officialCatalogFile, officialCapabilityAliases, resolvedFromHome, resolvedFromPrepared, teamEnv, isWorkspaceHome, preWorkspaceHome, isCapturedHome, capturedHomeRefusal, composeInstanceAgentsMd, parseYamlNested, withConfigFile,
|
|
30
|
-
findInstanceHome, findInstanceHomes, workspaceOf, stopInstanceSession, ensureRoot, findRoot, findAgent, findAgentAt, legacyLocalAgents, legacyCapturedHomes, listAgents, listInstances, servedIdentityLine, spawnInstanceAsync, instanceSoulDir, launchConfigsAt, explicitInstanceName, retireInstance, inspectInstanceSession, inputInstanceSession, attachInstanceSession, startInstanceSession, defaultRepo, RELATIONS, validateLaunchConfig, renderLaunchRecipe, describeLaunchCommand, redactLaunchRecipe, LAUNCH_HARNESSES, planLaunch, redactLaunchCommand, restartInstanceSession,
|
|
30
|
+
findInstanceHome, findInstanceHomes, workspaceOf, stopInstanceSession, ensureRoot, findRoot, findAgent, findAgentAt, legacyLocalAgents, legacyCapturedHomes, listAgents, listInstances, servedIdentityLine, spawnInstanceAsync, instanceSoulDir, launchConfigsAt, launchReportFor, explicitInstanceName, retireInstance, inspectInstanceSession, inputInstanceSession, attachInstanceSession, startInstanceSession, defaultRepo, RELATIONS, validateLaunchConfig, renderLaunchRecipe, describeLaunchCommand, redactLaunchRecipe, LAUNCH_HARNESSES, planLaunch, redactLaunchCommand, restartInstanceSession,
|
|
31
31
|
} from "../lib/core.mjs";
|
|
32
32
|
import {
|
|
33
33
|
writeFileAtomic, LOCK_FILE, readLock, writeLock, resolvePackages, memoizedRemote,
|
|
34
34
|
classifyPackageValue, parsePackageRequest } from "../lib/packages.mjs";
|
|
35
35
|
import { loadLocal, validateWorkspace, validateLocal, discoverPackageSouls, workspaceWarnings } from "../lib/workspace.mjs";
|
|
36
|
+
import { recordedTeams, reportRows, soulKeyOf, soulTeams, teamModel } from "../lib/teams.mjs";
|
|
37
|
+
import { launchLayers } from "../lib/launch-preference.mjs";
|
|
36
38
|
import { parseConfigData } from "../lib/config-data.mjs";
|
|
37
39
|
import * as remoteModule from "../lib/remote.mjs";
|
|
38
40
|
import YAML from "yaml";
|
|
@@ -72,7 +74,7 @@ function expandInlineValues(argv) {
|
|
|
72
74
|
const { argv: args, problem: argvProblem } = expandInlineValues(rawArgs);
|
|
73
75
|
let cmd = args[0];
|
|
74
76
|
const HELP_WORDS = new Set(["help", "--help", "-h"]);
|
|
75
|
-
const KERNEL_COMMANDS = new Set(["automations", "trigger", "capture", "capabilities", "doctor", "inspect", "instance", "operation", "package", "readiness", "souls", "launch-config", "experimental", "onboard", "pane", "recall", "retire", "root", "schedule", "server", "session", "setup", "spawn", "status", "sync", "update", "version", "workspace"]);
|
|
77
|
+
const KERNEL_COMMANDS = new Set(["automations", "trigger", "capture", "capabilities", "doctor", "inspect", "instance", "operation", "package", "readiness", "souls", "soul", "teams", "launch-config", "experimental", "onboard", "pane", "recall", "retire", "root", "schedule", "server", "session", "setup", "spawn", "status", "sync", "update", "version", "workspace"]);
|
|
76
78
|
/** Commands whose argv another parser reads (packages/record and packages/experimental parse process.argv). */
|
|
77
79
|
const OWN_ARGV_COMMANDS = new Set(["capture", "recall", "setup", "experimental"]);
|
|
78
80
|
/** Commands `--server <id>` runs on a registered server. */
|
|
@@ -411,18 +413,17 @@ function printWorkspaceInspect(doc) {
|
|
|
411
413
|
for (const p of doc.problems) console.log(` ! ${p.code}: ${p.message}`);
|
|
412
414
|
}
|
|
413
415
|
|
|
414
|
-
/** `{ teams, teamsSource }` for a session start of a workspace home: its
|
|
415
|
-
* live (
|
|
416
|
-
*
|
|
417
|
-
* answer. Anything that is not a readable workspace home gets nothing here — the start
|
|
416
|
+
/** `{ teams, defaultTeam, teamsSource }` for a session start of a workspace home: its teams read
|
|
417
|
+
* live (the workspace host + oats-local.yaml), which the launch hook re-checks joined memberships
|
|
418
|
+
* against — or the spawn record, marked `recorded`, when the read cannot answer. Anything that is not a readable workspace home gets nothing here — the start
|
|
418
419
|
* itself refuses it. */
|
|
419
420
|
async function homeLiveTeams(home) {
|
|
420
421
|
let meta;
|
|
421
422
|
try { meta = JSON.parse(readFileSync(join(home, "instance.json"), "utf8")); } catch { return {}; }
|
|
422
423
|
if (!isWorkspaceHome(meta)) return {};
|
|
423
424
|
const { liveTeams } = await import("../lib/instance-resolution.mjs");
|
|
424
|
-
const { teams, source } = await liveTeams(home, meta, { remoteOptions: remoteOptionsFromEnv() });
|
|
425
|
-
return Array.isArray(teams) ? { teams, teamsSource: source } : {};
|
|
425
|
+
const { teams, defaultTeam, source } = await liveTeams(home, meta, { remoteOptions: remoteOptionsFromEnv() });
|
|
426
|
+
return Array.isArray(teams) ? { teams, defaultTeam, teamsSource: source } : {};
|
|
426
427
|
}
|
|
427
428
|
|
|
428
429
|
// ---------- operation run: generic invoke through the capability engine ----------
|
|
@@ -1072,33 +1073,46 @@ const short = (oid) => (typeof oid === "string" ? oid.slice(0, 8) : "?");
|
|
|
1072
1073
|
/** Display name of a discovery: the workspace's name, or the standalone label (decision 10). */
|
|
1073
1074
|
const workspaceName = (discovery) => discovery.workspace?.name ?? `standalone:${memberLabel(discovery.key)}`;
|
|
1074
1075
|
const memberLabel = (key) => String(key).split("/").filter(Boolean).pop()?.replace(/\.git$/, "") || String(key);
|
|
1075
|
-
const teamLabel = (team) => team ?? "unassigned";
|
|
1076
1076
|
const originOf = (item) => (item.package ? `package ${item.package} v${item.version}` : `member ${item.repoKey} @ ${short(item.commit)}`);
|
|
1077
1077
|
|
|
1078
|
-
/** A soul's
|
|
1079
|
-
*
|
|
1080
|
-
|
|
1078
|
+
/** A soul's launch here (feature launch-preference): the `Launch` report a spawn with no flags would decide
|
|
1079
|
+
* (souls.launch, the soul's own launch, the host default), and the desktop-facts `harness`, `model`,
|
|
1080
|
+
* `harnessFrom` equal to its effective launch (`harnessFrom` "kernel-default" for the host default). */
|
|
1081
|
+
function soulLaunchFacts(entry, local, { launchConfigs, contextDir }) {
|
|
1082
|
+
const launch = launchReportFor({ layers: launchLayers({ definition: entry.definition, entry, key: soulKeyOf(entry), local }), launchConfigs, contextDir });
|
|
1083
|
+
return { harness: launch.effective.harness, model: launch.effective.model, harnessFrom: launch.from === "host" ? "kernel-default" : launch.from, launch };
|
|
1084
|
+
}
|
|
1081
1085
|
|
|
1082
1086
|
/** Rows of every soul and capability of confirmed members (+ external souls) + locked package
|
|
1083
1087
|
* capabilities. Souls have no private mode (0.26.0); a private member capability is listed with
|
|
1084
|
-
* `private: true` — repo-owned: usable only by its own repo's souls (E_CAPABILITY_PRIVATE).
|
|
1085
|
-
|
|
1088
|
+
* `private: true` — repo-owned: usable only by its own repo's souls (E_CAPABILITY_PRIVATE).
|
|
1089
|
+
* Each soul row carries its teams HERE (team model v2: `teams`, `defaultTeam`), from the committed
|
|
1090
|
+
* shared teams and `local` (oats-local.yaml); both null when its teams do not resolve (E_TEAM_*). */
|
|
1091
|
+
function workspaceItems(discovery, lock, local, deploymentDir) {
|
|
1086
1092
|
const souls = [];
|
|
1093
|
+
let launchConfigs = {};
|
|
1094
|
+
try { launchConfigs = deploymentDir ? launchConfigsAt(deploymentDir) : {}; } catch { launchConfigs = {}; }
|
|
1095
|
+
const soulDefaults = (entry) => soulLaunchFacts(entry, local, { launchConfigs, contextDir: deploymentDir });
|
|
1087
1096
|
const capabilities = [];
|
|
1097
|
+
const model = teamModel(discovery.standalone === true ? null : discovery.workspace, local, { workspaceKey: discovery.key ?? null });
|
|
1098
|
+
const teamsHere = (entry) => {
|
|
1099
|
+
try { const t = soulTeams(model, soulKeyOf(entry)); return { teams: reportRows(t.teams), defaultTeam: t.defaultTeam }; }
|
|
1100
|
+
catch (e) { if (String(e?.code).startsWith("E_TEAM_")) return { teams: null, defaultTeam: null }; throw e; }
|
|
1101
|
+
};
|
|
1088
1102
|
for (const m of discovery.members) {
|
|
1089
1103
|
if (!m.confirmed && !(discovery.standalone === true && m.key === discovery.key)) continue;
|
|
1090
|
-
for (const s of m.souls) souls.push({ name: s.name, origin: originOf(s), kind: "member", repoKey: s.repoKey, commit: s.commit,
|
|
1091
|
-
for (const c of m.capabilities) capabilities.push({ name: c.name, origin: originOf(c), kind: "member", repoKey: c.repoKey, commit: c.commit,
|
|
1104
|
+
for (const s of m.souls) souls.push({ name: s.name, key: soulKeyOf(s), origin: originOf(s), kind: "member", repoKey: s.repoKey, commit: s.commit, ...teamsHere(s), private: s.private, path: s.path, work: s.definition.work ?? null, description: s.definition.description ?? null, ...soulDefaults(s) });
|
|
1105
|
+
for (const c of m.capabilities) capabilities.push({ name: c.name, origin: originOf(c), kind: "member", repoKey: c.repoKey, commit: c.commit, private: c.private, path: c.path, layer: c.manifest.layer ?? null, version: c.manifest.version ?? null });
|
|
1092
1106
|
}
|
|
1093
1107
|
for (const ext of discovery.external || []) {
|
|
1094
1108
|
const s = ext.soul;
|
|
1095
|
-
souls.push({ name: s.name, origin: `external ${s.repoKey} @ ${short(s.commit)}`, kind: "external", repoKey: s.repoKey, commit: s.commit,
|
|
1109
|
+
souls.push({ name: s.name, key: soulKeyOf(s), origin: `external ${s.repoKey} @ ${short(s.commit)}`, kind: "external", repoKey: s.repoKey, commit: s.commit, ...teamsHere(s), private: s.private, path: s.path, work: s.definition.work ?? null, description: s.definition.description ?? null, ...soulDefaults(s) });
|
|
1096
1110
|
}
|
|
1097
1111
|
for (const s of discovery.packageSouls || []) {
|
|
1098
|
-
souls.push({ name: s.name, qualifiedName: s.qualifiedName, origin: originOf(s), kind: "package", package: s.package, version: s.version, repoKey: s.repoKey, commit: s.commit,
|
|
1112
|
+
souls.push({ name: s.name, key: soulKeyOf(s), qualifiedName: s.qualifiedName, origin: originOf(s), kind: "package", package: s.package, version: s.version, repoKey: s.repoKey, commit: s.commit, ...teamsHere(s), private: false, path: s.path, work: s.definition.work ?? null, description: s.definition.description ?? null, ...soulDefaults(s) });
|
|
1099
1113
|
}
|
|
1100
1114
|
for (const [id, entry] of Object.entries(lock?.packages || {})) {
|
|
1101
|
-
for (const name of entry.capabilities) capabilities.push({ name, origin: originOf({ package: id, version: entry.version }), kind: "package", package: id, version: entry.version, commit: entry.commit,
|
|
1115
|
+
for (const name of entry.capabilities) capabilities.push({ name, origin: originOf({ package: id, version: entry.version }), kind: "package", package: id, version: entry.version, commit: entry.commit, private: false });
|
|
1102
1116
|
}
|
|
1103
1117
|
const byName = (a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : a.origin < b.origin ? -1 : a.origin > b.origin ? 1 : 0);
|
|
1104
1118
|
souls.sort(byName);
|
|
@@ -1109,7 +1123,7 @@ function workspaceItems(discovery, lock) {
|
|
|
1109
1123
|
/** Membership rows for `sync` / `workspace status`. */
|
|
1110
1124
|
function memberRows(discovery) {
|
|
1111
1125
|
return discovery.members.map((m) => ({
|
|
1112
|
-
key: m.key, name: memberLabel(m.key), commit: m.commit ?? null, confirmed: m.confirmed, status: m.confirmed ? "confirmed" : m.reason, detail: m.detail ?? null,
|
|
1126
|
+
key: m.key, name: memberLabel(m.key), commit: m.commit ?? null, confirmed: m.confirmed, status: m.confirmed ? "confirmed" : m.reason, detail: m.detail ?? null,
|
|
1113
1127
|
souls: m.souls.map((s) => s.name), capabilities: m.capabilities.map((c) => c.name), publishes: m.publishes ?? null,
|
|
1114
1128
|
}));
|
|
1115
1129
|
}
|
|
@@ -1180,7 +1194,7 @@ async function performSync(ctx, bail, { onDiscovered } = {}) {
|
|
|
1180
1194
|
const members = memberRows(discovery);
|
|
1181
1195
|
const packages = packageRows(lock);
|
|
1182
1196
|
const changes = resolved.changes;
|
|
1183
|
-
const items = workspaceItems(discovery, lock);
|
|
1197
|
+
const items = workspaceItems(discovery, lock, ctx.local, ctx.deploymentDir);
|
|
1184
1198
|
const report = { syncApi: 1, automations: automationCounts(automations), standalone: discovery.standalone === true || undefined, workspace: { name: workspaceName(discovery), key: discovery.key, url: discovery.url, commit: discovery.commit, observedAt: discovery.observedAt, local: ctx.localPath, lock: lockFile }, members, packages, changes, problems, warnings: discovery.warnings ?? [] };
|
|
1185
1199
|
return { report, lock, discovery, items, lockFile, problems };
|
|
1186
1200
|
}
|
|
@@ -1216,10 +1230,8 @@ function printSyncReport(ctx, synced) {
|
|
|
1216
1230
|
const packageSouls = items.souls.filter((s) => s.kind === "package");
|
|
1217
1231
|
const disabledHere = items.souls.filter(isDisabled);
|
|
1218
1232
|
console.log(`souls ${items.souls.length} discovered (${memberSouls.length} members, ${externalSouls.length} external, ${packageSouls.length ? `${packageSouls.length} package, ` : ""}${disabledHere.length} disabled here) · ${repoOwned.length} private capabilit${repoOwned.length === 1 ? "y" : "ies"}${repoOwned.length ? ` (${repoOwned.map((c) => `${c.name}, ${memberLabel(c.repoKey)} only`).join("; ")})` : ""}`);
|
|
1219
|
-
const
|
|
1220
|
-
|
|
1221
|
-
for (const c of items.capabilities.filter((c) => c.kind === "member")) { const t = teams.get(c.team) || { souls: 0, capabilities: 0 }; t.capabilities++; teams.set(c.team, t); }
|
|
1222
|
-
console.log(`teams ${[...teams.entries()].sort(([a], [b]) => (a < b ? -1 : 1)).map(([team, n]) => `${team} ${n.souls} soul${n.souls === 1 ? "" : "s"}${n.capabilities ? `, ${n.capabilities} capabilit${n.capabilities === 1 ? "y" : "ies"}` : ""}`).join(" · ") || "(none)"}`);
|
|
1233
|
+
const sharedTeams = Object.keys(discovery.workspace?.teams || {}).sort();
|
|
1234
|
+
console.log(`teams ${sharedTeams.length ? `${sharedTeams.join(", ")} (shared)` : "(no shared teams)"} · this deployment's: oats teams`);
|
|
1223
1235
|
const ac = report.automations;
|
|
1224
1236
|
if (ac.triggers || ac.schedules) console.log(`automations ${ac.triggers} trigger${ac.triggers === 1 ? "" : "s"}, ${ac.schedules} schedule${ac.schedules === 1 ? "" : "s"} in the members (oats trigger list · oats schedule list)`);
|
|
1225
1237
|
for (const p of synced.problems ?? discovery.problems) console.log(`problem ${p.code} ${p.repoKey ? `${memberLabel(p.repoKey)}:` : ""}${p.path} ${p.message}`);
|
|
@@ -1417,32 +1429,46 @@ async function workspaceCmd() {
|
|
|
1417
1429
|
const locked = new Set(packages.map((p) => p.id));
|
|
1418
1430
|
const unsynced = declared.filter((id) => !locked.has(id));
|
|
1419
1431
|
const stale = packages.filter((p) => !declared.includes(p.id)).map((p) => p.id);
|
|
1420
|
-
const result = { workspaceStatusApi: 1, standalone: standalone || undefined, workspace: { name: workspaceName(discovery), key: discovery.key, url: discovery.url, commit: discovery.commit, observedAt: discovery.observedAt, local: ctx.localPath, teams:
|
|
1432
|
+
const result = { workspaceStatusApi: 1, standalone: standalone || undefined, workspace: { name: workspaceName(discovery), key: discovery.key, url: discovery.url, commit: discovery.commit, observedAt: discovery.observedAt, local: ctx.localPath, teams: sharedTeamRows(discovery) }, members, packages, declaredPackages: declared, unsynced, stale, external: (discovery.external || []).map((e) => ({ source: e.source, soul: e.soul.name })), problems: discovery.problems, warnings: [...(discovery.warnings ?? [])] };
|
|
1421
1433
|
// Workspace automations (0.29.0), from the snapshot `oats sync` took, placed on this host.
|
|
1422
1434
|
const actx = scopeAutomations(ctx.deploymentDir, {});
|
|
1423
1435
|
result.automations = { host: actx.host.name, snapshot: actx.snapshot ? { takenAt: actx.snapshot.takenAt, problems: actx.snapshot.problems.length } : null, rows: [...actx.triggers, ...actx.schedules].map((a) => ({ kind: a.kind, id: a.id, runsOn: a.runsOn, owner: a.owner, runsHere: a.placement.runsHere, reason: a.placement.reason, enabledHere: a.placement.enabledHere, origin: a.origin, ...(a.invalid ? { invalid: a.invalid } : {}) })) };
|
|
1436
|
+
// Automation trust (0.30): a workspace automation placed here but not trusted, and a trust entry
|
|
1437
|
+
// that names none, are warnings the operator acts on in oats-local.yaml.
|
|
1438
|
+
for (const a of [...actx.triggers, ...actx.schedules].filter((x) => x.placement.reason === "untrusted")) {
|
|
1439
|
+
result.warnings.push({ code: "automation-untrusted", kind: a.kind, id: a.id, message: `${a.kind} ${a.id} is declared for this host (${a.runsOn}, as ${a.owner}) but not trusted here, so it does not run`, remedy: A.trustRemedy(a.id) });
|
|
1440
|
+
}
|
|
1441
|
+
for (const entry of A.staleTrust(actx.host, [...actx.triggers, ...actx.schedules])) {
|
|
1442
|
+
result.warnings.push({ code: "automation-trust-stale", entry, message: `oats-local.yaml automations.trust names ${entry}, which is no workspace trigger or schedule${actx.snapshot ? "" : " (no automations snapshot yet: run oats sync)"}; its member may not have synced yet` });
|
|
1443
|
+
}
|
|
1424
1444
|
await workspaceStatusFacts(result, discovery, lock, ctx);
|
|
1425
1445
|
if (JSON_MODE) { jsonOk(result); return; }
|
|
1426
1446
|
console.log(`workspace ${workspaceName(discovery)} (${discovery.key} @ ${short(discovery.commit)}) local ${shortPath(ctx.localPath)}\n`);
|
|
1427
1447
|
if (standalone) console.log(` (${standaloneNote(discovery)})\n`);
|
|
1428
1448
|
console.log("Members:");
|
|
1429
|
-
printTable(["member", "status", "commit", "
|
|
1449
|
+
printTable(["member", "status", "commit", "souls", "capabilities", "publishes"], members.map((m) => [m.name, m.status, short(m.commit), m.souls.join(",") || "—", m.capabilities.join(",") || "—", m.publishes ? `${m.publishes.package} v${m.publishes.version ?? "?"}` : "—"]));
|
|
1430
1450
|
for (const m of members.filter((m) => !m.confirmed)) console.log(` ${m.name}: ${m.detail}`);
|
|
1431
1451
|
console.log("\nPackages:");
|
|
1432
1452
|
if (!packages.length) console.log(unsynced.length ? ` (none locked yet — \`oats sync\` resolves ${unsynced.join(", ")})` : " (none)");
|
|
1433
1453
|
else printTable(["package", "version", "source", "commit", "capabilities", "souls"], packages.map((p) => [p.id, p.version, p.source, short(p.commit), p.capabilities.join(","), p.souls.join(",") || "—"]));
|
|
1434
1454
|
if (packages.length && unsynced.length) console.log(` declared but not locked (run \`oats sync\`): ${unsynced.join(", ")}`);
|
|
1435
1455
|
if (stale.length) console.log(` locked but no longer declared (run \`oats sync\`): ${stale.join(", ")}`);
|
|
1436
|
-
if (result.external.length) console.log(`\nExternal: ${result.external.map((e) => `${e.soul} (${e.source.replace(/@([0-9a-f]{40})$/, (_, o) => `@${short(o)}`)}
|
|
1456
|
+
if (result.external.length) console.log(`\nExternal: ${result.external.map((e) => `${e.soul} (${e.source.replace(/@([0-9a-f]{40})$/, (_, o) => `@${short(o)}`)})`).join(" ")}`);
|
|
1437
1457
|
if (result.automations.rows.length) {
|
|
1438
1458
|
console.log(`\nAutomations (this host: ${result.automations.host ?? "unnamed — set host.name in oats-local.yaml"}):`);
|
|
1439
1459
|
printTable(["kind", "id", "runs on", "owner", "here"], result.automations.rows.map((r) => [r.kind, r.id, r.runsOn, r.owner, r.runsHere ? "runs here" : r.invalid ? "invalid" : r.reason ?? "disabled here"]));
|
|
1440
1460
|
}
|
|
1441
1461
|
if (discovery.problems.length) { console.log("\nProblems:"); for (const p of discovery.problems) console.log(` ${p.code} ${p.repoKey ? `${memberLabel(p.repoKey)}:` : ""}${p.path} ${p.message}`); }
|
|
1442
|
-
if (
|
|
1462
|
+
if (result.warnings.length) { console.log("\nWarnings:"); for (const w of result.warnings) console.log(` ${w.code} ${w.message}${w.remedy ? `\n ${w.remedy}` : ""}`); }
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
/** The committed (shared) teams as `workspace status` reports them: rows in label order (team model v2). */
|
|
1466
|
+
function sharedTeamRows(discovery) {
|
|
1467
|
+
const teams = discovery.standalone === true ? {} : discovery.workspace?.teams || {};
|
|
1468
|
+
return Object.keys(teams).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)).map((label) => ({ label, team: typeof teams[label]?.team === "string" ? teams[label].team : null, description: typeof teams[label]?.description === "string" ? teams[label].description : null }));
|
|
1443
1469
|
}
|
|
1444
1470
|
|
|
1445
|
-
/** `oats workspace status` facts (feature desktop-facts): the workspace
|
|
1471
|
+
/** `oats workspace status` facts (feature desktop-facts): the workspace defaults as rows; this
|
|
1446
1472
|
* computer (member clones and the rule that found each, disabled souls, the lock); file locations with
|
|
1447
1473
|
* browsable urls; each catalog package's newer version, when the official catalog has one. */
|
|
1448
1474
|
async function workspaceStatusFacts(result, discovery, lock, ctx) {
|
|
@@ -1454,7 +1480,6 @@ async function workspaceStatusFacts(result, discovery, lock, ctx) {
|
|
|
1454
1480
|
result.defaults = {
|
|
1455
1481
|
slots: Object.fromEntries(LAYERS.map((l) => [l, slot(defaults[l])])),
|
|
1456
1482
|
capabilities: rows(defaults.capabilities),
|
|
1457
|
-
byTeam: Object.fromEntries(Object.entries(defaults.byTeam && typeof defaults.byTeam === "object" ? defaults.byTeam : {}).map(([label, t]) => [label, { capabilities: rows(t?.capabilities) }])),
|
|
1458
1483
|
};
|
|
1459
1484
|
result.workspace.file = discovery.standalone === true ? null : { path: "oats-workspace.yaml", url: browseUrl(discovery.key, discovery.commit, "oats-workspace.yaml") };
|
|
1460
1485
|
for (const m of result.members) Object.assign(m, { url: browseUrl(m.key, m.commit), membershipFile: { path: "oats-membership.yaml", url: browseUrl(m.key, m.commit, "oats-membership.yaml") } });
|
|
@@ -1475,6 +1500,88 @@ async function workspaceStatusFacts(result, discovery, lock, ctx) {
|
|
|
1475
1500
|
}
|
|
1476
1501
|
}
|
|
1477
1502
|
|
|
1503
|
+
/** The deployment's team facts for the team verbs: oats-local.yaml and the committed shared teams
|
|
1504
|
+
* (the workspace host read now; none in a standalone view). */
|
|
1505
|
+
async function teamsContext(bail) {
|
|
1506
|
+
const ctx = workspaceContext(bail);
|
|
1507
|
+
let workspace = null, workspaceKey = null;
|
|
1508
|
+
if (!(typeof ctx.local.standalone === "string" && ctx.local.standalone)) {
|
|
1509
|
+
try { const { observeWorkspace } = await import("../lib/workspace.mjs"); const obs = await observeWorkspace(ctx.local.workspace, { remoteOptions: ctx.remoteOptions }); workspace = obs.workspace; workspaceKey = obs.key; }
|
|
1510
|
+
catch (e) { if (typeof e?.code === "string" && e.code.startsWith("E_")) return bail(e.code, e.message, e.details ?? e.provenance); throw e; }
|
|
1511
|
+
}
|
|
1512
|
+
return { deployment: ctx.deploymentDir, localPath: ctx.localPath, local: ctx.local, workspace, workspaceKey, remoteOptions: ctx.remoteOptions };
|
|
1513
|
+
}
|
|
1514
|
+
const labelsFlag = (name) => { const v = valueFlag(name); return v === undefined ? [] : String(v).split(",").map((l) => l.trim()).filter(Boolean); };
|
|
1515
|
+
const positional = (i) => (args[i] !== undefined && !args[i].startsWith("--") ? args[i] : undefined);
|
|
1516
|
+
|
|
1517
|
+
/** `oats teams [--json] | add <label> --team <id> [--description <d>] | remove <label> | default <label>` —
|
|
1518
|
+
* this deployment's teams (team model v2). Config only: never a provider call. */
|
|
1519
|
+
async function teamsCmd() {
|
|
1520
|
+
const bail = (code, msg, details) => (JSON_MODE ? jsonFail(code, msg, details) : die(msg));
|
|
1521
|
+
const usage = "usage: oats teams [--json] | oats teams add <label> --team <id> [--description <d>] | oats teams remove <label> | oats teams default <label> [--dir <deployment>] [--json]";
|
|
1522
|
+
const sub = positional(1);
|
|
1523
|
+
if (sub !== undefined && !["add", "remove", "default"].includes(sub)) bail("E_USAGE", usage);
|
|
1524
|
+
const label = sub ? positional(2) : undefined;
|
|
1525
|
+
if (sub && label === undefined) bail("E_BAD_ARGS", `oats teams ${sub} needs a team label — ${usage}`);
|
|
1526
|
+
const ctx = await teamsContext(bail);
|
|
1527
|
+
const V = await import("../lib/teams-verbs.mjs");
|
|
1528
|
+
let result = null;
|
|
1529
|
+
try {
|
|
1530
|
+
if (sub === "add") result = V.teamsAdd(ctx, label, { team: valueFlag("team"), description: valueFlag("description") });
|
|
1531
|
+
else if (sub === "remove") result = V.teamsRemove(ctx, label);
|
|
1532
|
+
else if (sub === "default") result = V.teamsDefault(ctx, label);
|
|
1533
|
+
} catch (e) { if (typeof e?.code === "string" && e.code.startsWith("E_")) return bail(e.code, e.message, e.details); throw e; }
|
|
1534
|
+
const doc = V.teamsDocument(result ? { ...ctx, local: result.local } : ctx);
|
|
1535
|
+
if (JSON_MODE) { jsonOk(result ? { ...doc, changed: result.changed } : doc); return; }
|
|
1536
|
+
if (result) console.log(result.changed ? `${sub === "add" ? `Declared team ${label}` : sub === "remove" ? `Removed team ${label}` : `The default team is now ${label}`} in ${shortPath(ctx.localPath)}` : "Nothing to change");
|
|
1537
|
+
console.log(`default ${doc.defaultTeam ?? "(none)"}`);
|
|
1538
|
+
if (!doc.teams.length) console.log("teams (none: `oats aweb setup` creates them, or `oats teams add <label> --team <id>`)");
|
|
1539
|
+
else printTable(["team", "id", "from", ""], doc.teams.map((t) => [t.label, t.team ?? "(no id yet)", t.from, t.default ? "default" : ""]));
|
|
1540
|
+
const souls = Object.entries(doc.souls.teams).map(([k, l]) => `${k}: ${l.join(",")}`);
|
|
1541
|
+
if (souls.length) console.log(`souls ${souls.join(" · ")}`);
|
|
1542
|
+
const defaults = Object.entries(doc.souls.default).map(([k, l]) => `${k}: ${l}`);
|
|
1543
|
+
if (defaults.length) console.log(`defaults ${defaults.join(" · ")}`);
|
|
1544
|
+
for (const p of doc.problems) console.log(`${p.severity === "failure" ? "problem" : "warning"} ${p.code} ${p.message} — ${p.fix}`);
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
/** `oats soul teams <soul>|'*' [--add a,b] [--remove a,b] [--default <label> | --clear-default] [--json]` —
|
|
1548
|
+
* which teams a soul belongs to here, and why (team model v2). Config only. */
|
|
1549
|
+
async function soulCmd() {
|
|
1550
|
+
const bail = (code, msg, details) => (JSON_MODE ? jsonFail(code, msg, details) : die(msg));
|
|
1551
|
+
const usage = "usage: oats soul teams <soul>|'*' [--add <label>[,…]] [--remove <label>[,…]] [--default <label> | --clear-default] [--dir <deployment>] [--json]";
|
|
1552
|
+
if (positional(1) !== "teams") bail("E_USAGE", usage);
|
|
1553
|
+
const name = positional(2);
|
|
1554
|
+
if (name === undefined) bail("E_BAD_ARGS", `oats soul teams needs a soul (or '*' for every soul) — ${usage}`);
|
|
1555
|
+
const edit = { add: labelsFlag("add"), remove: labelsFlag("remove"), setDefault: valueFlag("default") ?? null, clearDefault: args.includes("--clear-default") };
|
|
1556
|
+
const ctx = workspaceContext(bail);
|
|
1557
|
+
let teamsCtx, soul = "*", key = "*";
|
|
1558
|
+
if (name === "*") teamsCtx = await teamsContext(bail);
|
|
1559
|
+
else {
|
|
1560
|
+
// The soul is named as for spawn: E_SOUL_UNKNOWN / E_SOUL_AMBIGUOUS; package souls come from the lock.
|
|
1561
|
+
let lock = null;
|
|
1562
|
+
try { lock = existsSync(join(ctx.deploymentDir, LOCK_FILE)) ? readLock(ctx.deploymentDir) : null; } catch (e) { return bail(e.code || "E_LOCK_SCHEMA", e.message, e.details); }
|
|
1563
|
+
let discovery, entry;
|
|
1564
|
+
try {
|
|
1565
|
+
const { discoverOrStandalone, findSoulEntry } = await import("../lib/instance-resolution.mjs");
|
|
1566
|
+
discovery = await discoverOrStandalone(ctx.local, { lock, deployment: ctx.deploymentDir, remoteOptions: ctx.remoteOptions });
|
|
1567
|
+
entry = findSoulEntry(discovery, name);
|
|
1568
|
+
} catch (e) { if (typeof e?.code === "string" && e.code.startsWith("E_")) return bail(e.code, e.message, e.details ?? e.provenance); throw e; }
|
|
1569
|
+
soul = entry.name; key = soulKeyOf(entry);
|
|
1570
|
+
teamsCtx = { deployment: ctx.deploymentDir, localPath: ctx.localPath, local: ctx.local, workspace: discovery.standalone === true ? null : discovery.workspace, workspaceKey: discovery.key ?? null };
|
|
1571
|
+
}
|
|
1572
|
+
const V = await import("../lib/teams-verbs.mjs");
|
|
1573
|
+
const mutating = edit.add.length || edit.remove.length || edit.setDefault !== null || edit.clearDefault;
|
|
1574
|
+
let result = null, doc;
|
|
1575
|
+
try {
|
|
1576
|
+
if (mutating) result = V.soulTeamsEdit(teamsCtx, key, edit);
|
|
1577
|
+
doc = V.soulTeamsDocument(result ? { ...teamsCtx, local: result.local } : teamsCtx, { soul, key });
|
|
1578
|
+
} catch (e) { if (typeof e?.code === "string" && e.code.startsWith("E_")) return bail(e.code, e.message, e.details); throw e; }
|
|
1579
|
+
if (JSON_MODE) { jsonOk(result ? { ...doc, changed: result.changed } : doc); return; }
|
|
1580
|
+
if (result) console.log(result.changed ? `Updated the teams of ${key === "*" ? "every soul" : key} in ${shortPath(teamsCtx.localPath)}` : "Nothing to change");
|
|
1581
|
+
console.log(`${key === "*" ? "every soul" : key} on this computer: default ${doc.defaultTeam ? `${doc.defaultTeam.label} (${doc.defaultTeam.from})` : "(none)"}`);
|
|
1582
|
+
if (doc.teams.length) printTable(["team", "id", "from", "why"], doc.teams.map((t) => [t.default ? `${t.label} (default)` : t.label, t.team ?? "(no id yet)", t.from, t.via.join(",")]));
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1478
1585
|
/** `oats capabilities` / `oats souls` [--dir] [--json] — contract §6. */
|
|
1479
1586
|
async function itemsCmd(kind) {
|
|
1480
1587
|
const bail = (code, msg, details) => (JSON_MODE ? jsonFail(code, msg, details) : die(msg));
|
|
@@ -1482,7 +1589,7 @@ async function itemsCmd(kind) {
|
|
|
1482
1589
|
const discovery = await discoverForCli(ctx, bail);
|
|
1483
1590
|
let lock;
|
|
1484
1591
|
try { lock = readLock(ctx.deploymentDir); } catch (e) { return bail(e.code || "E_LOCK_SCHEMA", e.message, e.details); }
|
|
1485
|
-
const items = workspaceItems(discovery, lock)[kind];
|
|
1592
|
+
const items = workspaceItems(discovery, lock, ctx.local, ctx.deploymentDir)[kind];
|
|
1486
1593
|
// One command's reads at a commit are shared: many souls resolve over the same manifests and listings.
|
|
1487
1594
|
const remote = memoizedRemote(remoteModule);
|
|
1488
1595
|
if (kind === "capabilities") await capabilityFacts(items, discovery, lock, ctx, remote);
|
|
@@ -1491,8 +1598,8 @@ async function itemsCmd(kind) {
|
|
|
1491
1598
|
if (JSON_MODE) { jsonOk({ [`${kind}Api`]: 1, standalone: standalone || undefined, workspace: { name: workspaceName(discovery), key: discovery.key, commit: discovery.commit }, [kind]: items, problems: discovery.problems }); return; }
|
|
1492
1599
|
console.log(`${kind} of workspace ${workspaceName(discovery)} (${discovery.key} @ ${short(discovery.commit)})${standalone ? ` — ${standaloneNote(discovery)}` : ""}\n`);
|
|
1493
1600
|
if (!items.length) console.log(" (none)");
|
|
1494
|
-
else if (kind === "souls") printTable(["name", "origin", "
|
|
1495
|
-
else printTable(["name", "origin", "
|
|
1601
|
+
else if (kind === "souls") printTable(["name", "origin", "teams here", "work"], items.map((s) => [s.name, s.origin, s.teams === null ? "(invalid: oats teams)" : s.teams.map((t) => (t.default ? `${t.label}*` : t.label)).join(",") || "—", s.work ?? "—"]));
|
|
1602
|
+
else printTable(["name", "origin", "layer"], items.map((c) => [c.private ? `${c.name} (repo-owned)` : c.name, c.origin, c.layer ?? "—"]));
|
|
1496
1603
|
const unsynced = Object.keys(discovery.workspace?.packages || {}).filter((id) => !lock.packages[id]);
|
|
1497
1604
|
if (kind === "capabilities" && unsynced.length) console.log(`\n package capabilities of ${unsynced.join(", ")} appear after \`oats sync\``);
|
|
1498
1605
|
}
|
|
@@ -1636,6 +1743,16 @@ function soulRepoLabel(a, ws) {
|
|
|
1636
1743
|
const moved = stamp.current && stamp.commit && stamp.current !== stamp.commit ? ` (member now @ ${short7(stamp.current)})` : "";
|
|
1637
1744
|
return `${memberLabel(stamp.repoKey)} @ ${short7(stamp.commit)}${moved}`;
|
|
1638
1745
|
}
|
|
1746
|
+
/** A roster agent's soul key: the qualified name of a package soul, else the recorded agent (soul) name;
|
|
1747
|
+
* null when no instance records a workspace soul. Exactly soulKeyOf for the soul it was spawned from. */
|
|
1748
|
+
function agentSoulKey(a) {
|
|
1749
|
+
for (const i of a.instances || []) {
|
|
1750
|
+
const soul = i?.workspace?.soul;
|
|
1751
|
+
if (!soul || typeof soul !== "object") continue;
|
|
1752
|
+
return soul.package && typeof soul.qualifiedName === "string" ? soul.qualifiedName : (typeof i.agent === "string" ? i.agent : a.name);
|
|
1753
|
+
}
|
|
1754
|
+
return null;
|
|
1755
|
+
}
|
|
1639
1756
|
const short7 = (oid) => (typeof oid === "string" ? oid.slice(0, 7) : "?");
|
|
1640
1757
|
|
|
1641
1758
|
async function status() {
|
|
@@ -1648,6 +1765,9 @@ async function status() {
|
|
|
1648
1765
|
const verbose = args.includes("--verbose");
|
|
1649
1766
|
const problems = legacyLayoutProblems(root);
|
|
1650
1767
|
if (args.includes("--json")) {
|
|
1768
|
+
// The soul key (feature launch-preference): what souls.teams / souls.default / souls.launch and
|
|
1769
|
+
// `oats soul teams <key>` use — from the instances' records, so it needs no remote.
|
|
1770
|
+
for (const a of data) a.key = agentSoulKey(a);
|
|
1651
1771
|
if (ws) for (const a of data) {
|
|
1652
1772
|
const stamp = ws.souls.get(a.name);
|
|
1653
1773
|
if (stamp) a.soulSource = { repoKey: stamp.repoKey, commit: stamp.commit, path: stamp.path, ...(stamp.current ? { current: stamp.current, status: stamp.current === stamp.commit ? "current" : "moved" } : {}) };
|
|
@@ -1764,7 +1884,7 @@ async function spawnCmd() {
|
|
|
1764
1884
|
if (!agent || soulFetched || agent._dir !== dirname(soulDir)) agent = findAgent(root, soulName);
|
|
1765
1885
|
}
|
|
1766
1886
|
if (!agent) bail("E_SOUL_UNKNOWN", `soul "${name}" was fetched to ${shortPath(soulDir)} but is not readable as a soul there`);
|
|
1767
|
-
note(`(workspace soul: "${name}" from ${wsPrepared.soulEntry.repoKey} @ ${String(wsPrepared.soulEntry.commit).slice(0, 12)}${wsPrepared.
|
|
1887
|
+
note(`(workspace soul: "${name}" from ${wsPrepared.soulEntry.repoKey} @ ${String(wsPrepared.soulEntry.commit).slice(0, 12)}${wsPrepared.resolution.defaultTeam ? `, team ${wsPrepared.resolution.defaultTeam.label}` : ""}${soulFetched ? "; soul source fetched" : ""})`);
|
|
1768
1888
|
} catch (e) {
|
|
1769
1889
|
// Standalone (decisions 10/25): the ONLY package request is the kernel's own
|
|
1770
1890
|
// default; when the catalog cannot name it, say so instead of "add it to packages:"
|
|
@@ -1922,7 +2042,7 @@ async function spawnCmd() {
|
|
|
1922
2042
|
// A launch refusal (configuration, executable, environment reference,
|
|
1923
2043
|
// model, harness) is a fact about the selection, not a spawn-mechanism
|
|
1924
2044
|
// failure: it keeps its own code so a GUI can act on it.
|
|
1925
|
-
if (typeof e?.code === "string" && /^E_LAUNCH_|^E_MODEL_UNKNOWN$|^E_UNSUPPORTED_HARNESS$/.test(e.code)) { bail(e.code, e.message); throw e; }
|
|
2045
|
+
if (typeof e?.code === "string" && /^E_LAUNCH_|^E_MODEL_UNKNOWN$|^E_UNSUPPORTED_HARNESS$|^E_HARNESS_UNAVAILABLE$/.test(e.code)) { bail(e.code, e.message, e.details); throw e; }
|
|
1926
2046
|
// An unmet declared requirement is a fact about the soul's configuration
|
|
1927
2047
|
// (with a remedy), not a spawn-mechanism failure: keep its code and details.
|
|
1928
2048
|
if (e?.code === "E_REQUIREMENT_INACTIVE") { bail(e.code, e.message, { soul: e.soul, capabilities: e.capabilities, context: e.context, remedy: e.remedy }); throw e; }
|
|
@@ -2201,7 +2321,9 @@ async function triggerCmd() {
|
|
|
2201
2321
|
try {
|
|
2202
2322
|
const { observeWorkspace } = await import("../lib/workspace.mjs");
|
|
2203
2323
|
const { local } = loadLocal(ws());
|
|
2204
|
-
|
|
2324
|
+
// The labels declared here: the committed shared teams and this deployment's local ones.
|
|
2325
|
+
const shared = typeof local.workspace === "string" && !local.standalone ? (await observeWorkspace(local.workspace, { remoteOptions: remoteOptionsFromEnv() })).workspace : null;
|
|
2326
|
+
workspaceTeams = [...teamModel(shared, local).labels.keys()];
|
|
2205
2327
|
} catch { /* reported as unknown (null) */ }
|
|
2206
2328
|
return out(T.testTrigger(ws(), needId(), { workspaceTeams, ctx: ctx() }), (r) => [
|
|
2207
2329
|
`trigger ${r.id}: ${r.ok ? "ready" : "NOT ready"} (nothing was spawned)`,
|
|
@@ -2279,7 +2401,11 @@ async function sessionCmd() {
|
|
|
2279
2401
|
if (launchConfig === true) bad("--launch-config needs a configuration name, or none");
|
|
2280
2402
|
const harness = harnessFlag();
|
|
2281
2403
|
if (harness === true || (harness !== undefined && !LAUNCH_HARNESSES.includes(harness))) bad(`--harness must be one of ${LAUNCH_HARNESSES.join(", ")}`);
|
|
2282
|
-
|
|
2404
|
+
// --reselect-launch (feature launch-preference): the home's launch layers decide again; without it a
|
|
2405
|
+
// recorded launch stays frozen (a changed preference never reaches an existing home on its own).
|
|
2406
|
+
const reselectLaunch = args.includes("--reselect-launch");
|
|
2407
|
+
if (reselectLaunch && launchConfig !== undefined) bad("--reselect-launch applies the launch preferences; --launch-config names one explicitly — choose one");
|
|
2408
|
+
const opts = { model: model || undefined, launchConfig, harness, yolo: yoloFlag(), ...(reselectLaunch ? { reselectLaunch: true } : {}), env: process.env, ...(await homeLiveTeams(home)) };
|
|
2283
2409
|
if (args[1] === "restart") {
|
|
2284
2410
|
const grace = flag("stop-grace");
|
|
2285
2411
|
if (grace !== undefined) { if (grace === true || !/^\d+$/.test(String(grace)) || Number(grace) < 1 || Number(grace) > 300) bad("--stop-grace needs a number of seconds (1..300) to wait for the harness after SIGTERM"); opts.stopGraceMs = Number(grace) * 1000; }
|
|
@@ -2302,8 +2428,13 @@ async function sessionCmd() {
|
|
|
2302
2428
|
const file = flag("file");
|
|
2303
2429
|
if (!file || file === true) throw Object.assign(new Error("session upload needs --file <local path>"), { code: "E_BAD_ARGS" });
|
|
2304
2430
|
result = uploadAttachment({ file, home: home === true ? undefined : home });
|
|
2305
|
-
} else throw Object.assign(new Error("usage: oats session inspect|input|attach|start|restart|receive|upload --home /absolute/home [--text-file path] [--model id] [--launch-config name|none] [--harness pi|claude|codex] [--yolo|--no-yolo] [--stop-grace seconds] [--name file] [--file path] [--json]"), { code: "E_BAD_ARGS" });
|
|
2306
|
-
if (JSON_MODE) jsonOk(result);
|
|
2431
|
+
} else throw Object.assign(new Error("usage: oats session inspect|input|attach|start|restart|receive|upload --home /absolute/home [--text-file path] [--model id] [--launch-config name|none | --reselect-launch] [--harness pi|claude|codex] [--yolo|--no-yolo] [--stop-grace seconds] [--name file] [--file path] [--json]"), { code: "E_BAD_ARGS" });
|
|
2432
|
+
if (JSON_MODE) jsonOk(result);
|
|
2433
|
+
else {
|
|
2434
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2435
|
+
// A start's launch-hook warnings, as spawn prints its own (stderr keeps stdout one JSON document).
|
|
2436
|
+
for (const w of result?.warnings || []) console.error(` WARNING: ${w}`);
|
|
2437
|
+
}
|
|
2307
2438
|
} catch (e) { cmdFail(e.code || "E_SESSION_FAILED", e.message, e.details); }
|
|
2308
2439
|
}
|
|
2309
2440
|
|
|
@@ -2313,8 +2444,8 @@ async function paneCmd() {
|
|
|
2313
2444
|
|
|
2314
2445
|
/** `oats onboard [<dir>] --workspace <repo ref> [--json]` — workspace model v2 (decision 9).
|
|
2315
2446
|
*
|
|
2316
|
-
* Realizes a workspace on this machine in the directory the operator chooses
|
|
2317
|
-
* (docs/
|
|
2447
|
+
* Realizes a workspace on this machine in the directory the operator chooses
|
|
2448
|
+
* (docs/workspaces.md): writes
|
|
2318
2449
|
* `<dir>/oats-local.yaml` naming the workspace, creates `<dir>/agents/` (the
|
|
2319
2450
|
* instance homes), then runs exactly the `oats sync` path — discover over the
|
|
2320
2451
|
* remotes, confirm membership, resolve `packages:`, write `oats-lock.json`. Nothing is installed, no soul
|
|
@@ -2543,8 +2674,8 @@ async function capabilityCommand() {
|
|
|
2543
2674
|
const ws = meta.workspace && typeof meta.workspace === "object" ? meta.workspace : {};
|
|
2544
2675
|
const messaging = (meta.capabilities || []).find((c) => c.layer === "messaging")?.id;
|
|
2545
2676
|
homeMeta = { meta, messaging };
|
|
2546
|
-
homeTeamCtx = (
|
|
2547
|
-
teamCtx = homeTeamCtx(meta
|
|
2677
|
+
homeTeamCtx = (t) => teamEnv({ workspace: { key: ws.key, name: ws.name, deployment: ws.deployment }, teams: t.teams, defaultTeam: t.defaultTeam, teamsSource: t.source });
|
|
2678
|
+
teamCtx = homeTeamCtx({ ...recordedTeams(meta), source: "recorded" });
|
|
2548
2679
|
} else {
|
|
2549
2680
|
// Not inside a home: a v2 deployment (oats-local.yaml in reach) resolves
|
|
2550
2681
|
// through the workspace, exactly as a spawn of --soul would (below).
|
|
@@ -2567,7 +2698,7 @@ async function capabilityCommand() {
|
|
|
2567
2698
|
if (homeMeta && m.capability === homeMeta.messaging) {
|
|
2568
2699
|
const { liveTeams } = await import("../lib/instance-resolution.mjs");
|
|
2569
2700
|
const live = await liveTeams(instanceHome, homeMeta.meta, { remoteOptions: remoteOptionsFromEnv() });
|
|
2570
|
-
teamCtx = homeTeamCtx(live
|
|
2701
|
+
teamCtx = homeTeamCtx(live);
|
|
2571
2702
|
}
|
|
2572
2703
|
return runManifestCommand(m, { settings: capSettings[m.capability] || {}, origins: capOrigins[m.capability] }, teamCtx, () => m._dir, soulDir);
|
|
2573
2704
|
}
|
|
@@ -2692,7 +2823,7 @@ function versionCmd() {
|
|
|
2692
2823
|
// Phase B: `instance-modules` and `spawn-provider-payload` are advertised only once spawn
|
|
2693
2824
|
// runs on resolve/materialize (contract §6); a feature the binary does not implement is
|
|
2694
2825
|
// never listed.
|
|
2695
|
-
console.log(JSON.stringify({ schemaVersion: 1, name: "@awebai/oats", version: OATS_VERSION, desktopApi: 1, harnesses: ["pi", "claude", "codex"], sessionBackends: ["tmux", "herdr"], launchOptions: ["yolo"], remote: ["spawn", "retire", "status", "session", "session-start", "session-restart", "launch-config", "roster", "harvest", "schedule", "session-upload", "operations"], features: ["retire-home", "session-start", "session-restart", "launch-config", "schedule", "session-upload", "operations", "instance-git", "instance-git-remote", "souls-declarations", "lifecycle-plans", "retire-retention", "readiness", "spawn-preview", "instance-events", "instance-events-2", "schedule-history", "schedule-read-2", "spawn-preview-2", "spawn-idempotency", "spawn-idempotency-2", "spawn-apply-2", "workspace-v2", "instance-modules", "spawn-provider-payload", "served-identity", "packages-no-approval", "spawn-name", "settings-origins", "
|
|
2826
|
+
console.log(JSON.stringify({ schemaVersion: 1, name: "@awebai/oats", version: OATS_VERSION, desktopApi: 1, harnesses: ["pi", "claude", "codex"], sessionBackends: ["tmux", "herdr"], launchOptions: ["yolo"], remote: ["spawn", "retire", "status", "session", "session-start", "session-restart", "launch-config", "roster", "harvest", "schedule", "session-upload", "operations"], features: ["retire-home", "session-start", "session-restart", "launch-config", "schedule", "session-upload", "operations", "instance-git", "instance-git-remote", "souls-declarations", "lifecycle-plans", "retire-retention", "readiness", "spawn-preview", "instance-events", "instance-events-2", "schedule-history", "schedule-read-2", "spawn-preview-2", "spawn-idempotency", "spawn-idempotency-2", "spawn-apply-2", "workspace-v2", "instance-modules", "spawn-provider-payload", "served-identity", "packages-no-approval", "spawn-name", "settings-origins", "team-model-2", "settings-declared", "capabilities-private", "layers-from", "harness", "package-souls", "triggers", "automations", "desktop-facts", "launch-preference"], automationsApi: A.AUTOMATIONS_API, workspaceApi: 2, instanceGitApi: 1, spawnApplyApi: 1, soulsApi: 2, lifecycleApi: 1, readinessApi: 2, spawnPreviewApi: 2, eventsApi: 2, scheduleHistoryApi: 3, scheduleApi: SCHEDULE_API, operationsApi: 2 }));
|
|
2696
2827
|
return;
|
|
2697
2828
|
}
|
|
2698
2829
|
console.log(`@awebai/oats ${OATS_VERSION} (desktop API v1)`);
|
|
@@ -3044,7 +3175,7 @@ async function serverRouteCmd() {
|
|
|
3044
3175
|
// blame` pointing at the commit that last changed each command.
|
|
3045
3176
|
const TYPED_CLI_FAILURES = new Set(["unsafe-config-key", "unsafe-config-value"]);
|
|
3046
3177
|
/** Removed 0.24 verbs → their v2 replacement (workspace model v2, decision 5). Checked before capability dispatch. */
|
|
3047
|
-
const REMOVED_VERBS = { prepare: "`oats onboard` / `oats sync` to set up a workspace, and `oats spawn <soul> --preview` to see what a spawn would resolve (the captured/portable path was removed in 0.26)", create: "author souls/<name>/soul.yaml + AGENTS.md in a member repository, then `oats sync`", type: "the soul's own soul.yaml in its member repository (agent types were a classic config block)",
|
|
3178
|
+
const REMOVED_VERBS = { prepare: "`oats onboard` / `oats sync` to set up a workspace, and `oats spawn <soul> --preview` to see what a spawn would resolve (the captured/portable path was removed in 0.26)", create: "author souls/<name>/soul.yaml + AGENTS.md in a member repository, then `oats sync`", type: "the soul's own soul.yaml in its member repository (agent types were a classic config block)", install: "oats sync", restore: "oats sync", init: "oats-local.yaml + oats sync", use: "soul.yaml capabilities: { <cap>: { from } } + workspace defaults", trust: "declaring the package in packages: (package approval was removed; oats sync locks commit + integrity)", list: "oats workspace status | oats capabilities", catalog: "oats package add <id> <version> (bare versions resolve through package-catalog.json)", remove: "oats package remove <id>", migrate: "a rebuild (no migration: docs/design/2026-09-23-workspace-module-contracts.md)", config: "oats-local.yaml (host settings) and oats-workspace.yaml (shared)", inject: "injection overrides are not part of the workspace model yet; edit the capability inject in its member repo" };
|
|
3048
3179
|
try {
|
|
3049
3180
|
// The captured/portable path was removed in 0.26 (lead decisions on (e), D2/D3):
|
|
3050
3181
|
// its selectors and an inherited captured context are refused, never quietly
|
|
@@ -3100,6 +3231,8 @@ else if (cmd === "sync") await syncCmd();
|
|
|
3100
3231
|
else if (cmd === "package") await packageCmd();
|
|
3101
3232
|
else if (cmd === "workspace") await workspaceCmd();
|
|
3102
3233
|
else if (cmd === "capabilities" || cmd === "souls") await itemsCmd(cmd);
|
|
3234
|
+
else if (cmd === "teams") await teamsCmd();
|
|
3235
|
+
else if (cmd === "soul") await soulCmd();
|
|
3103
3236
|
else if (cmd === "status") await status();
|
|
3104
3237
|
else if (cmd === "pane") await paneCmd();
|
|
3105
3238
|
else if (cmd === "version" || cmd === "--version" || cmd === "-v") versionCmd();
|
|
@@ -3223,6 +3356,9 @@ Usage:
|
|
|
3223
3356
|
oats session start --home <absolute-home> start a STOPPED instance again in its existing home
|
|
3224
3357
|
[--model m] [--launch-config n|none] (recorded recipe as is; a selection re-resolves it
|
|
3225
3358
|
[--harness r] [--yolo|--no-yolo] against the scope; a named configuration is a unit)
|
|
3359
|
+
[--reselect-launch] applies the launch preferences now (oats-local.yaml
|
|
3360
|
+
souls.launch, the soul's launch); a changed preference
|
|
3361
|
+
never reaches an existing home without it or a respawn
|
|
3226
3362
|
oats session restart --home <abs-home> stop the running harness (SIGTERM, bounded wait,
|
|
3227
3363
|
[same flags] [--stop-grace <s>] never escalated) and start it again in place under
|
|
3228
3364
|
the same lock; a stop that is not observed is
|
|
@@ -3230,7 +3366,7 @@ Usage:
|
|
|
3230
3366
|
[--model <m>] [--json] (same identity, worktree, notes and launch env; no
|
|
3231
3367
|
spawn hooks); --model replaces the recorded model
|
|
3232
3368
|
for this and later starts; a live harness is refused
|
|
3233
|
-
oats spawn <agent> [--task <text>]
|
|
3369
|
+
oats spawn <agent> [--task <text> | --task-file <f>] spawn an instance (tmux/Herdr; --no-launch
|
|
3234
3370
|
[--purpose <slug>] [--repo <r>] = scaffold only); the agent is a workspace
|
|
3235
3371
|
[--parent <instance>] soul or a capability-defined agent
|
|
3236
3372
|
[--relation child|sibling|parent|unrelated] --relation + --relative-to anchor the
|
|
@@ -3238,7 +3374,9 @@ Usage:
|
|
|
3238
3374
|
[--relative-root <agents-root>] disambiguates same-named team anchors
|
|
3239
3375
|
[--work worktree|checkout|attached|workspace|directory] = sugar for --relative-to X --relation
|
|
3240
3376
|
[--work-dir <owner-work>] [--harness pi|claude|codex] [--backend tmux|herdr] [--herdr-socket <path>] [--yolo|--no-yolo] [--model <m>] [--branch <b>] child (default: unrelated, top-level)
|
|
3241
|
-
[--no-launch] [--json]
|
|
3377
|
+
[--no-launch] [--json] without --launch-config/--harness the launch is the
|
|
3378
|
+
soul's preference: oats-local.yaml souls.launch.<soul>,
|
|
3379
|
+
then souls.launch."*", then the soul's launch:, then pi
|
|
3242
3380
|
with team: declared, unknown souls
|
|
3243
3381
|
resolve across the team scope's repos
|
|
3244
3382
|
directory: owned home/work, config context may
|
|
@@ -3302,7 +3440,13 @@ Usage:
|
|
|
3302
3440
|
its own repo's souls) + the locked packages
|
|
3303
3441
|
oats souls [--dir <d>] [--json] every soul of every confirmed member + external souls
|
|
3304
3442
|
(souls have no private mode), with origin
|
|
3305
|
-
(member <key> @ <commit> | package <id> v<ver>) and
|
|
3443
|
+
(member <key> @ <commit> | package <id> v<ver>) and its
|
|
3444
|
+
teams on this deployment
|
|
3445
|
+
oats teams [--json] | add <label> --team <id> [--description <d>] | remove <label>
|
|
3446
|
+
| default <label> [--dir <d>] this deployment's teams (shared + local), the
|
|
3447
|
+
default; add/remove/default edit oats-local.yaml
|
|
3448
|
+
oats soul teams <soul>|'*' [--add a,b] [--remove a,b] [--default <l> | --clear-default]
|
|
3449
|
+
[--dir <d>] [--json] which teams a soul (or every soul) belongs to here
|
|
3306
3450
|
oats instance git <instance> [--home <abs>] [--dir <d>] [--json]
|
|
3307
3451
|
read-only Git observation of the instance's work
|
|
3308
3452
|
tree: branch, status (renames kept), ahead/behind
|