@patimweb/pi-teams 1.9.0 → 1.9.2
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.
- package/docs/listen-mode.md +6 -3
- package/package.json +1 -1
- package/src/extension/index.ts +6 -1
- package/src/watch/loop.ts +33 -5
package/docs/listen-mode.md
CHANGED
|
@@ -15,8 +15,11 @@ understanding before switching on.
|
|
|
15
15
|
Listen mode never starts on its own. It belongs to the session it was switched
|
|
16
16
|
on in and ends with it: quitting pi closes the watcher, and `/new` or `/resume`
|
|
17
17
|
stops it too. A stored `enabled: true` is a setting, not an instruction — set
|
|
18
|
-
`autoStart` if a session is meant to start listening by itself
|
|
19
|
-
|
|
18
|
+
`autoStart` if a session is meant to start listening by itself. Only a session
|
|
19
|
+
with a UI does: a pi started headless by another pi loads the same extensions
|
|
20
|
+
and the same configuration, and a watcher there would answer chats inside a
|
|
21
|
+
throwaway process and move the shared cursor past messages the real session
|
|
22
|
+
never sees.
|
|
20
23
|
|
|
21
24
|
## Settings
|
|
22
25
|
|
|
@@ -36,7 +39,7 @@ sensible for an unattended one.
|
|
|
36
39
|
| Key | Default | Meaning |
|
|
37
40
|
|-----|---------|---------|
|
|
38
41
|
| `enabled` | `false` | whether listen mode is switched on |
|
|
39
|
-
| `autoStart` | `false` | whether a
|
|
42
|
+
| `autoStart` | `false` | whether a session with a UI may start the watcher without being asked |
|
|
40
43
|
| `intervalSeconds` | `60` | seconds between polls (minimum 15) |
|
|
41
44
|
| `chats` | `[]` | **where** pi listens: glob patterns matched against topic, label, chat ID and participant names. Empty means every recent chat. |
|
|
42
45
|
| `from` | `[]` | **who** pi listens to: glob patterns matched against display name, UPN and e-mail. Empty means any sender. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patimweb/pi-teams",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.2",
|
|
4
4
|
"description": "Microsoft Teams integration for pi coding agent — acts fully as the signed-in user across multiple accounts and tenants: chats, channels, meetings, presence, with configurable guardrails",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
package/src/extension/index.ts
CHANGED
|
@@ -429,7 +429,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
429
429
|
// A stored setting is not a decision. A session starts the watcher only when
|
|
430
430
|
// the configuration opts into it; otherwise listen mode waits until it is
|
|
431
431
|
// switched on in this session with `/teams-listen on`.
|
|
432
|
-
|
|
432
|
+
// Only an interactive session listens on its own. A pi started headless by
|
|
433
|
+
// another pi (`--mode json -p`, e.g. a subagent) loads the same extensions
|
|
434
|
+
// and the same config: autoStart there would run a second watcher that
|
|
435
|
+
// answers chats inside a throwaway process and advances the shared cursor,
|
|
436
|
+
// so the real session never sees those messages.
|
|
437
|
+
if (connection.watch.autoStart && ctx.hasUI) startWatch(ctx, { explicit: true });
|
|
433
438
|
|
|
434
439
|
paintStatus(ctx);
|
|
435
440
|
|
package/src/watch/loop.ts
CHANGED
|
@@ -97,6 +97,18 @@ export interface WatchRuntimeStatus {
|
|
|
97
97
|
// One tick
|
|
98
98
|
// ---------------------------------------------------------------------------
|
|
99
99
|
|
|
100
|
+
/** A tick was asked to run without knowing who pi is signed in as. */
|
|
101
|
+
export class WatchIdentityError extends Error {
|
|
102
|
+
constructor(cause?: unknown) {
|
|
103
|
+
super(
|
|
104
|
+
"Listen mode is waiting: the signed-in user could not be determined, so pi's own replies " +
|
|
105
|
+
"could not be told apart from new messages. Retrying on the next poll." +
|
|
106
|
+
(cause ? ` ${formatGraphError(cause)}` : ""),
|
|
107
|
+
);
|
|
108
|
+
this.name = "WatchIdentityError";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
100
112
|
/**
|
|
101
113
|
* Bound on how many chats one tick examines.
|
|
102
114
|
*
|
|
@@ -165,8 +177,14 @@ export async function runWatchTick(
|
|
|
165
177
|
deps: WatchTickDeps,
|
|
166
178
|
state: WatchState,
|
|
167
179
|
watch: ResolvedWatchConfig,
|
|
168
|
-
me: SignedInUser
|
|
180
|
+
me: SignedInUser,
|
|
169
181
|
): Promise<WatchTickResult> {
|
|
182
|
+
// Who pi acts as is what tells its own replies apart from everybody else's
|
|
183
|
+
// (`isFromMe`). Without it, the answer pi just sent looks like a new message
|
|
184
|
+
// from a colleague and wakes pi again — a loop that answers itself. So a tick
|
|
185
|
+
// without an identity examines nothing, and in particular writes no cursor.
|
|
186
|
+
if (!me) throw new WatchIdentityError();
|
|
187
|
+
|
|
170
188
|
const now = deps.now();
|
|
171
189
|
pruneState(state, now);
|
|
172
190
|
|
|
@@ -337,8 +355,15 @@ export function startWatchLoop(options: WatchLoopOptions): WatchLoop {
|
|
|
337
355
|
* instead of starting a second one.
|
|
338
356
|
*/
|
|
339
357
|
let inFlight: Promise<void> | undefined;
|
|
358
|
+
/**
|
|
359
|
+
* Who pi acts as, looked up on the first tick that can reach Graph.
|
|
360
|
+
*
|
|
361
|
+
* A failed lookup is not an answer: it is retried on every tick until it
|
|
362
|
+
* succeeds, and no tick runs before it has. A watcher started before sign-in
|
|
363
|
+
* (autoStart in a fresh container, say) would otherwise settle for "nobody"
|
|
364
|
+
* for the whole session and wake on its own replies.
|
|
365
|
+
*/
|
|
340
366
|
let me: SignedInUser | undefined;
|
|
341
|
-
let meResolved = false;
|
|
342
367
|
let failureCount = 0;
|
|
343
368
|
|
|
344
369
|
const status: WatchRuntimeStatus = {
|
|
@@ -361,9 +386,12 @@ export function startWatchLoop(options: WatchLoopOptions): WatchLoop {
|
|
|
361
386
|
if (stopped) return;
|
|
362
387
|
|
|
363
388
|
try {
|
|
364
|
-
if (!
|
|
365
|
-
|
|
366
|
-
|
|
389
|
+
if (!me) {
|
|
390
|
+
try {
|
|
391
|
+
me = await getMe(connection);
|
|
392
|
+
} catch (err) {
|
|
393
|
+
throw new WatchIdentityError(err);
|
|
394
|
+
}
|
|
367
395
|
}
|
|
368
396
|
|
|
369
397
|
const result = await runWatchTick(
|