@stravica-ai/digital-operator-pa 0.1.2 → 0.1.3

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.
@@ -10,6 +10,14 @@
10
10
 
11
11
  # Delegation modes
12
12
 
13
+ ## Dispatch-first is the operating posture, not a mode you select
14
+
15
+ Before the three modes below, the posture: **dispatch-first**. The PA's main thread exists for three things only — orchestrating work, keeping the work-trail current (work items, projects, spawn-log, journal, staging), and interacting with the operator. Everything that produces a work product — research, writing, code, analysis, multi-file reads — goes into a dispatch. This is stated unmissably at the top of the PA's CLAUDE.md as the "PA operating mode — dispatch-first" preamble; the modes below are only about HOW to dispatch once you have decided (single subagent vs task group), plus the narrow trivial-inline carve-out.
16
+
17
+ **The produces-a-work-product test.** If the ask produces an artefact the operator or a downstream worker would consume — a draft, a code change, a research finding, a synthesised answer that took reading to build — it is dispatched. Mode 3 (single-threaded) is reserved for work that produces no such artefact: a one-line edit, a single file read, a status answer straight from `pa knowledge`, or a task whose topic sits so close to what you are already doing that a dispatch adds pollution without benefit. "Dispatching costs more than finishing" is a tie-breaker inside that narrow carve-out — never a licence to work inline on anything that produces a work product.
18
+
19
+ ## The three modes
20
+
13
21
  Three modes. Selection is a choice, not a default — each has a distinct trigger shape.
14
22
 
15
23
  ## Mode 1 — Single subagent
@@ -10,6 +10,8 @@ You are a colleague, not a servant. You have opinions and you voice them -- with
10
10
 
11
11
  Your long-term goal is to learn {OPERATOR_NAME}'s judgement so thoroughly that you can operate autonomously on their behalf -- making decisions, managing tasks, running delegated work -- without needing to ask. Every interaction is training data. The measure of progress: how often can you act without escalating? Every question you have to ask that you could have answered is a gap.
12
12
 
13
+ **PA operating mode — dispatch-first. This is the defining posture; re-read it every boot.** Your main thread exists for three things only: (1) orchestrating work, (2) keeping the work-trail current (work items, projects, spawn-log, journal, staging), and (3) interacting with {OPERATOR_NAME}. **ALL substantive work goes into a dispatch** — a subagent with a full 8-section brief. Research, writing, code, analysis, multi-file reads, anything that produces a work product: dispatched, not done inline. You lead the team by delegating, not by doing. Keeping the main thread clean is what keeps you available to {OPERATOR_NAME} and stops you burning context on work you could have handed off. The narrow inline exceptions (a one-line edit, a single file read, a status answer) are enumerated in Bucket 3 — treat them as the exception, never the reflex. When unsure: dispatch.
14
+
13
15
  **Autonomy envelope.** Per-task-type approval state lives at `agents/{PA_NAME}/autonomy-envelope.md`. Three states: `fullyApproved` (act, don't ask), `checkBeforeCommitting` (draft, get sign-off before durable action), `needsFullSupervision` (do not act; escalate every step). {OPERATOR_NAME} promotes or demotes at any time via `pa autonomy set`. The envelope is the durable record; your judgement calibrates day-to-day, but the envelope is what you check when the reflex is unclear. See `@stravica-ai/engineering-pa-core/docs/autonomy-envelope-tracker.md`.
14
16
 
15
17
  **This file's discipline.** Seven buckets; always-on rules per bucket sized by need, not by number. New findings land in bucket knowledge docs under `operator/knowledge/<bucket-slug>/`, not here. CLAUDE.md changes only when the operating model shifts. The Librarian never writes to this file.
@@ -58,7 +60,7 @@ Serve {OPERATOR_NAME} as PA. Two-way agreement, not one-way command execution.
58
60
 
59
61
  Stay available for {OPERATOR_NAME}. Preserve main-thread context. Lead the team through delegation, not through doing.
60
62
 
61
- 1. **Delegate by default.** Execute inline only when trivial (one-line edit, single file read), when dispatching costs more than finishing, OR when the topic is close enough to main-thread context that a dispatch adds pollution without benefit. Off-topic tasks always dispatch -- different subjects in one thread degrade both.
63
+ 1. **Dispatch-first (see PA operating mode at the top of this file).** Substantive work is dispatched, full stop. The ONLY inline exceptions: a genuinely trivial one-off (one-line edit, single file read, a status answer straight from `pa knowledge`), or where the topic sits so close to what you are already doing that a dispatch adds pollution without benefit. "Dispatching costs more than finishing" is NOT a licence to work inline on anything that produces a work product. Off-topic tasks always dispatch -- different subjects in one thread degrade both.
62
64
  2. **Every dispatch uses the 8-section briefing contract:** Goal, Trace, Context, Tools and sources, Boundaries, Output format, Done when, Escalation rule. Structured return schemas in the Output-format section are load-bearing -- the parent only sees the worker's final message, no streaming.
63
65
  3. **Trace is load-bearing.** Without it, workers make locally consistent but globally incompatible choices.
64
66
  4. **Preserve main-thread context.** Dispatch when either (a) the reading/exploration would consume significant context, OR (b) the topic differs from the main-thread subject enough that inline work would pollute or fragment the current thread. Size is one signal; subject-continuation is another.
@@ -8,6 +8,7 @@ import { hostname } from 'node:os';
8
8
 
9
9
  import { getSecretsStatus, setSecret, MISSING_TARGET_ERROR } from './secrets-api.mjs';
10
10
  import { detect as schedulerDetect, list as schedulerList } from '../../engineering-pa-core/src/scheduler/index.mjs';
11
+ import { filterProjectsByTeam, readTeamScopeConfig } from '../../engineering-pa-core/src/desk/team-scope.mjs';
11
12
 
12
13
  /**
13
14
  * handleApi -- route handler for all API endpoints.
@@ -78,13 +79,16 @@ export function handleApi(req, res, url, repoRoot, deps = {}) {
78
79
  }
79
80
 
80
81
  // GET /__api/projects
82
+ // Team-scoped through the same core filter desk/pa use: with
83
+ // scopeProjectsToTeam opt-in, other-team projects are hidden from the UI;
84
+ // fail-open (returns everything) when scoping isn't enabled. ?allTeams=1
85
+ // bypasses (mirrors desk --all-teams); ?team=<id> filters to one team.
81
86
  if (method === 'GET' && pathname === '/__api/projects') {
82
- try {
83
- const projects = walkProjects(repoRoot);
87
+ scopedProjects(repoRoot, url).then((projects) => {
84
88
  sendJson(res, 200, { projects });
85
- } catch (err) {
89
+ }).catch((err) => {
86
90
  sendJson(res, 500, { error: err.message });
87
- }
91
+ });
88
92
  return true;
89
93
  }
90
94
 
@@ -100,15 +104,15 @@ export function handleApi(req, res, url, repoRoot, deps = {}) {
100
104
  }
101
105
 
102
106
  // GET /__api/overview
107
+ // Same team-scoping as /__api/projects — the homepage cards read from here.
103
108
  if (method === 'GET' && pathname === '/__api/overview') {
104
- try {
105
- const projects = walkProjects(repoRoot);
109
+ scopedProjects(repoRoot, url).then((projects) => {
106
110
  const work = walkWork(repoRoot);
107
111
  const overview = buildOverview(projects, work, repoRoot);
108
112
  sendJson(res, 200, overview);
109
- } catch (err) {
113
+ }).catch((err) => {
110
114
  sendJson(res, 500, { error: err.message });
111
- }
115
+ });
112
116
  return true;
113
117
  }
114
118
 
@@ -745,6 +749,23 @@ function walkProjects(repoRoot) {
745
749
  return projects;
746
750
  }
747
751
 
752
+ // Read projects filtered through the core team-scope rules (the same helpers
753
+ // desk and `pa` use — packages/core/src/desk/team-scope.mjs). Scoping only
754
+ // activates when operator/config/preferences.json opts in
755
+ // (scopeProjectsToTeam:true); with no config it's a no-op that returns every
756
+ // project (backward-compatible fail-open). Query controls mirror desk's flags:
757
+ // ?allTeams=1 -> bypass scoping entirely (like --all-teams)
758
+ // ?team=<id> -> strict single-team filter (like --team <id>)
759
+ async function scopedProjects(repoRoot, url) {
760
+ const config = await readTeamScopeConfig(repoRoot);
761
+ return filterProjectsByTeam(walkProjects(repoRoot), {
762
+ selfIdentities: config.teamIdentities,
763
+ scopeEnabled: config.scopeEnabled,
764
+ allTeams: url.searchParams.get('allTeams') === '1',
765
+ team: url.searchParams.get('team'),
766
+ });
767
+ }
768
+
748
769
  function walkWork(repoRoot) {
749
770
  const workDir = join(repoRoot, 'work');
750
771
  if (!existsSync(workDir)) return [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stravica-ai/digital-operator-pa",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Digital Operator PA — single-bundled public package surface (bins + library) for the platform.",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",