pi-sage 0.2.14 → 0.2.15

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.
@@ -0,0 +1,50 @@
1
+ import type { CallerContext } from "./types.js";
2
+
3
+ export type InputSource = "interactive" | "rpc" | "extension";
4
+ export type InteractiveOrRpcSource = "interactive" | "rpc";
5
+
6
+ export function isKnownInputSource(value: string): value is InputSource {
7
+ return value === "interactive" || value === "rpc" || value === "extension";
8
+ }
9
+
10
+ export function updateLastInteractiveOrRpcSource(
11
+ previous: InteractiveOrRpcSource | undefined,
12
+ source: string
13
+ ): InteractiveOrRpcSource | undefined {
14
+ if (source === "interactive" || source === "rpc") {
15
+ return source;
16
+ }
17
+ return previous;
18
+ }
19
+
20
+ export function buildCallerContextFromSignals(input: {
21
+ lastInteractiveOrRpcSource: InteractiveOrRpcSource | undefined;
22
+ unknownSourceSeen: boolean;
23
+ hasUI: boolean;
24
+ roleHint?: string;
25
+ isCI?: boolean;
26
+ isSubagent?: boolean;
27
+ }): CallerContext | null {
28
+ const { lastInteractiveOrRpcSource, unknownSourceSeen, hasUI, roleHint, isCI, isSubagent } = input;
29
+ if (unknownSourceSeen) return null;
30
+ if (!lastInteractiveOrRpcSource) return null;
31
+
32
+ const normalizedRole = roleHint?.trim().toLowerCase();
33
+ const isRpcSource = lastInteractiveOrRpcSource === "rpc";
34
+ const interactive = hasUI && lastInteractiveOrRpcSource === "interactive";
35
+ const isInteractiveSupervisor = interactive && normalizedRole === "supervisor";
36
+
37
+ return {
38
+ session: {
39
+ interactive
40
+ },
41
+ agent: {
42
+ role: normalizedRole ?? "primary",
43
+ isSubagent: Boolean(isSubagent),
44
+ isRpcOrchestrated: isRpcSource || Boolean(normalizedRole && normalizedRole !== "primary" && !isInteractiveSupervisor)
45
+ },
46
+ runtime: {
47
+ mode: isCI ? "ci" : hasUI ? (isRpcSource ? "rpc" : "interactive") : "non-interactive"
48
+ }
49
+ };
50
+ }
@@ -7,6 +7,12 @@ import {
7
7
  isHardCostCapExceeded,
8
8
  makeBlockedResult
9
9
  } from "./policy.js";
10
+ import {
11
+ buildCallerContextFromSignals,
12
+ isKnownInputSource,
13
+ updateLastInteractiveOrRpcSource,
14
+ type InteractiveOrRpcSource
15
+ } from "./caller-context.js";
10
16
  import {
11
17
  isSageRunnerPolicyError,
12
18
  runSageSingleShot,
@@ -34,8 +40,6 @@ import type {
34
40
  ToolProfile
35
41
  } from "./types.js";
36
42
 
37
- type InputSource = "interactive" | "rpc" | "extension";
38
-
39
43
  type ModelLike = {
40
44
  provider: string;
41
45
  id: string;
@@ -74,12 +78,22 @@ export default function registerSageExtension(pi: ExtensionAPI): void {
74
78
  sessionCostTotal: 0
75
79
  };
76
80
 
77
- let lastInputSource: InputSource | undefined;
81
+ let lastInteractiveOrRpcSource: InteractiveOrRpcSource | undefined;
82
+ let unknownInputSourceSeen = false;
78
83
 
79
84
  pi.on("input", (event) => {
80
- if (event.source === "interactive" || event.source === "rpc" || event.source === "extension") {
81
- lastInputSource = event.source;
85
+ const source = String(event.source ?? "");
86
+
87
+ if (!isKnownInputSource(source)) {
88
+ unknownInputSourceSeen = true;
89
+ return { action: "continue" };
82
90
  }
91
+
92
+ lastInteractiveOrRpcSource = updateLastInteractiveOrRpcSource(lastInteractiveOrRpcSource, source);
93
+ if (source === "interactive" || source === "rpc") {
94
+ unknownInputSourceSeen = false;
95
+ }
96
+
83
97
  return { action: "continue" };
84
98
  });
85
99
 
@@ -166,7 +180,7 @@ export default function registerSageExtension(pi: ExtensionAPI): void {
166
180
  });
167
181
  }
168
182
 
169
- const callerContext = buildCallerContext(lastInputSource, ctx.hasUI);
183
+ const callerContext = buildCallerContext(lastInteractiveOrRpcSource, unknownInputSourceSeen, ctx.hasUI);
170
184
  const eligibility = isEligibleCaller(callerContext);
171
185
  if (!eligibility.ok) {
172
186
  return makeBlockedResult({
@@ -389,28 +403,19 @@ async function runSageWithFallback(
389
403
  }
390
404
  }
391
405
 
392
- function buildCallerContext(lastInputSource: InputSource | undefined, hasUI: boolean): CallerContext | null {
393
- if (!lastInputSource) return null;
394
-
395
- const roleHint = getRoleHint();
396
- const isRpcSource = lastInputSource === "rpc";
397
- const isSubagent = process.env.PI_SAGE_SUBAGENT === "1";
398
- const interactive = hasUI && lastInputSource === "interactive";
399
- const isInteractiveSupervisor = interactive && roleHint === "supervisor";
400
-
401
- return {
402
- session: {
403
- interactive
404
- },
405
- agent: {
406
- role: roleHint ?? "primary",
407
- isSubagent,
408
- isRpcOrchestrated: isRpcSource || Boolean(roleHint && roleHint !== "primary" && !isInteractiveSupervisor)
409
- },
410
- runtime: {
411
- mode: process.env.CI ? "ci" : hasUI ? (isRpcSource ? "rpc" : "interactive") : "non-interactive"
412
- }
413
- };
406
+ function buildCallerContext(
407
+ lastInteractiveOrRpcSource: InteractiveOrRpcSource | undefined,
408
+ unknownInputSourceSeen: boolean,
409
+ hasUI: boolean
410
+ ): CallerContext | null {
411
+ return buildCallerContextFromSignals({
412
+ lastInteractiveOrRpcSource,
413
+ unknownSourceSeen: unknownInputSourceSeen,
414
+ hasUI,
415
+ roleHint: getRoleHint(),
416
+ isCI: Boolean(process.env.CI),
417
+ isSubagent: process.env.PI_SAGE_SUBAGENT === "1"
418
+ });
414
419
  }
415
420
 
416
421
  function getRoleHint(): string | undefined {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-sage",
3
- "version": "0.2.14",
3
+ "version": "0.2.15",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Interactive-only advisory Sage extension for Pi",