@patimweb/pi-teams 1.5.1 → 1.6.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patimweb/pi-teams",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
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",
@@ -131,36 +131,91 @@ instead of trying anyway.
131
131
  ## How to format a Teams message
132
132
 
133
133
  A Teams chat is a chat bubble, not a document. What arrives badly is long
134
- prose: the reader has to hunt for the point, and it reads as if a machine wrote
135
- it. The body is rendered as HTML — markdown is converted for you, so write
134
+ prose: the answer is buried, the reader has to hunt for it, and it reads as if a
135
+ machine wrote it. "Keep it short" does not fix that on its own — a shape does.
136
+ The body is rendered as HTML, and markdown is converted for you, so write
136
137
  markdown and let the tool do the rest.
137
138
 
138
- **Reach for this shape, not for paragraphs:**
139
+ **The shape, which is nearly always this:**
139
140
 
140
141
  ```
141
- Kurz: die QA-Umgebung läuft wieder.
142
+ QA läuft wieder.
142
143
 
143
144
  - Ursache: abgelaufenes Zertifikat
144
145
  - Fix: erneuert und neu deployt
145
146
  - Offen: Monitoring für 90 Tage
146
147
  ```
147
148
 
148
- - **Lead with the answer.** The first line should work as the whole message for
149
- someone who reads nothing else.
150
- - **Three short paragraphs at most.** If it needs more, it is a document — put
151
- it in a file or a wiki page and link it.
149
+ Line one is the answer. Then a blank line, then only what the reader needs in
150
+ order to act on it. Five lines is already a long chat message.
151
+
152
+ **Before and after.** Both versions say the same thing; only one is a chat:
153
+
154
+ > **So nicht:** Ich habe mir die QA-Umgebung jetzt angeschaut und konnte den
155
+ > Fehler reproduzieren. Das Problem liegt darin, dass das TLS-Zertifikat
156
+ > abgelaufen war, wodurch alle Requests fehlgeschlagen sind. Ich habe es dann
157
+ > erneuert und die Umgebung neu deployt, jetzt läuft wieder alles. Ich würde
158
+ > allerdings noch empfehlen, das Monitoring einzurichten, damit das nicht
159
+ > wieder passiert.
160
+
161
+ > **So:** QA läuft wieder.
162
+ >
163
+ > - Ursache: abgelaufenes TLS-Zertifikat
164
+ > - Fix: erneuert und neu deployt
165
+ > - Empfehlung: Monitoring auf die Ablaufdaten
166
+
167
+ **Three skeletons that cover most messages:**
168
+
169
+ *Antwort auf eine Frage* — the answer, then at most one line of reasoning:
170
+
171
+ ```
172
+ Ja, Freitag passt.
173
+
174
+ Ich bin bis 14:00 im Workshop, danach jederzeit.
175
+ ```
176
+
177
+ *Status* — outcome first, then what is still open:
178
+
179
+ ```
180
+ Deployment ist durch, Release Notes stehen im Wiki: <Link>
181
+
182
+ Offen: Smoke-Test auf Produktion, mache ich heute Nachmittag.
183
+ ```
184
+
185
+ *Entscheidung nötig* — the decision, the options, the deadline:
186
+
187
+ ```
188
+ Ich brauche bis morgen eine Entscheidung zum Sync-Intervall.
189
+
190
+ - 5 Min: schnell, ~3× Last auf dem SAP-System
191
+ - 15 Min: entspannt, dafür Verzögerung im Reporting
192
+ ```
193
+
194
+ **Rules**
195
+
196
+ - **The first line carries the message.** Someone who reads only that line
197
+ should know the outcome.
152
198
  - **Bullets for anything enumerable** — causes, options, next steps. Three items
153
- and up belong in a list, not in a sentence with commas.
199
+ and up are a list; two are a sentence.
200
+ - **One idea per line.** A line break is free; a clause chain held together by
201
+ commas is not.
154
202
  - **Bold the one thing that must not be missed**, and only that. Bold used
155
203
  everywhere is bold nowhere.
156
204
  - **Numbers, names and dates exactly as they are.** Do not round a figure or
157
205
  paraphrase a deadline into something friendlier.
158
206
  - **One question per message.** Two questions get one answer.
207
+ - **No greeting and no closing.** "Hallo zusammen", "Ich habe mir das mal
208
+ angeschaut", "Kurz zusammengefasst:" and "Sag Bescheid, wenn du Fragen hast"
209
+ are all noise in a chat. The message starts with the answer and stops when it
210
+ is done.
211
+ - **Over five lines, it is a document.** Put it in a wiki page or a file and send
212
+ the link with one sentence around it.
159
213
 
160
214
  **What Teams renders:** `**bold**`, `*italic*`, `` `code` ``, `~~struck~~`,
161
- `[label](url)`, `- bullets`, `1. numbered`, ``` fences. A `# heading` arrives as
162
- a bold line — fine for structure, not for hierarchy. **Tables are not supported
163
- in a chat**: convert them to bullets, or put the table in a file and link it.
215
+ `[label](url)`, `- bullets`, `1. numbered`, and code fences. A `# heading`
216
+ arrives as a bold line — fine for structure, not for hierarchy. **Tables are not
217
+ supported in a chat**: convert them to bullets, or put the table in a file and
218
+ link it.
164
219
 
165
220
  **Language and tone:** answer in the language of the conversation, in the
166
221
  register of the people in it. Short forms are normal in a chat ("passt", "ok,
@@ -323,9 +378,9 @@ teams_watch:
323
378
  2. If an answer is warranted, send it with `teams_send_chat_message` to the
324
379
  **chat ID from the prompt** — never re-resolve the chat by name. pi posts as
325
380
  the user, so the wrong chat is a wrong statement in the user's name.
326
- 3. Keep it to the shape in *How to format a Teams message*: answer first, three
327
- short paragraphs at most, bullets for lists. An unrequested message has to be
328
- readable at a glance.
381
+ 3. Keep it to the shape in *How to format a Teams message*: the answer on the
382
+ first line, at most five short lines, bullets for anything enumerable. An
383
+ unrequested message has to be readable at a glance.
329
384
  4. If no answer is warranted, say so in one line and stop. Do not react, do not
330
385
  touch other chats, do not post to a channel.
331
386
 
@@ -10,6 +10,7 @@ import { Type } from "typebox";
10
10
  import { replyToChannelMessage, sendChannelMessage } from "../graph/messages.ts";
11
11
  import { resolveUserId } from "../graph/me.ts";
12
12
  import { auditWrite } from "../safety/audit.ts";
13
+ import { CHAT_MESSAGE_BODY_DESCRIPTION, CHAT_SHAPE_GUIDELINE } from "../utils/chat-style.ts";
13
14
  import { applyAiFooter } from "../utils/disclosure.ts";
14
15
  import { assertAccess } from "../safety/index.ts";
15
16
  import { truncate } from "../utils/formatting.ts";
@@ -58,10 +59,7 @@ async function resolveMentions(
58
59
  const sharedParams = {
59
60
  channel: Type.String({ description: "Channel name or ID, or a 'Team/Channel' path" }),
60
61
  team: Type.Optional(Type.String({ description: "Team name or ID (omit when using a path)" })),
61
- body: Type.String({
62
- description:
63
- "Message text as lightweight markdown (bold, italics, code, bullets, numbered lists, links)",
64
- }),
62
+ body: Type.String({ description: CHAT_MESSAGE_BODY_DESCRIPTION }),
65
63
  account: AccountParam,
66
64
  tenant: TenantParam,
67
65
  mentions: Type.Optional(
@@ -85,7 +83,7 @@ export const teamsSendChannelMessageTool = {
85
83
  promptGuidelines: [
86
84
  "Write in the user's voice — the post is attributed to them, not to an assistant.",
87
85
  "Show the exact text and the target channel before posting when there is any ambiguity.",
88
- "Format for a chat, not for a document: lead with the answer, three short paragraphs at most, bullets for lists. Bold, italics, code, links and bullets are rendered; headings become bold lines and tables are not supported — keep those out.",
86
+ CHAT_SHAPE_GUIDELINE,
89
87
  "Answer in the language of the conversation you are writing into, and match its register.",
90
88
  ],
91
89
 
@@ -180,6 +178,7 @@ export const teamsReplyChannelMessageTool = {
180
178
  messageId: Type.String({ description: "ID of the thread's opening message" }),
181
179
  }),
182
180
  promptSnippet: "Reply in a Teams channel thread as the user",
181
+ promptGuidelines: [CHAT_SHAPE_GUIDELINE],
183
182
 
184
183
  async execute(
185
184
  _toolCallId: string,
@@ -21,6 +21,7 @@ import { formatGraphError } from "../utils/errors.ts";
21
21
  import { resolveUserId } from "../graph/me.ts";
22
22
  import { auditWrite } from "../safety/audit.ts";
23
23
  import { assertAccess } from "../safety/index.ts";
24
+ import { CHAT_MESSAGE_BODY_DESCRIPTION, CHAT_SHAPE_GUIDELINE } from "../utils/chat-style.ts";
24
25
  import { applyAiFooter } from "../utils/disclosure.ts";
25
26
  import { truncate } from "../utils/formatting.ts";
26
27
  import { requireChat } from "./resolve.ts";
@@ -56,10 +57,7 @@ export const teamsSendChatMessageTool = {
56
57
  "where the mention should appear.",
57
58
  parameters: Type.Object({
58
59
  chat: Type.String({ description: "Chat ID, group chat topic, or a participant's name/e-mail" }),
59
- body: Type.String({
60
- description:
61
- "Message text as lightweight markdown (bold, italics, code, bullets, numbered lists, links)",
62
- }),
60
+ body: Type.String({ description: CHAT_MESSAGE_BODY_DESCRIPTION }),
63
61
  account: AccountParam,
64
62
  tenant: TenantParam,
65
63
  mentions: Type.Optional(
@@ -79,7 +77,7 @@ export const teamsSendChatMessageTool = {
79
77
  "Write the message in the user's voice — it is sent from their account, not from an assistant.",
80
78
  "Show the user the exact text before sending when the intent is even slightly ambiguous.",
81
79
  "Do not add signatures, or a note that the message was written by an AI: the sending tool appends the configured AI disclosure itself (on by default), so writing one into the body duplicates it.",
82
- "Format for a chat, not for a document: lead with the answer, three short paragraphs at most, bullets for lists. Bold, italics, code, links and bullets are rendered; headings become bold lines and tables are not supported — keep those out.",
80
+ CHAT_SHAPE_GUIDELINE,
83
81
  "Answer in the language of the conversation you are writing into, and match its register.",
84
82
  ],
85
83
 
@@ -14,6 +14,7 @@
14
14
  import { Type } from "typebox";
15
15
  import { updateMessage } from "../graph/messages.ts";
16
16
  import { auditWrite } from "../safety/audit.ts";
17
+ import { CHAT_MESSAGE_BODY_DESCRIPTION, CHAT_SHAPE_GUIDELINE } from "../utils/chat-style.ts";
17
18
  import { applyAiFooter } from "../utils/disclosure.ts";
18
19
  import { requireChat } from "./resolve.ts";
19
20
  import {
@@ -36,7 +37,7 @@ export const teamsUpdateMessageTool = {
36
37
  "Use this instead of deleting a message and sending a corrected copy.",
37
38
  parameters: Type.Object({
38
39
  messageId: Type.String({ description: "ID of the message to edit" }),
39
- body: Type.String({ description: "The new message text (lightweight markdown by default)" }),
40
+ body: Type.String({ description: CHAT_MESSAGE_BODY_DESCRIPTION }),
40
41
  chat: Type.String({ description: "Chat ID, group chat topic, or a participant's name/e-mail" }),
41
42
  html: Type.Optional(Type.Boolean({ description: "Treat 'body' as raw HTML instead of markdown" })),
42
43
  account: AccountParam,
@@ -45,6 +46,7 @@ export const teamsUpdateMessageTool = {
45
46
  promptSnippet: "Edit a message the user sent in Teams",
46
47
  promptGuidelines: [
47
48
  "Use teams_update_message to correct a message the user already sent, rather than deleting it and posting a replacement.",
49
+ CHAT_SHAPE_GUIDELINE,
48
50
  ],
49
51
 
50
52
  async execute(
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The shape of a Teams message.
3
+ *
4
+ * A chat bubble is read at a glance, on a phone, between two other things. The
5
+ * failure mode is well known and it is not a lack of language skill: the model
6
+ * writes a *document*. It opens with context, builds up to the point, and
7
+ * closes with a summary — so the reader has to hunt for the answer, which is
8
+ * exactly the "wall of text" people complain about.
9
+ *
10
+ * Telling the model to "be brief" does not fix it. Vague length advice is the
11
+ * weakest kind of formatting instruction; an explicit shape and a worked
12
+ * example are the strongest, and they have to sit where the message is being
13
+ * composed rather than in a document the model may or may not have loaded.
14
+ *
15
+ * So one shape, stated the same way in every place that matters:
16
+ *
17
+ * - the `body` parameter description — in context at the moment of writing,
18
+ * which is the highest-attention placement available,
19
+ * - the per-tool guideline bullet — in context for the whole session, and
20
+ * flat in the Guidelines list, so it has to name its tools,
21
+ * - the skill and the listen-mode prompt — which carry the worked example
22
+ * (`skills/teams-collaboration/SKILL.md`).
23
+ *
24
+ * Keeping it in one module is the point. Four tools used to carry their own
25
+ * copy of one sentence, they had drifted, and the sentence they shared was
26
+ * "three short paragraphs at most" — which permits prose, and got prose.
27
+ */
28
+
29
+ /**
30
+ * What a message body is, and what shape it has.
31
+ *
32
+ * Used as the `body` parameter description on every tool that sends one, so it
33
+ * is read at the moment the text is written. Kept short on purpose: it is in
34
+ * the tool schema, and every tool in this package is in context at once.
35
+ */
36
+ export const CHAT_MESSAGE_BODY_DESCRIPTION =
37
+ "Message text as lightweight markdown, in chat shape: the answer on the first line, then at most 5 short " +
38
+ "lines, `- ` bullets for anything enumerable, one idea per line. No greeting, no closing, no summary. " +
39
+ "Rendered: **bold**, *italic*, `code`, ~~struck~~, [label](url), `- ` bullets, `1. ` numbered, ``` fences. " +
40
+ "A `#` heading arrives as a bold line; tables are not rendered at all.";
41
+
42
+ /**
43
+ * The same shape as one guideline bullet.
44
+ *
45
+ * `promptGuidelines` are appended flat to the system prompt's Guidelines
46
+ * section with no tool-name prefix, so this names every tool it applies to —
47
+ * otherwise the model cannot tell which "the message body" means. It stays on
48
+ * one line for the same reason: it is a bullet in someone else's list.
49
+ */
50
+ export const CHAT_SHAPE_GUIDELINE =
51
+ "teams_send_chat_message, teams_send_channel_message, teams_reply_channel_message and teams_update_message " +
52
+ "write chat shape, not a document: the answer on the first line, at most 5 short lines, `- ` bullets for " +
53
+ "enumerable items, no greeting, no closing summary, no tables.";
54
+
55
+ /**
56
+ * The shape as prose, for the listen-mode prompt.
57
+ *
58
+ * Listen mode is the case where this matters most: an answer that arrives
59
+ * unrequested has to be readable at a glance, because the recipient is not
60
+ * waiting for it and did not ask for it.
61
+ */
62
+ export const CHAT_SHAPE_FOR_LISTEN_MODE =
63
+ "Format it as a chat bubble, not a document: the answer on the first line, at most 5 short lines, " +
64
+ "`- ` bullets for anything enumerable, no greeting and no closing. The recipient is not expecting this " +
65
+ "message, so it has to be readable at a glance.";
@@ -16,6 +16,7 @@
16
16
  */
17
17
 
18
18
  import type { MessageSummary, SignedInUser } from "../types.ts";
19
+ import { CHAT_SHAPE_FOR_LISTEN_MODE } from "../utils/chat-style.ts";
19
20
  import type { WatchEvent } from "./loop.ts";
20
21
 
21
22
  function senderName(message: MessageSummary): string {
@@ -114,9 +115,7 @@ export function composeWatchPrompt(event: WatchEvent, me?: SignedInUser): string
114
115
  "the sending tool appends the configured AI disclosure itself.",
115
116
  "- If it does not: say so in one line and stop.",
116
117
  "",
117
- "Format it for a chat bubble, not a document: the answer first, three short paragraphs at most, " +
118
- "bullets for anything enumerable, no tables. The recipient is not expecting this message, so it has " +
119
- "to be readable at a glance.",
118
+ CHAT_SHAPE_FOR_LISTEN_MODE,
120
119
  "",
121
120
  "Do not react, do not touch other chats, and do not send anything to a channel.",
122
121
  ].join("\n");