@topy-ai/maggie 0.7.52 → 0.7.54
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/README-zh-TW.md +28 -4
- package/README.md +93 -60
- package/bin/maggie.js +67 -12
- package/bundled-contracts/maggie-assistant-tools/booking-receptionist-example-v1.json +161 -0
- package/bundled-contracts/maggie-assistant-tools/tool-registry-v1.schema.json +268 -20
- package/bundled-contracts/maggie-deployment/bridge-host-example-v1.json +49 -7
- package/bundled-contracts/maggiedash/booking-email-templates-v1.json +181 -13
- package/bundled-contracts/maggiedash/booking-rules-v1.json +1699 -0
- package/bundled-contracts/maggiedash/stripe-booking-capabilities-v1.json +398 -39
- package/bundled-references/maggiedash-booking/PRD.md +22 -11
- package/bundled-references/maggiedash-booking/QA-TEST-PLAN.md +1 -1
- package/bundled-references/maggiedash-booking/RULES.md +167 -0
- package/bundled-references/maggiedash-booking/RUNTIME-OPERATIONS.md +2 -2
- package/bundled-references/maggiedash-booking/STRIPE-INTEGRATION.md +42 -4
- package/bundled-references/provider-adapter-contract.md +16 -3
- package/bundled-skills/catalog.json +1 -1
- package/bundled-skills/maggie-assistant-tools/SKILL.md +54 -3
- package/bundled-skills/maggie-assistant-tools/references/approvals-and-audit.md +27 -0
- package/bundled-skills/maggie-assistant-tools/references/booking-receptionist.md +45 -0
- package/bundled-skills/maggie-assistant-tools/references/bridge-host.md +54 -0
- package/bundled-skills/maggie-auth-reference/SKILL.md +18 -0
- package/bundled-skills/maggie-blog/SKILL.md +8 -0
- package/bundled-skills/maggie-booking/SKILL.md +66 -14
- package/bundled-skills/maggie-content-localization/SKILL.md +26 -0
- package/bundled-skills/maggie-dash/SKILL.md +28 -2
- package/bundled-skills/maggie-deployment/SKILL.md +48 -0
- package/bundled-skills/maggie-deployment/references/cloudflare.md +19 -0
- package/bundled-skills/maggie-deployment/references/vps.md +92 -0
- package/bundled-skills/maggie-design/SKILL.md +98 -3
- package/bundled-skills/maggie-design/references/app-ui-patterns.md +79 -0
- package/bundled-skills/maggie-feedback/SKILL.md +27 -2
- package/bundled-skills/maggie-marketplace/SKILL.md +12 -0
- package/bundled-skills/maggie-marketplace/references/directory-search.md +51 -0
- package/bundled-skills/maggie-marketplace/references/directory.mjs +112 -0
- package/bundled-skills/maggie-mobile-app/SKILL.md +65 -6
- package/bundled-skills/maggie-mobile-app/references/payments-and-push.md +78 -0
- package/bundled-skills/maggie-qa-workflow/SKILL.md +60 -0
- package/bundled-skills/maggie-seo-geo/SKILL.md +33 -0
- package/bundled-skills/maggie-service-booking/SKILL.md +28 -3
- package/bundled-skills/maggie-social-share/SKILL.md +56 -4
- package/bundled-skills/maggie-social-share/references/queue-and-connections.md +28 -0
- package/bundled-tools/clis/maggie_assistant_tools.py +84 -3
- package/bundled-tools/clis/maggie_booking.py +24 -12
- package/bundled-tools/clis/maggie_booking_connect_qa.py +136 -0
- package/bundled-tools/clis/maggie_booking_rules.py +476 -0
- package/bundled-tools/clis/maggie_bridge_host.py +77 -1
- package/bundled-tools/clis/maggie_dash.py +173 -3
- package/bundled-tools/clis/maggie_deployment_ops.py +524 -0
- package/bundled-tools/clis/maggie_design.py +15 -2
- package/bundled-tools/clis/maggie_design_bulk.py +290 -0
- package/bundled-tools/clis/maggie_design_lint.py +205 -3
- package/bundled-tools/clis/maggie_feedback.py +138 -28
- package/bundled-tools/clis/maggie_live_urls.py +138 -0
- package/bundled-tools/clis/maggie_localization.py +3 -0
- package/bundled-tools/clis/maggie_localization_keys.py +151 -0
- package/bundled-tools/clis/maggie_media_generate.py +174 -0
- package/bundled-tools/clis/maggie_mobile.py +219 -4
- package/bundled-tools/clis/maggie_product_release.py +49 -1
- package/bundled-tools/clis/maggie_qa_app_preflight.py +122 -4
- package/bundled-tools/clis/maggie_qa_fit.py +217 -0
- package/bundled-tools/clis/maggie_qa_workflow.py +5 -0
- package/bundled-tools/clis/maggie_schema_complexity.py +84 -0
- package/bundled-tools/clis/maggie_seo_routes.py +154 -0
- package/bundled-tools/clis/maggie_service_migration.py +272 -0
- package/bundled-tools/clis/maggie_social_queue.py +2 -0
- package/bundled-tools/clis/maggie_social_share.py +174 -7
- package/bundled-tools/integrations/maggie-api-pull.md +22 -0
- package/bundled-tools/runtime/assistant_approval.py +98 -0
- package/package.json +1 -1
- package/references/provider-adapter-contract.md +16 -3
- package/bundled-contracts/maggiedash/execution-board.json +0 -5463
- package/bundled-references/maggiedash-booking/CURRENT-STATE.md +0 -210
- package/bundled-references/maggiedash-booking/EXECUTION-BOARD.json +0 -5463
- package/bundled-references/maggiedash-booking/ORAWELLNESS-INTEGRATION-AUDIT.md +0 -270
- package/bundled-references/maggiedash-booking/PROGRESS.md +0 -279
- package/bundled-references/maggiedash-booking/README.md +0 -288
- package/bundled-references/maggiedash-booking/SKILLS-AND-CLI.md +0 -551
- package/bundled-references/maggiedash-booking/TASK-RUNBOOK.md +0 -107
- package/bundled-references/maggiedash-booking/TASKS.md +0 -178
|
@@ -77,6 +77,8 @@ def verify(secret: bytes, token: str, identity: str, scope: str, calls: list[dic
|
|
|
77
77
|
if not hmac.compare_digest(hmac.new(secret, body, hashlib.sha256).digest(), signature):
|
|
78
78
|
raise ApprovalError("approval token signature does not match")
|
|
79
79
|
approval = json.loads(body)
|
|
80
|
+
if approval.get("kind") == "standing":
|
|
81
|
+
raise ApprovalError("a standing approval only covers workflow runs; use check_standing")
|
|
80
82
|
if approval.get("expires", 0) < (now if now is not None else time.time()):
|
|
81
83
|
raise ApprovalError("approval expired; ask again")
|
|
82
84
|
if not hmac.compare_digest(approval.get("principal", ""), principal_hash(identity)):
|
|
@@ -109,3 +111,99 @@ class TurnStore:
|
|
|
109
111
|
if not token:
|
|
110
112
|
raise ApprovalError("no pending approval for this turn (already used or never proposed)")
|
|
111
113
|
return token
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# --- Standing approvals -------------------------------------------------------
|
|
117
|
+
#
|
|
118
|
+
# Scheduled and event-driven workflows ("when someone cancels, offer the slot to
|
|
119
|
+
# the waitlist") cannot ask for a card every time. A standing approval is a
|
|
120
|
+
# signed grant bound to one workflow, its tools, per-tool limits and an expiry.
|
|
121
|
+
# Every run inside the limits is audited with channel "workflow" and the grant
|
|
122
|
+
# id; a call outside the limits raises OutsideStandingApproval and the caller
|
|
123
|
+
# falls back to a normal approval card. Revocation and daily usage live on the
|
|
124
|
+
# server (StandingStore models them).
|
|
125
|
+
|
|
126
|
+
MAX_STANDING_DAYS = 90
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class OutsideStandingApproval(ApprovalError):
|
|
130
|
+
"""The call is not covered by the grant: ask with a normal approval card."""
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def issue_standing(secret: bytes, identity: str, scope: str, workflow: str, grants: list[dict], days: int = 30, now: float | None = None) -> tuple[str, dict]:
|
|
134
|
+
if not workflow or not grants:
|
|
135
|
+
raise ApprovalError("a standing approval needs a workflow and at least one tool grant")
|
|
136
|
+
if not 1 <= days <= MAX_STANDING_DAYS:
|
|
137
|
+
raise ApprovalError(f"a standing approval lasts 1-{MAX_STANDING_DAYS} days; renew it instead of making it permanent")
|
|
138
|
+
for grant in grants:
|
|
139
|
+
if not grant.get("name") or not isinstance(grant.get("maxPerDay"), int) or grant["maxPerDay"] < 1:
|
|
140
|
+
raise ApprovalError("every tool grant needs a name and maxPerDay")
|
|
141
|
+
approval = {
|
|
142
|
+
"kind": "standing",
|
|
143
|
+
"id": secrets.token_hex(16),
|
|
144
|
+
"workflow": workflow,
|
|
145
|
+
"grants": [{key: grant[key] for key in ("name", "maxPerDay", "maxPerRun", "fixedArgs", "maxAmountMinor") if key in grant} for grant in grants],
|
|
146
|
+
"principal": principal_hash(identity),
|
|
147
|
+
"scope": scope,
|
|
148
|
+
"expires": int((now if now is not None else time.time()) + days * 86400),
|
|
149
|
+
}
|
|
150
|
+
body = canonical(approval).encode("utf-8")
|
|
151
|
+
return _b64(body) + "." + _b64(hmac.new(secret, body, hashlib.sha256).digest()), approval
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _decode(secret: bytes, token: str) -> dict:
|
|
155
|
+
try:
|
|
156
|
+
body_text, signature_text = token.split(".", 1)
|
|
157
|
+
body, signature = _unb64(body_text), _unb64(signature_text)
|
|
158
|
+
except (ValueError, TypeError) as error:
|
|
159
|
+
raise ApprovalError("approval token is malformed") from error
|
|
160
|
+
if not hmac.compare_digest(hmac.new(secret, body, hashlib.sha256).digest(), signature):
|
|
161
|
+
raise ApprovalError("approval token signature does not match")
|
|
162
|
+
return json.loads(body)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def check_standing(secret: bytes, token: str, identity: str, scope: str, workflow: str, call: dict, used_today: int, run_calls: int = 0, revoked: set[str] | None = None, now: float | None = None) -> dict:
|
|
166
|
+
"""Allow one workflow call under a standing approval, or raise (OutsideStandingApproval = ask with a card)."""
|
|
167
|
+
approval = _decode(secret, token)
|
|
168
|
+
if approval.get("kind") != "standing":
|
|
169
|
+
raise ApprovalError("not a standing approval")
|
|
170
|
+
if approval["id"] in (revoked or set()):
|
|
171
|
+
raise OutsideStandingApproval("this standing approval was revoked; ask again")
|
|
172
|
+
if approval.get("expires", 0) < (now if now is not None else time.time()):
|
|
173
|
+
raise OutsideStandingApproval("this standing approval expired; ask the owner to renew it")
|
|
174
|
+
if not hmac.compare_digest(approval.get("principal", ""), principal_hash(identity)) or approval.get("scope") != scope:
|
|
175
|
+
raise ApprovalError("standing approval belongs to another owner or workspace")
|
|
176
|
+
if approval.get("workflow") != workflow:
|
|
177
|
+
raise OutsideStandingApproval("the standing approval covers another workflow")
|
|
178
|
+
grant = next((g for g in approval.get("grants", []) if g.get("name") == call.get("name")), None)
|
|
179
|
+
if grant is None:
|
|
180
|
+
raise OutsideStandingApproval(f"{call.get('name')} is not part of this workflow's standing approval")
|
|
181
|
+
if used_today >= grant["maxPerDay"]:
|
|
182
|
+
raise OutsideStandingApproval(f"daily limit of {grant['maxPerDay']} reached for {call['name']}")
|
|
183
|
+
if grant.get("maxPerRun") and run_calls >= grant["maxPerRun"]:
|
|
184
|
+
raise OutsideStandingApproval(f"limit of {grant['maxPerRun']} per run reached for {call['name']}")
|
|
185
|
+
args = call.get("args", {})
|
|
186
|
+
for key, value in (grant.get("fixedArgs") or {}).items():
|
|
187
|
+
if args.get(key) != value:
|
|
188
|
+
raise OutsideStandingApproval(f"{call['name']}: {key} differs from the approved value")
|
|
189
|
+
if grant.get("maxAmountMinor") is not None and int(args.get("amountMinor", 0)) > grant["maxAmountMinor"]:
|
|
190
|
+
raise OutsideStandingApproval(f"{call['name']}: amount above the approved maximum")
|
|
191
|
+
return {"approvalId": approval["id"], "workflow": workflow, "auditChannel": "workflow", "idempotencyKey": idempotency_key(approval["id"], call)}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class StandingStore:
|
|
195
|
+
"""Server-side model: revocations and per-day usage per grant and tool."""
|
|
196
|
+
|
|
197
|
+
def __init__(self) -> None:
|
|
198
|
+
self.revoked: set[str] = set()
|
|
199
|
+
self.usage: dict[tuple[str, str, str], int] = {}
|
|
200
|
+
|
|
201
|
+
def revoke(self, approval_id: str) -> None:
|
|
202
|
+
self.revoked.add(approval_id)
|
|
203
|
+
|
|
204
|
+
def run(self, secret: bytes, token: str, identity: str, scope: str, workflow: str, call: dict, day: str, run_calls: int = 0, now: float | None = None) -> dict:
|
|
205
|
+
approval_id = _decode(secret, token).get("id", "")
|
|
206
|
+
key = (approval_id, call.get("name", ""), day)
|
|
207
|
+
allowed = check_standing(secret, token, identity, scope, workflow, call, self.usage.get(key, 0), run_calls, self.revoked, now)
|
|
208
|
+
self.usage[key] = self.usage.get(key, 0) + 1
|
|
209
|
+
return allowed
|
package/package.json
CHANGED
|
@@ -173,9 +173,22 @@ rules from a redacted JSON manifest; see the Social Share skill for shapes.
|
|
|
173
173
|
|
|
174
174
|
## Structured model output
|
|
175
175
|
|
|
176
|
-
Keep structured-output schemas small.
|
|
177
|
-
|
|
178
|
-
|
|
176
|
+
Keep structured-output schemas small. The limit is **schema complexity**, not
|
|
177
|
+
vocabulary size: roughly enum values × repeated array items × nesting.
|
|
178
|
+
Gemini rejected a JSON schema whose enum had 125 values with
|
|
179
|
+
`400 INVALID_ARGUMENT`, and the caller silently fell back for every request.
|
|
180
|
+
It also rejected a plan schema with a 5-6 value `template_key` enum on each
|
|
181
|
+
of up to 20 array items (`maxItems: 20`) — immediately and with no detail —
|
|
182
|
+
while 4 values or `maxItems: 10` passed. So:
|
|
183
|
+
|
|
184
|
+
- never put an `enum` inside repeated array items; describe the allowed
|
|
185
|
+
values in the prompt or the field `description` and validate server-side;
|
|
186
|
+
- keep `maxItems` small on arrays of objects, and always set it;
|
|
187
|
+
- run `maggie tool maggie_social_share.py schema-check schema.json`
|
|
188
|
+
(`tools/clis/maggie_schema_complexity.py`) before a release; it fails on
|
|
189
|
+
the patterns above and reports an estimated complexity score.
|
|
190
|
+
|
|
191
|
+
For a large vocabulary (styles, categories, catalogue IDs):
|
|
179
192
|
|
|
180
193
|
1. ask for a free string (optionally with a short list of examples in the
|
|
181
194
|
prompt) instead of a large `enum`;
|