@thanh01.pmt/curriculum-kit 1.0.5 → 1.0.7
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/dist/{index-BIhxrUEa.d.cts → index-BEicVaQ9.d.cts} +60 -5
- package/dist/{index-BvWC_xXe.d.ts → index-BfDHBO7f.d.ts} +60 -5
- package/dist/index.cjs +401 -341
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.mjs +399 -322
- package/dist/index.mjs.map +1 -1
- package/dist/media/index.cjs +30 -0
- package/dist/media/index.cjs.map +1 -1
- package/dist/media/index.d.cts +8 -0
- package/dist/media/index.d.ts +8 -0
- package/dist/media/index.mjs +30 -0
- package/dist/media/index.mjs.map +1 -1
- package/dist/workflow/index.cjs +460 -47
- package/dist/workflow/index.cjs.map +1 -1
- package/dist/workflow/index.d.cts +2 -1
- package/dist/workflow/index.d.ts +2 -1
- package/dist/workflow/index.mjs +457 -27
- package/dist/workflow/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -14,35 +14,16 @@ var supabaseJs = require('@supabase/supabase-js');
|
|
|
14
14
|
var url = require('url');
|
|
15
15
|
var jsonrepair = require('jsonrepair');
|
|
16
16
|
var rest = require('@octokit/rest');
|
|
17
|
-
var
|
|
17
|
+
var workflow = require('workflow');
|
|
18
18
|
|
|
19
19
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
20
20
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
21
21
|
|
|
22
|
-
function _interopNamespace(e) {
|
|
23
|
-
if (e && e.__esModule) return e;
|
|
24
|
-
var n = Object.create(null);
|
|
25
|
-
if (e) {
|
|
26
|
-
Object.keys(e).forEach(function (k) {
|
|
27
|
-
if (k !== 'default') {
|
|
28
|
-
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
29
|
-
Object.defineProperty(n, k, d.get ? d : {
|
|
30
|
-
enumerable: true,
|
|
31
|
-
get: function () { return e[k]; }
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
n.default = e;
|
|
37
|
-
return Object.freeze(n);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
22
|
var fs2__default = /*#__PURE__*/_interopDefault(fs2);
|
|
41
23
|
var path3__default = /*#__PURE__*/_interopDefault(path3);
|
|
42
24
|
var pLimit__default = /*#__PURE__*/_interopDefault(pLimit);
|
|
43
25
|
var fsPromises__default = /*#__PURE__*/_interopDefault(fsPromises);
|
|
44
26
|
var crypto__default = /*#__PURE__*/_interopDefault(crypto);
|
|
45
|
-
var workflowPkg__namespace = /*#__PURE__*/_interopNamespace(workflowPkg);
|
|
46
27
|
|
|
47
28
|
var __defProp = Object.defineProperty;
|
|
48
29
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -4261,9 +4242,9 @@ _Unit: {{UNIT_ID}} | Module: {{MODULE_ID}} | Format: Mermaid Diagrams + Printabl
|
|
|
4261
4242
|
var SLIDE_LAYOUT_PRESETS = [
|
|
4262
4243
|
{
|
|
4263
4244
|
id: "title-hero",
|
|
4264
|
-
name: "
|
|
4245
|
+
name: "Title & Speaker Cover",
|
|
4265
4246
|
category: "cover",
|
|
4266
|
-
description: "
|
|
4247
|
+
description: "High-impact opening slide with large title, subtitle, and speaker credentials.",
|
|
4267
4248
|
layout: {
|
|
4268
4249
|
boxes: [
|
|
4269
4250
|
{
|
|
@@ -4308,22 +4289,22 @@ var SLIDE_LAYOUT_PRESETS = [
|
|
|
4308
4289
|
textAlign: center
|
|
4309
4290
|
-->
|
|
4310
4291
|
|
|
4311
|
-
# \u{1F680}
|
|
4312
|
-
###
|
|
4292
|
+
# \u{1F680} Engineering Scalable Cloud Systems
|
|
4293
|
+
### Principles, Bottlenecks, and Real-World Architecture Patterns
|
|
4313
4294
|
|
|
4314
|
-
**
|
|
4295
|
+
**Presenter:** Senior Systems Architect | **Duration:** 45 mins | **Level:** Intermediate
|
|
4315
4296
|
|
|
4316
4297
|
<!--
|
|
4317
4298
|
Presenter Notes:
|
|
4318
|
-
-
|
|
4319
|
-
-
|
|
4299
|
+
- Welcome the audience and outline today's primary engineering goals.
|
|
4300
|
+
- Spark curiosity with an opening hook about scaling under high concurrency.
|
|
4320
4301
|
-->`
|
|
4321
4302
|
},
|
|
4322
4303
|
{
|
|
4323
4304
|
id: "two-column-split",
|
|
4324
|
-
name: "
|
|
4305
|
+
name: "Two-Column Comparison",
|
|
4325
4306
|
category: "split",
|
|
4326
|
-
description: "
|
|
4307
|
+
description: "Balanced side-by-side comparison for legacy vs modern patterns or pros vs cons.",
|
|
4327
4308
|
layout: {
|
|
4328
4309
|
boxes: [
|
|
4329
4310
|
{
|
|
@@ -4358,38 +4339,38 @@ Presenter Notes:
|
|
|
4358
4339
|
pos: [670, 150, 530, 510]
|
|
4359
4340
|
-->
|
|
4360
4341
|
|
|
4361
|
-
## \u2696\uFE0F
|
|
4342
|
+
## \u2696\uFE0F Monolithic Polling vs Event-Driven Architecture
|
|
4362
4343
|
|
|
4363
4344
|
<div class="columns-2">
|
|
4364
4345
|
<div>
|
|
4365
4346
|
|
|
4366
|
-
### \u{1F534}
|
|
4367
|
-
-
|
|
4368
|
-
-
|
|
4369
|
-
-
|
|
4347
|
+
### \u{1F534} Legacy Polling
|
|
4348
|
+
- Heavy database CPU overhead on idle cycles
|
|
4349
|
+
- High network latency between updates
|
|
4350
|
+
- Prone to cascading timeouts during spikes
|
|
4370
4351
|
|
|
4371
4352
|
</div>
|
|
4372
4353
|
<div>
|
|
4373
4354
|
|
|
4374
|
-
### \u{1F7E2}
|
|
4375
|
-
-
|
|
4376
|
-
-
|
|
4377
|
-
-
|
|
4355
|
+
### \u{1F7E2} Event-Driven Streams
|
|
4356
|
+
- Real-time event propagation via webhooks
|
|
4357
|
+
- Zero idle compute waste with serverless consumers
|
|
4358
|
+
- Automatic backpressure and queue isolation
|
|
4378
4359
|
|
|
4379
4360
|
</div>
|
|
4380
4361
|
</div>
|
|
4381
4362
|
|
|
4382
4363
|
<!--
|
|
4383
4364
|
Presenter Notes:
|
|
4384
|
-
-
|
|
4385
|
-
-
|
|
4365
|
+
- Emphasize the core failure mode of periodic polling under sudden load spikes.
|
|
4366
|
+
- Ask the room if anyone has experienced database lockups caused by polling loops.
|
|
4386
4367
|
-->`
|
|
4387
4368
|
},
|
|
4388
4369
|
{
|
|
4389
4370
|
id: "code-explainer",
|
|
4390
|
-
name: "
|
|
4371
|
+
name: "Code Walkthrough & Analysis",
|
|
4391
4372
|
category: "code",
|
|
4392
|
-
description: "
|
|
4373
|
+
description: "Syntax-highlighted code block on the left with step-by-step key annotations on the right.",
|
|
4393
4374
|
layout: {
|
|
4394
4375
|
boxes: [
|
|
4395
4376
|
{
|
|
@@ -4424,16 +4405,16 @@ Presenter Notes:
|
|
|
4424
4405
|
pos: [740, 135, 460, 535]
|
|
4425
4406
|
-->
|
|
4426
4407
|
|
|
4427
|
-
## \u{1F4BB}
|
|
4408
|
+
## \u{1F4BB} Robust Async Transaction Pipeline
|
|
4428
4409
|
|
|
4429
4410
|
<div class="columns-2">
|
|
4430
4411
|
<div>
|
|
4431
4412
|
|
|
4432
4413
|
\`\`\`ts
|
|
4433
|
-
//
|
|
4414
|
+
// Process payment with idempotency guarantee
|
|
4434
4415
|
export async function processPayment(order: Order) {
|
|
4435
4416
|
const isValid = await validateOrder(order);
|
|
4436
|
-
if (!isValid) throw new Error("
|
|
4417
|
+
if (!isValid) throw new Error("ValidationFailed");
|
|
4437
4418
|
|
|
4438
4419
|
const receipt = await chargeCard(order);
|
|
4439
4420
|
return sendConfirmation(receipt);
|
|
@@ -4443,25 +4424,26 @@ export async function processPayment(order: Order) {
|
|
|
4443
4424
|
</div>
|
|
4444
4425
|
<div>
|
|
4445
4426
|
|
|
4446
|
-
### \u{1F50D}
|
|
4447
|
-
1. **
|
|
4448
|
-
2. **
|
|
4449
|
-
3. **
|
|
4427
|
+
### \u{1F50D} Key Engineering Invariants:
|
|
4428
|
+
1. **Early Guard Clause:** Validates payload immediately at line 3 before triggering external calls.
|
|
4429
|
+
2. **Deterministic Sequence:** Enforces strict order of operations using native \`await\`.
|
|
4430
|
+
3. **Audit Trail:** Returns immutable payment receipt upon successful execution.
|
|
4450
4431
|
|
|
4451
4432
|
</div>
|
|
4452
4433
|
</div>
|
|
4453
4434
|
|
|
4454
4435
|
<!--
|
|
4455
4436
|
Presenter Notes:
|
|
4456
|
-
-
|
|
4457
|
-
-
|
|
4437
|
+
- Walk through lines 1 to 7 sequentially.
|
|
4438
|
+
- Cold-call check: "What happens if line 6 throws a network timeout?"
|
|
4439
|
+
- Cognitive scaffolding: Highlight the importance of idempotency keys in payment APIs.
|
|
4458
4440
|
-->`
|
|
4459
4441
|
},
|
|
4460
4442
|
{
|
|
4461
4443
|
id: "metrics-3-card",
|
|
4462
|
-
name: "
|
|
4444
|
+
name: "3-Metric Key Results Grid",
|
|
4463
4445
|
category: "metrics",
|
|
4464
|
-
description: "
|
|
4446
|
+
description: "Three high-visibility KPI stat cards for benchmarks, impact, and success criteria.",
|
|
4465
4447
|
layout: {
|
|
4466
4448
|
boxes: [
|
|
4467
4449
|
{
|
|
@@ -4506,43 +4488,43 @@ Presenter Notes:
|
|
|
4506
4488
|
pos: [860, 165, 340, 485]
|
|
4507
4489
|
-->
|
|
4508
4490
|
|
|
4509
|
-
## \u{1F4CA}
|
|
4491
|
+
## \u{1F4CA} Performance & Reliability Benchmarks
|
|
4510
4492
|
|
|
4511
4493
|
<div class="columns-3">
|
|
4512
4494
|
<div>
|
|
4513
4495
|
|
|
4514
|
-
# \u26A1 99.
|
|
4515
|
-
###
|
|
4516
|
-
|
|
4496
|
+
# \u26A1 99.99%
|
|
4497
|
+
### SLA Availability
|
|
4498
|
+
High-availability multi-region cluster with automated failover routing.
|
|
4517
4499
|
|
|
4518
4500
|
</div>
|
|
4519
4501
|
<div>
|
|
4520
4502
|
|
|
4521
|
-
# \u{1F680}
|
|
4522
|
-
###
|
|
4523
|
-
|
|
4503
|
+
# \u{1F680} 15ms
|
|
4504
|
+
### P99 Latency
|
|
4505
|
+
Sub-20ms roundtrip response time via distributed edge caching.
|
|
4524
4506
|
|
|
4525
4507
|
</div>
|
|
4526
4508
|
<div>
|
|
4527
4509
|
|
|
4528
|
-
# \u{1F6E1}\uFE0F
|
|
4529
|
-
###
|
|
4530
|
-
|
|
4510
|
+
# \u{1F6E1}\uFE0F Zero
|
|
4511
|
+
### Unhandled Breaches
|
|
4512
|
+
Full SOC2 Type II compliance and end-to-end payload encryption.
|
|
4531
4513
|
|
|
4532
4514
|
</div>
|
|
4533
4515
|
</div>
|
|
4534
4516
|
|
|
4535
4517
|
<!--
|
|
4536
4518
|
Presenter Notes:
|
|
4537
|
-
-
|
|
4538
|
-
-
|
|
4519
|
+
- Highlight the 15ms P99 latency target as the primary technical milestone.
|
|
4520
|
+
- Connect these metrics directly to the architecture decisions discussed next.
|
|
4539
4521
|
-->`
|
|
4540
4522
|
},
|
|
4541
4523
|
{
|
|
4542
4524
|
id: "process-flow",
|
|
4543
|
-
name: "
|
|
4525
|
+
name: "Architecture & Process Flow",
|
|
4544
4526
|
category: "diagram",
|
|
4545
|
-
description: "
|
|
4527
|
+
description: "Multi-stage workflow with responsive Mermaid diagram and phase summaries.",
|
|
4546
4528
|
layout: {
|
|
4547
4529
|
boxes: [
|
|
4548
4530
|
{
|
|
@@ -4577,33 +4559,33 @@ Presenter Notes:
|
|
|
4577
4559
|
pos: [80, 465, 1120, 205]
|
|
4578
4560
|
-->
|
|
4579
4561
|
|
|
4580
|
-
## \u{1F504}
|
|
4562
|
+
## \u{1F504} End-to-End Data Ingestion Pipeline
|
|
4581
4563
|
|
|
4582
4564
|
\`\`\`mermaid
|
|
4583
4565
|
graph LR
|
|
4584
|
-
A[1. Client
|
|
4585
|
-
B --> C[3. Auth
|
|
4586
|
-
C --> D[4.
|
|
4566
|
+
A[1. Client Ingress] --> B[2. API Gateway]
|
|
4567
|
+
B --> C[3. Auth Verifier]
|
|
4568
|
+
C --> D[4. Distributed DB]
|
|
4587
4569
|
style A fill:#0284c7,stroke:#38bdf8,stroke-width:2px,color:#fff
|
|
4588
4570
|
style B fill:#1e293b,stroke:#64748b,stroke-width:2px,color:#fff
|
|
4589
4571
|
style C fill:#059669,stroke:#34d399,stroke-width:2px,color:#fff
|
|
4590
4572
|
style D fill:#7c3aed,stroke:#a78bfa,stroke-width:2px,color:#fff
|
|
4591
4573
|
\`\`\`
|
|
4592
4574
|
|
|
4593
|
-
- **
|
|
4594
|
-
- **
|
|
4575
|
+
- **Ingress & Gateway:** Rate-limits traffic and applies reverse-proxy load balancing.
|
|
4576
|
+
- **Verification & Storage:** Decodes cryptographic JWT tokens before transactional write commit.
|
|
4595
4577
|
|
|
4596
4578
|
<!--
|
|
4597
4579
|
Presenter Notes:
|
|
4598
|
-
-
|
|
4599
|
-
-
|
|
4580
|
+
- Trace each hop across the diagram from left to right.
|
|
4581
|
+
- Ask: "Where should rate limiting be applied to prevent DDoS attacks?"
|
|
4600
4582
|
-->`
|
|
4601
4583
|
},
|
|
4602
4584
|
{
|
|
4603
4585
|
id: "quote-highlight",
|
|
4604
|
-
name: "
|
|
4586
|
+
name: "Core Principle & Quote Highlight",
|
|
4605
4587
|
category: "quote",
|
|
4606
|
-
description: "
|
|
4588
|
+
description: "Prominent quote callout for fundamental engineering rules or memorable mantras.",
|
|
4607
4589
|
layout: {
|
|
4608
4590
|
boxes: [
|
|
4609
4591
|
{
|
|
@@ -4634,23 +4616,23 @@ Presenter Notes:
|
|
|
4634
4616
|
fontSize: 1.4rem
|
|
4635
4617
|
-->
|
|
4636
4618
|
|
|
4637
|
-
## \u{1F4A1}
|
|
4619
|
+
## \u{1F4A1} Foundational Architecture Principle
|
|
4638
4620
|
|
|
4639
|
-
> "
|
|
4621
|
+
> "Simplicity is prerequisite for reliability. A well-engineered distributed system is one where a new engineer can understand the request flow within their first hour."
|
|
4640
4622
|
>
|
|
4641
4623
|
> \u2014 **Edsger W. Dijkstra**
|
|
4642
4624
|
|
|
4643
4625
|
<!--
|
|
4644
4626
|
Presenter Notes:
|
|
4645
|
-
-
|
|
4646
|
-
-
|
|
4627
|
+
- Pause for 5 seconds of silence to let the quote sink in.
|
|
4628
|
+
- Ask: "How do our current architectural choices reflect this principle?"
|
|
4647
4629
|
-->`
|
|
4648
4630
|
},
|
|
4649
4631
|
{
|
|
4650
4632
|
id: "quiz-checkpoint",
|
|
4651
|
-
name: "
|
|
4633
|
+
name: "Interactive Formative Checkpoint",
|
|
4652
4634
|
category: "assessment",
|
|
4653
|
-
description: "
|
|
4635
|
+
description: "Interactive multiple-choice diagnostic checkpoint with A/B/C/D voting options.",
|
|
4654
4636
|
layout: {
|
|
4655
4637
|
boxes: [
|
|
4656
4638
|
{
|
|
@@ -4689,27 +4671,27 @@ Presenter Notes:
|
|
|
4689
4671
|
fontSize: 1.15rem
|
|
4690
4672
|
-->
|
|
4691
4673
|
|
|
4692
|
-
## \u{1F3AF}
|
|
4674
|
+
## \u{1F3AF} Formative Checkpoint: Verify Your Understanding
|
|
4693
4675
|
|
|
4694
|
-
> **
|
|
4676
|
+
> **Question:** In an event-driven architecture, which pattern guarantees that messages are never permanently lost if a downstream consumer crashes?
|
|
4695
4677
|
|
|
4696
|
-
- **A.**
|
|
4697
|
-
- **B.**
|
|
4698
|
-
- **C.**
|
|
4699
|
-
- **D.**
|
|
4678
|
+
- **A.** Direct synchronous HTTP request with short 2-second timeout
|
|
4679
|
+
- **B.** Persistent Message Broker with Dead-Letter Queue (DLQ) & retry policy
|
|
4680
|
+
- **C.** In-memory client RAM cache without persistence
|
|
4681
|
+
- **D.** Discarding failed packets and prompting the user to re-submit
|
|
4700
4682
|
|
|
4701
4683
|
<!--
|
|
4702
4684
|
Presenter Notes:
|
|
4703
|
-
-
|
|
4704
|
-
-
|
|
4705
|
-
-
|
|
4685
|
+
- Give students 30 seconds to vote on option A, B, C, or D.
|
|
4686
|
+
- Correct answer: B.
|
|
4687
|
+
- Explain why options A and C introduce catastrophic data loss in production.
|
|
4706
4688
|
-->`
|
|
4707
4689
|
},
|
|
4708
4690
|
{
|
|
4709
4691
|
id: "tiered-practice-3cards",
|
|
4710
|
-
name: "
|
|
4692
|
+
name: "Tiered Differentiation (Bronze / Silver / Gold)",
|
|
4711
4693
|
category: "content",
|
|
4712
|
-
description: "
|
|
4694
|
+
description: "Three progressive competency tiers allowing self-paced learning acceleration.",
|
|
4713
4695
|
layout: {
|
|
4714
4696
|
boxes: [
|
|
4715
4697
|
{
|
|
@@ -4752,46 +4734,46 @@ Presenter Notes:
|
|
|
4752
4734
|
pos: [850, 140, 350, 520]
|
|
4753
4735
|
-->
|
|
4754
4736
|
|
|
4755
|
-
## \u{1F6E0}\uFE0F
|
|
4737
|
+
## \u{1F6E0}\uFE0F Hands-On Challenge: 3-Tier Differentiation
|
|
4756
4738
|
|
|
4757
4739
|
<div class="columns-3">
|
|
4758
4740
|
<div>
|
|
4759
4741
|
|
|
4760
|
-
### \u{1F949}
|
|
4761
|
-
-
|
|
4762
|
-
-
|
|
4763
|
-
- *
|
|
4742
|
+
### \u{1F949} Bronze Tier
|
|
4743
|
+
- Implement baseline function
|
|
4744
|
+
- Pass 3 baseline unit tests
|
|
4745
|
+
- *Goal: Foundational mastery*
|
|
4764
4746
|
|
|
4765
4747
|
</div>
|
|
4766
4748
|
<div>
|
|
4767
4749
|
|
|
4768
|
-
### \u{1F948}
|
|
4769
|
-
-
|
|
4770
|
-
-
|
|
4771
|
-
- *
|
|
4750
|
+
### \u{1F948} Silver Tier
|
|
4751
|
+
- Add boundary error handling
|
|
4752
|
+
- Enforce O(N) memory complexity
|
|
4753
|
+
- *Goal: Production-ready code*
|
|
4772
4754
|
|
|
4773
4755
|
</div>
|
|
4774
4756
|
<div>
|
|
4775
4757
|
|
|
4776
|
-
### \u{1F947}
|
|
4777
|
-
-
|
|
4778
|
-
-
|
|
4779
|
-
- *
|
|
4758
|
+
### \u{1F947} Gold Tier
|
|
4759
|
+
- Design distributed retry queue
|
|
4760
|
+
- Add 95% automated test coverage
|
|
4761
|
+
- *Goal: Architectural leadership*
|
|
4780
4762
|
|
|
4781
4763
|
</div>
|
|
4782
4764
|
</div>
|
|
4783
4765
|
|
|
4784
4766
|
<!--
|
|
4785
4767
|
Presenter Notes:
|
|
4786
|
-
-
|
|
4787
|
-
-
|
|
4768
|
+
- All learners begin at Bronze to establish baseline competency.
|
|
4769
|
+
- Accelerate self-directed learners to Silver and Gold as they complete each tier.
|
|
4788
4770
|
-->`
|
|
4789
4771
|
},
|
|
4790
4772
|
{
|
|
4791
4773
|
id: "agenda-timeline",
|
|
4792
|
-
name: "
|
|
4774
|
+
name: "Session Agenda & Milestones",
|
|
4793
4775
|
category: "content",
|
|
4794
|
-
description: "
|
|
4776
|
+
description: "Structured time-budgeted agenda mapping stages to measurable deliverables.",
|
|
4795
4777
|
layout: {
|
|
4796
4778
|
boxes: [
|
|
4797
4779
|
{
|
|
@@ -4818,25 +4800,25 @@ Presenter Notes:
|
|
|
4818
4800
|
pos: [80, 140, 1120, 520]
|
|
4819
4801
|
-->
|
|
4820
4802
|
|
|
4821
|
-
## \u{1F5FA}\uFE0F
|
|
4803
|
+
## \u{1F5FA}\uFE0F Milestone Roadmap & Time Budget
|
|
4822
4804
|
|
|
4823
|
-
|
|
|
4805
|
+
| Allocated Time | Session Stage | Core Learning Focus | Deliverable Milestone |
|
|
4824
4806
|
|:---:|---|---|---|
|
|
4825
|
-
| **05
|
|
4826
|
-
| **15
|
|
4827
|
-
| **20
|
|
4828
|
-
| **05
|
|
4807
|
+
| **05 mins** | Warm-Up | Real-World Scenario & Context | Identify system bottleneck |
|
|
4808
|
+
| **15 mins** | Concept Discovery | Core Architectural Patterns | Deconstruct message queue models |
|
|
4809
|
+
| **20 mins** | Hands-On Lab | Tiered Code Implementation | Complete Bronze + Silver tasks |
|
|
4810
|
+
| **05 mins** | Wrap-Up | Formative Checkpoint & Debrief | Consolidate key principles |
|
|
4829
4811
|
|
|
4830
4812
|
<!--
|
|
4831
4813
|
Presenter Notes:
|
|
4832
|
-
-
|
|
4814
|
+
- Walk through the time budget so students understand expectations and pacing.
|
|
4833
4815
|
-->`
|
|
4834
4816
|
},
|
|
4835
4817
|
{
|
|
4836
4818
|
id: "takeaways-summary",
|
|
4837
|
-
name: "
|
|
4819
|
+
name: "Key Takeaways & Wrap-up",
|
|
4838
4820
|
category: "content",
|
|
4839
|
-
description: "
|
|
4821
|
+
description: "Three memorable synthesis takeaways with actionable next steps.",
|
|
4840
4822
|
layout: {
|
|
4841
4823
|
boxes: [
|
|
4842
4824
|
{
|
|
@@ -4875,18 +4857,18 @@ Presenter Notes:
|
|
|
4875
4857
|
fontSize: 1.1rem
|
|
4876
4858
|
-->
|
|
4877
4859
|
|
|
4878
|
-
## \u{1F3C1}
|
|
4860
|
+
## \u{1F3C1} Key Takeaways & Action Items
|
|
4879
4861
|
|
|
4880
|
-
1. **
|
|
4881
|
-
2. **
|
|
4882
|
-
3. **
|
|
4862
|
+
1. **Decouple Before Scaling:** Separate producer rate from consumer processing capacity.
|
|
4863
|
+
2. **Design for Failure:** Always configure retries, backoff intervals, and dead-letter queues.
|
|
4864
|
+
3. **Measure End-to-End:** Track P99 latency and error rates across all distributed boundaries.
|
|
4883
4865
|
|
|
4884
|
-
> \u{1F4DD} **
|
|
4866
|
+
> \u{1F4DD} **Next Action Item:** Complete the Silver Tier refactoring challenge before our next peer-review session.
|
|
4885
4867
|
|
|
4886
4868
|
<!--
|
|
4887
4869
|
Presenter Notes:
|
|
4888
|
-
-
|
|
4889
|
-
-
|
|
4870
|
+
- Deliver a crisp 2-minute wrap-up reinforcing the 3 core takeaways.
|
|
4871
|
+
- Acknowledge strong peer collaboration during the hands-on lab.
|
|
4890
4872
|
-->`
|
|
4891
4873
|
}
|
|
4892
4874
|
];
|
|
@@ -9575,14 +9557,14 @@ init_errors();
|
|
|
9575
9557
|
|
|
9576
9558
|
// src/workflow/gateSettings.ts
|
|
9577
9559
|
var DEFAULT_GATE_SETTINGS = Object.freeze({
|
|
9578
|
-
LEARNER_PROFILE: "
|
|
9579
|
-
PROJECT_BRIEF: "
|
|
9580
|
-
REFERENCE_PACK: "
|
|
9581
|
-
CURRICULUM_FRAMEWORK: "
|
|
9582
|
-
KNOWLEDGE_EXPOSITION: "
|
|
9583
|
-
STYLE_GUIDE: "
|
|
9584
|
-
ART_DIRECTION: "
|
|
9585
|
-
LESSON: "
|
|
9560
|
+
LEARNER_PROFILE: "OFF",
|
|
9561
|
+
PROJECT_BRIEF: "OFF",
|
|
9562
|
+
REFERENCE_PACK: "OFF",
|
|
9563
|
+
CURRICULUM_FRAMEWORK: "OFF",
|
|
9564
|
+
KNOWLEDGE_EXPOSITION: "OFF",
|
|
9565
|
+
STYLE_GUIDE: "OFF",
|
|
9566
|
+
ART_DIRECTION: "OFF",
|
|
9567
|
+
LESSON: "OFF",
|
|
9586
9568
|
ACT: "OFF",
|
|
9587
9569
|
QUIZ: "OFF",
|
|
9588
9570
|
SLIDE: "OFF",
|
|
@@ -9590,7 +9572,7 @@ var DEFAULT_GATE_SETTINGS = Object.freeze({
|
|
|
9590
9572
|
WKS: "OFF",
|
|
9591
9573
|
HANDOUT: "OFF",
|
|
9592
9574
|
EXT: "OFF",
|
|
9593
|
-
CODE: "
|
|
9575
|
+
CODE: "OFF",
|
|
9594
9576
|
MEDIA_SCRIPT: "OFF",
|
|
9595
9577
|
EXIT_TICKET: "OFF"
|
|
9596
9578
|
});
|
|
@@ -11479,9 +11461,10 @@ Fix ALL issues above and output the complete corrected document.` }],
|
|
|
11479
11461
|
const taskState = pipelineState.tasks[lessonCode] || {};
|
|
11480
11462
|
const currentLessonInfo = taskState.LESSON;
|
|
11481
11463
|
const lessonGate = gateModeFor(gates, "LESSON");
|
|
11482
|
-
|
|
11483
|
-
|
|
11484
|
-
|
|
11464
|
+
const humanApproved = currentLessonInfo?.state === "approved" || currentLessonInfo?.review?.decision === "APPROVED" || approvedArtifacts.includes("LESSON") || approvedArtifacts.includes(lessonCode);
|
|
11465
|
+
const needsJudge = lessonGate === "LLM_JUDGE" && !humanApproved;
|
|
11466
|
+
let isLessonApproved = lessonGate === "OFF" || humanApproved;
|
|
11467
|
+
if (needsJudge && lessonContent) {
|
|
11485
11468
|
onProgress?.("@reviewer", `\u{1F916} Executing LLM-as-Judge academic evaluation on LESSON_${lessonCode}.md...`);
|
|
11486
11469
|
try {
|
|
11487
11470
|
let injectYamlReviewMetadata2 = function(content, meta) {
|
|
@@ -12520,6 +12503,8 @@ async function generateSingleArtifact(req) {
|
|
|
12520
12503
|
extraContext,
|
|
12521
12504
|
config,
|
|
12522
12505
|
contextSot = {},
|
|
12506
|
+
mediaPolicy,
|
|
12507
|
+
researchContext,
|
|
12523
12508
|
options = {},
|
|
12524
12509
|
onChunk
|
|
12525
12510
|
} = req;
|
|
@@ -12557,7 +12542,10 @@ ${langRule}
|
|
|
12557
12542
|
3. PEDAGOGICAL GROUNDING: Strictly align with the ${pedagogy.toUpperCase()} model, Bloom's Revised Taxonomy, and IEEE/ACM CS2023 guidelines.
|
|
12558
12543
|
4. SCAFFOLDING LEVEL: "${scaffoldingLevel.toUpperCase()}".${scaffoldingRule}
|
|
12559
12544
|
5. TARGET AUDIENCE: ${gradeLevel ? `Grade ${gradeLevel}, ` : ""}${targetAge} on ${hwStr}.${deviceRule}${classDynamicsRule}${extraContextRule}
|
|
12560
|
-
6. OUTPUT INTEGRITY: Output clean Markdown starting directly with YAML frontmatter. Do not wrap entire response in triple backticks.${artifactSpecializedPrompt}
|
|
12545
|
+
6. OUTPUT INTEGRITY: Output clean Markdown starting directly with YAML frontmatter. Do not wrap entire response in triple backticks.${artifactSpecializedPrompt}${mediaPolicy ? `
|
|
12546
|
+
|
|
12547
|
+
IMAGE PLANNING (media ledger) \u2014 QUOTA GEN: t\u1ED1i \u0111a ${mediaPolicy.maxGenImagesPerArtifact} \u1EA3nh AI cho artifact n\xE0y (\u1EA3nh search kho kh\xF4ng gi\u1EDBi h\u1EA1n).
|
|
12548
|
+
Khi n\u1ED9i dung c\u1EA7n minh h\u1ECDa (diagram quy tr\xECnh, s\u01A1 \u0111\u1ED3 kh\xE1i ni\u1EC7m, step-by-step visual), ch\xE8n placeholder \u0111\xFAng format [IMAGE: slug] (slug ch\u1EEF th\u01B0\u1EDDng-g\u1EA1ch ngang, unique trong b\xE0i, vd img-for-loop-diagram) T\u1EA0I \u0110\xDANG CH\u1ED6 c\u1EA7n \u1EA3nh, tr\xEAn d\xF2ng ri\xEAng. KH\xD4NG t\u1EF1 sinh \u1EA3nh, KH\xD4NG d\xF9ng markdown image \u2014 placeholder s\u1EBD \u0111\u01B0\u1EE3c thay b\u1EB1ng \u1EA3nh th\u1EADt sau khi Media Curator duy\u1EC7t. Ch\u1EC9 \u0111\u1EB7t cho \u1EA3nh TH\u1EF0C S\u1EF0 c\u1EA7n thi\u1EBFt.` : ""}`;
|
|
12561
12549
|
const userPrompt = `### CONTEXT GROUND TRUTH:
|
|
12562
12550
|
- Topic / Concept Domain: "${topic}"
|
|
12563
12551
|
- Lesson ID: "${lessonId || "ON_DEMAND"}"
|
|
@@ -12572,6 +12560,9 @@ ${selectedDevices ? `- Device Mode: "${selectedDevices}"
|
|
|
12572
12560
|
` : ""}${extraContext ? `- User Specific Guidance & Requirements: "${extraContext}"
|
|
12573
12561
|
` : ""}${config ? `- Full Task Config: ${JSON.stringify(config)}
|
|
12574
12562
|
` : ""}
|
|
12563
|
+
${researchContext ? `### RESEARCH GROUND TRUTH (fact-checked context \u2014 \u01B0u ti\xEAn d\xF9ng, KH\xD4NG b\u1ECBa s\u1ED1 li\u1EC7u):
|
|
12564
|
+
${researchContext.slice(0, 4e3)}
|
|
12565
|
+
` : ""}
|
|
12575
12566
|
${contextSot.framework ? `### SOT CURRICULUM FRAMEWORK:
|
|
12576
12567
|
${contextSot.framework.slice(0, 1500)}
|
|
12577
12568
|
` : ""}
|
|
@@ -12612,13 +12603,171 @@ Generate the complete, thorough, publication-grade "${artifactType.toUpperCase()
|
|
|
12612
12603
|
}
|
|
12613
12604
|
);
|
|
12614
12605
|
const { thought, content } = extractThoughtAndContent(rawResult);
|
|
12615
|
-
|
|
12616
|
-
|
|
12617
|
-
|
|
12618
|
-
|
|
12619
|
-
|
|
12620
|
-
|
|
12606
|
+
const finalContent = content || accumulatedContent;
|
|
12607
|
+
const finalThought = thought || accumulatedThought;
|
|
12608
|
+
const gates = resolveGateSettings(req.gateSettings);
|
|
12609
|
+
const kitGateType = {
|
|
12610
|
+
lesson: "LESSON",
|
|
12611
|
+
slide: "SLIDE",
|
|
12612
|
+
act: "ACT",
|
|
12613
|
+
quiz: "QUIZ",
|
|
12614
|
+
guide: "GUIDE",
|
|
12615
|
+
handout: "HANDOUT",
|
|
12616
|
+
worksheet: "WKS",
|
|
12617
|
+
ext: "EXT",
|
|
12618
|
+
codelab: "CODE",
|
|
12619
|
+
exit_ticket: "EXIT_TICKET"
|
|
12620
|
+
}[artifactType];
|
|
12621
|
+
const gateMode = kitGateType ? gateModeFor(gates, kitGateType) : "OFF";
|
|
12622
|
+
if (gateMode === "OFF") {
|
|
12623
|
+
return { artifactType, filename: meta.filename, content: finalContent, persona: meta.persona, thought: finalThought };
|
|
12624
|
+
}
|
|
12625
|
+
const injectReviewMeta = (doc, metaMap) => {
|
|
12626
|
+
const fm = doc.match(/^---\s*\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
|
|
12627
|
+
if (!fm) {
|
|
12628
|
+
return `---
|
|
12629
|
+
${Object.entries(metaMap).map(([k, v]) => `${k}: "${v}"`).join("\n")}
|
|
12630
|
+
---
|
|
12631
|
+
|
|
12632
|
+
${doc}`;
|
|
12633
|
+
}
|
|
12634
|
+
let yaml = fm[1] ?? "";
|
|
12635
|
+
for (const [k, v] of Object.entries(metaMap)) {
|
|
12636
|
+
const re = new RegExp(`^${k}:.*$`, "m");
|
|
12637
|
+
if (re.test(yaml)) yaml = yaml.replace(re, `${k}: "${v}"`);
|
|
12638
|
+
else yaml += `
|
|
12639
|
+
${k}: "${v}"`;
|
|
12640
|
+
}
|
|
12641
|
+
return doc.replace(fm[0], `---
|
|
12642
|
+
${yaml.trim()}
|
|
12643
|
+
---
|
|
12644
|
+
`);
|
|
12621
12645
|
};
|
|
12646
|
+
if (gateMode === "HITL") {
|
|
12647
|
+
return {
|
|
12648
|
+
artifactType,
|
|
12649
|
+
filename: meta.filename,
|
|
12650
|
+
content: injectReviewMeta(finalContent, { review_status: "PENDING_REVIEW", review_decision: "PENDING" }),
|
|
12651
|
+
persona: meta.persona,
|
|
12652
|
+
thought: finalThought,
|
|
12653
|
+
pausedForReview: true,
|
|
12654
|
+
gateStatus: "awaiting_approval"
|
|
12655
|
+
};
|
|
12656
|
+
}
|
|
12657
|
+
const judgeObjectives = [{ code: "A1", description: `Artifact fulfills its pedagogical contract for topic "${topic}" with complete required sections`, bloomLevel: "Analyze" }];
|
|
12658
|
+
try {
|
|
12659
|
+
const verdict = await LLMJudgeEngine.evaluateArtifactWithLLM({
|
|
12660
|
+
targetArtifactType: artifactType.toUpperCase(),
|
|
12661
|
+
lessonId: lessonId || "ON_DEMAND",
|
|
12662
|
+
expectedLanguage: language.toLowerCase().includes("viet") ? "vietnamese" : "english",
|
|
12663
|
+
targetObjectives: judgeObjectives,
|
|
12664
|
+
generatedContentJson: JSON.stringify({ content: finalContent, artifactType, lessonId: lessonId || "ON_DEMAND", title: displayTitle })
|
|
12665
|
+
});
|
|
12666
|
+
const passed = Boolean(verdict.passed) && verdict.score >= 85;
|
|
12667
|
+
if (passed) {
|
|
12668
|
+
return {
|
|
12669
|
+
artifactType,
|
|
12670
|
+
filename: meta.filename,
|
|
12671
|
+
content: injectReviewMeta(finalContent, {
|
|
12672
|
+
review_status: "APPROVED",
|
|
12673
|
+
reviewed_by: "@reviewer (LLM-as-Judge)",
|
|
12674
|
+
review_decision: "APPROVED"
|
|
12675
|
+
}),
|
|
12676
|
+
persona: meta.persona,
|
|
12677
|
+
thought: finalThought,
|
|
12678
|
+
judgeScore: verdict.score,
|
|
12679
|
+
judgeCritique: verdict.critique
|
|
12680
|
+
};
|
|
12681
|
+
}
|
|
12682
|
+
const repairFeedback = [verdict.autoRepairInstructions, ...verdict.weaknesses || []].filter(Boolean).join("\n");
|
|
12683
|
+
const repairedRaw = await streamCurriculumAIInference(
|
|
12684
|
+
[
|
|
12685
|
+
{ role: "user", content: `You previously authored \`${meta.filename}\` and the academic Judge REJECTED it with the critique below.
|
|
12686
|
+
Rewrite the COMPLETE corrected document: fix every listed issue, keep the mandatory structure, frontmatter and all approved sections. Output 100% clean Markdown only.
|
|
12687
|
+
|
|
12688
|
+
JUDGE CRITIQUE:
|
|
12689
|
+
${repairFeedback}
|
|
12690
|
+
|
|
12691
|
+
CURRENT DOCUMENT:
|
|
12692
|
+
${finalContent}` }
|
|
12693
|
+
],
|
|
12694
|
+
userPrompt,
|
|
12695
|
+
{ ...options, systemPersona: systemPrompt },
|
|
12696
|
+
(chunk, type) => {
|
|
12697
|
+
if (onChunk) onChunk(chunk, type === "thought" ? "thought" : "content");
|
|
12698
|
+
}
|
|
12699
|
+
);
|
|
12700
|
+
const repairedExtract = extractThoughtAndContent(repairedRaw);
|
|
12701
|
+
const repaired = repairedExtract.content || repairedRaw;
|
|
12702
|
+
if (repaired && repaired.trim().length > 100) {
|
|
12703
|
+
const reVerdict = await LLMJudgeEngine.evaluateArtifactWithLLM({
|
|
12704
|
+
targetArtifactType: artifactType.toUpperCase(),
|
|
12705
|
+
lessonId: lessonId || "ON_DEMAND",
|
|
12706
|
+
expectedLanguage: language.toLowerCase().includes("viet") ? "vietnamese" : "english",
|
|
12707
|
+
targetObjectives: judgeObjectives,
|
|
12708
|
+
generatedContentJson: JSON.stringify({ content: repaired, artifactType, lessonId: lessonId || "ON_DEMAND", title: displayTitle })
|
|
12709
|
+
});
|
|
12710
|
+
if (reVerdict.passed && reVerdict.score >= 85) {
|
|
12711
|
+
return {
|
|
12712
|
+
artifactType,
|
|
12713
|
+
filename: meta.filename,
|
|
12714
|
+
content: injectReviewMeta(repaired, {
|
|
12715
|
+
review_status: "APPROVED",
|
|
12716
|
+
reviewed_by: "@reviewer (LLM-as-Judge)",
|
|
12717
|
+
review_decision: "APPROVED"
|
|
12718
|
+
}),
|
|
12719
|
+
persona: meta.persona,
|
|
12720
|
+
thought: repairedExtract.thought || "",
|
|
12721
|
+
judgeScore: reVerdict.score,
|
|
12722
|
+
judgeCritique: reVerdict.critique
|
|
12723
|
+
};
|
|
12724
|
+
}
|
|
12725
|
+
return {
|
|
12726
|
+
artifactType,
|
|
12727
|
+
filename: meta.filename,
|
|
12728
|
+
content: injectReviewMeta(repaired, {
|
|
12729
|
+
review_status: "NEEDS_REVISION",
|
|
12730
|
+
reviewed_by: "@reviewer (LLM-as-Judge)",
|
|
12731
|
+
review_decision: "NEEDS_REVISION"
|
|
12732
|
+
}),
|
|
12733
|
+
persona: meta.persona,
|
|
12734
|
+
thought: repairedExtract.thought || "",
|
|
12735
|
+
pausedForReview: true,
|
|
12736
|
+
gateStatus: "judge_needs_revision",
|
|
12737
|
+
judgeScore: reVerdict.score,
|
|
12738
|
+
judgeCritique: reVerdict.critique
|
|
12739
|
+
};
|
|
12740
|
+
}
|
|
12741
|
+
return {
|
|
12742
|
+
artifactType,
|
|
12743
|
+
filename: meta.filename,
|
|
12744
|
+
content: injectReviewMeta(finalContent, {
|
|
12745
|
+
review_status: "NEEDS_REVISION",
|
|
12746
|
+
reviewed_by: "@reviewer (LLM-as-Judge)",
|
|
12747
|
+
review_decision: "NEEDS_REVISION"
|
|
12748
|
+
}),
|
|
12749
|
+
persona: meta.persona,
|
|
12750
|
+
thought: finalThought,
|
|
12751
|
+
pausedForReview: true,
|
|
12752
|
+
gateStatus: "judge_needs_revision",
|
|
12753
|
+
judgeScore: verdict.score,
|
|
12754
|
+
judgeCritique: verdict.critique
|
|
12755
|
+
};
|
|
12756
|
+
} catch (judgeErr) {
|
|
12757
|
+
return {
|
|
12758
|
+
artifactType,
|
|
12759
|
+
filename: meta.filename,
|
|
12760
|
+
content: injectReviewMeta(finalContent, {
|
|
12761
|
+
review_status: "NEEDS_REVISION",
|
|
12762
|
+
review_decision: "NEEDS_REVISION",
|
|
12763
|
+
critique: `Judge unavailable (fail-closed): ${String(judgeErr?.message || judgeErr).slice(0, 300)}`
|
|
12764
|
+
}),
|
|
12765
|
+
persona: meta.persona,
|
|
12766
|
+
thought: finalThought,
|
|
12767
|
+
pausedForReview: true,
|
|
12768
|
+
gateStatus: "judge_error"
|
|
12769
|
+
};
|
|
12770
|
+
}
|
|
12622
12771
|
} catch (error) {
|
|
12623
12772
|
throw createAiInferenceError({
|
|
12624
12773
|
errorCode: "ERR_AI_ALL_PROVIDERS_FAILED",
|
|
@@ -13374,43 +13523,15 @@ function safeParseJson(rawText) {
|
|
|
13374
13523
|
return null;
|
|
13375
13524
|
}
|
|
13376
13525
|
}
|
|
13377
|
-
async function readSseStream(reader, decoder, onData) {
|
|
13378
|
-
let buffer = "";
|
|
13379
|
-
while (true) {
|
|
13380
|
-
const { done, value } = await reader.read();
|
|
13381
|
-
if (done) break;
|
|
13382
|
-
buffer += decoder.decode(value, { stream: true });
|
|
13383
|
-
const lines = buffer.split("\n");
|
|
13384
|
-
buffer = lines.pop() || "";
|
|
13385
|
-
for (const line of lines) {
|
|
13386
|
-
const trimmed = line.trim();
|
|
13387
|
-
if (!trimmed || trimmed.startsWith(":")) continue;
|
|
13388
|
-
if (trimmed === "data: [DONE]") return;
|
|
13389
|
-
if (trimmed.startsWith("data: ")) {
|
|
13390
|
-
const dataStr = trimmed.slice(6).trim();
|
|
13391
|
-
if (dataStr) onData(dataStr);
|
|
13392
|
-
}
|
|
13393
|
-
}
|
|
13394
|
-
}
|
|
13395
|
-
}
|
|
13396
13526
|
async function streamLLMWithFallback(systemPrompt, userPrompt, apiKeys = {}, onChunk) {
|
|
13397
|
-
apiKeys.alibaba || apiKeys.dashscope || process.env.ALIBABA_API_KEY || process.env.DASHSCOPE_API_KEY;
|
|
13527
|
+
const alibabaKey = apiKeys.alibaba || apiKeys.dashscope || process.env.ALIBABA_API_KEY || process.env.DASHSCOPE_API_KEY;
|
|
13398
13528
|
const nvidiaKey = apiKeys.nvidia || process.env.NVIDIA_API_KEY;
|
|
13399
|
-
apiKeys.deepseek || process.env.DEEPSEEK_API_KEY;
|
|
13529
|
+
const deepseekKey = apiKeys.deepseek || process.env.DEEPSEEK_API_KEY;
|
|
13400
13530
|
const openrouterKey = apiKeys.openrouter || process.env.OPENROUTER_API_KEY;
|
|
13401
|
-
apiKeys.gemini || process.env.GEMINI_API_KEY;
|
|
13531
|
+
const geminiKey = apiKeys.gemini || process.env.GEMINI_API_KEY;
|
|
13402
13532
|
const openrouterPreset = process.env.OPENROUTER_FREE_PRESET || "@preset/coding-free";
|
|
13403
|
-
const
|
|
13404
|
-
{
|
|
13405
|
-
role: "system",
|
|
13406
|
-
content: `${systemPrompt}
|
|
13407
|
-
|
|
13408
|
-
THINKING DIRECTIVE: Keep reasoning concise, structured, and focused strictly on the core pedagogical trade-offs in 4-6 concise bullet points (under 120 words). Then output the JSON immediately.`
|
|
13409
|
-
},
|
|
13410
|
-
{ role: "user", content: userPrompt }
|
|
13411
|
-
];
|
|
13533
|
+
const candidates = [];
|
|
13412
13534
|
if (openrouterKey && isProviderEnabled("openrouter")) {
|
|
13413
|
-
const t0 = Date.now();
|
|
13414
13535
|
const orModels = [
|
|
13415
13536
|
openrouterPreset,
|
|
13416
13537
|
"openrouter/free",
|
|
@@ -13420,174 +13541,82 @@ THINKING DIRECTIVE: Keep reasoning concise, structured, and focused strictly on
|
|
|
13420
13541
|
"minimax/minimax-m3:free",
|
|
13421
13542
|
"cohere/north-mini-code:free"
|
|
13422
13543
|
].filter((m) => isModelAllowed(m));
|
|
13423
|
-
const
|
|
13424
|
-
|
|
13425
|
-
try {
|
|
13426
|
-
const controller = new AbortController();
|
|
13427
|
-
const timeout = setTimeout(() => controller.abort(), 25e3);
|
|
13428
|
-
const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
|
13429
|
-
method: "POST",
|
|
13430
|
-
headers: {
|
|
13431
|
-
"Content-Type": "application/json",
|
|
13432
|
-
"Authorization": `Bearer ${openrouterKey}`
|
|
13433
|
-
},
|
|
13434
|
-
body: JSON.stringify({
|
|
13435
|
-
model,
|
|
13436
|
-
messages,
|
|
13437
|
-
stream: true,
|
|
13438
|
-
temperature: 0.3
|
|
13439
|
-
}),
|
|
13440
|
-
signal: controller.signal
|
|
13441
|
-
});
|
|
13442
|
-
clearTimeout(timeout);
|
|
13443
|
-
if (res.ok && res.body) {
|
|
13444
|
-
const reader = res.body.getReader();
|
|
13445
|
-
const decoder = new TextDecoder();
|
|
13446
|
-
let fullContent = "";
|
|
13447
|
-
await readSseStream(reader, decoder, (dataStr) => {
|
|
13448
|
-
try {
|
|
13449
|
-
const parsed = JSON.parse(dataStr);
|
|
13450
|
-
const delta = parsed.choices?.[0]?.delta;
|
|
13451
|
-
if (delta?.content) {
|
|
13452
|
-
fullContent += delta.content;
|
|
13453
|
-
onChunk?.(delta.content, "content");
|
|
13454
|
-
}
|
|
13455
|
-
} catch {
|
|
13456
|
-
}
|
|
13457
|
-
});
|
|
13458
|
-
if (fullContent.trim()) {
|
|
13459
|
-
console.log(`[curriculum-kit:LayerPrefill] \u2705 Tier 1 OpenRouter (${model}) streamed in ${Date.now() - t0}ms`);
|
|
13460
|
-
return fullContent.trim();
|
|
13461
|
-
}
|
|
13462
|
-
}
|
|
13463
|
-
} catch (e) {
|
|
13464
|
-
console.warn(`[curriculum-kit:LayerPrefill] Tier 1 OpenRouter stream failed for ${model} in ${Date.now() - t0}ms:`, e?.message || e);
|
|
13465
|
-
}
|
|
13544
|
+
for (const m of Array.from(new Set(orModels))) {
|
|
13545
|
+
candidates.push({ provider: "openrouter", model: m, apiKey: openrouterKey });
|
|
13466
13546
|
}
|
|
13467
13547
|
}
|
|
13468
13548
|
if (nvidiaKey && isProviderEnabled("nvidia")) {
|
|
13469
|
-
const t0 = Date.now();
|
|
13470
13549
|
const nemotronModels = [
|
|
13471
13550
|
"nvidia/nemotron-3-ultra-550b-a55b",
|
|
13472
13551
|
"nvidia/nemotron-3.5-lightning-30b-a3b",
|
|
13473
13552
|
"nvidia/nemotron-3-super-120b-a12b",
|
|
13474
13553
|
"nvidia/llama-3.1-nemotron-70b-instruct"
|
|
13475
13554
|
].filter((m) => isModelAllowed(m));
|
|
13476
|
-
for (const
|
|
13477
|
-
|
|
13478
|
-
|
|
13479
|
-
|
|
13480
|
-
|
|
13481
|
-
|
|
13482
|
-
|
|
13483
|
-
|
|
13484
|
-
|
|
13485
|
-
|
|
13486
|
-
|
|
13487
|
-
|
|
13488
|
-
|
|
13489
|
-
|
|
13490
|
-
|
|
13491
|
-
|
|
13492
|
-
|
|
13493
|
-
|
|
13494
|
-
|
|
13495
|
-
|
|
13496
|
-
const reader = res.body.getReader();
|
|
13497
|
-
const decoder = new TextDecoder();
|
|
13498
|
-
let fullContent = "";
|
|
13499
|
-
await readSseStream(reader, decoder, (dataStr) => {
|
|
13500
|
-
try {
|
|
13501
|
-
const parsed = JSON.parse(dataStr);
|
|
13502
|
-
const delta = parsed.choices?.[0]?.delta;
|
|
13503
|
-
if (delta?.content) {
|
|
13504
|
-
fullContent += delta.content;
|
|
13505
|
-
onChunk?.(delta.content, "content");
|
|
13506
|
-
}
|
|
13507
|
-
} catch {
|
|
13508
|
-
}
|
|
13509
|
-
});
|
|
13510
|
-
if (fullContent.trim()) {
|
|
13511
|
-
console.log(`[curriculum-kit:LayerPrefill] \u2705 Tier 2 NVIDIA NIM (${model}) streamed in ${Date.now() - t0}ms`);
|
|
13512
|
-
return fullContent.trim();
|
|
13513
|
-
}
|
|
13514
|
-
}
|
|
13515
|
-
} catch (e) {
|
|
13516
|
-
console.warn(`[curriculum-kit:LayerPrefill] Tier 2 NVIDIA NIM stream failed for ${model} in ${Date.now() - t0}ms:`, e?.message || e);
|
|
13517
|
-
}
|
|
13555
|
+
for (const m of nemotronModels) {
|
|
13556
|
+
candidates.push({ provider: "nvidia", model: m, apiKey: nvidiaKey });
|
|
13557
|
+
}
|
|
13558
|
+
}
|
|
13559
|
+
if (alibabaKey && isProviderEnabled("alibaba")) {
|
|
13560
|
+
const aliModels = ["qwen-plus", "qwen-turbo", "qwen3.7-plus"].filter((m) => isModelAllowed(m));
|
|
13561
|
+
for (const m of aliModels) {
|
|
13562
|
+
candidates.push({ provider: "alibaba", model: m, apiKey: alibabaKey });
|
|
13563
|
+
}
|
|
13564
|
+
}
|
|
13565
|
+
if (deepseekKey && isProviderEnabled("deepseek")) {
|
|
13566
|
+
const dsModels = ["deepseek-chat", "deepseek-coder"].filter((m) => isModelAllowed(m));
|
|
13567
|
+
for (const m of dsModels) {
|
|
13568
|
+
candidates.push({ provider: "deepseek", model: m, apiKey: deepseekKey });
|
|
13569
|
+
}
|
|
13570
|
+
}
|
|
13571
|
+
if (geminiKey && (isProviderEnabled("gemini") || isProviderEnabled("google"))) {
|
|
13572
|
+
const gModels = ["gemini-1.5-flash", "gemini-2.0-flash", "gemini-1.5-pro"].filter((m) => isModelAllowed(m));
|
|
13573
|
+
for (const m of gModels) {
|
|
13574
|
+
candidates.push({ provider: "google", model: m, apiKey: geminiKey });
|
|
13518
13575
|
}
|
|
13519
13576
|
}
|
|
13520
13577
|
const fallbackChain = getDesignatedFallbackChain();
|
|
13521
13578
|
for (const target of fallbackChain) {
|
|
13522
|
-
|
|
13523
|
-
|
|
13524
|
-
|
|
13525
|
-
|
|
13526
|
-
|
|
13527
|
-
|
|
13528
|
-
|
|
13529
|
-
|
|
13530
|
-
|
|
13531
|
-
|
|
13532
|
-
|
|
13533
|
-
|
|
13534
|
-
|
|
13535
|
-
|
|
13536
|
-
|
|
13537
|
-
|
|
13538
|
-
|
|
13539
|
-
|
|
13540
|
-
|
|
13541
|
-
|
|
13542
|
-
|
|
13543
|
-
|
|
13544
|
-
|
|
13545
|
-
|
|
13546
|
-
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
13551
|
-
|
|
13552
|
-
|
|
13553
|
-
|
|
13579
|
+
if (!candidates.some((c) => c.provider === target.provider && c.model === target.model)) {
|
|
13580
|
+
candidates.push({
|
|
13581
|
+
provider: target.provider,
|
|
13582
|
+
model: target.model,
|
|
13583
|
+
apiKey: apiKeys[target.provider]
|
|
13584
|
+
});
|
|
13585
|
+
}
|
|
13586
|
+
}
|
|
13587
|
+
const systemInstructions = `${systemPrompt}
|
|
13588
|
+
|
|
13589
|
+
THINKING DIRECTIVE: Keep reasoning concise, structured, and focused strictly on the core pedagogical trade-offs in 4-6 concise bullet points (under 120 words). Then output the JSON immediately.`;
|
|
13590
|
+
for (const candidate of candidates) {
|
|
13591
|
+
const t0 = Date.now();
|
|
13592
|
+
try {
|
|
13593
|
+
const modelInstance = getAIModel({
|
|
13594
|
+
provider: candidate.provider,
|
|
13595
|
+
modelName: candidate.model,
|
|
13596
|
+
apiKey: candidate.apiKey
|
|
13597
|
+
});
|
|
13598
|
+
const streamResult = ai.streamText({
|
|
13599
|
+
model: modelInstance,
|
|
13600
|
+
system: systemInstructions,
|
|
13601
|
+
prompt: userPrompt,
|
|
13602
|
+
temperature: 0.2,
|
|
13603
|
+
abortSignal: AbortSignal.timeout(25e3)
|
|
13604
|
+
});
|
|
13605
|
+
let fullContent = "";
|
|
13606
|
+
for await (const part of streamResult.fullStream) {
|
|
13607
|
+
if (part.type === "reasoning-delta") {
|
|
13608
|
+
onChunk?.(part.text, "thought");
|
|
13609
|
+
} else if (part.type === "text-delta") {
|
|
13610
|
+
fullContent += part.text;
|
|
13611
|
+
onChunk?.(part.text, "content");
|
|
13554
13612
|
}
|
|
13555
|
-
} catch {
|
|
13556
13613
|
}
|
|
13557
|
-
|
|
13558
|
-
|
|
13559
|
-
|
|
13560
|
-
method: "POST",
|
|
13561
|
-
headers: {
|
|
13562
|
-
"Content-Type": "application/json",
|
|
13563
|
-
"Authorization": `Bearer ${openrouterKey}`
|
|
13564
|
-
},
|
|
13565
|
-
body: JSON.stringify({
|
|
13566
|
-
model,
|
|
13567
|
-
messages,
|
|
13568
|
-
stream: true,
|
|
13569
|
-
temperature: 0.2
|
|
13570
|
-
})
|
|
13571
|
-
});
|
|
13572
|
-
if (res.ok && res.body) {
|
|
13573
|
-
const reader = res.body.getReader();
|
|
13574
|
-
const decoder = new TextDecoder();
|
|
13575
|
-
let fullContent = "";
|
|
13576
|
-
await readSseStream(reader, decoder, (dataStr) => {
|
|
13577
|
-
try {
|
|
13578
|
-
const parsed = JSON.parse(dataStr);
|
|
13579
|
-
const delta = parsed.choices?.[0]?.delta;
|
|
13580
|
-
if (delta?.content) {
|
|
13581
|
-
fullContent += delta.content;
|
|
13582
|
-
onChunk?.(delta.content, "content");
|
|
13583
|
-
}
|
|
13584
|
-
} catch {
|
|
13585
|
-
}
|
|
13586
|
-
});
|
|
13587
|
-
if (fullContent.trim()) return fullContent.trim();
|
|
13588
|
-
}
|
|
13589
|
-
} catch {
|
|
13614
|
+
if (fullContent.trim()) {
|
|
13615
|
+
console.log(`[curriculum-kit:VercelAI] \u2705 ${candidate.provider} (${candidate.model}) streamed in ${Date.now() - t0}ms`);
|
|
13616
|
+
return fullContent.trim();
|
|
13590
13617
|
}
|
|
13618
|
+
} catch (e) {
|
|
13619
|
+
console.warn(`[curriculum-kit:VercelAI] Candidate ${candidate.provider} (${candidate.model}) failed in ${Date.now() - t0}ms:`, e?.message || e);
|
|
13591
13620
|
}
|
|
13592
13621
|
}
|
|
13593
13622
|
return null;
|
|
@@ -14454,7 +14483,7 @@ template_contract: "artifact-template-v1"
|
|
|
14454
14483
|
async function withRateLimitBackoff(options) {
|
|
14455
14484
|
let attempt = 1;
|
|
14456
14485
|
try {
|
|
14457
|
-
const meta =
|
|
14486
|
+
const meta = workflow.getStepMetadata();
|
|
14458
14487
|
if (meta && typeof meta.attempt === "number") {
|
|
14459
14488
|
attempt = meta.attempt;
|
|
14460
14489
|
}
|
|
@@ -14470,7 +14499,7 @@ async function withRateLimitBackoff(options) {
|
|
|
14470
14499
|
if (isRateLimited || isTransientError) {
|
|
14471
14500
|
const retryAfterSeconds = Math.min(120, Math.pow(2, attempt) * 2);
|
|
14472
14501
|
const reason = isRateLimited ? "Rate limit exceeded (429)" : `Transient server error (${status || "network"})`;
|
|
14473
|
-
throw new
|
|
14502
|
+
throw new workflow.RetryableError(`[${options.stepName}] ${reason}. Retrying attempt ${attempt + 1}...`, {
|
|
14474
14503
|
retryAfter: `${retryAfterSeconds}s`
|
|
14475
14504
|
});
|
|
14476
14505
|
}
|
|
@@ -14755,10 +14784,10 @@ var approvalPayloadSchema = zod.z.object({
|
|
|
14755
14784
|
feedback: zod.z.string().optional().describe("Actionable feedback or required changes if rejected"),
|
|
14756
14785
|
timestamp: zod.z.string().optional().describe("ISO timestamp of the approval action")
|
|
14757
14786
|
});
|
|
14758
|
-
var
|
|
14759
|
-
|
|
14760
|
-
|
|
14761
|
-
}
|
|
14787
|
+
var lessonApprovalHook = workflow.defineHook();
|
|
14788
|
+
function approvalHookToken(projectId, lessonId, artifactType) {
|
|
14789
|
+
return `approval:${projectId}:${lessonId}:${artifactType}`;
|
|
14790
|
+
}
|
|
14762
14791
|
|
|
14763
14792
|
// src/standards/standardsCoverageGate.ts
|
|
14764
14793
|
function resolveStatementRef(ref, packs) {
|
|
@@ -15108,9 +15137,9 @@ async function generateMilestoneWorkflow(input) {
|
|
|
15108
15137
|
} else if (lessonGate === "HITL") {
|
|
15109
15138
|
const hookToken = "gate:lesson:" + milestoneId;
|
|
15110
15139
|
console.log(" \u23F8 [Gate] LESSON awaiting HUMAN review (HITL) \u2014 hook " + hookToken);
|
|
15111
|
-
const
|
|
15112
|
-
const
|
|
15113
|
-
const hook =
|
|
15140
|
+
const workflowPkg = await import('workflow');
|
|
15141
|
+
const createHook = workflowPkg.createHook;
|
|
15142
|
+
const hook = createHook({ token: hookToken });
|
|
15114
15143
|
const review = await hook;
|
|
15115
15144
|
console.log(" \u2705 [Gate] Human verdict: " + (review.approved ? "APPROVED" : "REJECTED") + (review.feedback ? " \u2014 " + review.feedback : ""));
|
|
15116
15145
|
if (!review.approved) {
|
|
@@ -15130,7 +15159,7 @@ async function generateMilestoneWorkflow(input) {
|
|
|
15130
15159
|
standardsContext: standardsContext || void 0,
|
|
15131
15160
|
modelOptions: input.modelOptions
|
|
15132
15161
|
});
|
|
15133
|
-
const hook2 =
|
|
15162
|
+
const hook2 = createHook({ token: hookToken + ":v2" });
|
|
15134
15163
|
const review2 = await hook2;
|
|
15135
15164
|
console.log(" \u2705 [Gate] Human verdict (v2): " + (review2.approved ? "APPROVED" : "REJECTED"));
|
|
15136
15165
|
if (!review2.approved) {
|
|
@@ -15318,7 +15347,7 @@ async function generateRoadmapWorkflow(input) {
|
|
|
15318
15347
|
}
|
|
15319
15348
|
}
|
|
15320
15349
|
if (i + batchSize < milestones.length) {
|
|
15321
|
-
await
|
|
15350
|
+
await workflow.sleep("2s");
|
|
15322
15351
|
}
|
|
15323
15352
|
}
|
|
15324
15353
|
const successfulMilestones = milestoneOutcomes.filter((m) => m.status === "FULFILLED").length;
|
|
@@ -16740,6 +16769,36 @@ var MediaLedger = class {
|
|
|
16740
16769
|
doneCount() {
|
|
16741
16770
|
return this.listByStatus("done").length;
|
|
16742
16771
|
}
|
|
16772
|
+
/**
|
|
16773
|
+
* Scan artifact content và seed pending entries cho mọi placeholder [IMAGE: slug].
|
|
16774
|
+
* Được gọi sau khi agent sinh artifact (trước khi Curator duyệt) — đây là cầu nối
|
|
16775
|
+
* duy nhất giữa "agent chèn placeholder" và "sổ cái có entry để Curator/Fulfiller xử".
|
|
16776
|
+
* Idempotent: slug đã có entry active (pending/approved/done) → giữ nguyên;
|
|
16777
|
+
* chỉ re-describe khi entry trước bị rejected/failed.
|
|
16778
|
+
*/
|
|
16779
|
+
seedFromArtifact(artifactId, content) {
|
|
16780
|
+
const seeded = [];
|
|
16781
|
+
const re = /\[IMAGE\s*:\s*([a-z0-9][a-z0-9-]*)\s*\]/gi;
|
|
16782
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16783
|
+
let m;
|
|
16784
|
+
while ((m = re.exec(content)) !== null) {
|
|
16785
|
+
const slug = m[1].toLowerCase();
|
|
16786
|
+
if (seen.has(slug)) continue;
|
|
16787
|
+
seen.add(slug);
|
|
16788
|
+
const existing = this.entries.get(slug);
|
|
16789
|
+
const isActive = existing && (existing.status === "pending" || existing.status === "approved" || existing.status === "done");
|
|
16790
|
+
if (isActive) continue;
|
|
16791
|
+
seeded.push(this.upsert({
|
|
16792
|
+
slug,
|
|
16793
|
+
artifactId,
|
|
16794
|
+
name: slug.replace(/^(img-|image-)/, "").replace(/-/g, " "),
|
|
16795
|
+
description: `Placeholder ch\xE8n b\u1EDFi agent trong artifact ${artifactId} \u2014 ch\u1EDD Curator enrich (m\xF4 t\u1EA3 g\u1ED1c ch\u01B0a c\xF3).`,
|
|
16796
|
+
mode: "generate",
|
|
16797
|
+
status: "pending"
|
|
16798
|
+
}));
|
|
16799
|
+
}
|
|
16800
|
+
return seeded;
|
|
16801
|
+
}
|
|
16743
16802
|
};
|
|
16744
16803
|
|
|
16745
16804
|
// src/media/curator.ts
|
|
@@ -17154,6 +17213,7 @@ exports.WorksheetSchema = WorksheetSchema;
|
|
|
17154
17213
|
exports.activityTools = activityTools;
|
|
17155
17214
|
exports.analystTools = analystTools;
|
|
17156
17215
|
exports.analyzeProjectCreationIntent = analyzeProjectCreationIntent;
|
|
17216
|
+
exports.approvalHookToken = approvalHookToken;
|
|
17157
17217
|
exports.approvalPayloadSchema = approvalPayloadSchema;
|
|
17158
17218
|
exports.assertAcyclic = assertAcyclic;
|
|
17159
17219
|
exports.assessorTools = assessorTools;
|