ac-teams 0.0.7 → 0.0.8

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.
@@ -14,10 +14,13 @@ export async function visitMember(ctx: ExtensionContext, host: PanelHost, socket
14
14
  if (ctx.mode !== "tui") throw new Error("Live sessions require an interactive terminal");
15
15
  if (id === host.self) return;
16
16
  const member = (await host.snapshot()).members.find(member => member.id === id);
17
+ // A member returns to whichever main session attached it by detaching.
18
+ if (host.child && member?.role === "main") { await exec("tmux", ["-S", socket, "detach-client", "-s", host.self], { timeout: 5000, maxBuffer: 65536 }); return; }
17
19
  if (!member || !isLive(member)) throw new Error(`Member ${id} is stopped or unavailable; resume it from /teams`);
18
- if (id !== "root" && !member.terminal) throw new Error("This member has no live terminal (it was launched headlessly)");
19
- if (host.self !== "root") {
20
- await exec("tmux", ["-S", socket, ...(id === "root" ? ["detach-client", "-s", host.self] : ["switch-client", "-t", id])], { timeout: 5000, maxBuffer: 65536 });
20
+ if (member.role === "main") throw new Error(`${memberName(member)} is a main session in its own terminal; switch to it there`);
21
+ if (!member.terminal) throw new Error("This member has no live terminal (it was launched headlessly)");
22
+ if (host.child) {
23
+ await exec("tmux", ["-S", socket, "switch-client", "-t", id], { timeout: 5000, maxBuffer: 65536 });
21
24
  return;
22
25
  }
23
26
  const code = await ctx.ui.custom<number>((tui, _theme, _keys, done) => {
package/src/types.ts CHANGED
@@ -43,10 +43,22 @@ export interface Member {
43
43
  access?: Access;
44
44
  /** Provider fixed at an enforced member's first launch; member telemetry cannot change it. */
45
45
  provider?: string;
46
+ /** Authority: a main session or a member. Absent once a main session has left the team. */
47
+ role?: "main" | "member";
48
+ /** State recorded when the coordinator owner stopped this member; the next owner's resume pass consumes it. */
49
+ handover?: "busy" | "idle" | "input";
50
+ /** Promotion in progress: the last completed step and what a rollback restores. */
51
+ promotion?: { step: "stopped" | "moved" | "role" | "entry"; sessionFile: string; parent?: string; access?: Access; provider?: string };
46
52
  }
47
53
  export const memberNameMaxLength = 40;
48
54
  export const memberNamePattern = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)?(?![\s\S])/;
49
55
  export function memberName(member: Pick<Member, "id" | "name">): string { return member.id === "root" ? "Main" : member.name ?? member.id; }
56
+ export function isMain(member: Pick<Member, "role"> | undefined): boolean { return member?.role === "main"; }
57
+ /** Mains control every member and other mains; members control only their descendants, never a main. */
58
+ export function canControl(state: TeamState, actor: string, id: string): boolean {
59
+ const target = state.members[id];
60
+ return !!target && id !== actor && (isMain(state.members[actor]) ? target.role !== undefined : !isMain(target) && isDescendant(state, id, actor));
61
+ }
50
62
  export const taskLabelMaxLength = 60;
51
63
  export const taskLabelPattern = /^[^\s\x00-\x1f\x7f-\x9f]+(?: [^\s\x00-\x1f\x7f-\x9f]+){0,2}(?![\s\S])/;
52
64
  export function taskName(task: Pick<Task, "number" | "label">): string { return `#${task.number} ${task.label}`; }
@@ -75,9 +87,10 @@ export interface Message {
75
87
  time: string;
76
88
  }
77
89
  export interface TeamState {
78
- version: 3;
90
+ version: 4;
79
91
  id: string;
80
- root: string;
92
+ /** Lets an unsandboxed session of the same user add itself as a main session. */
93
+ key: string;
81
94
  limit: number;
82
95
  depth: number;
83
96
  members: Record<string, Member>;
@@ -120,13 +133,15 @@ export interface AgentArgs {
120
133
  access?: AccessRequest;
121
134
  }
122
135
  export interface UserAction {
123
- action: "task_request" | "feedback" | "resume";
136
+ action: "task_request" | "feedback" | "resume" | "promote" | "leave";
124
137
  id?: string;
138
+ /** Promote: the user approved running a sandboxed member with full access. */
139
+ confirmed?: boolean;
125
140
  text?: string;
126
141
  model?: string;
127
142
  thinking?: string;
128
143
  }
129
- export type Operation = "agent" | "task" | "message" | "join" | "ack" | "event" | "control_result" | "ui";
144
+ export type Operation = "agent" | "task" | "message" | "join" | "ack" | "event" | "control_result" | "ui" | "add_main";
130
145
  export function isLive(member: Pick<Member, "status">): boolean {
131
146
  return ["starting", "busy", "idle", "stopping"].includes(member.status);
132
147
  }
package/src/ui-types.ts CHANGED
@@ -10,10 +10,14 @@ export interface TeamSnapshot {
10
10
 
11
11
  export interface PanelHost {
12
12
  self: string;
13
+ /** True in a member session; false in a main session. */
14
+ child: boolean;
13
15
  snapshot(): Promise<TeamSnapshot>;
14
16
  visit(id: string): Promise<void>;
15
17
  requestTask(text: string): Promise<string>;
16
18
  feedback(task: TaskView, text: string): Promise<string>;
17
19
  resume(memberId: string): Promise<void>;
20
+ /** Stops a member and relaunches it as a main session (user only). */
21
+ promote(memberId: string): Promise<void>;
18
22
  drafts: Map<string, string>;
19
23
  }