gestalt-mobile 0.42.0 → 0.42.1

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.
@@ -102,6 +102,10 @@ export class AgentActivityRegistry {
102
102
  kind: 'collaboration',
103
103
  childId: child.id,
104
104
  childStatus: 'notLoaded',
105
+ // The bounded roster/process read is authoritative as a unit. If a
106
+ // child disappeared, its previous runtime-local process projection
107
+ // cannot remain eligible as live work after this reconciliation.
108
+ childOwnedProcesses: [],
105
109
  });
106
110
  // A malformed/unknown `thread/list` row is not proof that a child is
107
111
  // healthy. Preserve it as disconnected but keep the aggregate qualified
@@ -49,6 +49,47 @@ export function classifyAgentActivity(activity) {
49
49
  const subagentsSettled = activity.aggregateSubagents === 'idle' || activity.aggregateSubagents === 'blocked';
50
50
  return rootSettled && subagentsSettled ? 'settled' : 'observe';
51
51
  }
52
+ /**
53
+ * Classifies actor state against the plan position that can legally run next.
54
+ * Completed canonical executors remain visible evidence, but their terminal
55
+ * topology cannot indefinitely turn a fresh read into a reconciliation loop.
56
+ */
57
+ function classifySupervisedActivity(activity, plan) {
58
+ const selected = plan.steps.findIndex((step) => step.id === plan.currentStepId || step.state === 'WIP');
59
+ const index = selected >= 0 ? selected : plan.steps.findIndex((step) => step.state !== 'DONE');
60
+ if (index < 0)
61
+ return classifyAgentActivity(activity);
62
+ const position = index + 1;
63
+ const retained = activity.subagents.filter((child) => {
64
+ const match = /^L([1-9]\d*)$/.exec(child.canonicalPosition ?? '');
65
+ if (!match)
66
+ return true;
67
+ const historical = Number(match[1]) < position;
68
+ const supervisedProcess = child.ownedProcesses?.some((process) => process.ownership === 'supervisor' &&
69
+ (process.state === 'running' || process.state === 'detached-active'));
70
+ return !historical || supervisedProcess;
71
+ });
72
+ // An aggregate-only observation cannot prove which child is historical.
73
+ if (retained.length === activity.subagents.length)
74
+ return classifyAgentActivity(activity);
75
+ if (activity.root.state === 'awaitingHuman' ||
76
+ retained.some((child) => child.state === 'awaitingHuman'))
77
+ return 'attention';
78
+ if (activity.root.state === 'working' ||
79
+ retained.some((child) => child.state === 'working' ||
80
+ child.state === 'awaitingAgent' ||
81
+ child.ownedProcesses?.some((process) => (child.state !== 'disconnected' || process.ownership === 'supervisor') &&
82
+ (process.state === 'running' || process.state === 'detached-active'))))
83
+ return 'active';
84
+ if (activity.root.state === 'disconnected' ||
85
+ retained.some((child) => child.state === 'disconnected'))
86
+ return 'reconcile';
87
+ const rootSettled = activity.root.state === 'idle' ||
88
+ activity.root.state === 'blocked' ||
89
+ activity.root.state === 'awaitingAgent';
90
+ const childrenSettled = retained.every((child) => child.state === 'idle' || child.state === 'blocked');
91
+ return rootSettled && childrenSettled ? 'settled' : 'observe';
92
+ }
52
93
  /** A deliberately pure, exhaustive safety gate. Adapters may only enact this result. */
53
94
  export function decideAutopilot(input) {
54
95
  const { state, plan, activity, hasPendingInteraction, now, policy } = input;
@@ -93,7 +134,7 @@ export function decideAutopilot(input) {
93
134
  return { kind: 'reconcile' };
94
135
  if (Date.parse(now) - Date.parse(activity.root.observedAt) > policy.staleAfterMs)
95
136
  return { kind: 'reconcile' };
96
- const disposition = classifyAgentActivity(activity);
137
+ const disposition = classifySupervisedActivity(activity, plan);
97
138
  if (disposition === 'attention')
98
139
  return { kind: 'observe' };
99
140
  if (disposition === 'reconcile')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gestalt-mobile",
3
- "version": "0.42.0",
3
+ "version": "0.42.1",
4
4
  "description": "Mobile-first web relay for durable Codex development sessions",
5
5
  "keywords": [
6
6
  "codex",