@saadnvd1/agent-os 0.3.766 → 0.3.768

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.
@@ -25,7 +25,7 @@ import { getProject } from "../../projects";
25
25
  import { planFor, splitOf } from "../../plans/features";
26
26
  import { moveTask } from "../move-flow";
27
27
  import * as client from "./client";
28
- import { meetsNeeds, needsOf } from "../placement";
28
+ import { meetsNeeds, needsOf, taskPinnedToMac } from "../placement";
29
29
  import { remotePlanPath } from "./plan-path";
30
30
  import { DEFAULT_PRIORITY, isPriority } from "../schedule-types";
31
31
  import { localHolds } from "../hold-checks";
@@ -55,6 +55,9 @@ function checkedSplit(s: Session, text: string | null) {
55
55
  }
56
56
  }
57
57
 
58
+ const pinned = (s: Session, project: { working_directory: string }) =>
59
+ taskPinnedToMac(s.id, expandHome(project.working_directory));
60
+
58
61
  /**
59
62
  * Where a written plan is built: "there" (approved on the machine that
60
63
  * planned it) or "home" (moved here first). Pieces are checked against this
@@ -70,6 +73,9 @@ export function implementTarget(
70
73
  // The project lives there: nothing here to build it in.
71
74
  if (!project || isRemoteHost(project.host_id)) return "there";
72
75
  if (split) return "home";
76
+ // An explicit Mac pin builds on the Mac, wherever the plan ran.
77
+ if (pinned(s, project))
78
+ return meetsNeeds(["mac"], machineCapabilities()) ? "home" : "there";
73
79
  const { needs } = needsOf({
74
80
  projectId: project.id,
75
81
  projectPath: expandHome(project.working_directory),
@@ -229,6 +235,7 @@ export async function handoffHome(
229
235
  projectPath: expandHome(project.working_directory),
230
236
  prompt: s.task_prompt ?? "",
231
237
  });
238
+ if (pinned(s, project) && !needs.includes("mac")) needs.push("mac");
232
239
  const holds = localHolds(
233
240
  {
234
241
  needs,
@@ -79,6 +79,28 @@ export function inferNeeds(projectPath: string): Need[] {
79
79
  return [];
80
80
  }
81
81
 
82
+ // Whether the starter asked for a Mac outright: the request's `machine`
83
+ // (stored on the queue row, which shares the task's id), a recipe's, or the
84
+ // project's own machine setting. A need read from the brief or the files
85
+ // doesn't count: that one a plan can ignore.
86
+ export function pinnedToMac(
87
+ stored: string | null,
88
+ projectPath: string
89
+ ): boolean {
90
+ const asked = parseNeeds(stored);
91
+ if (asked) return asked.includes("mac");
92
+ return !!loadProjectConfig(projectPath).config.machine?.needs?.includes(
93
+ "mac"
94
+ );
95
+ }
96
+
97
+ export function taskPinnedToMac(taskId: string, projectPath: string): boolean {
98
+ const row = db
99
+ .prepare(`SELECT machine_needs FROM task_queue WHERE id = ?`)
100
+ .get(taskId) as { machine_needs: string | null } | undefined;
101
+ return pinnedToMac(row?.machine_needs ?? null, projectPath);
102
+ }
103
+
82
104
  export function needsOf(input: {
83
105
  projectId: string;
84
106
  projectPath: string;
@@ -13,6 +13,7 @@ import { usesGitCrypt } from "../worktree-add";
13
13
  import { list as hostTasks } from "./contract/client";
14
14
  import {
15
15
  asPlanPhase,
16
+ pinnedToMac,
16
17
  place,
17
18
  unplaceable,
18
19
  wantsPlacing,
@@ -90,11 +91,22 @@ async function peerOptions(
90
91
  return out;
91
92
  }
92
93
 
93
- // A start that plans first goes by its plan phase: it needs no Mac or
94
- // simulator, and a plan-only one that no machine can take starts here
94
+ // A start that plans first goes by its plan phase: it needs no simulator, and a plan-only one that no machine can take starts here
95
95
  // as ever (it never waits on load).
96
96
  const plansFirst = (r: QueueRow) => !!(r.plan || r.plan_only);
97
97
 
98
+ // Planning needs no simulator or Xcode, and a need read from the files or the
99
+ // brief doesn't hold it. Only an explicit Mac pin does: the work is Mac-only
100
+ // (often because the other machine can't reach what it touches).
101
+ const planNeeds = (r: QueueRow, needs: Need[]): Need[] => {
102
+ const project = getProject(r.project_id);
103
+ return project &&
104
+ needs.includes("mac") &&
105
+ pinnedToMac(r.machine_needs, expandHome(project.working_directory))
106
+ ? ["mac"]
107
+ : [];
108
+ };
109
+
98
110
  // Marks each start the home machine's load rules out as remote and returns
99
111
  // where it goes. "stale": the line moved while the machines were asked, so
100
112
  // this look is over, and the one a change during it asks for sees the line
@@ -143,7 +155,7 @@ export async function placeCandidates(
143
155
  asked(c),
144
156
  home,
145
157
  options,
146
- plan ? [] : (needsOf.get(c.id) ?? []),
158
+ plan ? planNeeds(r, needsOf.get(c.id) ?? []) : (needsOf.get(c.id) ?? []),
147
159
  now
148
160
  );
149
161
  if (!spot) continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saadnvd1/agent-os",
3
- "version": "0.3.766",
3
+ "version": "0.3.768",
4
4
  "description": "Self-hosted web UI for managing Claude Code sessions",
5
5
  "bin": {
6
6
  "agent-os": "scripts/agent-os"